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 }