1 /* BasicDirectoryModel.java --
2    Copyright (C) 2005, 2006  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 package javax.swing.plaf.basic;
39 
40 import java.beans.PropertyChangeEvent;
41 import java.beans.PropertyChangeListener;
42 import java.io.File;
43 import java.util.Collections;
44 import java.util.Comparator;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.Vector;
48 import javax.swing.AbstractListModel;
49 import javax.swing.JFileChooser;
50 import javax.swing.SwingUtilities;
51 import javax.swing.event.ListDataEvent;
52 import javax.swing.filechooser.FileSystemView;
53 
54 
55 /**
56  * Implements an AbstractListModel for directories where the source
57  * of the files is a JFileChooser object.
58  *
59  * This class is used for sorting and ordering the file list in
60  * a JFileChooser L&F object.
61  */
62 public class BasicDirectoryModel extends AbstractListModel
63   implements PropertyChangeListener
64 {
65   /** The list of files itself */
66   private Vector contents;
67 
68   /**
69    * The directories in the list.
70    */
71   private Vector directories;
72 
73   /**
74    * The files in the list.
75    */
76   private Vector files;
77 
78   /** The listing mode of the associated JFileChooser,
79       either FILES_ONLY, DIRECTORIES_ONLY or FILES_AND_DIRECTORIES */
80   private int listingMode;
81 
82   /** The JFileCooser associated with this model */
83   private JFileChooser filechooser;
84 
85   /**
86    * The thread that loads the file view.
87    */
88   private DirectoryLoadThread loadThread;
89 
90   /**
91    * This thread is responsible for loading file lists from the
92    * current directory and updating the model.
93    */
94   private class DirectoryLoadThread extends Thread
95   {
96 
97     /**
98      * Updates the Swing list model.
99      */
100     private class UpdateSwingRequest
101       implements Runnable
102     {
103 
104       private List added;
105       private int addIndex;
106       private List removed;
107       private int removeIndex;
108       private boolean cancel;
109 
UpdateSwingRequest(List add, int ai, List rem, int ri)110       UpdateSwingRequest(List add, int ai, List rem, int ri)
111       {
112         added = add;
113         addIndex = ai;
114         removed = rem;
115         removeIndex = ri;
116         cancel = false;
117       }
118 
run()119       public void run()
120       {
121         if (! cancel)
122           {
123             int numRemoved = removed == null ? 0 : removed.size();
124             int numAdded = added == null ? 0 : added.size();
125             synchronized (contents)
126               {
127                 if (numRemoved > 0)
128                   contents.removeAll(removed);
129                 if (numAdded > 0)
130                   contents.addAll(added);
131 
132                 files = null;
133                 directories = null;
134               }
135             if (numRemoved > 0 && numAdded == 0)
136               fireIntervalRemoved(BasicDirectoryModel.this, removeIndex,
137                                   removeIndex + numRemoved - 1);
138             else if (numRemoved == 0 && numAdded > 0)
139               fireIntervalAdded(BasicDirectoryModel.this, addIndex,
140                                 addIndex + numAdded - 1);
141             else
142               fireContentsChanged();
143           }
144       }
145 
cancel()146       void cancel()
147       {
148         cancel = true;
149       }
150     }
151 
152     /**
153      * The directory beeing loaded.
154      */
155     File directory;
156 
157     /**
158      * Stores all UpdateSwingRequests that are sent to the event queue.
159      */
160     private UpdateSwingRequest pending;
161 
162     /**
163      * Creates a new DirectoryLoadThread that loads the specified
164      * directory.
165      *
166      * @param dir the directory to load
167      */
DirectoryLoadThread(File dir)168     DirectoryLoadThread(File dir)
169     {
170       super("Basic L&F directory loader");
171       directory = dir;
172     }
173 
run()174     public void run()
175     {
176       FileSystemView fsv = filechooser.getFileSystemView();
177       File[] files = fsv.getFiles(directory,
178                                   filechooser.isFileHidingEnabled());
179 
180       // Occasional check if we have been interrupted.
181       if (isInterrupted())
182         return;
183 
184       // Check list for accepted files.
185       Vector accepted = new Vector();
186       for (int i = 0; i < files.length; i++)
187         {
188           if (filechooser.accept(files[i]))
189             accepted.add(files[i]);
190         }
191 
192       // Occasional check if we have been interrupted.
193       if (isInterrupted())
194         return;
195 
196       // Sort list.
197       sort(accepted);
198 
199       // Now split up directories from files so that we get the directories
200       // listed before the files.
201       Vector newFiles = new Vector();
202       Vector newDirectories = new Vector();
203       for (Iterator i = accepted.iterator(); i.hasNext();)
204         {
205           File f = (File) i.next();
206           boolean traversable = filechooser.isTraversable(f);
207           if (traversable)
208             newDirectories.add(f);
209           else if (! traversable && filechooser.isFileSelectionEnabled())
210             newFiles.add(f);
211 
212           // Occasional check if we have been interrupted.
213           if (isInterrupted())
214             return;
215 
216         }
217 
218       // Build up new file cache. Try to update only the changed elements.
219       // This will be important for actions like adding new files or
220       // directories inside a large file list.
221       Vector newCache = new Vector(newDirectories);
222       newCache.addAll(newFiles);
223 
224       int newSize = newCache.size();
225       int oldSize = contents.size();
226       if (newSize < oldSize)
227         {
228           // Check for removed interval.
229           int start = -1;
230           int end = -1;
231           boolean found = false;
232           for (int i = 0; i < newSize && !found; i++)
233             {
234               if (! newCache.get(i).equals(contents.get(i)))
235                 {
236                   start = i;
237                   end = i + oldSize - newSize;
238                   found = true;
239                 }
240             }
241           if (start >= 0 && end > start
242               && contents.subList(end, oldSize)
243                                     .equals(newCache.subList(start, newSize)))
244             {
245               // Occasional check if we have been interrupted.
246               if (isInterrupted())
247                 return;
248 
249               Vector removed = new Vector(contents.subList(start, end));
250               UpdateSwingRequest r = new UpdateSwingRequest(null, 0,
251                                                             removed, start);
252               invokeLater(r);
253               newCache = null;
254             }
255         }
256       else if (newSize > oldSize)
257         {
258           // Check for inserted interval.
259           int start = oldSize;
260           int end = newSize;
261           boolean found = false;
262           for (int i = 0; i < oldSize && ! found; i++)
263             {
264               if (! newCache.get(i).equals(contents.get(i)))
265                 {
266                   start = i;
267                   boolean foundEnd = false;
268                   for (int j = i; j < newSize && ! foundEnd; j++)
269                     {
270                       if (newCache.get(j).equals(contents.get(i)))
271                         {
272                           end = j;
273                           foundEnd = true;
274                         }
275                     }
276                   end = i + oldSize - newSize;
277                 }
278             }
279           if (start >= 0 && end > start
280               && newCache.subList(end, newSize)
281                                     .equals(contents.subList(start, oldSize)))
282             {
283               // Occasional check if we have been interrupted.
284               if (isInterrupted())
285                 return;
286 
287               List added = newCache.subList(start, end);
288               UpdateSwingRequest r = new UpdateSwingRequest(added, start,
289                                                             null, 0);
290               invokeLater(r);
291               newCache = null;
292             }
293         }
294 
295       // Handle complete list changes (newCache != null).
296       if (newCache != null && ! contents.equals(newCache))
297         {
298           // Occasional check if we have been interrupted.
299           if (isInterrupted())
300             return;
301           UpdateSwingRequest r = new UpdateSwingRequest(newCache, 0,
302                                                         contents, 0);
303           invokeLater(r);
304         }
305     }
306 
307     /**
308      * Wraps SwingUtilities.invokeLater() and stores the request in
309      * a Vector so that we can still cancel it later.
310      *
311      * @param update the request to invoke
312      */
invokeLater(UpdateSwingRequest update)313     private void invokeLater(UpdateSwingRequest update)
314     {
315       pending = update;
316       SwingUtilities.invokeLater(update);
317     }
318 
319     /**
320      * Cancels all pending update requests that might be in the AWT
321      * event queue.
322      */
cancelPending()323     void cancelPending()
324     {
325       if (pending != null)
326         pending.cancel();
327     }
328   }
329 
330   /** A Comparator class/object for sorting the file list. */
331   private Comparator comparator = new Comparator()
332     {
333       public int compare(Object o1, Object o2)
334       {
335         if (lt((File) o1, (File) o2))
336           return -1;
337         else
338           return 1;
339       }
340     };
341 
342   /**
343    * Creates a new BasicDirectoryModel object.
344    *
345    * @param filechooser DOCUMENT ME!
346    */
BasicDirectoryModel(JFileChooser filechooser)347   public BasicDirectoryModel(JFileChooser filechooser)
348   {
349     this.filechooser = filechooser;
350     filechooser.addPropertyChangeListener(this);
351     listingMode = filechooser.getFileSelectionMode();
352     contents = new Vector();
353     validateFileCache();
354   }
355 
356   /**
357    * Returns whether a given (File) object is included in the list.
358    *
359    * @param o - The file object to test.
360    *
361    * @return <code>true</code> if the list contains the given object.
362    */
contains(Object o)363   public boolean contains(Object o)
364   {
365     return contents.contains(o);
366   }
367 
368   /**
369    * Fires a content change event.
370    */
fireContentsChanged()371   public void fireContentsChanged()
372   {
373     fireContentsChanged(this, 0, getSize() - 1);
374   }
375 
376   /**
377    * Returns a Vector of (java.io.File) objects containing
378    * the directories in this list.
379    *
380    * @return a Vector
381    */
getDirectories()382   public Vector<File> getDirectories()
383   {
384     // Synchronize this with the UpdateSwingRequest for the case when
385     // contents is modified.
386     synchronized (contents)
387       {
388         Vector dirs = directories;
389         if (dirs == null)
390           {
391             // Initializes this in getFiles().
392             getFiles();
393             dirs = directories;
394           }
395         return dirs;
396       }
397   }
398 
399   /**
400    * Returns the (java.io.File) object at
401    * an index in the list.
402    *
403    * @param index The list index
404    * @return a File object
405    */
getElementAt(int index)406   public Object getElementAt(int index)
407   {
408     if (index > getSize() - 1)
409       return null;
410     return contents.elementAt(index);
411   }
412 
413   /**
414    * Returns a Vector of (java.io.File) objects containing
415    * the files in this list.
416    *
417    * @return a Vector
418    */
getFiles()419   public Vector<File>  getFiles()
420   {
421     synchronized (contents)
422       {
423         Vector f = files;
424         if (f == null)
425           {
426             f = new Vector();
427             Vector d = new Vector(); // Directories;
428             for (Iterator i = contents.iterator(); i.hasNext();)
429               {
430                 File file = (File) i.next();
431                 if (filechooser.isTraversable(file))
432                   d.add(file);
433                 else
434                   f.add(file);
435               }
436             files = f;
437             directories = d;
438           }
439         return f;
440       }
441   }
442 
443   /**
444    * Returns the size of the list, which only includes directories
445    * if the JFileChooser is set to DIRECTORIES_ONLY.
446    *
447    * Otherwise, both directories and files are included in the count.
448    *
449    * @return The size of the list.
450    */
getSize()451   public int getSize()
452   {
453     return contents.size();
454   }
455 
456   /**
457    * Returns the index of an (java.io.File) object in the list.
458    *
459    * @param o The object - normally a File.
460    *
461    * @return the index of that object, or -1 if it is not in the list.
462    */
indexOf(Object o)463   public int indexOf(Object o)
464   {
465     return contents.indexOf(o);
466   }
467 
468   /**
469    * Obsoleted method which does nothing.
470    */
intervalAdded(ListDataEvent e)471   public void intervalAdded(ListDataEvent e)
472   {
473     // obsoleted
474   }
475 
476   /**
477    * Obsoleted method which does nothing.
478    */
intervalRemoved(ListDataEvent e)479   public void intervalRemoved(ListDataEvent e)
480   {
481     // obsoleted
482   }
483 
484   /**
485    * Obsoleted method which does nothing.
486    */
invalidateFileCache()487   public void invalidateFileCache()
488   {
489     // obsoleted
490   }
491 
492   /**
493    * Less than, determine the relative order in the list of two files
494    * for sorting purposes.
495    *
496    * The order is: directories < files, and thereafter alphabetically,
497    * using the default locale collation.
498    *
499    * @param a the first file
500    * @param b the second file
501    *
502    * @return <code>true</code> if a > b, <code>false</code> if a < b.
503    */
lt(File a, File b)504   protected boolean lt(File a, File b)
505   {
506     boolean aTrav = filechooser.isTraversable(a);
507     boolean bTrav = filechooser.isTraversable(b);
508 
509     if (aTrav == bTrav)
510       {
511         String aname = a.getName().toLowerCase();
512         String bname = b.getName().toLowerCase();
513         return (aname.compareTo(bname) < 0) ? true : false;
514       }
515     else
516       {
517         if (aTrav)
518           return true;
519         else
520           return false;
521       }
522   }
523 
524   /**
525    * Listens for a property change; the change in file selection mode of the
526    * associated JFileChooser. Reloads the file cache on that event.
527    *
528    * @param e - A PropertyChangeEvent.
529    */
propertyChange(PropertyChangeEvent e)530   public void propertyChange(PropertyChangeEvent e)
531   {
532     String property = e.getPropertyName();
533     if (property.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)
534         || property.equals(JFileChooser.FILE_FILTER_CHANGED_PROPERTY)
535         || property.equals(JFileChooser.FILE_HIDING_CHANGED_PROPERTY)
536         || property.equals(JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY)
537         || property.equals(JFileChooser.FILE_VIEW_CHANGED_PROPERTY)
538         )
539       {
540         validateFileCache();
541       }
542   }
543 
544   /**
545    * Renames a file - However, does <I>not</I> re-sort the list
546    * or replace the old file with the new one in the list.
547    *
548    * @param oldFile The old file
549    * @param newFile The new file name
550    *
551    * @return <code>true</code> if the rename succeeded
552    */
renameFile(File oldFile, File newFile)553   public boolean renameFile(File oldFile, File newFile)
554   {
555     return oldFile.renameTo( newFile );
556   }
557 
558   /**
559    * Sorts a Vector of File objects.
560    *
561    * @param v The Vector to sort.
562    */
sort(Vector<? extends File> v)563   protected void sort(Vector<? extends File> v)
564   {
565     Collections.sort(v, comparator);
566   }
567 
568   /**
569    * Re-loads the list of files
570    */
validateFileCache()571   public void validateFileCache()
572   {
573     File dir = filechooser.getCurrentDirectory();
574     if (dir != null)
575       {
576         // Cancel all pending requests.
577         if (loadThread != null)
578           {
579             loadThread.interrupt();
580             loadThread.cancelPending();
581           }
582         loadThread = new DirectoryLoadThread(dir);
583         loadThread.start();
584       }
585   }
586 }
587