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