View Javadoc
1   //
2   // $Id: JUploadFileView.java 112 2007-05-07 02:45:28 +0000 (lun., 07 mai 2007)
3   // felfert $
4   //
5   // jupload - A file upload applet.
6   // Copyright 2007 The JUpload Team
7   //
8   // Created: 2007-04-06
9   // Creator: etienne_sf
10  // Last modified: $Date: 2011-04-29 15:46:52 +0200 (ven., 29 avr. 2011) $
11  //
12  // This program is free software; you can redistribute it and/or modify it under
13  // the terms of the GNU General Public License as published by the Free Software
14  // Foundation; either version 2 of the License, or (at your option) any later
15  // version. This program is distributed in the hope that it will be useful, but
16  // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17  // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18  // details. You should have received a copy of the GNU General Public License
19  // along with this program; if not, write to the Free Software Foundation, Inc.,
20  // 675 Mass Ave, Cambridge, MA 02139, USA.
21  
22  package wjhk.jupload2.gui;
23  
24  import java.awt.image.BufferedImage;
25  import java.beans.PropertyChangeEvent;
26  import java.beans.PropertyChangeListener;
27  import java.io.File;
28  import java.util.Enumeration;
29  import java.util.concurrent.ConcurrentHashMap;
30  import java.util.concurrent.ExecutorService;
31  import java.util.concurrent.Executors;
32  import java.util.concurrent.ThreadFactory;
33  
34  import javax.swing.Icon;
35  import javax.swing.ImageIcon;
36  import javax.swing.JFileChooser;
37  import javax.swing.filechooser.FileView;
38  
39  import wjhk.jupload2.filedata.PictureFileData;
40  import wjhk.jupload2.policies.DefaultUploadPolicy;
41  import wjhk.jupload2.policies.PictureUploadPolicy;
42  import wjhk.jupload2.policies.UploadPolicy;
43  
44  // //////////////////////////////////////////////////////////////////////////////////////////////////
45  // ///////////////////////////// local class: JUploadFileView
46  // //////////////////////////////////////////////////////////////////////////////////////////////////
47  
48  /**
49   * The IconWorker class loads a icon from a file. It's called from a backup
50   * thread created by the JUploadFileView class. This allows to load/calculate
51   * icons in background. This prevent the applet to be freezed while icons are
52   * loading. <BR>
53   * Instances of this class can have the following status, in this order: <DIR>
54   * <LI>STATUS_NOT_LOADED: This icon is not loaded, and its loading is not
55   * requested. This status is the default one, on creation. <LI>
56   * STATUS_TO_BE_LOADED: This icon is on the list of icon to load. This status is
57   * written by the JUploadFileView#execute(IconWorker) method. <LI>
58   * STATUS_LOADING: Indicates the IconWorker#loadIcon() has been called, but is
59   * not finished. <LI>STATUS_LOADED: The icon is loaded, and ready to be
60   * displayed. <LI>STATUS_ERROR_WHILE_LOADING: Too bad, the applet could not load
61   * the icon. It won't be tried again. </DIR>
62   */
63  class IconWorker implements Runnable {
64  
65      /** Indicates that an error occurs, during the icon creation */
66      final static int STATUS_ERROR_WHILE_LOADING = -1;
67  
68      /** Indicates that the icon for this file has been loaded */
69      final static int STATUS_LOADED = 1;
70  
71      /**
72       * Indicated that the creation of the icon for this file has started. But it
73       * is not ready yet.
74       */
75      final static int STATUS_LOADING = 2;
76  
77      /**
78       * Indicates the loading of the icon for this file has been requested, but
79       * has not started yet.
80       */
81      final static int STATUS_TO_BE_LOADED = 3;
82  
83      /**
84       * Indicates the loading of the icon for this file is not currently
85       * requested. The loading may have been requested, then cancelled, for
86       * instance of the user changes the current directory or closes the file
87       * chooser.
88       */
89      final static int STATUS_NOT_LOADED = 4;
90  
91      /** The current upload policy */
92      UploadPolicy uploadPolicy = null;
93  
94      /** The current file chooser. */
95      JFileChooser fileChooser = null;
96  
97      /** The current file view */
98      JUploadFileView fileView = null;
99  
100     /** The file whose icon must be loaded. */
101     File file = null;
102 
103     /** The icon for this file. */
104     Icon icon = null;
105 
106     /** Current loading status for this worker */
107     int status = STATUS_NOT_LOADED;
108 
109     /**
110      * The constructor only stores the file. The background thread will call the
111      * loadIcon method.
112      * 
113      * @param file The file whose icon must be loaded/calculated.
114      */
115     IconWorker(UploadPolicy uploadPolicy, JFileChooser fileChooser,
116             JUploadFileView fileView, File file) {
117         this.uploadPolicy = uploadPolicy;
118         this.fileChooser = fileChooser;
119         this.fileView = fileView;
120         this.file = file;
121     }
122 
123     /**
124      * Returns the currently loaded icon for this file.
125      * 
126      * @return The Icon to be displayed for this file.
127      */
128     Icon getIcon() {
129         switch (this.status) {
130             case STATUS_LOADED:
131                 return this.icon;
132             case STATUS_NOT_LOADED:
133                 // ?? This picture should not be in this state. Perhaps the user
134                 // changes of directory, then went back to it.
135                 // We ask again to calculate its icon.
136                 this.fileView.execute(this);
137                 return JUploadFileView.emptyIcon;
138             default:
139                 return JUploadFileView.emptyIcon;
140         }// switch
141     }// getIcon
142 
143     /**
144      * Get the icon from the current upload policy, for this file. This methods
145      * does something only if the current status for the icon is
146      * {@link #STATUS_TO_BE_LOADED}. If not, this method does nothing.
147      */
148     void loadIcon() {
149         try {
150             if (this.status == STATUS_TO_BE_LOADED) {
151                 this.status = STATUS_LOADING;
152 
153                 // This thread is of the lower possible priority. So we first
154                 // give a change for other thread to work
155                 Thread.yield();
156 
157                 // This class is used only to do this call, in a separate
158                 // thread.
159                 this.icon = this.uploadPolicy.fileViewGetIcon(this.file);
160                 this.status = STATUS_LOADED;
161 
162                 // This thread is of the lower possible priority. So we first
163                 // give a change for other thread to work
164                 Thread.yield();
165 
166                 // Let's notify the fact the work is done.
167                 this.fileChooser.repaint();
168 
169                 // A try to minimize memory footprint
170                 PictureFileData.freeMemory(this.getClass().getName()
171                         + ".loadIcon()", this.uploadPolicy);
172             }
173         } catch (OutOfMemoryError e) {
174             this.uploadPolicy
175                     .displayWarn("OutOfMemoryError in IconWorker.loadIcon() ["
176                             + e.getMessage() + "]");
177             this.status = STATUS_ERROR_WHILE_LOADING;
178             this.icon = null;
179         }
180     }
181 
182     /** Implementation of the Runnable interface */
183     public void run() {
184         loadIcon();
185     }
186 }
187 
188 // //////////////////////////////////////////////////////////////////////////////////////////////////
189 // ///////////////// JUploadFileView
190 // //////////////////////////////////////////////////////////////////////////////////////////////////
191 
192 /**
193  * This class provides the icon view for the file selector.
194  * 
195  * @author etienne_sf
196  */
197 public class JUploadFileView extends FileView implements
198         PropertyChangeListener, ThreadFactory {
199 
200     /**
201      * This thread group is used to contain all icon worker threads. Its
202      * priority is the MIN_PRIORITY, to try to minimize CPU footprint. Its
203      * thread max priority is set in the
204      * {@link JUploadFileView#JUploadFileView(UploadPolicy, JFileChooser)}
205      * constructor.
206      */
207     ThreadGroup iconWorkerThreadGroup = new ThreadGroup("JUpload ThreadGroup");
208 
209     /** The current upload policy. */
210     UploadPolicy uploadPolicy = null;
211 
212     /** The current file chooser. */
213     JFileChooser fileChooser = null;
214 
215     /** This map will contain all instances of {@link IconWorker}. */
216     ConcurrentHashMap<String, IconWorker> hashMap = new ConcurrentHashMap<String, IconWorker>(
217             1000, (float) 0.5, 3);
218 
219     /**
220      * This executor will crate icons from files, one at a time. It is used to
221      * create these icon asynchronously.
222      * 
223      * @see #execute(IconWorker)
224      */
225     ExecutorService executorService = null;
226 
227     /**
228      * An empty icon, having the good file size.
229      */
230     public static ImageIcon emptyIcon = null;
231 
232     /**
233      * Creates a new instance.
234      * 
235      * @param uploadPolicy The upload policy to apply.
236      * @param fileChooser The desired file chooser to use.
237      */
238     public JUploadFileView(UploadPolicy uploadPolicy, JFileChooser fileChooser) {
239         this.uploadPolicy = uploadPolicy;
240         this.fileChooser = fileChooser;
241         this.fileChooser.addPropertyChangeListener(this);
242 
243         // The real interest of the thread group, here, is to lower the priority
244         // of the icon workers threads:
245         this.iconWorkerThreadGroup.setMaxPriority(Thread.MIN_PRIORITY);
246 
247         // emptyIcon needs an upload policy, to be set, but we'll create it
248         // only once.
249         if (emptyIcon == null
250                 || emptyIcon.getIconHeight() != uploadPolicy
251                         .getFileChooserIconSize()) {
252             // The empty icon has not been calculated yet, or its size changed
253             // since the icon creation. This can happen when the applet is
254             // reloaded, and the applet parameter changed: the static attribute
255             // are not recalculated.
256             // Let's construct the resized picture.
257             emptyIcon = new ImageIcon(new BufferedImage(uploadPolicy
258                     .getFileChooserIconSize(), uploadPolicy
259                     .getFileChooserIconSize(), BufferedImage.TYPE_INT_ARGB_PRE));
260         }
261     }
262 
263     synchronized void execute(IconWorker iconWorker) {
264         if (this.executorService == null || this.executorService.isShutdown()) {
265             this.executorService = Executors.newSingleThreadExecutor();
266         }
267         iconWorker.status = IconWorker.STATUS_TO_BE_LOADED;
268         this.executorService.execute(iconWorker);
269     }
270 
271     /**
272      * Stop all current and to come thread. To be called when the file chooser
273      * is closed.
274      */
275     synchronized public void shutdownNow() {
276         if (this.executorService != null) {
277             stopRunningJobs();
278 
279             this.executorService.shutdownNow();
280             this.executorService = null;
281         }
282     }
283 
284     /**
285      * Lazily mark all jobs as not done. No particular thread management.
286      */
287     private void stopRunningJobs() {
288         Enumeration<IconWorker> e = this.hashMap.elements();
289         IconWorker iw = null;
290         while (e.hasMoreElements()) {
291             iw = e.nextElement();
292             if (iw.status == IconWorker.STATUS_TO_BE_LOADED) {
293                 iw.status = IconWorker.STATUS_NOT_LOADED;
294             }
295         }
296     }
297 
298     /**
299      * Waiting for JFileChooser events. Currently managed:
300      * DIRECTORY_CHANGED_PROPERTY, to stop the to be loaded icons.
301      * 
302      * @param e
303      */
304     public void propertyChange(PropertyChangeEvent e) {
305         String prop = e.getPropertyName();
306         // If the directory changed, don't show an image.
307         if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(prop)) {
308             // We stops all running job. If the user gets back to this
309             // directory, the non calculated icons will be added to the job
310             // list.
311             this.uploadPolicy.displayDebug(
312                     "[JUploadFileView] Directory changed", 50);
313             stopRunningJobs();
314         }
315     }
316 
317     // ///////////////////////////////////////////////////////////////////////:
318     // /////////////////////// Methods from the FileView class
319     // ///////////////////////////////////////////////////////////////////////:
320 
321     /** #see javax.swing.filechooser.FileView#getDescription(File)) */
322     @Override
323     public String getDescription(File f) {
324         return null; // let the L&F FileView figure this out
325     }
326 
327     /**
328      * The fileChooserIconFromFileContent applet parameter defies which icon is
329      * to be returned here.
330      * 
331      * @see javax.swing.filechooser.FileView#getIcon(java.io.File)
332      * @see UploadPolicy#PROP_FILE_CHOOSER_ICON_FROM_FILE_CONTENT
333      */
334     @Override
335     public Icon getIcon(File file) {
336         // For DefaultUploadPolicy, a value of 1 means calculating the icon.
337         // For PictureUploadPolicy and sisters, a value of 0 also means
338         // calculating the icon
339         // Otherwise: return null, for default icon.
340         if (!((this.uploadPolicy.getFileChooserIconFromFileContent() == 1 && this.uploadPolicy instanceof DefaultUploadPolicy) || (this.uploadPolicy
341                 .getFileChooserIconFromFileContent() == 0 && this.uploadPolicy instanceof PictureUploadPolicy))) {
342             return null;
343         }
344         // For PictureUploadPolicy and sisters, a value of 0
345         if (file.isDirectory()) {
346             // We let the L&F display the system icon for directories.
347             return null;
348         }
349         IconWorker iconWorker = this.hashMap.get(file.getAbsolutePath());
350         if (iconWorker == null) {
351             // This file has not been loaded.
352             iconWorker = new IconWorker(this.uploadPolicy, this.fileChooser,
353                     this, file);
354             // We store it in the global Icon container.
355             this.hashMap.put(file.getAbsolutePath(), iconWorker);
356             // Then, we ask the current Thread to load its icon. It will be done
357             // later.
358             execute(iconWorker);
359             // We currently have no icon to display.
360             return emptyIcon;
361         }
362         // Ok, let's take the icon.
363         return iconWorker.getIcon();
364     }
365 
366     /** #see {@link javax.swing.filechooser.FileView#getName(File)} */
367     @Override
368     public String getName(File f) {
369         return null; // let the L&F FileView figure this out
370     }
371 
372     /** #see {@link javax.swing.filechooser.FileView#getTypeDescription(File)} */
373     @Override
374     public String getTypeDescription(File f) {
375         return null; // let the L&F FileView figure this out
376     }
377 
378     /** #see {@link javax.swing.filechooser.FileView#isTraversable(File)} */
379     @Override
380     public Boolean isTraversable(File f) {
381         return null; // let the L&F FileView figure this out
382     }
383 
384     /**
385      * Implementation of ThreadFactory. Creates a thread in the
386      * iconWorkerThreadGroup thread group. This thread group has the lower
387      * available priority.
388      * 
389      * @param runnable The runnable instance to start.
390      * @return The newly created thread
391      */
392     public Thread newThread(Runnable runnable) {
393         Thread thread = new Thread(this.iconWorkerThreadGroup, runnable);
394         thread.setPriority(Thread.MIN_PRIORITY);
395         return thread;
396     }
397 }