497 lines · 16.8 KB · Java
RawDownload
/* * Copyright (c) 1998, 2009, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing.plaf.basic; import java.io.File;import java.util.*;import java.util.concurrent.Callable;import javax.swing.*;import javax.swing.filechooser.*;import javax.swing.event.*;import java.beans.*; import sun.awt.shell.ShellFolder; /** * Basic implementation of a file list. * * @author Jeff Dinkins */public class BasicDirectoryModel extends AbstractListModel<Object> implements PropertyChangeListener {     private JFileChooser filechooser = null;    // PENDING(jeff) pick the size more sensibly    private Vector<File> fileCache = new Vector<File>(50);    private LoadFilesThread loadThread = null;    private Vector<File> files = null;    private Vector<File> directories = null;    private int fetchID = 0;     private PropertyChangeSupport changeSupport;     private boolean busy = false;     public BasicDirectoryModel(JFileChooser filechooser) {        this.filechooser = filechooser;        validateFileCache();    }     public void propertyChange(PropertyChangeEvent e) {        String prop = e.getPropertyName();        if(prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY ||           prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY ||           prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY ||           prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY ||           prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) {            validateFileCache();        } else if ("UI".equals(prop)) {            Object old = e.getOldValue();            if (old instanceof BasicFileChooserUI) {                BasicFileChooserUI ui = (BasicFileChooserUI) old;                BasicDirectoryModel model = ui.getModel();                if (model != null) {                    model.invalidateFileCache();                }            }        } else if ("JFileChooserDialogIsClosingProperty".equals(prop)) {            invalidateFileCache();        }    }     /**     * This method is used to interrupt file loading thread.     */    public void invalidateFileCache() {        if (loadThread != null) {            loadThread.interrupt();            loadThread.cancelRunnables();            loadThread = null;        }    }     public Vector<File> getDirectories() {        synchronized(fileCache) {            if (directories != null) {                return directories;            }            Vector fls = getFiles();            return directories;        }    }     public Vector<File> getFiles() {        synchronized(fileCache) {            if (files != null) {                return files;            }            files = new Vector<File>();            directories = new Vector<File>();            directories.addElement(filechooser.getFileSystemView().createFileObject(                filechooser.getCurrentDirectory(), "..")            );             for (int i = 0; i < getSize(); i++) {                File f = fileCache.get(i);                if (filechooser.isTraversable(f)) {                    directories.add(f);                } else {                    files.add(f);                }            }            return files;        }    }     public void validateFileCache() {        File currentDirectory = filechooser.getCurrentDirectory();        if (currentDirectory == null) {            return;        }        if (loadThread != null) {            loadThread.interrupt();            loadThread.cancelRunnables();        }         setBusy(true, ++fetchID);         loadThread = new LoadFilesThread(currentDirectory, fetchID);        loadThread.start();    }     /**     * Renames a file in the underlying file system.     *     * @param oldFile a <code>File</code> object representing     *        the existing file     * @param newFile a <code>File</code> object representing     *        the desired new file name     * @return <code>true</code> if rename succeeded,     *        otherwise <code>false</code>     * @since 1.4     */    public boolean renameFile(File oldFile, File newFile) {        synchronized(fileCache) {            if (oldFile.renameTo(newFile)) {                validateFileCache();                return true;            }            return false;        }    }      public void fireContentsChanged() {        // System.out.println("BasicDirectoryModel: firecontentschanged");        fireContentsChanged(this, 0, getSize()-1);    }     public int getSize() {        return fileCache.size();    }     public boolean contains(Object o) {        return fileCache.contains(o);    }     public int indexOf(Object o) {        return fileCache.indexOf(o);    }     public Object getElementAt(int index) {        return fileCache.get(index);    }     /**     * Obsolete - not used.     */    public void intervalAdded(ListDataEvent e) {    }     /**     * Obsolete - not used.     */    public void intervalRemoved(ListDataEvent e) {    }     protected void sort(Vector<? extends File> v){        ShellFolder.sort(v);    }     // Obsolete - not used    protected boolean lt(File a, File b) {        // First ignore case when comparing        int diff = a.getName().toLowerCase().compareTo(b.getName().toLowerCase());        if (diff != 0) {            return diff < 0;        } else {            // May differ in case (e.g. "mail" vs. "Mail")            return a.getName().compareTo(b.getName()) < 0;        }    }      class LoadFilesThread extends Thread {        File currentDirectory = null;        int fid;        Vector<DoChangeContents> runnables = new Vector<DoChangeContents>(10);         public LoadFilesThread(File currentDirectory, int fid) {            super("Basic L&F File Loading Thread");            this.currentDirectory = currentDirectory;            this.fid = fid;        }         public void run() {            run0();            setBusy(false, fid);        }         public void run0() {            FileSystemView fileSystem = filechooser.getFileSystemView();             if (isInterrupted()) {                return;            }             File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());             if (isInterrupted()) {                return;            }             final Vector<File> newFileCache = new Vector<File>();            Vector<File> newFiles = new Vector<File>();             // run through the file list, add directories and selectable files to fileCache            // Note that this block must be OUTSIDE of Invoker thread because of            // deadlock possibility with custom synchronized FileSystemView            for (File file : list) {                if (filechooser.accept(file)) {                    boolean isTraversable = filechooser.isTraversable(file);                     if (isTraversable) {                        newFileCache.addElement(file);                    } else if (filechooser.isFileSelectionEnabled()) {                        newFiles.addElement(file);                    }                     if (isInterrupted()) {                        return;                    }                }            }             // First sort alphabetically by filename            sort(newFileCache);            sort(newFiles);             newFileCache.addAll(newFiles);             // To avoid loads of synchronizations with Invoker and improve performance we            // execute the whole block on the COM thread            DoChangeContents doChangeContents = ShellFolder.invoke(new Callable<DoChangeContents>() {                public DoChangeContents call() {                    int newSize = newFileCache.size();                    int oldSize = fileCache.size();                     if (newSize > oldSize) {                        //see if interval is added                        int start = oldSize;                        int end = newSize;                        for (int i = 0; i < oldSize; i++) {                            if (!newFileCache.get(i).equals(fileCache.get(i))) {                                start = i;                                for (int j = i; j < newSize; j++) {                                    if (newFileCache.get(j).equals(fileCache.get(i))) {                                        end = j;                                        break;                                    }                                }                                break;                            }                        }                        if (start >= 0 && end > start                            && newFileCache.subList(end, newSize).equals(fileCache.subList(start, oldSize))) {                            if (isInterrupted()) {                                return null;                            }                            return new DoChangeContents(newFileCache.subList(start, end), start, null, 0, fid);                        }                    } else if (newSize < oldSize) {                        //see if interval is removed                        int start = -1;                        int end = -1;                        for (int i = 0; i < newSize; i++) {                            if (!newFileCache.get(i).equals(fileCache.get(i))) {                                start = i;                                end = i + oldSize - newSize;                                break;                            }                        }                        if (start >= 0 && end > start                            && fileCache.subList(end, oldSize).equals(newFileCache.subList(start, newSize))) {                            if (isInterrupted()) {                                return null;                            }                            return new DoChangeContents(null, 0, new Vector(fileCache.subList(start, end)), start, fid);                        }                    }                    if (!fileCache.equals(newFileCache)) {                        if (isInterrupted()) {                            cancelRunnables(runnables);                        }                        return new DoChangeContents(newFileCache, 0, fileCache, 0, fid);                    }                    return null;                }            });             if (doChangeContents != null) {                runnables.addElement(doChangeContents);                SwingUtilities.invokeLater(doChangeContents);            }        }          public void cancelRunnables(Vector<DoChangeContents> runnables) {            for (DoChangeContents runnable : runnables) {                runnable.cancel();            }        }         public void cancelRunnables() {            cancelRunnables(runnables);        }   }      /**     * Adds a PropertyChangeListener to the listener list. The listener is     * registered for all bound properties of this class.     * <p>     * If <code>listener</code> is <code>null</code>,     * no exception is thrown and no action is performed.     *     * @param    listener  the property change listener to be added     *     * @see #removePropertyChangeListener     * @see #getPropertyChangeListeners     *     * @since 1.6     */    public void addPropertyChangeListener(PropertyChangeListener listener) {        if (changeSupport == null) {            changeSupport = new PropertyChangeSupport(this);        }        changeSupport.addPropertyChangeListener(listener);    }     /**     * Removes a PropertyChangeListener from the listener list.     * <p>     * If listener is null, no exception is thrown and no action is performed.     *     * @param listener the PropertyChangeListener to be removed     *     * @see #addPropertyChangeListener     * @see #getPropertyChangeListeners     *     * @since 1.6     */    public void removePropertyChangeListener(PropertyChangeListener listener) {        if (changeSupport != null) {            changeSupport.removePropertyChangeListener(listener);        }    }     /**     * Returns an array of all the property change listeners     * registered on this component.     *     * @return all of this component's <code>PropertyChangeListener</code>s     *         or an empty array if no property change     *         listeners are currently registered     *     * @see      #addPropertyChangeListener     * @see      #removePropertyChangeListener     * @see      java.beans.PropertyChangeSupport#getPropertyChangeListeners     *     * @since 1.6     */    public PropertyChangeListener[] getPropertyChangeListeners() {        if (changeSupport == null) {            return new PropertyChangeListener[0];        }        return changeSupport.getPropertyChangeListeners();    }     /**     * Support for reporting bound property changes for boolean properties.     * This method can be called when a bound property has changed and it will     * send the appropriate PropertyChangeEvent to any registered     * PropertyChangeListeners.     *     * @param propertyName the property whose value has changed     * @param oldValue the property's previous value     * @param newValue the property's new value     *     * @since 1.6     */    protected void firePropertyChange(String propertyName,                                      Object oldValue, Object newValue) {        if (changeSupport != null) {            changeSupport.firePropertyChange(propertyName,                                             oldValue, newValue);        }    }      /**     * Set the busy state for the model. The model is considered     * busy when it is running a separate (interruptable)     * thread in order to load the contents of a directory.     */    private synchronized void setBusy(final boolean busy, int fid) {        if (fid == fetchID) {            boolean oldValue = this.busy;            this.busy = busy;             if (changeSupport != null && busy != oldValue) {                SwingUtilities.invokeLater(new Runnable() {                    public void run() {                        firePropertyChange("busy", !busy, busy);                    }                });            }        }    }      class DoChangeContents implements Runnable {        private List<File> addFiles;        private List<File> remFiles;        private boolean doFire = true;        private int fid;        private int addStart = 0;        private int remStart = 0;         public DoChangeContents(List<File> addFiles, int addStart, List<File> remFiles, int remStart, int fid) {            this.addFiles = addFiles;            this.addStart = addStart;            this.remFiles = remFiles;            this.remStart = remStart;            this.fid = fid;        }         synchronized void cancel() {                doFire = false;        }         public synchronized void run() {            if (fetchID == fid && doFire) {                int remSize = (remFiles == null) ? 0 : remFiles.size();                int addSize = (addFiles == null) ? 0 : addFiles.size();                synchronized(fileCache) {                    if (remSize > 0) {                        fileCache.removeAll(remFiles);                    }                    if (addSize > 0) {                        fileCache.addAll(addStart, addFiles);                    }                    files = null;                    directories = null;                }                if (remSize > 0 && addSize == 0) {                    fireIntervalRemoved(BasicDirectoryModel.this, remStart, remStart + remSize - 1);                } else if (addSize > 0 && remSize == 0 && addStart + addSize <= fileCache.size()) {                    fireIntervalAdded(BasicDirectoryModel.this, addStart, addStart + addSize - 1);                } else {                    fireContentsChanged();                }            }        }    }}