1. /*
  2. * @(#)BasicDirectoryModel.java 1.28 03/01/23
  3. *
  4. * Copyright 2003 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 getDirectories() {
  49. synchronized(fileCache) {
  50. if (directories != null) {
  51. return directories;
  52. }
  53. Vector fls = getFiles();
  54. return directories;
  55. }
  56. }
  57. public Vector 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. }
  86. fetchID++;
  87. loadThread = new LoadFilesThread(currentDirectory, fetchID);
  88. loadThread.start();
  89. }
  90. /**
  91. * Renames a file in the underlying file system.
  92. *
  93. * @param oldFile a <code>File</code> object representing
  94. * the existing file
  95. * @param newFile a <code>File</code> object representing
  96. * the desired new file name
  97. * @return <code>true</code> if rename succeeded,
  98. * otherwise <code>false</code>
  99. * @since 1.4
  100. */
  101. public boolean renameFile(File oldFile, File newFile) {
  102. synchronized(fileCache) {
  103. if (oldFile.renameTo(newFile)) {
  104. validateFileCache();
  105. return true;
  106. }
  107. return false;
  108. }
  109. }
  110. public void fireContentsChanged() {
  111. // System.out.println("BasicDirectoryModel: firecontentschanged");
  112. fireContentsChanged(this, 0, getSize()-1);
  113. }
  114. public int getSize() {
  115. return fileCache.size();
  116. }
  117. public boolean contains(Object o) {
  118. return fileCache.contains(o);
  119. }
  120. public int indexOf(Object o) {
  121. return fileCache.indexOf(o);
  122. }
  123. public Object getElementAt(int index) {
  124. return fileCache.get(index);
  125. }
  126. /**
  127. * Obsolete - not used.
  128. */
  129. public void intervalAdded(ListDataEvent e) {
  130. }
  131. /**
  132. * Obsolete - not used.
  133. */
  134. public void intervalRemoved(ListDataEvent e) {
  135. }
  136. protected void sort(Vector v){
  137. ShellFolder.sortFiles(v);
  138. }
  139. // Obsolete - not used
  140. protected boolean lt(File a, File b) {
  141. // First ignore case when comparing
  142. int diff = a.getName().toLowerCase().compareTo(b.getName().toLowerCase());
  143. if (diff != 0) {
  144. return diff < 0;
  145. } else {
  146. // May differ in case (e.g. "mail" vs. "Mail")
  147. return a.getName().compareTo(b.getName()) < 0;
  148. }
  149. }
  150. class LoadFilesThread extends Thread {
  151. File currentDirectory = null;
  152. int fid;
  153. Vector runnables = new Vector(10);
  154. public LoadFilesThread(File currentDirectory, int fid) {
  155. super("Basic L&F File Loading Thread");
  156. this.currentDirectory = currentDirectory;
  157. this.fid = fid;
  158. }
  159. private void invokeLater(Runnable runnable) {
  160. runnables.addElement(runnable);
  161. SwingUtilities.invokeLater(runnable);
  162. }
  163. public void run() {
  164. FileSystemView fileSystem = filechooser.getFileSystemView();
  165. File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());
  166. Vector acceptsList = new Vector();
  167. if (isInterrupted()) {
  168. return;
  169. }
  170. // run through the file list, add directories and selectable files to fileCache
  171. for (int i = 0; i < list.length; i++) {
  172. if(filechooser.accept(list[i])) {
  173. acceptsList.addElement(list[i]);
  174. }
  175. }
  176. if (isInterrupted()) {
  177. return;
  178. }
  179. // First sort alphabetically by filename
  180. sort(acceptsList);
  181. Vector newDirectories = new Vector(50);
  182. Vector newFiles = new Vector();
  183. // run through list grabbing directories in chunks of ten
  184. for(int i = 0; i < acceptsList.size(); i++) {
  185. File f = (File) acceptsList.elementAt(i);
  186. boolean isTraversable = filechooser.isTraversable(f);
  187. if (isTraversable) {
  188. newDirectories.addElement(f);
  189. } else if (!isTraversable && filechooser.isFileSelectionEnabled()) {
  190. newFiles.addElement(f);
  191. }
  192. if(isInterrupted()) {
  193. return;
  194. }
  195. }
  196. Vector newFileCache = new Vector(newDirectories);
  197. newFileCache.addAll(newFiles);
  198. int newSize = newFileCache.size();
  199. int oldSize = fileCache.size();
  200. if (newSize > oldSize) {
  201. //see if interval is added
  202. int start = oldSize;
  203. int end = newSize;
  204. for (int i = 0; i < oldSize; i++) {
  205. if (!newFileCache.get(i).equals(fileCache.get(i))) {
  206. start = i;
  207. for (int j = i; j < newSize; j++) {
  208. if (newFileCache.get(j).equals(fileCache.get(i))) {
  209. end = j;
  210. break;
  211. }
  212. }
  213. break;
  214. }
  215. }
  216. if (start >= 0 && end > start
  217. && newFileCache.subList(end, newSize).equals(fileCache.subList(start, oldSize))) {
  218. invokeLater(new DoChangeContents(newFileCache.subList(start, end), start, null, 0, fid));
  219. newFileCache = null;
  220. }
  221. } else if (newSize < oldSize) {
  222. //see if interval is removed
  223. int start = -1;
  224. int end = -1;
  225. for (int i = 0; i < newSize; i++) {
  226. if (!newFileCache.get(i).equals(fileCache.get(i))) {
  227. start = i;
  228. end = i + oldSize - newSize;
  229. break;
  230. }
  231. }
  232. if (start >= 0 && end > start
  233. && fileCache.subList(end, oldSize).equals(newFileCache.subList(start, newSize))) {
  234. invokeLater(new DoChangeContents(null, 0, new Vector(fileCache.subList(start, end)),
  235. start, fid));
  236. newFileCache = null;
  237. }
  238. }
  239. if (newFileCache != null && !fileCache.equals(newFileCache)) {
  240. invokeLater(new DoChangeContents(newFileCache, 0, fileCache, 0, fid));
  241. }
  242. if (isInterrupted()) {
  243. cancelRunnables(runnables);
  244. }
  245. }
  246. public void cancelRunnables(Vector runnables) {
  247. for(int i = 0; i < runnables.size(); i++) {
  248. ((DoChangeContents)runnables.elementAt(i)).cancel();
  249. }
  250. }
  251. }
  252. class DoChangeContents implements Runnable {
  253. private List addFiles;
  254. private List remFiles;
  255. private boolean doFire = true;
  256. private int fid;
  257. private int addStart = 0;
  258. private int remStart = 0;
  259. private int change;
  260. public DoChangeContents(List addFiles, int addStart, List remFiles, int remStart, int fid) {
  261. this.addFiles = addFiles;
  262. this.addStart = addStart;
  263. this.remFiles = remFiles;
  264. this.remStart = remStart;
  265. this.fid = fid;
  266. }
  267. synchronized void cancel() {
  268. doFire = false;
  269. }
  270. public synchronized void run() {
  271. if (fetchID == fid && doFire) {
  272. int remSize = (remFiles == null) ? 0 : remFiles.size();
  273. int addSize = (addFiles == null) ? 0 : addFiles.size();
  274. synchronized(fileCache) {
  275. if (remSize > 0) {
  276. fileCache.removeAll(remFiles);
  277. }
  278. if (addSize > 0) {
  279. fileCache.addAll(addStart, addFiles);
  280. }
  281. files = null;
  282. directories = null;
  283. }
  284. if (remSize > 0 && addSize == 0) {
  285. fireIntervalRemoved(BasicDirectoryModel.this, remStart, remStart + remSize - 1);
  286. } else if (addSize > 0 && remSize == 0 && fileCache.size() > addSize) {
  287. fireIntervalAdded(BasicDirectoryModel.this, addStart, addStart + addSize - 1);
  288. } else {
  289. fireContentsChanged();
  290. }
  291. }
  292. }
  293. }
  294. }