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 }