001/* BasicDirectoryModel.java -- 002 Copyright (C) 2005, 2006 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038package javax.swing.plaf.basic; 039 040import java.beans.PropertyChangeEvent; 041import java.beans.PropertyChangeListener; 042import java.io.File; 043import java.util.Collections; 044import java.util.Comparator; 045import java.util.Iterator; 046import java.util.List; 047import java.util.Vector; 048import javax.swing.AbstractListModel; 049import javax.swing.JFileChooser; 050import javax.swing.SwingUtilities; 051import javax.swing.event.ListDataEvent; 052import javax.swing.filechooser.FileSystemView; 053 054 055/** 056 * Implements an AbstractListModel for directories where the source 057 * of the files is a JFileChooser object. 058 * 059 * This class is used for sorting and ordering the file list in 060 * a JFileChooser L&F object. 061 */ 062public class BasicDirectoryModel extends AbstractListModel 063 implements PropertyChangeListener 064{ 065 /** The list of files itself */ 066 private Vector contents; 067 068 /** 069 * The directories in the list. 070 */ 071 private Vector directories; 072 073 /** 074 * The files in the list. 075 */ 076 private Vector files; 077 078 /** The listing mode of the associated JFileChooser, 079 either FILES_ONLY, DIRECTORIES_ONLY or FILES_AND_DIRECTORIES */ 080 private int listingMode; 081 082 /** The JFileCooser associated with this model */ 083 private JFileChooser filechooser; 084 085 /** 086 * The thread that loads the file view. 087 */ 088 private DirectoryLoadThread loadThread; 089 090 /** 091 * This thread is responsible for loading file lists from the 092 * current directory and updating the model. 093 */ 094 private class DirectoryLoadThread extends Thread 095 { 096 097 /** 098 * Updates the Swing list model. 099 */ 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 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 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 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 */ 168 DirectoryLoadThread(File dir) 169 { 170 super("Basic L&F directory loader"); 171 directory = dir; 172 } 173 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 */ 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 */ 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 */ 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 */ 363 public boolean contains(Object o) 364 { 365 return contents.contains(o); 366 } 367 368 /** 369 * Fires a content change event. 370 */ 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 */ 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 */ 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 */ 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 */ 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 */ 463 public int indexOf(Object o) 464 { 465 return contents.indexOf(o); 466 } 467 468 /** 469 * Obsoleted method which does nothing. 470 */ 471 public void intervalAdded(ListDataEvent e) 472 { 473 // obsoleted 474 } 475 476 /** 477 * Obsoleted method which does nothing. 478 */ 479 public void intervalRemoved(ListDataEvent e) 480 { 481 // obsoleted 482 } 483 484 /** 485 * Obsoleted method which does nothing. 486 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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