1 /* 2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.apple.laf; 27 28 29 import java.awt.ComponentOrientation; 30 import java.beans.*; 31 import java.io.File; 32 import java.util.*; 33 import javax.swing.*; 34 import javax.swing.event.ListDataEvent; 35 import javax.swing.filechooser.FileSystemView; 36 import javax.swing.table.AbstractTableModel; 37 38 /** 39 * NavServices-like implementation of a file Table 40 * 41 * Some of it came from BasicDirectoryModel 42 */ 43 @SuppressWarnings("serial") // Superclass is not serializable across versions 44 class AquaFileSystemModel extends AbstractTableModel implements PropertyChangeListener { 45 private final JTable fFileList; 46 private FilesLoader filesLoader = null; 47 private Vector<File> files = null; 48 49 JFileChooser filechooser = null; 50 Vector<SortableFile> fileCache = null; 51 Object fileCacheLock; 52 53 Vector<File> directories = null; 54 int fetchID = 0; 55 56 private final boolean[] fSortAscending = {true, true}; 57 // private boolean fSortAscending = true; 58 private boolean fSortNames = true; 59 private final String[] fColumnNames; 60 public static final String SORT_BY_CHANGED = "sortByChanged"; 61 public static final String SORT_ASCENDING_CHANGED = "sortAscendingChanged"; 62 63 public AquaFileSystemModel(final JFileChooser filechooser, final JTable filelist, final String[] colNames) { 64 fileCacheLock = new Object(); 65 this.filechooser = filechooser; 66 fFileList = filelist; 67 fColumnNames = colNames; 68 validateFileCache(); 69 updateSelectionMode(); 70 } 71 72 void updateSelectionMode() { 73 // Save dialog lists can't be multi select, because all we're selecting is the next folder to open 74 final boolean b = filechooser.isMultiSelectionEnabled() && filechooser.getDialogType() != JFileChooser.SAVE_DIALOG; 75 fFileList.setSelectionMode(b ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION); 76 } 77 78 public void propertyChange(final PropertyChangeEvent e) { 79 final String prop = e.getPropertyName(); 80 if (prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY || prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY || prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY || prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY) { 81 invalidateFileCache(); 82 validateFileCache(); 83 } else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) { 84 updateSelectionMode(); 85 } else if (prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) { 86 invalidateFileCache(); 87 validateFileCache(); 88 } else if (prop.equals("componentOrientation")) { 89 ComponentOrientation o = (ComponentOrientation) e.getNewValue(); 90 JFileChooser cc = (JFileChooser) e.getSource(); 91 if (o != e.getOldValue()) { 92 cc.applyComponentOrientation(o); 93 } 94 fFileList.setComponentOrientation(o); 95 fFileList.getParent().getParent().setComponentOrientation(o); 96 97 } 98 if (prop == SORT_BY_CHANGED) {// $ Ought to just resort 99 fSortNames = (((Integer)e.getNewValue()).intValue() == 0); 100 invalidateFileCache(); 101 validateFileCache(); 102 fFileList.repaint(); 103 } 104 if (prop == SORT_ASCENDING_CHANGED) { 105 final int sortColumn = (fSortNames ? 0 : 1); 106 fSortAscending[sortColumn] = ((Boolean)e.getNewValue()).booleanValue(); 107 invalidateFileCache(); 108 validateFileCache(); 109 fFileList.repaint(); 110 } 111 } 112 113 public void invalidateFileCache() { 114 files = null; 115 directories = null; 116 117 synchronized(fileCacheLock) { 118 if (fileCache != null) { 119 final int lastRow = fileCache.size(); 120 fileCache = null; 121 fireTableRowsDeleted(0, lastRow); 122 } 123 } 124 } 125 126 public Vector<File> getDirectories() { 127 if (directories != null) { return directories; } 128 return directories; 129 } 130 131 public Vector<File> getFiles() { 132 if (files != null) { return files; } 133 files = new Vector<File>(); 134 directories = new Vector<File>(); 135 directories.addElement(filechooser.getFileSystemView().createFileObject(filechooser.getCurrentDirectory(), "..")); 136 137 synchronized(fileCacheLock) { 138 for (int i = 0; i < fileCache.size(); i++) { 139 final SortableFile sf = fileCache.elementAt(i); 140 final File f = sf.fFile; 141 if (filechooser.isTraversable(f)) { 142 directories.addElement(f); 143 } else { 144 files.addElement(f); 145 } 146 } 147 } 148 149 return files; 150 } 151 152 public void runWhenDone(final Runnable runnable){ 153 synchronized (fileCacheLock) { 154 if (filesLoader != null) { 155 if (filesLoader.loadThread.isAlive()) { 156 filesLoader.queuedTasks.add(runnable); 157 return; 158 } 159 } 160 161 SwingUtilities.invokeLater(runnable); 162 } 163 } 164 165 public void validateFileCache() { 166 final File currentDirectory = filechooser.getCurrentDirectory(); 167 168 if (currentDirectory == null) { 169 invalidateFileCache(); 170 return; 171 } 172 173 if (filesLoader != null) { 174 // interrupt 175 filesLoader.loadThread.interrupt(); 176 } 177 178 fetchID++; 179 180 // PENDING(jeff) pick the size more sensibly 181 invalidateFileCache(); 182 synchronized(fileCacheLock) { 183 fileCache = new Vector<SortableFile>(50); 184 } 185 186 filesLoader = new FilesLoader(currentDirectory, fetchID); 187 } 188 189 public int getColumnCount() { 190 return 2; 191 } 192 193 public String getColumnName(final int col) { 194 return fColumnNames[col]; 195 } 196 197 public Class<? extends Object> getColumnClass(final int col) { 198 if (col == 0) return File.class; 199 return Date.class; 200 } 201 202 public int getRowCount() { 203 synchronized(fileCacheLock) { 204 if (fileCache != null) { 205 return fileCache.size(); 206 } 207 return 0; 208 } 209 } 210 211 // SAK: Part of fix for 3168263. The fileCache contains 212 // SortableFiles, so when finding a file in the list we need to 213 // first create a sortable file. 214 public boolean contains(final File o) { 215 synchronized(fileCacheLock) { 216 if (fileCache != null) { 217 return fileCache.contains(new SortableFile(o)); 218 } 219 return false; 220 } 221 } 222 223 public int indexOf(final File o) { 224 synchronized(fileCacheLock) { 225 if (fileCache != null) { 226 final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1]; 227 final int row = fileCache.indexOf(new SortableFile(o)); 228 return isAscending ? row : fileCache.size() - row - 1; 229 } 230 return 0; 231 } 232 } 233 234 // AbstractListModel interface 235 public Object getElementAt(final int row) { 236 return getValueAt(row, 0); 237 } 238 239 // AbstractTableModel interface 240 241 public Object getValueAt(int row, final int col) { 242 if (row < 0 || col < 0) return null; 243 final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1]; 244 synchronized(fileCacheLock) { 245 if (fileCache != null) { 246 if (!isAscending) row = fileCache.size() - row - 1; 247 return fileCache.elementAt(row).getValueAt(col); 248 } 249 return null; 250 } 251 } 252 253 // PENDING(jeff) - implement 254 public void intervalAdded(final ListDataEvent e) { 255 } 256 257 // PENDING(jeff) - implement 258 public void intervalRemoved(final ListDataEvent e) { 259 } 260 261 protected void sort(final Vector<Object> v) { 262 if (fSortNames) sSortNames.quickSort(v, 0, v.size() - 1); 263 else sSortDates.quickSort(v, 0, v.size() - 1); 264 } 265 266 // Liberated from the 1.1 SortDemo 267 // 268 // This is a generic version of C.A.R Hoare's Quick Sort 269 // algorithm. This will handle arrays that are already 270 // sorted, and arrays with duplicate keys.<BR> 271 // 272 // If you think of a one dimensional array as going from 273 // the lowest index on the left to the highest index on the right 274 // then the parameters to this function are lowest index or 275 // left and highest index or right. The first time you call 276 // this function it will be with the parameters 0, a.length - 1. 277 // 278 // @param a an integer array 279 // @param lo0 left boundary of array partition 280 // @param hi0 right boundary of array partition 281 abstract class QuickSort { 282 final void quickSort(final Vector<Object> v, final int lo0, final int hi0) { 283 int lo = lo0; 284 int hi = hi0; 285 SortableFile mid; 286 287 if (hi0 > lo0) { 288 // Arbitrarily establishing partition element as the midpoint of 289 // the array. 290 mid = (SortableFile)v.elementAt((lo0 + hi0) / 2); 291 292 // loop through the array until indices cross 293 while (lo <= hi) { 294 // find the first element that is greater than or equal to 295 // the partition element starting from the left Index. 296 // 297 // Nasty to have to cast here. Would it be quicker 298 // to copy the vectors into arrays and sort the arrays? 299 while ((lo < hi0) && lt((SortableFile)v.elementAt(lo), mid)) { 300 ++lo; 301 } 302 303 // find an element that is smaller than or equal to 304 // the partition element starting from the right Index. 305 while ((hi > lo0) && lt(mid, (SortableFile)v.elementAt(hi))) { 306 --hi; 307 } 308 309 // if the indexes have not crossed, swap 310 if (lo <= hi) { 311 swap(v, lo, hi); 312 ++lo; 313 --hi; 314 } 315 } 316 317 // If the right index has not reached the left side of array 318 // must now sort the left partition. 319 if (lo0 < hi) { 320 quickSort(v, lo0, hi); 321 } 322 323 // If the left index has not reached the right side of array 324 // must now sort the right partition. 325 if (lo < hi0) { 326 quickSort(v, lo, hi0); 327 } 328 329 } 330 } 331 332 private void swap(final Vector<Object> a, final int i, final int j) { 333 final Object T = a.elementAt(i); 334 a.setElementAt(a.elementAt(j), i); 335 a.setElementAt(T, j); 336 } 337 338 protected abstract boolean lt(SortableFile a, SortableFile b); 339 } 340 341 class QuickSortNames extends QuickSort { 342 protected boolean lt(final SortableFile a, final SortableFile b) { 343 final String aLower = a.fName.toLowerCase(); 344 final String bLower = b.fName.toLowerCase(); 345 return aLower.compareTo(bLower) < 0; 346 } 347 } 348 349 class QuickSortDates extends QuickSort { 350 protected boolean lt(final SortableFile a, final SortableFile b) { 351 return a.fDateValue < b.fDateValue; 352 } 353 } 354 355 // for speed in sorting, displaying 356 class SortableFile /* extends FileView */{ 357 File fFile; 358 String fName; 359 long fDateValue; 360 Date fDate; 361 362 SortableFile(final File f) { 363 fFile = f; 364 fName = fFile.getName(); 365 fDateValue = fFile.lastModified(); 366 fDate = new Date(fDateValue); 367 } 368 369 public Object getValueAt(final int col) { 370 if (col == 0) return fFile; 371 return fDate; 372 } 373 374 public boolean equals(final Object other) { 375 final SortableFile otherFile = (SortableFile)other; 376 return otherFile.fFile.equals(fFile); 377 } 378 379 @Override 380 public int hashCode() { 381 return Objects.hashCode(fFile); 382 } 383 } 384 385 class FilesLoader implements Runnable { 386 Vector<Runnable> queuedTasks = new Vector<>(); 387 File currentDirectory = null; 388 int fid; 389 Thread loadThread; 390 391 public FilesLoader(final File currentDirectory, final int fid) { 392 this.currentDirectory = currentDirectory; 393 this.fid = fid; 394 String name = "Aqua L&F File Loading Thread"; 395 this.loadThread = new Thread(null, this, name, 0, false); 396 this.loadThread.start(); 397 } 398 399 @Override 400 public void run() { 401 final Vector<DoChangeContents> runnables = new Vector<DoChangeContents>(10); 402 final FileSystemView fileSystem = filechooser.getFileSystemView(); 403 404 final File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled()); 405 406 final Vector<Object> acceptsList = new Vector<Object>(); 407 408 for (final File element : list) { 409 // Return all files to the file chooser. The UI will disable or enable 410 // the file name if the current filter approves. 411 acceptsList.addElement(new SortableFile(element)); 412 } 413 414 // Sort based on settings. 415 sort(acceptsList); 416 417 // Don't separate directories from files 418 Vector<SortableFile> chunk = new Vector<SortableFile>(10); 419 final int listSize = acceptsList.size(); 420 // run through list grabbing file/dirs in chunks of ten 421 for (int i = 0; i < listSize;) { 422 SortableFile f; 423 for (int j = 0; j < 10 && i < listSize; j++, i++) { 424 f = (SortableFile)acceptsList.elementAt(i); 425 chunk.addElement(f); 426 } 427 final DoChangeContents runnable = new DoChangeContents(chunk, fid); 428 runnables.addElement(runnable); 429 SwingUtilities.invokeLater(runnable); 430 chunk = new Vector<SortableFile>(10); 431 if (loadThread.isInterrupted()) { 432 // interrupted, cancel all runnables 433 cancelRunnables(runnables); 434 return; 435 } 436 } 437 438 synchronized (fileCacheLock) { 439 for (final Runnable r : queuedTasks) { 440 SwingUtilities.invokeLater(r); 441 } 442 } 443 } 444 445 public void cancelRunnables(final Vector<DoChangeContents> runnables) { 446 for (int i = 0; i < runnables.size(); i++) { 447 runnables.elementAt(i).cancel(); 448 } 449 } 450 } 451 452 class DoChangeContents implements Runnable { 453 private Vector<SortableFile> contentFiles; 454 private boolean doFire = true; 455 private final Object lock = new Object(); 456 private final int fid; 457 458 public DoChangeContents(final Vector<SortableFile> files, final int fid) { 459 this.contentFiles = files; 460 this.fid = fid; 461 } 462 463 synchronized void cancel() { 464 synchronized(lock) { 465 doFire = false; 466 } 467 } 468 469 public void run() { 470 if (fetchID == fid) { 471 synchronized(lock) { 472 if (doFire) { 473 synchronized(fileCacheLock) { 474 if (fileCache != null) { 475 for (int i = 0; i < contentFiles.size(); i++) { 476 fileCache.addElement(contentFiles.elementAt(i)); 477 fireTableRowsInserted(i, i); 478 } 479 } 480 } 481 } 482 contentFiles = null; 483 directories = null; 484 } 485 } 486 } 487 } 488 489 final QuickSortNames sSortNames = new QuickSortNames(); 490 final QuickSortDates sSortDates = new QuickSortDates(); 491 }