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