       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.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      * @param getLargeIcon whether to return large icon (ignored in base implementation)
     196      * @return The icon used to display this shell folder
     197      */
     198     public Image getIcon(boolean getLargeIcon) {
     199         return null;
     200     }
     201
     202
     203     // Static
     204
     205     private static final ShellFolderManager shellFolderManager;
     206
     207     private static final Invoker invoker;
     208
     209     static {
     210         String managerClassName = (String)Toolkit.getDefaultToolkit().
     211                                       getDesktopProperty("Shell.shellFolderManager");
     212         Class<?> managerClass = null;
     213         try {
     214             managerClass = Class.forName(managerClassName, false, null);
     215             if (!ShellFolderManager.class.isAssignableFrom(managerClass)) {
     216                 managerClass = null;
     217             }
     218         // swallow the exceptions below and use default shell folder
     219         } catch(ClassNotFoundException e) {
     220         } catch(NullPointerException e) {
     221         } catch(SecurityException e) {
     222         }
     223
     224         if (managerClass == null) {
     225             managerClass = ShellFolderManager.class;
     226         }
     227         try {
     228             shellFolderManager =
     229                 (ShellFolderManager)managerClass.getDeclaredConstructor().newInstance();
     230         } catch (ReflectiveOperationException e) {
     231             throw new Error("Could not instantiate 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
     247         if (!Files.exists(Paths.get(file.getPath()), LinkOption.NOFOLLOW_LINKS)) {
     248             throw new FileNotFoundException();
     249         }
     250         return shellFolderManager.createShellFolder(file);
     251     }
     252
     253     /**
     254      * @param key a {@code String}
     255      * @return An Object matching the string {@code key}.
     256      * @see ShellFolderManager#get(String)
     257      */
     258     public static Object get(String key) {
     259         return shellFolderManager.get(key);
     260     }
     261
     262     /**
     263      * Does {@code dir} represent a "computer" such as a node on the network, or
     264      * "My Computer" on the desktop.
     265      */
     266     public static boolean isComputerNode(File dir) {
     267         return shellFolderManager.isComputerNode(dir);
     268     }
     269
     270     /**
     271      * @return Whether this is a file system root directory
     272      */
     273     public static boolean isFileSystemRoot(File dir) {
     274         return shellFolderManager.isFileSystemRoot(dir);
     275     }
     276
     277     /**
     278      * Canonicalizes files that don't have symbolic links in their path.
     279      * Normalizes files that do, preserving symbolic links from being resolved.
     280      */
     281     public static File getNormalizedFile(File f) throws IOException {
     282         File canonical = f.getCanonicalFile();
     283         if (f.equals(canonical)) {
     284             // path of f doesn't contain symbolic links
     285             return canonical;
     286         }
     287
     288         // preserve symbolic links from being resolved
     289         return new File(f.toURI().normalize());
     290     }
     291
     292     // Override File methods
     293
     294     public static void sort(final List<? extends File> files) {
     295         if (files == null || files.size() <= 1) {
     296             return;
     297         }
     298
     299         // To avoid loads of synchronizations with Invoker and improve performance we
     300         // synchronize the whole code of the sort method once
     301         invoke(new Callable<Void>() {
     302             public Void call() {
     303                 // Check that we can use the ShellFolder.sortChildren() method:
     304                 //   1. All files have the same non-null parent
     305                 //   2. All files is ShellFolders
     306                 File commonParent = null;
     307
     308                 for (File file : files) {
     309                     File parent = file.getParentFile();
     310
     311                     if (parent == null || !(file instanceof ShellFolder)) {
     312                         commonParent = null;
     313
     314                         break;
     315                     }
     316
     317                     if (commonParent == null) {
     318                         commonParent = parent;
     319                     } else {
     320                         if (commonParent != parent && !commonParent.equals(parent)) {
     321                             commonParent = null;
     322
     323                             break;
     324                         }
     325                     }
     326                 }
     327
     328                 if (commonParent instanceof ShellFolder) {
     329                     ((ShellFolder) commonParent).sortChildren(files);
     330                 } else {
     331                     Collections.sort(files, FILE_COMPARATOR);
     332                 }
     333
     334                 return null;
     335             }
     336         });
     337     }
     338
     339     public void sortChildren(final List<? extends File> files) {
     340         // To avoid loads of synchronizations with Invoker and improve performance we
     341         // synchronize the whole code of the sort method once
     342         invoke(new Callable<Void>() {
     343             public Void call() {
     344                 Collections.sort(files, FILE_COMPARATOR);
     345
     346                 return null;
     347             }
     348         });
     349     }
     350
     351     public boolean isAbsolute() {
     352         return (!isFileSystem() || super.isAbsolute());
     353     }
     354
     355     public File getAbsoluteFile() {
     356         return (isFileSystem() ? super.getAbsoluteFile() : this);
     357     }
     358
     359     public boolean canRead() {
     360         return (isFileSystem() ? super.canRead() : true);       // ((Fix?))
     361     }
     362
     363     /**
     364      * Returns true if folder allows creation of children.
     365      * True for the "Desktop" folder, but false for the "My Computer"
     366      * folder.
     367      */
     368     public boolean canWrite() {
     369         return (isFileSystem() ? super.canWrite() : false);     // ((Fix?))
     370     }
     371
     372     public boolean exists() {
     373         // Assume top-level drives exist, because state is uncertain for
     374         // removable drives.
     375         return (!isFileSystem() || isFileSystemRoot(this) || super.exists()) ;
     376     }
     377
     378     public boolean isDirectory() {
     379         return (isFileSystem() ? super.isDirectory() : true);   // ((Fix?))
     380     }
     381
     382     public boolean isFile() {
     383         return (isFileSystem() ? super.isFile() : !isDirectory());      // ((Fix?))
     384     }
     385
     386     public long lastModified() {
     387         return (isFileSystem() ? super.lastModified() : 0L);    // ((Fix?))
     388     }
     389
     390     public long length() {
     391         return (isFileSystem() ? super.length() : 0L);  // ((Fix?))
     392     }
     393
     394     public boolean createNewFile() throws IOException {
     395         return (isFileSystem() ? super.createNewFile() : false);
     396     }
     397
     398     public boolean delete() {
     399         return (isFileSystem() ? super.delete() : false);       // ((Fix?))
     400     }
     401
     402     public void deleteOnExit() {
     403         if (isFileSystem()) {
     404             super.deleteOnExit();
     405         } else {
     406             // Do nothing       // ((Fix?))
     407         }
     408     }
     409
     410     public boolean mkdir() {
     411         return (isFileSystem() ? super.mkdir() : false);
     412     }
     413
     414     public boolean mkdirs() {
     415         return (isFileSystem() ? super.mkdirs() : false);
     416     }
     417
     418     public boolean renameTo(File dest) {
     419         return (isFileSystem() ? super.renameTo(dest) : false); // ((Fix?))
     420     }
     421
     422     public boolean setLastModified(long time) {
     423         return (isFileSystem() ? super.setLastModified(time) : false); // ((Fix?))
     424     }
     425
     426     public boolean setReadOnly() {
     427         return (isFileSystem() ? super.setReadOnly() : false); // ((Fix?))
     428     }
     429
     430     public String toString() {
     431         return (isFileSystem() ? super.toString() : getDisplayName());
     432     }
     433
     434     public static ShellFolderColumnInfo[] getFolderColumns(File dir) {
     435         ShellFolderColumnInfo[] columns = null;
     436
     437         if (dir instanceof ShellFolder) {
     438             columns = ((ShellFolder) dir).getFolderColumns();
     439         }
     440
     441         if (columns == null) {
     442             columns = new ShellFolderColumnInfo[]{
     443                     new ShellFolderColumnInfo(COLUMN_NAME, 150,
     444                             SwingConstants.LEADING, true, null,
     445                             FILE_COMPARATOR),
     446                     new ShellFolderColumnInfo(COLUMN_SIZE, 75,
     447                             SwingConstants.RIGHT, true, null,
     448                             DEFAULT_COMPARATOR, true),
     449                     new ShellFolderColumnInfo(COLUMN_DATE, 130,
     450                             SwingConstants.LEADING, true, null,
     451                             DEFAULT_COMPARATOR, true)
     452             };
     453         }
     454
     455         return columns;
     456     }
     457
     458     public ShellFolderColumnInfo[] getFolderColumns() {
     459         return null;
     460     }
     461
     462     public static Object getFolderColumnValue(File file, int column) {
     463         if (file instanceof ShellFolder) {
     464             Object value = ((ShellFolder)file).getFolderColumnValue(column);
     465             if (value != null) {
     466                 return value;
     467             }
     468         }
     469
     470         if (file == null || !file.exists()) {
     471             return null;
     472         }
     473
     474         switch (column) {
     475             case 0:
     476                 // By default, file name will be rendered using getSystemDisplayName()
     477                 return file;
     478
     479             case 1: // size
     480                 return file.isDirectory() ? null : Long.valueOf(file.length());
     481
     482             case 2: // date
     483                 if (isFileSystemRoot(file)) {
     484                     return null;
     485                 }
     486                 long time = file.lastModified();
     487                 return (time == 0L) ? null : new Date(time);
     488
     489             default:
     490                 return null;
     491         }
     492     }
     493
     494     public Object getFolderColumnValue(int column) {
     495         return null;
     496     }
     497
     498     /**
     499      * Invokes the {@code task} which doesn't throw checked exceptions
     500      * from its {@code call} method. If invokation is interrupted then Thread.currentThread().isInterrupted() will
     501      * be set and result will be {@code null}
     502      */
     503     public static <T> T invoke(Callable<T> task) {
     504         try {
     505             return invoke(task, RuntimeException.class);
     506         } catch (InterruptedException e) {
     507             return null;
     508         }
     509     }
     510
     511     /**
     512      * Invokes the {@code task} which throws checked exceptions from its {@code call} method.
     513      * If invokation is interrupted then Thread.currentThread().isInterrupted() will
     514      * be set and InterruptedException will be thrown as well.
     515      */
     516     public static <T, E extends Throwable> T invoke(Callable<T> task, Class<E> exceptionClass)
     517             throws InterruptedException, E {
     518         try {
     519             return invoker.invoke(task);
     520         } catch (Exception e) {
     521             if (e instanceof RuntimeException) {
     522                 // Rethrow unchecked exceptions
     523                 throw (RuntimeException) e;
     524             }
     525
     526             if (e instanceof InterruptedException) {
     527                 // Set isInterrupted flag for current thread
     528                 Thread.currentThread().interrupt();
     529
     530                 // Rethrow InterruptedException
     531                 throw (InterruptedException) e;
     532             }
     533
     534             if (exceptionClass.isInstance(e)) {
     535                 throw exceptionClass.cast(e);
     536             }
     537
     538             throw new RuntimeException("Unexpected error", e);
     539         }
     540     }
     541
     542     /**
     543      * Interface allowing to invoke tasks in different environments on different platforms.
     544      */
     545     public static interface Invoker {
     546         /**
     547          * Invokes a callable task.
     548          *
     549          * @param task a task to invoke
     550          * @throws Exception {@code InterruptedException} or an exception that was thrown from the {@code task}
     551          * @return the result of {@code task}'s invokation
     552          */
     553         <T> T invoke(Callable<T> task) throws Exception;
     554     }
     555
     556     /**
     557      * Provides a default comparator for the default column set
     558      */
     559     private static final Comparator<Object> DEFAULT_COMPARATOR = new Comparator<Object>() {
     560         public int compare(Object o1, Object o2) {
     561             int gt;
     562
     563             if (o1 == null && o2 == null) {
     564                 gt = 0;
     565             } else if (o1 != null && o2 == null) {
     566                 gt = 1;
     567             } else if (o1 == null && o2 != null) {
     568                 gt = -1;
     569             } else if (o1 instanceof Comparable) {
     570                 @SuppressWarnings("unchecked")
     571                 Comparable<Object> o = (Comparable<Object>) o1;
     572                 gt = o.compareTo(o2);
     573             } else {
     574                 gt = 0;
     575             }
     576
     577             return gt;
     578         }
     579     };
     580
     581     private static final Comparator<File> FILE_COMPARATOR = new Comparator<File>() {
     582         public int compare(File f1, File f2) {
     583             ShellFolder sf1 = null;
     584             ShellFolder sf2 = null;
     585
     586             if (f1 instanceof ShellFolder) {
     587                 sf1 = (ShellFolder) f1;
     588                 if (sf1.isFileSystem()) {
     589                     sf1 = null;
     590                 }
     591             }
     592             if (f2 instanceof ShellFolder) {
     593                 sf2 = (ShellFolder) f2;
     594                 if (sf2.isFileSystem()) {
     595                     sf2 = null;
     596                 }
     597             }
     598
     599             if (sf1 != null && sf2 != null) {
     600                 return sf1.compareTo(sf2);
     601             } else if (sf1 != null) {
     602                 // Non-file shellfolders sort before files
     603                 return -1;
     604             } else if (sf2 != null) {
     605                 return 1;
     606             } else {
     607                 String name1 = f1.getName();
     608                 String name2 = f2.getName();
     609
     610                 // First ignore case when comparing
     611                 int diff = name1.compareToIgnoreCase(name2);
     612                 if (diff != 0) {
     613                     return diff;
     614                 } else {
     615                     // May differ in case (e.g. "mail" vs. "Mail")
     616                     // We need this test for consistent sorting
     617                     return name1.compareTo(name2);
     618                 }
     619             }
     620         }
     621     };
     622 }