1 /* 2 * Copyright (c) 2000, 2014, 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 sun.awt.shell; 27 28 import javax.swing.*; 29 import java.awt.Image; 30 import java.awt.Toolkit; 31 import java.io.*; 32 import java.io.FileNotFoundException; 33 import java.util.*; 34 import java.util.concurrent.Callable; 35 36 /** 37 * @author Michael Martak 38 * @since 1.4 39 */ 40 @SuppressWarnings("serial") // JDK-implementation class 41 public abstract class ShellFolder extends File { 42 private static final String COLUMN_NAME = "FileChooser.fileNameHeaderText"; 43 private static final String COLUMN_SIZE = "FileChooser.fileSizeHeaderText"; 44 private static final String COLUMN_DATE = "FileChooser.fileDateHeaderText"; 45 46 protected ShellFolder parent; 47 48 /** 49 * Create a file system shell folder from a file 50 */ 51 ShellFolder(ShellFolder parent, String pathname) { 52 super((pathname != null) ? pathname : "ShellFolder"); 53 this.parent = parent; 54 } 55 56 /** 57 * @return Whether this is a file system shell folder 58 */ 59 public boolean isFileSystem() { 60 return (!getPath().startsWith("ShellFolder")); 61 } 62 63 /** 64 * This method must be implemented to make sure that no instances 65 * of <code>ShellFolder</code> are ever serialized. If <code>isFileSystem()</code> returns 66 * <code>true</code>, then the object should be representable with an instance of 67 * <code>java.io.File</code> instead. If not, then the object is most likely 68 * depending on some internal (native) state and cannot be serialized. 69 * 70 * @return a java.io.File replacement object, or null 71 * if no suitable replacement can be found. 72 */ 73 protected abstract Object writeReplace() throws java.io.ObjectStreamException; 74 75 /** 76 * Returns the path for this object's parent, 77 * or <code>null</code> if this object does not name a parent 78 * folder. 79 * 80 * @return the path as a String for this object's parent, 81 * or <code>null</code> if this object does not name a parent 82 * folder 83 * 84 * @see java.io.File#getParent() 85 * @since 1.4 86 */ 87 public String getParent() { 88 if (parent == null && isFileSystem()) { 89 return super.getParent(); 90 } 91 if (parent != null) { 92 return (parent.getPath()); 93 } else { 94 return null; 95 } 96 } 97 98 /** 99 * Returns a File object representing this object's parent, 100 * or <code>null</code> if this object does not name a parent 101 * folder. 102 * 103 * @return a File object representing this object's parent, 104 * or <code>null</code> if this object does not name a parent 105 * folder 106 * 107 * @see java.io.File#getParentFile() 108 * @since 1.4 109 */ 110 public File getParentFile() { 111 if (parent != null) { 112 return parent; 113 } else if (isFileSystem()) { 114 return super.getParentFile(); 115 } else { 116 return null; 117 } 118 } 119 120 public File[] listFiles() { 121 return listFiles(true); 122 } 123 124 public File[] listFiles(boolean includeHiddenFiles) { 125 File[] files = super.listFiles(); 126 127 if (!includeHiddenFiles) { 128 Vector<File> v = new Vector<>(); 129 int nameCount = (files == null) ? 0 : files.length; 130 for (int i = 0; i < nameCount; i++) { 131 if (!files[i].isHidden()) { 132 v.addElement(files[i]); 133 } 134 } 135 files = v.toArray(new File[v.size()]); 136 } 137 138 return files; 139 } 140 141 142 /** 143 * @return Whether this shell folder is a link 144 */ 145 public abstract boolean isLink(); 146 147 /** 148 * @return The shell folder linked to by this shell folder, or null 149 * if this shell folder is not a link 150 */ 151 public abstract ShellFolder getLinkLocation() throws FileNotFoundException; 152 153 /** 154 * @return The name used to display this shell folder 155 */ 156 public abstract String getDisplayName(); 157 158 /** 159 * @return The type of shell folder as a string 160 */ 161 public abstract String getFolderType(); 162 163 /** 164 * @return The executable type as a string 165 */ 166 public abstract String getExecutableType(); 167 168 /** 169 * Compares this ShellFolder with the specified ShellFolder for order. 170 * 171 * @see #compareTo(Object) 172 */ 173 public int compareTo(File file2) { 174 if (file2 == null || !(file2 instanceof ShellFolder) 175 || ((file2 instanceof ShellFolder) && ((ShellFolder)file2).isFileSystem())) { 176 177 if (isFileSystem()) { 178 return super.compareTo(file2); 179 } else { 180 return -1; 181 } 182 } else { 183 if (isFileSystem()) { 184 return 1; 185 } else { 186 return getName().compareTo(file2.getName()); 187 } 188 } 189 } 190 191 /** 192 * @param getLargeIcon whether to return large icon (ignored in base implementation) 193 * @return The icon used to display this shell folder 194 */ 195 public Image getIcon(boolean getLargeIcon) { 196 return null; 197 } 198 199 200 // Static 201 202 private static final ShellFolderManager shellFolderManager; 203 204 private static final Invoker invoker; 205 206 static { 207 String managerClassName = (String)Toolkit.getDefaultToolkit(). 208 getDesktopProperty("Shell.shellFolderManager"); 209 Class<?> managerClass = null; 210 try { 211 managerClass = Class.forName(managerClassName, false, null); 212 if (!ShellFolderManager.class.isAssignableFrom(managerClass)) { 213 managerClass = null; 214 } 215 // swallow the exceptions below and use default shell folder 216 } catch(ClassNotFoundException e) { 217 } catch(NullPointerException e) { 218 } catch(SecurityException e) { 219 } 220 221 if (managerClass == null) { 222 managerClass = ShellFolderManager.class; 223 } 224 try { 225 shellFolderManager = 226 (ShellFolderManager)managerClass.newInstance(); 227 } catch (InstantiationException e) { 228 throw new Error("Could not instantiate Shell Folder Manager: " 229 + managerClass.getName()); 230 } catch (IllegalAccessException e) { 231 throw new Error ("Could not access Shell Folder Manager: " 232 + managerClass.getName()); 233 } 234 235 invoker = shellFolderManager.createInvoker(); 236 } 237 238 /** 239 * Return a shell folder from a file object 240 * @exception FileNotFoundException if file does not exist 241 */ 242 public static ShellFolder getShellFolder(File file) throws FileNotFoundException { 243 if (file instanceof ShellFolder) { 244 return (ShellFolder)file; 245 } 246 if (!file.exists()) { 247 throw new FileNotFoundException(); 248 } 249 return shellFolderManager.createShellFolder(file); 250 } 251 252 /** 253 * @param key a <code>String</code> 254 * @return An Object matching the string <code>key</code>. 255 * @see ShellFolderManager#get(String) 256 */ 257 public static Object get(String key) { 258 return shellFolderManager.get(key); 259 } 260 261 /** 262 * Does <code>dir</code> represent a "computer" such as a node on the network, or 263 * "My Computer" on the desktop. 264 */ 265 public static boolean isComputerNode(File dir) { 266 return shellFolderManager.isComputerNode(dir); 267 } 268 269 /** 270 * @return Whether this is a file system root directory 271 */ 272 public static boolean isFileSystemRoot(File dir) { 273 return shellFolderManager.isFileSystemRoot(dir); 274 } 275 276 /** 277 * Canonicalizes files that don't have symbolic links in their path. 278 * Normalizes files that do, preserving symbolic links from being resolved. 279 */ 280 public static File getNormalizedFile(File f) throws IOException { 281 File canonical = f.getCanonicalFile(); 282 if (f.equals(canonical)) { 283 // path of f doesn't contain symbolic links 284 return canonical; 285 } 286 287 // preserve symbolic links from being resolved 288 return new File(f.toURI().normalize()); 289 } 290 291 // Override File methods 292 293 public static void sort(final List<? extends File> files) { 294 if (files == null || files.size() <= 1) { 295 return; 296 } 297 298 // To avoid loads of synchronizations with Invoker and improve performance we 299 // synchronize the whole code of the sort method once 300 invoke(new Callable<Void>() { 301 public Void call() { 302 // Check that we can use the ShellFolder.sortChildren() method: 303 // 1. All files have the same non-null parent 304 // 2. All files is ShellFolders 305 File commonParent = null; 306 307 for (File file : files) { 308 File parent = file.getParentFile(); 309 310 if (parent == null || !(file instanceof ShellFolder)) { 311 commonParent = null; 312 313 break; 314 } 315 316 if (commonParent == null) { 317 commonParent = parent; 318 } else { 319 if (commonParent != parent && !commonParent.equals(parent)) { 320 commonParent = null; 321 322 break; 323 } 324 } 325 } 326 327 if (commonParent instanceof ShellFolder) { 328 ((ShellFolder) commonParent).sortChildren(files); 329 } else { 330 Collections.sort(files, FILE_COMPARATOR); 331 } 332 333 return null; 334 } 335 }); 336 } 337 338 public void sortChildren(final List<? extends File> files) { 339 // To avoid loads of synchronizations with Invoker and improve performance we 340 // synchronize the whole code of the sort method once 341 invoke(new Callable<Void>() { 342 public Void call() { 343 Collections.sort(files, FILE_COMPARATOR); 344 345 return null; 346 } 347 }); 348 } 349 350 public boolean isAbsolute() { 351 return (!isFileSystem() || super.isAbsolute()); 352 } 353 354 public File getAbsoluteFile() { 355 return (isFileSystem() ? super.getAbsoluteFile() : this); 356 } 357 358 public boolean canRead() { 359 return (isFileSystem() ? super.canRead() : true); // ((Fix?)) 360 } 361 362 /** 363 * Returns true if folder allows creation of children. 364 * True for the "Desktop" folder, but false for the "My Computer" 365 * folder. 366 */ 367 public boolean canWrite() { 368 return (isFileSystem() ? super.canWrite() : false); // ((Fix?)) 369 } 370 371 public boolean exists() { 372 // Assume top-level drives exist, because state is uncertain for 373 // removable drives. 374 return (!isFileSystem() || isFileSystemRoot(this) || super.exists()) ; 375 } 376 377 public boolean isDirectory() { 378 return (isFileSystem() ? super.isDirectory() : true); // ((Fix?)) 379 } 380 381 public boolean isFile() { 382 return (isFileSystem() ? super.isFile() : !isDirectory()); // ((Fix?)) 383 } 384 385 public long lastModified() { 386 return (isFileSystem() ? super.lastModified() : 0L); // ((Fix?)) 387 } 388 389 public long length() { 390 return (isFileSystem() ? super.length() : 0L); // ((Fix?)) 391 } 392 393 public boolean createNewFile() throws IOException { 394 return (isFileSystem() ? super.createNewFile() : false); 395 } 396 397 public boolean delete() { 398 return (isFileSystem() ? super.delete() : false); // ((Fix?)) 399 } 400 401 public void deleteOnExit() { 402 if (isFileSystem()) { 403 super.deleteOnExit(); 404 } else { 405 // Do nothing // ((Fix?)) 406 } 407 } 408 409 public boolean mkdir() { 410 return (isFileSystem() ? super.mkdir() : false); 411 } 412 413 public boolean mkdirs() { 414 return (isFileSystem() ? super.mkdirs() : false); 415 } 416 417 public boolean renameTo(File dest) { 418 return (isFileSystem() ? super.renameTo(dest) : false); // ((Fix?)) 419 } 420 421 public boolean setLastModified(long time) { 422 return (isFileSystem() ? super.setLastModified(time) : false); // ((Fix?)) 423 } 424 425 public boolean setReadOnly() { 426 return (isFileSystem() ? super.setReadOnly() : false); // ((Fix?)) 427 } 428 429 public String toString() { 430 return (isFileSystem() ? super.toString() : getDisplayName()); 431 } 432 433 public static ShellFolderColumnInfo[] getFolderColumns(File dir) { 434 ShellFolderColumnInfo[] columns = null; 435 436 if (dir instanceof ShellFolder) { 437 columns = ((ShellFolder) dir).getFolderColumns(); 438 } 439 440 if (columns == null) { 441 columns = new ShellFolderColumnInfo[]{ 442 new ShellFolderColumnInfo(COLUMN_NAME, 150, 443 SwingConstants.LEADING, true, null, 444 FILE_COMPARATOR), 445 new ShellFolderColumnInfo(COLUMN_SIZE, 75, 446 SwingConstants.RIGHT, true, null, 447 DEFAULT_COMPARATOR, true), 448 new ShellFolderColumnInfo(COLUMN_DATE, 130, 449 SwingConstants.LEADING, true, null, 450 DEFAULT_COMPARATOR, true) 451 }; 452 } 453 454 return columns; 455 } 456 457 public ShellFolderColumnInfo[] getFolderColumns() { 458 return null; 459 } 460 461 public static Object getFolderColumnValue(File file, int column) { 462 if (file instanceof ShellFolder) { 463 Object value = ((ShellFolder)file).getFolderColumnValue(column); 464 if (value != null) { 465 return value; 466 } 467 } 468 469 if (file == null || !file.exists()) { 470 return null; 471 } 472 473 switch (column) { 474 case 0: 475 // By default, file name will be rendered using getSystemDisplayName() 476 return file; 477 478 case 1: // size 479 return file.isDirectory() ? null : Long.valueOf(file.length()); 480 481 case 2: // date 482 if (isFileSystemRoot(file)) { 483 return null; 484 } 485 long time = file.lastModified(); 486 return (time == 0L) ? null : new Date(time); 487 488 default: 489 return null; 490 } 491 } 492 493 public Object getFolderColumnValue(int column) { 494 return null; 495 } 496 497 /** 498 * Invokes the {@code task} which doesn't throw checked exceptions 499 * from its {@code call} method. If invokation is interrupted then Thread.currentThread().isInterrupted() will 500 * be set and result will be {@code null} 501 */ 502 public static <T> T invoke(Callable<T> task) { 503 try { 504 return invoke(task, RuntimeException.class); 505 } catch (InterruptedException e) { 506 return null; 507 } 508 } 509 510 /** 511 * Invokes the {@code task} which throws checked exceptions from its {@code call} method. 512 * If invokation is interrupted then Thread.currentThread().isInterrupted() will 513 * be set and InterruptedException will be thrown as well. 514 */ 515 public static <T, E extends Throwable> T invoke(Callable<T> task, Class<E> exceptionClass) 516 throws InterruptedException, E { 517 try { 518 return invoker.invoke(task); 519 } catch (Exception e) { 520 if (e instanceof RuntimeException) { 521 // Rethrow unchecked exceptions 522 throw (RuntimeException) e; 523 } 524 525 if (e instanceof InterruptedException) { 526 // Set isInterrupted flag for current thread 527 Thread.currentThread().interrupt(); 528 529 // Rethrow InterruptedException 530 throw (InterruptedException) e; 531 } 532 533 if (exceptionClass.isInstance(e)) { 534 throw exceptionClass.cast(e); 535 } 536 537 throw new RuntimeException("Unexpected error", e); 538 } 539 } 540 541 /** 542 * Interface allowing to invoke tasks in different environments on different platforms. 543 */ 544 public static interface Invoker { 545 /** 546 * Invokes a callable task. 547 * 548 * @param task a task to invoke 549 * @throws Exception {@code InterruptedException} or an exception that was thrown from the {@code task} 550 * @return the result of {@code task}'s invokation 551 */ 552 <T> T invoke(Callable<T> task) throws Exception; 553 } 554 555 /** 556 * Provides a default comparator for the default column set 557 */ 558 private static final Comparator<Object> DEFAULT_COMPARATOR = new Comparator<Object>() { 559 public int compare(Object o1, Object o2) { 560 int gt; 561 562 if (o1 == null && o2 == null) { 563 gt = 0; 564 } else if (o1 != null && o2 == null) { 565 gt = 1; 566 } else if (o1 == null && o2 != null) { 567 gt = -1; 568 } else if (o1 instanceof Comparable) { 569 @SuppressWarnings("unchecked") 570 Comparable<Object> o = (Comparable<Object>) o1; 571 gt = o.compareTo(o2); 572 } else { 573 gt = 0; 574 } 575 576 return gt; 577 } 578 }; 579 580 private static final Comparator<File> FILE_COMPARATOR = new Comparator<File>() { 581 public int compare(File f1, File f2) { 582 ShellFolder sf1 = null; 583 ShellFolder sf2 = null; 584 585 if (f1 instanceof ShellFolder) { 586 sf1 = (ShellFolder) f1; 587 if (sf1.isFileSystem()) { 588 sf1 = null; 589 } 590 } 591 if (f2 instanceof ShellFolder) { 592 sf2 = (ShellFolder) f2; 593 if (sf2.isFileSystem()) { 594 sf2 = null; 595 } 596 } 597 598 if (sf1 != null && sf2 != null) { 599 return sf1.compareTo(sf2); 600 } else if (sf1 != null) { 601 // Non-file shellfolders sort before files 602 return -1; 603 } else if (sf2 != null) { 604 return 1; 605 } else { 606 String name1 = f1.getName(); 607 String name2 = f2.getName(); 608 609 // First ignore case when comparing 610 int diff = name1.compareToIgnoreCase(name2); 611 if (diff != 0) { 612 return diff; 613 } else { 614 // May differ in case (e.g. "mail" vs. "Mail") 615 // We need this test for consistent sorting 616 return name1.compareTo(name2); 617 } 618 } 619 } 620 }; 621 }