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 }