1. /*
  2. * @(#)BasicDirectoryModel.java 1.19 00/02/02
  3. *
  4. * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.plaf.basic;
  11. import java.io.File;
  12. import java.util.Vector;
  13. import javax.swing.*;
  14. import javax.swing.filechooser.*;
  15. import javax.swing.event.*;
  16. import java.beans.*;
  17. /**
  18. * Basic implementation of a file list.
  19. *
  20. * @version %i% %g%
  21. * @author Jeff Dinkins
  22. */
  23. public class BasicDirectoryModel extends AbstractListModel implements PropertyChangeListener {
  24. private JFileChooser filechooser = null;
  25. private Vector fileCache = null;
  26. private LoadFilesThread loadThread = null;
  27. private Vector files = null;
  28. private Vector directories = null;
  29. private int fetchID = 0;
  30. public BasicDirectoryModel(JFileChooser filechooser) {
  31. this.filechooser = filechooser;
  32. validateFileCache();
  33. }
  34. public void propertyChange(PropertyChangeEvent e) {
  35. String prop = e.getPropertyName();
  36. if(prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY ||
  37. prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY ||
  38. prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY ||
  39. prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY ||
  40. prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) {
  41. invalidateFileCache();
  42. validateFileCache();
  43. }
  44. }
  45. public void invalidateFileCache() {
  46. files = null;
  47. directories = null;
  48. fileCache = null;
  49. }
  50. public Vector getDirectories() {
  51. if(directories != null) {
  52. return directories;
  53. }
  54. Vector fls = getFiles();
  55. return directories;
  56. }
  57. public Vector getFiles() {
  58. if(files != null) {
  59. return files;
  60. }
  61. files = new Vector();
  62. directories = new Vector();
  63. directories.addElement(filechooser.getFileSystemView().createFileObject(
  64. filechooser.getCurrentDirectory(), "..")
  65. );
  66. if(fileCache != null) {
  67. for(int i = 0; i < getSize(); i++) {
  68. File f = (File) fileCache.elementAt(i);
  69. if(filechooser.isTraversable(f)) {
  70. directories.addElement(f);
  71. } else {
  72. files.addElement(f);
  73. }
  74. }
  75. }
  76. return files;
  77. }
  78. public void validateFileCache() {
  79. File currentDirectory = filechooser.getCurrentDirectory();
  80. if(currentDirectory == null) {
  81. invalidateFileCache();
  82. return;
  83. }
  84. if(loadThread != null) {
  85. // interrupt
  86. loadThread.interrupt();
  87. }
  88. fetchID++;
  89. // PENDING(jeff) pick the size more sensibly
  90. invalidateFileCache();
  91. fileCache = new Vector(50);
  92. loadThread = new LoadFilesThread(currentDirectory, fetchID);
  93. loadThread.start();
  94. }
  95. // PENDING(jeff) - this is inefficient - should sent out
  96. // incremental adjustment values instead of saying that the
  97. // whole list has changed.
  98. public void fireContentsChanged() {
  99. // System.out.println("BasicDirectoryModel: firecontentschanged");
  100. files = null;
  101. directories = null;
  102. fireContentsChanged(this, 0, getSize()-1);
  103. }
  104. public int getSize() {
  105. if(fileCache != null) {
  106. return fileCache.size();
  107. } else {
  108. return 0;
  109. }
  110. }
  111. public boolean contains(Object o) {
  112. if(fileCache != null) {
  113. return fileCache.contains(o);
  114. } else {
  115. return false;
  116. }
  117. }
  118. public int indexOf(Object o) {
  119. if(fileCache != null) {
  120. return fileCache.indexOf(o);
  121. } else {
  122. return 0;
  123. }
  124. }
  125. public Object getElementAt(int index) {
  126. if(fileCache != null) {
  127. return fileCache.elementAt(index);
  128. } else {
  129. return null;
  130. }
  131. }
  132. // PENDING(jeff) - implement
  133. public void intervalAdded(ListDataEvent e) {
  134. }
  135. // PENDING(jeff) - implement
  136. public void intervalRemoved(ListDataEvent e) {
  137. }
  138. protected void sort(Vector v){
  139. quickSort(v, 0, v.size()-1);
  140. }
  141. // Liberated from the 1.1 SortDemo
  142. //
  143. // This is a generic version of C.A.R Hoare's Quick Sort
  144. // algorithm. This will handle arrays that are already
  145. // sorted, and arrays with duplicate keys.<BR>
  146. //
  147. // If you think of a one dimensional array as going from
  148. // the lowest index on the left to the highest index on the right
  149. // then the parameters to this function are lowest index or
  150. // left and highest index or right. The first time you call
  151. // this function it will be with the parameters 0, a.length - 1.
  152. //
  153. // @param a an integer array
  154. // @param lo0 left boundary of array partition
  155. // @param hi0 right boundary of array partition
  156. private void quickSort(Vector v, int lo0, int hi0) {
  157. int lo = lo0;
  158. int hi = hi0;
  159. File mid;
  160. if (hi0 > lo0) {
  161. // Arbitrarily establishing partition element as the midpoint of
  162. // the array.
  163. mid = (File) v.elementAt((lo0 + hi0) / 2);
  164. // loop through the array until indices cross
  165. while(lo <= hi) {
  166. // find the first element that is greater than or equal to
  167. // the partition element starting from the left Index.
  168. //
  169. // Nasty to have to cast here. Would it be quicker
  170. // to copy the vectors into arrays and sort the arrays?
  171. while((lo < hi0) && lt((File)v.elementAt(lo), mid)) {
  172. ++lo;
  173. }
  174. // find an element that is smaller than or equal to
  175. // the partition element starting from the right Index.
  176. while((hi > lo0) && lt(mid, (File)v.elementAt(hi))) {
  177. --hi;
  178. }
  179. // if the indexes have not crossed, swap
  180. if(lo <= hi) {
  181. swap(v, lo, hi);
  182. ++lo;
  183. --hi;
  184. }
  185. }
  186. // If the right index has not reached the left side of array
  187. // must now sort the left partition.
  188. if(lo0 < hi) {
  189. quickSort(v, lo0, hi);
  190. }
  191. // If the left index has not reached the right side of array
  192. // must now sort the right partition.
  193. if(lo < hi0) {
  194. quickSort(v, lo, hi0);
  195. }
  196. }
  197. }
  198. private void swap(Vector a, int i, int j) {
  199. Object T = a.elementAt(i);
  200. a.setElementAt(a.elementAt(j), i);
  201. a.setElementAt(T, j);
  202. }
  203. protected boolean lt(File a, File b) {
  204. // ignore case when comparing
  205. return a.getName().toLowerCase().compareTo(b.getName().toLowerCase()) < 0;
  206. }
  207. class LoadFilesThread extends Thread {
  208. File currentDirectory = null;
  209. int fid;
  210. public LoadFilesThread(File currentDirectory, int fid) {
  211. super("Basic L&F File Loading Thread");
  212. this.currentDirectory = currentDirectory;
  213. this.fid = fid;
  214. }
  215. public void run() {
  216. Vector runnables = new Vector(10);
  217. FileSystemView fileSystem = filechooser.getFileSystemView();
  218. File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());
  219. Vector acceptsList = new Vector();
  220. // run through the file list, add directories and selectable files to fileCache
  221. for (int i = 0; i < list.length; i++) {
  222. if(filechooser.accept(list[i])) {
  223. acceptsList.addElement(list[i]);
  224. }
  225. }
  226. // First sort alphabetically by filename
  227. sort(acceptsList);
  228. Vector directories = new Vector(10);
  229. Vector files = new Vector();
  230. // run through list grabbing directories in chunks of ten
  231. for(int i = 0; i < acceptsList.size(); i++) {
  232. File f = (File) acceptsList.elementAt(i);
  233. boolean isTraversable = filechooser.isTraversable(f);
  234. if(isTraversable) {
  235. directories.addElement(f);
  236. } else if(!isTraversable && filechooser.isFileSelectionEnabled()) {
  237. files.addElement(f);
  238. }
  239. if((directories.size() == 10) || (i == acceptsList.size()-1)) {
  240. DoChangeContents runnable = new DoChangeContents(directories, fid);
  241. runnables.addElement(runnable);
  242. SwingUtilities.invokeLater(runnable);
  243. directories = new Vector(10);
  244. }
  245. if(isInterrupted()) {
  246. // interrupted, cancel all runnables
  247. cancelRunnables(runnables);
  248. return;
  249. }
  250. }
  251. // PENDING(jeff) - run through the files in blocks instead of
  252. // sending them along as one big chunk
  253. DoChangeContents runnable = new DoChangeContents(files, fid);
  254. runnables.addElement(runnable);
  255. SwingUtilities.invokeLater(runnable);
  256. if(isInterrupted()) {
  257. // interrupted, blow out
  258. cancelRunnables(runnables);
  259. return;
  260. }
  261. }
  262. public void cancelRunnables(Vector runnables) {
  263. for(int i = 0; i < runnables.size(); i++) {
  264. ((DoChangeContents)runnables.elementAt(i)).cancel();
  265. }
  266. }
  267. }
  268. class DoChangeContents implements Runnable {
  269. private Vector files;
  270. private boolean doFire = true;
  271. private Object lock = new Object();
  272. private int fid;
  273. public DoChangeContents(Vector files, int fid) {
  274. this.files = files;
  275. this.fid = fid;
  276. }
  277. synchronized void cancel() {
  278. synchronized(lock) {
  279. doFire = false;
  280. }
  281. }
  282. public void run() {
  283. if(fetchID == fid) {
  284. synchronized(lock) {
  285. if(doFire) {
  286. if(fileCache != null) {
  287. for(int i = 0; i < files.size(); i++) {
  288. fileCache.addElement(files.elementAt(i));
  289. }
  290. }
  291. }
  292. fireContentsChanged();
  293. }
  294. }
  295. }
  296. }
  297. }