// // $Id: JUploadFileView.java 112 2007-05-07 02:45:28 +0000 (lun., 07 mai 2007) // felfert $ // // jupload - A file upload applet. // Copyright 2007 The JUpload Team // // Created: 2007-04-06 // Creator: etienne_sf // Last modified: $Date: 2009-07-02 11:49:12 -0300 (Qui, 02 Jul 2009) $ // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software // Foundation; either version 2 of the License, or (at your option) any later // version. This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more // details. You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation, Inc., // 675 Mass Ave, Cambridge, MA 02139, USA. package wjhk.jupload2.gui; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.util.Enumeration; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JFileChooser; import javax.swing.filechooser.FileView; import wjhk.jupload2.filedata.PictureFileData; import wjhk.jupload2.policies.DefaultUploadPolicy; import wjhk.jupload2.policies.PictureUploadPolicy; import wjhk.jupload2.policies.UploadPolicy; // ////////////////////////////////////////////////////////////////////////////////////////////////// // ///////////////////////////// local class: JUploadFileView // ////////////////////////////////////////////////////////////////////////////////////////////////// /** * The IconWorker class loads a icon from a file. It's called from a backup * thread created by the JUploadFileView class. This allows to load/calculate * icons in background. This prevent the applet to be freezed while icons are * loading.
* Instances of this class can have the following status, in this order: *
  • STATUS_NOT_LOADED: This icon is not loaded, and its loading is not * requested. This status is the default one, on creation.
  • * STATUS_TO_BE_LOADED: This icon is on the list of icon to load. This status is * written by the JUploadFileView#execute(IconWorker) method.
  • * STATUS_LOADING: Indicates the IconWorker#loadIcon() has been called, but is * not finished.
  • STATUS_LOADED: The icon is loaded, and ready to be * displayed.
  • STATUS_ERROR_WHILE_LOADING: Too bad, the applet could not load * the icon. It won't be tried again.
  • */ class IconWorker implements Runnable { /** Indicates that an error occurs, during the icon creation */ final static int STATUS_ERROR_WHILE_LOADING = -1; /** Indicates that the icon for this file has been loaded */ final static int STATUS_LOADED = 1; /** * Indicated that the creation of the icon for this file has started. But it * is not ready yet. */ final static int STATUS_LOADING = 2; /** * Indicates the loading of the icon for this file has been requested, but * has not started yet. */ final static int STATUS_TO_BE_LOADED = 3; /** * Indicates the loading of the icon for this file is not currently * requested. The loading may have been requested, then cancelled, for * instance of the user changes the current directory or closes the file * chooser. */ final static int STATUS_NOT_LOADED = 4; /** The current upload policy */ UploadPolicy uploadPolicy = null; /** The current file chooser. */ JFileChooser fileChooser = null; /** The current file view */ JUploadFileView fileView = null; /** The file whose icon must be loaded. */ File file = null; /** The icon for this file. */ Icon icon = null; /** Current loading status for this worker */ int status = STATUS_NOT_LOADED; /** * The constructor only stores the file. The background thread will call the * loadIcon method. * * @param file The file whose icon must be loaded/calculated. */ IconWorker(UploadPolicy uploadPolicy, JFileChooser fileChooser, JUploadFileView fileView, File file) { this.uploadPolicy = uploadPolicy; this.fileChooser = fileChooser; this.fileView = fileView; this.file = file; } /** * Returns the currently loaded icon for this file. * * @return The Icon to be displayed for this file. */ Icon getIcon() { switch (this.status) { case STATUS_LOADED: return this.icon; case STATUS_NOT_LOADED: // ?? This picture should not be in this state. Perhaps the user // changes of directory, then went back to it. // We ask again to calculate its icon. this.fileView.execute(this); return JUploadFileView.emptyIcon; default: return JUploadFileView.emptyIcon; }// switch }// getIcon /** * Get the icon from the current upload policy, for this file. This methods * does something only if the current status for the icon is * {@link #STATUS_TO_BE_LOADED}. If not, this method does nothing. */ void loadIcon() { try { if (this.status == STATUS_TO_BE_LOADED) { this.status = STATUS_LOADING; // This thread is of the lower possible priority. So we first // give a change for other thread to work Thread.yield(); // This class is used only to do this call, in a separate // thread. this.icon = this.uploadPolicy.fileViewGetIcon(this.file); this.status = STATUS_LOADED; // This thread is of the lower possible priority. So we first // give a change for other thread to work Thread.yield(); // Let's notify the fact the work is done. this.fileChooser.repaint(); // A try to minimize memory footprint PictureFileData.freeMemory(this.getClass().getName() + ".loadIcon()", this.uploadPolicy); } } catch (OutOfMemoryError e) { this.uploadPolicy .displayWarn("OutOfMemoryError in IconWorker.loadIcon()"); this.status = STATUS_ERROR_WHILE_LOADING; this.icon = null; } } /** Implementation of the Runnable interface */ public void run() { loadIcon(); } } // ////////////////////////////////////////////////////////////////////////////////////////////////// // ///////////////// JUploadFileView // ////////////////////////////////////////////////////////////////////////////////////////////////// /** * This class provides the icon view for the file selector. * * @author etienne_sf */ public class JUploadFileView extends FileView implements PropertyChangeListener, ThreadFactory { /** * This thread group is used to contain all icon worker threads. Its * priority is the MIN_PRIORITY, to try to minimize CPU footprint. Its * thread max priority is set in the * {@link JUploadFileView#JUploadFileView(UploadPolicy, JFileChooser)} * constructor. */ ThreadGroup iconWorkerThreadGroup = new ThreadGroup("JUpload ThreadGroup"); /** The current upload policy. */ UploadPolicy uploadPolicy = null; /** The current file chooser. */ JFileChooser fileChooser = null; /** This map will contain all instances of {@link IconWorker}. */ ConcurrentHashMap hashMap = new ConcurrentHashMap( 1000, (float) 0.5, 3); /** * This executor will crate icons from files, one at a time. It is used to * create these icon asynchronously. * * @see #execute(IconWorker) */ ExecutorService executorService = null; /** * An empty icon, having the good file size. */ public static ImageIcon emptyIcon = null; /** * Creates a new instance. * * @param uploadPolicy The upload policy to apply. * @param fileChooser The desired file chooser to use. */ public JUploadFileView(UploadPolicy uploadPolicy, JFileChooser fileChooser) { this.uploadPolicy = uploadPolicy; this.fileChooser = fileChooser; this.fileChooser.addPropertyChangeListener(this); // The real interest of the thread group, here, is to lower the priority // of the icon workers threads: this.iconWorkerThreadGroup.setMaxPriority(Thread.MIN_PRIORITY); // emptyIcon needs an upload policy, to be set, but we'll create it // only once. if (emptyIcon == null || emptyIcon.getIconHeight() != uploadPolicy .getFileChooserIconSize()) { // The empty icon has not been calculated yet, or its size changed // since the icon creation. This can happen when the applet is // reloaded, and the applet parameter changed: the static attribute // are not recalculated. // Let's construct the resized picture. emptyIcon = new ImageIcon(new BufferedImage(uploadPolicy .getFileChooserIconSize(), uploadPolicy .getFileChooserIconSize(), BufferedImage.TYPE_INT_ARGB_PRE)); } } synchronized void execute(IconWorker iconWorker) { if (this.executorService == null || this.executorService.isShutdown()) { this.executorService = Executors.newSingleThreadExecutor(); } iconWorker.status = IconWorker.STATUS_TO_BE_LOADED; this.executorService.execute(iconWorker); } /** * Stop all current and to come thread. To be called when the file chooser * is closed. */ synchronized public void shutdownNow() { if (this.executorService != null) { stopRunningJobs(); this.executorService.shutdownNow(); this.executorService = null; } } /** * Lazily mark all jobs as not done. No particular thread management. */ private void stopRunningJobs() { Enumeration e = this.hashMap.elements(); IconWorker iw = null; while (e.hasMoreElements()) { iw = e.nextElement(); if (iw.status == IconWorker.STATUS_TO_BE_LOADED) { iw.status = IconWorker.STATUS_NOT_LOADED; } } } /** * Waiting for JFileChooser events. Currently managed: * DIRECTORY_CHANGED_PROPERTY, to stop the to be loaded icons. * * @param e */ public void propertyChange(PropertyChangeEvent e) { String prop = e.getPropertyName(); // If the directory changed, don't show an image. if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(prop)) { // We stops all running job. If the user gets back to this // directory, the non calculated icons will be added to the job // list. this.uploadPolicy.displayDebug( "[JUploadFileView] Directory changed", 50); stopRunningJobs(); } } // ///////////////////////////////////////////////////////////////////////: // /////////////////////// Methods from the FileView class // ///////////////////////////////////////////////////////////////////////: /** #see javax.swing.filechooser.FileView#getDescription(File)) */ @Override public String getDescription(File f) { return null; // let the L&F FileView figure this out } /** * The fileChooserIconFromFileContent applet parameter defies which icon is * to be returned here. * * @see javax.swing.filechooser.FileView#getIcon(java.io.File) * @see UploadPolicy#PROP_FILE_CHOOSER_ICON_FROM_FILE_CONTENT */ @Override public Icon getIcon(File file) { // For DefaultUploadPolicy, a value of 1 means calculating the icon. // For PictureUploadPolicy and sisters, a value of 0 also means // calculating the icon // Otherwise: return null, for default icon. if (!((this.uploadPolicy.getFileChooserIconFromFileContent() == 1 && this.uploadPolicy instanceof DefaultUploadPolicy) || (this.uploadPolicy .getFileChooserIconFromFileContent() == 0 && this.uploadPolicy instanceof PictureUploadPolicy))) { return null; } // For PictureUploadPolicy and sisters, a value of 0 if (file.isDirectory()) { // We let the L&F display the system icon for directories. return null; } IconWorker iconWorker = this.hashMap.get(file.getAbsolutePath()); if (iconWorker == null) { // This file has not been loaded. iconWorker = new IconWorker(this.uploadPolicy, this.fileChooser, this, file); // We store it in the global Icon container. this.hashMap.put(file.getAbsolutePath(), iconWorker); // Then, we ask the current Thread to load its icon. It will be done // later. execute(iconWorker); // We currently have no icon to display. return emptyIcon; } // Ok, let's take the icon. return iconWorker.getIcon(); } /** #see {@link javax.swing.filechooser.FileView#getName(File)} */ @Override public String getName(File f) { return null; // let the L&F FileView figure this out } /** #see {@link javax.swing.filechooser.FileView#getTypeDescription(File)} */ @Override public String getTypeDescription(File f) { return null; // let the L&F FileView figure this out } /** #see {@link javax.swing.filechooser.FileView#isTraversable(File)} */ @Override public Boolean isTraversable(File f) { return null; // let the L&F FileView figure this out } /** * Implementation of ThreadFactory. Creates a thread in the * iconWorkerThreadGroup thread group. This thread group has the lower * available priority. * * @param runnable The runnable instance to start. * @return The newly created thread */ public Thread newThread(Runnable runnable) { Thread thread = new Thread(this.iconWorkerThreadGroup, runnable); thread.setPriority(Thread.MIN_PRIORITY); return thread; } }