Source for javax.swing.plaf.basic.BasicDirectoryModel

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