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