1. /*
  2. * @(#)BasicDirectoryModel.java 1.31 04/05/05
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import java.io.File;
  9. import java.util.*;
  10. import javax.swing.*;
  11. import javax.swing.filechooser.*;
  12. import javax.swing.event.*;
  13. import java.beans.*;
  14. import sun.awt.shell.ShellFolder;
  15. /**
  16. * Basic implementation of a file list.
  17. *
  18. * @version %i% %g%
  19. * @author Jeff Dinkins
  20. */
  21. public class BasicDirectoryModel extends AbstractListModel implements PropertyChangeListener {
  22. private JFileChooser filechooser = null;
  23. // PENDING(jeff) pick the size more sensibly
  24. private Vector fileCache = new Vector(50);
  25. private LoadFilesThread loadThread = null;
  26. private Vector files = null;
  27. private Vector directories = null;
  28. private int fetchID = 0;
  29. public BasicDirectoryModel(JFileChooser filechooser) {
  30. this.filechooser = filechooser;
  31. validateFileCache();
  32. }
  33. public void propertyChange(PropertyChangeEvent e) {
  34. String prop = e.getPropertyName();
  35. if(prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY ||
  36. prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY ||
  37. prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY ||
  38. prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY ||
  39. prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) {
  40. validateFileCache();
  41. }
  42. }
  43. /**
  44. * Obsolete - not used.
  45. */
  46. public void invalidateFileCache() {
  47. }
  48. public Vector<File> getDirectories() {
  49. synchronized(fileCache) {
  50. if (directories != null) {
  51. return directories;
  52. }
  53. Vector fls = getFiles();
  54. return directories;
  55. }
  56. }
  57. public Vector<File> getFiles() {
  58. synchronized(fileCache) {
  59. if (files != null) {
  60. return files;
  61. }
  62. files = new Vector();
  63. directories = new Vector();
  64. directories.addElement(filechooser.getFileSystemView().createFileObject(
  65. filechooser.getCurrentDirectory(), "..")
  66. );
  67. for (int i = 0; i < getSize(); i++) {
  68. File f = (File)fileCache.get(i);
  69. if (filechooser.isTraversable(f)) {
  70. directories.add(f);
  71. } else {
  72. files.add(f);
  73. }
  74. }
  75. return files;
  76. }
  77. }
  78. public void validateFileCache() {
  79. File currentDirectory = filechooser.getCurrentDirectory();
  80. if (currentDirectory == null) {
  81. return;
  82. }
  83. if (loadThread != null) {
  84. loadThread.interrupt();
  85. loadThread.cancelRunnables();
  86. }
  87. fetchID++;
  88. loadThread = new LoadFilesThread(currentDirectory, fetchID);
  89. loadThread.start();
  90. }
  91. /**
  92. * Renames a file in the underlying file system.
  93. *
  94. * @param oldFile a <code>File</code> object representing
  95. * the existing file
  96. * @param newFile a <code>File</code> object representing
  97. * the desired new file name
  98. * @return <code>true</code> if rename succeeded,
  99. * otherwise <code>false</code>
  100. * @since 1.4
  101. */
  102. public boolean renameFile(File oldFile, File newFile) {
  103. synchronized(fileCache) {
  104. if (oldFile.renameTo(newFile)) {
  105. validateFileCache();
  106. return true;
  107. }
  108. return false;
  109. }
  110. }
  111. public void fireContentsChanged() {
  112. // System.out.println("BasicDirectoryModel: firecontentschanged");
  113. fireContentsChanged(this, 0, getSize()-1);
  114. }
  115. public int getSize() {
  116. return fileCache.size();
  117. }
  118. public boolean contains(Object o) {
  119. return fileCache.contains(o);
  120. }
  121. public int indexOf(Object o) {
  122. return fileCache.indexOf(o);
  123. }
  124. public Object getElementAt(int index) {
  125. return fileCache.get(index);
  126. }
  127. /**
  128. * Obsolete - not used.
  129. */
  130. public void intervalAdded(ListDataEvent e) {
  131. }
  132. /**
  133. * Obsolete - not used.
  134. */
  135. public void intervalRemoved(ListDataEvent e) {
  136. }
  137. protected void sort(Vector<? extends File> v){
  138. ShellFolder.sortFiles(v);
  139. }
  140. // Obsolete - not used
  141. protected boolean lt(File a, File b) {
  142. // First ignore case when comparing
  143. int diff = a.getName().toLowerCase().compareTo(b.getName().toLowerCase());
  144. if (diff != 0) {
  145. return diff < 0;
  146. } else {
  147. // May differ in case (e.g. "mail" vs. "Mail")
  148. return a.getName().compareTo(b.getName()) < 0;
  149. }
  150. }
  151. class LoadFilesThread extends Thread {
  152. File currentDirectory = null;
  153. int fid;
  154. Vector runnables = new Vector(10);
  155. public LoadFilesThread(File currentDirectory, int fid) {
  156. super("Basic L&F File Loading Thread");
  157. this.currentDirectory = currentDirectory;
  158. this.fid = fid;
  159. }
  160. private void invokeLater(Runnable runnable) {
  161. runnables.addElement(runnable);
  162. SwingUtilities.invokeLater(runnable);
  163. }
  164. public void run() {
  165. FileSystemView fileSystem = filechooser.getFileSystemView();
  166. File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());
  167. Vector<File> acceptsList = new Vector<File>();
  168. if (isInterrupted()) {
  169. return;
  170. }
  171. // run through the file list, add directories and selectable files to fileCache
  172. for (int i = 0; i < list.length; i++) {
  173. if(filechooser.accept(list[i])) {
  174. acceptsList.addElement(list[i]);
  175. }
  176. }
  177. if (isInterrupted()) {
  178. return;
  179. }
  180. // First sort alphabetically by filename
  181. sort(acceptsList);
  182. Vector newDirectories = new Vector(50);
  183. Vector newFiles = new Vector();
  184. // run through list grabbing directories in chunks of ten
  185. for(int i = 0; i < acceptsList.size(); i++) {
  186. File f = (File) acceptsList.elementAt(i);
  187. boolean isTraversable = filechooser.isTraversable(f);
  188. if (isTraversable) {
  189. newDirectories.addElement(f);
  190. } else if (!isTraversable && filechooser.isFileSelectionEnabled()) {
  191. newFiles.addElement(f);
  192. }
  193. if(isInterrupted()) {
  194. return;
  195. }
  196. }
  197. Vector newFileCache = new Vector(newDirectories);
  198. newFileCache.addAll(newFiles);
  199. int newSize = newFileCache.size();
  200. int oldSize = fileCache.size();
  201. if (newSize > oldSize) {
  202. //see if interval is added
  203. int start = oldSize;
  204. int end = newSize;
  205. for (int i = 0; i < oldSize; i++) {
  206. if (!newFileCache.get(i).equals(fileCache.get(i))) {
  207. start = i;
  208. for (int j = i; j < newSize; j++) {
  209. if (newFileCache.get(j).equals(fileCache.get(i))) {
  210. end = j;
  211. break;
  212. }
  213. }
  214. break;
  215. }
  216. }
  217. if (start >= 0 && end > start
  218. && newFileCache.subList(end, newSize).equals(fileCache.subList(start, oldSize))) {
  219. if(isInterrupted()) {
  220. return;
  221. }
  222. invokeLater(new DoChangeContents(newFileCache.subList(start, end), start, null, 0, fid));
  223. newFileCache = null;
  224. }
  225. } else if (newSize < oldSize) {
  226. //see if interval is removed
  227. int start = -1;
  228. int end = -1;
  229. for (int i = 0; i < newSize; i++) {
  230. if (!newFileCache.get(i).equals(fileCache.get(i))) {
  231. start = i;
  232. end = i + oldSize - newSize;
  233. break;
  234. }
  235. }
  236. if (start >= 0 && end > start
  237. && fileCache.subList(end, oldSize).equals(newFileCache.subList(start, newSize))) {
  238. if(isInterrupted()) {
  239. return;
  240. }
  241. invokeLater(new DoChangeContents(null, 0, new Vector(fileCache.subList(start, end)),
  242. start, fid));
  243. newFileCache = null;
  244. }
  245. }
  246. if (newFileCache != null && !fileCache.equals(newFileCache)) {
  247. if (isInterrupted()) {
  248. cancelRunnables(runnables);
  249. }
  250. invokeLater(new DoChangeContents(newFileCache, 0, fileCache, 0, fid));
  251. }
  252. }
  253. public void cancelRunnables(Vector runnables) {
  254. for(int i = 0; i < runnables.size(); i++) {
  255. ((DoChangeContents)runnables.elementAt(i)).cancel();
  256. }
  257. }
  258. public void cancelRunnables() {
  259. cancelRunnables(runnables);
  260. }
  261. }
  262. class DoChangeContents implements Runnable {
  263. private List addFiles;
  264. private List remFiles;
  265. private boolean doFire = true;
  266. private int fid;
  267. private int addStart = 0;
  268. private int remStart = 0;
  269. private int change;
  270. public DoChangeContents(List addFiles, int addStart, List remFiles, int remStart, int fid) {
  271. this.addFiles = addFiles;
  272. this.addStart = addStart;
  273. this.remFiles = remFiles;
  274. this.remStart = remStart;
  275. this.fid = fid;
  276. }
  277. synchronized void cancel() {
  278. doFire = false;
  279. }
  280. public synchronized void run() {
  281. if (fetchID == fid && doFire) {
  282. int remSize = (remFiles == null) ? 0 : remFiles.size();
  283. int addSize = (addFiles == null) ? 0 : addFiles.size();
  284. synchronized(fileCache) {
  285. if (remSize > 0) {
  286. fileCache.removeAll(remFiles);
  287. }
  288. if (addSize > 0) {
  289. fileCache.addAll(addStart, addFiles);
  290. }
  291. files = null;
  292. directories = null;
  293. }
  294. if (remSize > 0 && addSize == 0) {
  295. fireIntervalRemoved(BasicDirectoryModel.this, remStart, remStart + remSize - 1);
  296. } else if (addSize > 0 && remSize == 0 && fileCache.size() > addSize) {
  297. fireIntervalAdded(BasicDirectoryModel.this, addStart, addStart + addSize - 1);
  298. } else {
  299. fireContentsChanged();
  300. }
  301. }
  302. }
  303. }
  304. }