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