/* * 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(); } } } }}
/*
* 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();
}
}
}
}
}