1 /*
2  * Copyright (c) 1998, 2017, 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 javax.swing.filechooser;
27 
28 import java.awt.Image;
29 import java.beans.PropertyChangeListener;
30 import java.io.File;
31 import java.io.FileNotFoundException;
32 import java.io.IOException;
33 import java.lang.ref.WeakReference;
34 import java.security.AccessController;
35 import java.security.PrivilegedAction;
36 import java.text.MessageFormat;
37 import java.util.ArrayList;
38 import java.util.List;
39 
40 import javax.swing.Icon;
41 import javax.swing.ImageIcon;
42 import javax.swing.JFileChooser;
43 import javax.swing.UIManager;
44 
45 import jdk.internal.ref.CleanerFactory;
46 import sun.awt.shell.ShellFolder;
47 
48 /**
49  * FileSystemView is JFileChooser's gateway to the
50  * file system. Since the JDK1.1 File API doesn't allow
51  * access to such information as root partitions, file type
52  * information, or hidden file bits, this class is designed
53  * to intuit as much OS-specific file system information as
54  * possible.
55  *
56  * <p>
57  *
58  * Java Licensees may want to provide a different implementation of
59  * FileSystemView to better handle a given operating system.
60  *
61  * @author Jeff Dinkins
62  */
63 
64 // PENDING(jeff) - need to provide a specification for
65 // how Mac/OS2/BeOS/etc file systems can modify FileSystemView
66 // to handle their particular type of file system.
67 
68 public abstract class FileSystemView {
69 
70     static FileSystemView windowsFileSystemView = null;
71     static FileSystemView unixFileSystemView = null;
72     //static FileSystemView macFileSystemView = null;
73     static FileSystemView genericFileSystemView = null;
74 
75     private boolean useSystemExtensionHiding =
76             UIManager.getDefaults().getBoolean("FileChooser.useSystemExtensionHiding");
77 
78     /**
79      * Returns the file system view.
80      * @return the file system view
81      */
getFileSystemView()82     public static FileSystemView getFileSystemView() {
83         if(File.separatorChar == '\\') {
84             if(windowsFileSystemView == null) {
85                 windowsFileSystemView = new WindowsFileSystemView();
86             }
87             return windowsFileSystemView;
88         }
89 
90         if(File.separatorChar == '/') {
91             if(unixFileSystemView == null) {
92                 unixFileSystemView = new UnixFileSystemView();
93             }
94             return unixFileSystemView;
95         }
96 
97         // if(File.separatorChar == ':') {
98         //    if(macFileSystemView == null) {
99         //      macFileSystemView = new MacFileSystemView();
100         //    }
101         //    return macFileSystemView;
102         //}
103 
104         if(genericFileSystemView == null) {
105             genericFileSystemView = new GenericFileSystemView();
106         }
107         return genericFileSystemView;
108     }
109 
110     /**
111      * Constructs a FileSystemView.
112      */
FileSystemView()113     public FileSystemView() {
114         final WeakReference<FileSystemView> weakReference = new WeakReference<>(this);
115         final PropertyChangeListener pcl = evt -> {
116             final FileSystemView fsv = weakReference.get();
117             if (fsv != null && evt.getPropertyName().equals("lookAndFeel")) {
118                 fsv.useSystemExtensionHiding =
119                         UIManager.getDefaults().getBoolean(
120                                 "FileChooser.useSystemExtensionHiding");
121             }
122         };
123 
124         UIManager.addPropertyChangeListener(pcl);
125         CleanerFactory.cleaner().register(this, () -> {
126             UIManager.removePropertyChangeListener(pcl);
127         });
128     }
129 
130     /**
131      * Determines if the given file is a root in the navigable tree(s).
132      * Examples: Windows 98 has one root, the Desktop folder. DOS has one root
133      * per drive letter, <code>C:\</code>, <code>D:\</code>, etc. Unix has one root,
134      * the <code>"/"</code> directory.
135      *
136      * The default implementation gets information from the <code>ShellFolder</code> class.
137      *
138      * @param f a <code>File</code> object representing a directory
139      * @return <code>true</code> if <code>f</code> is a root in the navigable tree.
140      * @see #isFileSystemRoot
141      */
isRoot(File f)142     public boolean isRoot(File f) {
143         if (f == null || !f.isAbsolute()) {
144             return false;
145         }
146 
147         File[] roots = getRoots();
148         for (File root : roots) {
149             if (root.equals(f)) {
150                 return true;
151             }
152         }
153         return false;
154     }
155 
156     /**
157      * Returns true if the file (directory) can be visited.
158      * Returns false if the directory cannot be traversed.
159      *
160      * @param f the <code>File</code>
161      * @return <code>true</code> if the file/directory can be traversed, otherwise <code>false</code>
162      * @see JFileChooser#isTraversable
163      * @see FileView#isTraversable
164      * @since 1.4
165      */
isTraversable(File f)166     public Boolean isTraversable(File f) {
167         return Boolean.valueOf(f.isDirectory());
168     }
169 
170     /**
171      * Name of a file, directory, or folder as it would be displayed in
172      * a system file browser. Example from Windows: the "M:\" directory
173      * displays as "CD-ROM (M:)"
174      *
175      * The default implementation gets information from the ShellFolder class.
176      *
177      * @param f a <code>File</code> object
178      * @return the file name as it would be displayed by a native file chooser
179      * @see JFileChooser#getName
180      * @since 1.4
181      */
getSystemDisplayName(File f)182     public String getSystemDisplayName(File f) {
183         if (f == null) {
184             return null;
185         }
186 
187         String name = f.getName();
188 
189         if (!name.equals("..") && !name.equals(".") &&
190                 (useSystemExtensionHiding || !isFileSystem(f) || isFileSystemRoot(f)) &&
191                 (f instanceof ShellFolder || f.exists())) {
192 
193             try {
194                 name = getShellFolder(f).getDisplayName();
195             } catch (FileNotFoundException e) {
196                 return null;
197             }
198 
199             if (name == null || name.length() == 0) {
200                 name = f.getPath(); // e.g. "/"
201             }
202         }
203 
204         return name;
205     }
206 
207     /**
208      * Type description for a file, directory, or folder as it would be displayed in
209      * a system file browser. Example from Windows: the "Desktop" folder
210      * is described as "Desktop".
211      *
212      * Override for platforms with native ShellFolder implementations.
213      *
214      * @param f a <code>File</code> object
215      * @return the file type description as it would be displayed by a native file chooser
216      * or null if no native information is available.
217      * @see JFileChooser#getTypeDescription
218      * @since 1.4
219      */
getSystemTypeDescription(File f)220     public String getSystemTypeDescription(File f) {
221         return null;
222     }
223 
224     /**
225      * Icon for a file, directory, or folder as it would be displayed in
226      * a system file browser. Example from Windows: the "M:\" directory
227      * displays a CD-ROM icon.
228      *
229      * The default implementation gets information from the ShellFolder class.
230      *
231      * @param f a <code>File</code> object
232      * @return an icon as it would be displayed by a native file chooser
233      * @see JFileChooser#getIcon
234      * @since 1.4
235      */
getSystemIcon(File f)236     public Icon getSystemIcon(File f) {
237         if (f == null) {
238             return null;
239         }
240 
241         ShellFolder sf;
242 
243         try {
244             sf = getShellFolder(f);
245         } catch (FileNotFoundException e) {
246             return null;
247         }
248 
249         Image img = sf.getIcon(false);
250 
251         if (img != null) {
252             return new ImageIcon(img, sf.getFolderType());
253         } else {
254             return UIManager.getIcon(f.isDirectory() ? "FileView.directoryIcon" : "FileView.fileIcon");
255         }
256     }
257 
258     /**
259      * On Windows, a file can appear in multiple folders, other than its
260      * parent directory in the filesystem. Folder could for example be the
261      * "Desktop" folder which is not the same as file.getParentFile().
262      *
263      * @param folder a <code>File</code> object representing a directory or special folder
264      * @param file a <code>File</code> object
265      * @return <code>true</code> if <code>folder</code> is a directory or special folder and contains <code>file</code>.
266      * @since 1.4
267      */
isParent(File folder, File file)268     public boolean isParent(File folder, File file) {
269         if (folder == null || file == null) {
270             return false;
271         } else if (folder instanceof ShellFolder) {
272                 File parent = file.getParentFile();
273                 if (parent != null && parent.equals(folder)) {
274                     return true;
275                 }
276             File[] children = getFiles(folder, false);
277             for (File child : children) {
278                 if (file.equals(child)) {
279                     return true;
280                 }
281             }
282             return false;
283         } else {
284             return folder.equals(file.getParentFile());
285         }
286     }
287 
288     /**
289      *
290      * @param parent a <code>File</code> object representing a directory or special folder
291      * @param fileName a name of a file or folder which exists in <code>parent</code>
292      * @return a File object. This is normally constructed with <code>new
293      * File(parent, fileName)</code> except when parent and child are both
294      * special folders, in which case the <code>File</code> is a wrapper containing
295      * a <code>ShellFolder</code> object.
296      * @since 1.4
297      */
getChild(File parent, String fileName)298     public File getChild(File parent, String fileName) {
299         if (parent instanceof ShellFolder) {
300             File[] children = getFiles(parent, false);
301             for (File child : children) {
302                 if (child.getName().equals(fileName)) {
303                     return child;
304                 }
305             }
306         }
307         return createFileObject(parent, fileName);
308     }
309 
310 
311     /**
312      * Checks if <code>f</code> represents a real directory or file as opposed to a
313      * special folder such as <code>"Desktop"</code>. Used by UI classes to decide if
314      * a folder is selectable when doing directory choosing.
315      *
316      * @param f a <code>File</code> object
317      * @return <code>true</code> if <code>f</code> is a real file or directory.
318      * @since 1.4
319      */
isFileSystem(File f)320     public boolean isFileSystem(File f) {
321         if (f instanceof ShellFolder) {
322             ShellFolder sf = (ShellFolder)f;
323             // Shortcuts to directories are treated as not being file system objects,
324             // so that they are never returned by JFileChooser.
325             return sf.isFileSystem() && !(sf.isLink() && sf.isDirectory());
326         } else {
327             return true;
328         }
329     }
330 
331     /**
332      * Creates a new folder with a default folder name.
333      *
334      * @param containingDir a {@code File} object denoting directory to contain the new folder
335      * @return a {@code File} object denoting the newly created folder
336      * @throws IOException if new folder could not be created
337      */
createNewFolder(File containingDir)338     public abstract File createNewFolder(File containingDir) throws IOException;
339 
340     /**
341      * Returns whether a file is hidden or not.
342      *
343      * @param f a {@code File} object
344      * @return true if the given {@code File} denotes a hidden file
345      */
isHiddenFile(File f)346     public boolean isHiddenFile(File f) {
347         return f.isHidden();
348     }
349 
350 
351     /**
352      * Is dir the root of a tree in the file system, such as a drive
353      * or partition. Example: Returns true for "C:\" on Windows 98.
354      *
355      * @param dir a <code>File</code> object representing a directory
356      * @return <code>true</code> if <code>f</code> is a root of a filesystem
357      * @see #isRoot
358      * @since 1.4
359      */
isFileSystemRoot(File dir)360     public boolean isFileSystemRoot(File dir) {
361         return ShellFolder.isFileSystemRoot(dir);
362     }
363 
364     /**
365      * Used by UI classes to decide whether to display a special icon
366      * for drives or partitions, e.g. a "hard disk" icon.
367      *
368      * The default implementation has no way of knowing, so always returns false.
369      *
370      * @param dir a directory
371      * @return <code>false</code> always
372      * @since 1.4
373      */
isDrive(File dir)374     public boolean isDrive(File dir) {
375         return false;
376     }
377 
378     /**
379      * Used by UI classes to decide whether to display a special icon
380      * for a floppy disk. Implies isDrive(dir).
381      *
382      * The default implementation has no way of knowing, so always returns false.
383      *
384      * @param dir a directory
385      * @return <code>false</code> always
386      * @since 1.4
387      */
isFloppyDrive(File dir)388     public boolean isFloppyDrive(File dir) {
389         return false;
390     }
391 
392     /**
393      * Used by UI classes to decide whether to display a special icon
394      * for a computer node, e.g. "My Computer" or a network server.
395      *
396      * The default implementation has no way of knowing, so always returns false.
397      *
398      * @param dir a directory
399      * @return <code>false</code> always
400      * @since 1.4
401      */
isComputerNode(File dir)402     public boolean isComputerNode(File dir) {
403         return ShellFolder.isComputerNode(dir);
404     }
405 
406 
407     /**
408      * Returns all root partitions on this system. For example, on
409      * Windows, this would be the "Desktop" folder, while on DOS this
410      * would be the A: through Z: drives.
411      *
412      * @return an array of {@code File} objects representing all root partitions
413      *         on this system
414      */
getRoots()415     public File[] getRoots() {
416         // Don't cache this array, because filesystem might change
417         File[] roots = (File[])ShellFolder.get("roots");
418 
419         for (int i = 0; i < roots.length; i++) {
420             if (isFileSystemRoot(roots[i])) {
421                 roots[i] = createFileSystemRoot(roots[i]);
422             }
423         }
424         return roots;
425     }
426 
427 
428     // Providing default implementations for the remaining methods
429     // because most OS file systems will likely be able to use this
430     // code. If a given OS can't, override these methods in its
431     // implementation.
432 
433     /**
434      * Returns the home directory.
435      * @return the home directory
436      */
getHomeDirectory()437     public File getHomeDirectory() {
438         return createFileObject(System.getProperty("user.home"));
439     }
440 
441     /**
442      * Return the user's default starting directory for the file chooser.
443      *
444      * @return a <code>File</code> object representing the default
445      *         starting folder
446      * @since 1.4
447      */
getDefaultDirectory()448     public File getDefaultDirectory() {
449         File f = (File)ShellFolder.get("fileChooserDefaultFolder");
450         if (isFileSystemRoot(f)) {
451             f = createFileSystemRoot(f);
452         }
453         return f;
454     }
455 
456     /**
457      * Returns a File object constructed in dir from the given filename.
458      *
459      * @param dir an abstract pathname denoting a directory
460      * @param filename a {@code String} representation of a pathname
461      * @return a {@code File} object created from {@code dir} and {@code filename}
462      */
createFileObject(File dir, String filename)463     public File createFileObject(File dir, String filename) {
464         if(dir == null) {
465             return new File(filename);
466         } else {
467             return new File(dir, filename);
468         }
469     }
470 
471     /**
472      * Returns a File object constructed from the given path string.
473      *
474      * @param path {@code String} representation of path
475      * @return a {@code File} object created from the given {@code path}
476      */
createFileObject(String path)477     public File createFileObject(String path) {
478         File f = new File(path);
479         if (isFileSystemRoot(f)) {
480             f = createFileSystemRoot(f);
481         }
482         return f;
483     }
484 
485 
486     /**
487      * Gets the list of shown (i.e. not hidden) files.
488      *
489      * @param dir the root directory of files to be returned
490      * @param useFileHiding determine if hidden files are returned
491      * @return an array of {@code File} objects representing files and
492      *         directories in the given {@code dir}. It includes hidden
493      *         files if {@code useFileHiding} is false.
494      */
getFiles(File dir, boolean useFileHiding)495     public File[] getFiles(File dir, boolean useFileHiding) {
496         List<File> files = new ArrayList<File>();
497 
498         // add all files in dir
499         if (!(dir instanceof ShellFolder)) {
500             try {
501                 dir = getShellFolder(dir);
502             } catch (FileNotFoundException e) {
503                 return new File[0];
504             }
505         }
506 
507         File[] names = ((ShellFolder) dir).listFiles(!useFileHiding);
508 
509         if (names == null) {
510             return new File[0];
511         }
512 
513         for (File f : names) {
514             if (Thread.currentThread().isInterrupted()) {
515                 break;
516             }
517 
518             if (!(f instanceof ShellFolder)) {
519                 if (isFileSystemRoot(f)) {
520                     f = createFileSystemRoot(f);
521                 }
522                 try {
523                     f = ShellFolder.getShellFolder(f);
524                 } catch (FileNotFoundException e) {
525                     // Not a valid file (wouldn't show in native file chooser)
526                     // Example: C:\pagefile.sys
527                     continue;
528                 } catch (InternalError e) {
529                     // Not a valid file (wouldn't show in native file chooser)
530                     // Example C:\Winnt\Profiles\joe\history\History.IE5
531                     continue;
532                 }
533             }
534             if (!useFileHiding || !isHiddenFile(f)) {
535                 files.add(f);
536             }
537         }
538 
539         return files.toArray(new File[files.size()]);
540     }
541 
542 
543 
544     /**
545      * Returns the parent directory of <code>dir</code>.
546      * @param dir the <code>File</code> being queried
547      * @return the parent directory of <code>dir</code>, or
548      *   <code>null</code> if <code>dir</code> is <code>null</code>
549      */
getParentDirectory(File dir)550     public File getParentDirectory(File dir) {
551         if (dir == null || !dir.exists()) {
552             return null;
553         }
554 
555         ShellFolder sf;
556 
557         try {
558             sf = getShellFolder(dir);
559         } catch (FileNotFoundException e) {
560             return null;
561         }
562 
563         File psf = sf.getParentFile();
564 
565         if (psf == null) {
566             return null;
567         }
568 
569         if (isFileSystem(psf)) {
570             File f = psf;
571             if (!f.exists()) {
572                 // This could be a node under "Network Neighborhood".
573                 File ppsf = psf.getParentFile();
574                 if (ppsf == null || !isFileSystem(ppsf)) {
575                     // We're mostly after the exists() override for windows below.
576                     f = createFileSystemRoot(f);
577                 }
578             }
579             return f;
580         } else {
581             return psf;
582         }
583     }
584 
585     /**
586      * Returns an array of files representing the values which will be shown
587      * in the file chooser selector.
588      *
589      * @return an array of {@code File} objects. The array returned may be
590      * possibly empty if there are no appropriate permissions.
591      * @since 9
592      */
getChooserComboBoxFiles()593     public File[] getChooserComboBoxFiles() {
594         return (File[]) ShellFolder.get("fileChooserComboBoxFolders");
595     }
596 
597     /**
598      * Returns an array of files representing the values to show by default in
599      * the file chooser shortcuts panel.
600      *
601      * @return an array of {@code File} objects. The array returned may be
602      * possibly empty if there are no appropriate permissions.
603      * @since 12
604      */
getChooserShortcutPanelFiles()605     final public File[] getChooserShortcutPanelFiles() {
606         return (File[]) ShellFolder.get("fileChooserShortcutPanelFolders");
607     }
608 
609     /**
610      * Returns whether the specified file denotes a shell interpreted link which
611      * can be obtained by the {@link #getLinkLocation(File)}.
612      *
613      * @param file a file
614      * @return whether this is a link
615      * @throws NullPointerException if {@code file} equals {@code null}
616      * @throws SecurityException if the caller does not have necessary
617      *                           permissions
618      * @see #getLinkLocation(File)
619      * @since 9
620      */
isLink(File file)621     public boolean isLink(File file) {
622         if (file == null) {
623             throw new NullPointerException("file is null");
624         }
625         try {
626             return ShellFolder.getShellFolder(file).isLink();
627         } catch (FileNotFoundException e) {
628             return false;
629         }
630     }
631 
632     /**
633      * Returns the regular file referenced by the specified link file if
634      * the specified file is a shell interpreted link.
635      * Returns {@code null} if the specified file is not
636      * a shell interpreted link.
637      *
638      * @param file a file
639      * @return the linked file or {@code null}.
640      * @throws FileNotFoundException if the linked file does not exist
641      * @throws NullPointerException if {@code file} equals {@code null}
642      * @throws SecurityException if the caller does not have necessary
643      *                           permissions
644      * @since 9
645      */
getLinkLocation(File file)646     public File getLinkLocation(File file) throws FileNotFoundException {
647         if (file == null) {
648             throw new NullPointerException("file is null");
649         }
650         ShellFolder shellFolder;
651         try {
652             shellFolder = ShellFolder.getShellFolder(file);
653         } catch (FileNotFoundException e) {
654             return null;
655         }
656         return shellFolder.isLink() ? shellFolder.getLinkLocation() : null;
657     }
658 
659     /**
660      * Throws {@code FileNotFoundException} if file not found or current thread was interrupted
661      */
getShellFolder(File f)662     ShellFolder getShellFolder(File f) throws FileNotFoundException {
663         if (!(f instanceof ShellFolder) && !(f instanceof FileSystemRoot) && isFileSystemRoot(f)) {
664             f = createFileSystemRoot(f);
665         }
666 
667         try {
668             return ShellFolder.getShellFolder(f);
669         } catch (InternalError e) {
670             System.err.println("FileSystemView.getShellFolder: f="+f);
671             e.printStackTrace();
672             return null;
673         }
674     }
675 
676     /**
677      * Creates a new <code>File</code> object for <code>f</code> with correct
678      * behavior for a file system root directory.
679      *
680      * @param f a <code>File</code> object representing a file system root
681      *          directory, for example "/" on Unix or "C:\" on Windows.
682      * @return a new <code>File</code> object
683      * @since 1.4
684      */
createFileSystemRoot(File f)685     protected File createFileSystemRoot(File f) {
686         return new FileSystemRoot(f);
687     }
688 
689     @SuppressWarnings("serial") // Same-version serialization only
690     static class FileSystemRoot extends File {
FileSystemRoot(File f)691         public FileSystemRoot(File f) {
692             super(f,"");
693         }
694 
FileSystemRoot(String s)695         public FileSystemRoot(String s) {
696             super(s);
697         }
698 
isDirectory()699         public boolean isDirectory() {
700             return true;
701         }
702 
getName()703         public String getName() {
704             return getPath();
705         }
706     }
707 }
708 
709 /**
710  * FileSystemView that handles some specific unix-isms.
711  */
712 class UnixFileSystemView extends FileSystemView {
713 
714     private static final String newFolderString =
715             UIManager.getString("FileChooser.other.newFolder");
716     private static final String newFolderNextString  =
717             UIManager.getString("FileChooser.other.newFolder.subsequent");
718 
719     /**
720      * Creates a new folder with a default folder name.
721      */
createNewFolder(File containingDir)722     public File createNewFolder(File containingDir) throws IOException {
723         if(containingDir == null) {
724             throw new IOException("Containing directory is null:");
725         }
726         File newFolder;
727         // Unix - using OpenWindows' default folder name. Can't find one for Motif/CDE.
728         newFolder = createFileObject(containingDir, newFolderString);
729         int i = 1;
730         while (newFolder.exists() && i < 100) {
731             newFolder = createFileObject(containingDir, MessageFormat.format(
732                     newFolderNextString, i));
733             i++;
734         }
735 
736         if(newFolder.exists()) {
737             throw new IOException("Directory already exists:" + newFolder.getAbsolutePath());
738         } else {
739             if(!newFolder.mkdirs()) {
740                 throw new IOException(newFolder.getAbsolutePath());
741             }
742         }
743 
744         return newFolder;
745     }
746 
isFileSystemRoot(File dir)747     public boolean isFileSystemRoot(File dir) {
748         return dir != null && dir.getAbsolutePath().equals("/");
749     }
750 
isDrive(File dir)751     public boolean isDrive(File dir) {
752         return isFloppyDrive(dir);
753     }
754 
isFloppyDrive(File dir)755     public boolean isFloppyDrive(File dir) {
756         // Could be looking at the path for Solaris, but wouldn't be reliable.
757         // For example:
758         // return (dir != null && dir.getAbsolutePath().toLowerCase().startsWith("/floppy"));
759         return false;
760     }
761 
isComputerNode(File dir)762     public boolean isComputerNode(File dir) {
763         if (dir != null) {
764             String parent = dir.getParent();
765             if (parent != null && parent.equals("/net")) {
766                 return true;
767             }
768         }
769         return false;
770     }
771 }
772 
773 
774 /**
775  * FileSystemView that handles some specific windows concepts.
776  */
777 class WindowsFileSystemView extends FileSystemView {
778 
779     private static final String newFolderString =
780             UIManager.getString("FileChooser.win32.newFolder");
781     private static final String newFolderNextString  =
782             UIManager.getString("FileChooser.win32.newFolder.subsequent");
783 
isTraversable(File f)784     public Boolean isTraversable(File f) {
785         return Boolean.valueOf(isFileSystemRoot(f) || isComputerNode(f) || f.isDirectory());
786     }
787 
getChild(File parent, String fileName)788     public File getChild(File parent, String fileName) {
789         if (fileName.startsWith("\\")
790             && !fileName.startsWith("\\\\")
791             && isFileSystem(parent)) {
792 
793             //Path is relative to the root of parent's drive
794             String path = parent.getAbsolutePath();
795             if (path.length() >= 2
796                 && path.charAt(1) == ':'
797                 && Character.isLetter(path.charAt(0))) {
798 
799                 return createFileObject(path.substring(0, 2) + fileName);
800             }
801         }
802         return super.getChild(parent, fileName);
803     }
804 
805     /**
806      * Type description for a file, directory, or folder as it would be displayed in
807      * a system file browser. Example from Windows: the "Desktop" folder
808      * is described as "Desktop".
809      *
810      * The Windows implementation gets information from the ShellFolder class.
811      */
getSystemTypeDescription(File f)812     public String getSystemTypeDescription(File f) {
813         if (f == null) {
814             return null;
815         }
816 
817         try {
818             return getShellFolder(f).getFolderType();
819         } catch (FileNotFoundException e) {
820             return null;
821         }
822     }
823 
824     /**
825      * @return the Desktop folder.
826      */
getHomeDirectory()827     public File getHomeDirectory() {
828         File[] roots = getRoots();
829         return (roots.length == 0) ? null : roots[0];
830     }
831 
832     /**
833      * Creates a new folder with a default folder name.
834      */
createNewFolder(File containingDir)835     public File createNewFolder(File containingDir) throws IOException {
836         if(containingDir == null) {
837             throw new IOException("Containing directory is null:");
838         }
839         // Using NT's default folder name
840         File newFolder = createFileObject(containingDir, newFolderString);
841         int i = 2;
842         while (newFolder.exists() && i < 100) {
843             newFolder = createFileObject(containingDir, MessageFormat.format(
844                 newFolderNextString, i));
845             i++;
846         }
847 
848         if(newFolder.exists()) {
849             throw new IOException("Directory already exists:" + newFolder.getAbsolutePath());
850         } else {
851             if(!newFolder.mkdirs()) {
852                 throw new IOException(newFolder.getAbsolutePath());
853             }
854         }
855 
856         return newFolder;
857     }
858 
isDrive(File dir)859     public boolean isDrive(File dir) {
860         return isFileSystemRoot(dir);
861     }
862 
isFloppyDrive(final File dir)863     public boolean isFloppyDrive(final File dir) {
864         String path = AccessController.doPrivileged(new PrivilegedAction<String>() {
865             public String run() {
866                 return dir.getAbsolutePath();
867             }
868         });
869 
870         return path != null && (path.equals("A:\\") || path.equals("B:\\"));
871     }
872 
873     /**
874      * Returns a File object constructed from the given path string.
875      */
createFileObject(String path)876     public File createFileObject(String path) {
877         // Check for missing backslash after drive letter such as "C:" or "C:filename"
878         if (path.length() >= 2 && path.charAt(1) == ':' && Character.isLetter(path.charAt(0))) {
879             if (path.length() == 2) {
880                 path += "\\";
881             } else if (path.charAt(2) != '\\') {
882                 path = path.substring(0, 2) + "\\" + path.substring(2);
883             }
884         }
885         return super.createFileObject(path);
886     }
887 
888     @SuppressWarnings("serial") // anonymous class
createFileSystemRoot(File f)889     protected File createFileSystemRoot(File f) {
890         // Problem: Removable drives on Windows return false on f.exists()
891         // Workaround: Override exists() to always return true.
892         return new FileSystemRoot(f) {
893             public boolean exists() {
894                 return true;
895             }
896         };
897     }
898 
899 }
900 
901 /**
902  * Fallthrough FileSystemView in case we can't determine the OS.
903  */
904 class GenericFileSystemView extends FileSystemView {
905 
906     private static final String newFolderString =
907             UIManager.getString("FileChooser.other.newFolder");
908 
909     /**
910      * Creates a new folder with a default folder name.
911      */
912     public File createNewFolder(File containingDir) throws IOException {
913         if(containingDir == null) {
914             throw new IOException("Containing directory is null:");
915         }
916         // Using NT's default folder name
917         File newFolder = createFileObject(containingDir, newFolderString);
918 
919         if(newFolder.exists()) {
920             throw new IOException("Directory already exists:" + newFolder.getAbsolutePath());
921         } else {
922             if(!newFolder.mkdirs()) {
923                 throw new IOException(newFolder.getAbsolutePath());
924             }
925         }
926         return newFolder;
927     }
928 
929 }
930