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      */
  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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      * Icon for a file, directory, or folder as it would be displayed in
 260      * a system file browser for the requested size.
 261      *
 262      * The default implementation gets information from the ShellFolder class.
 263      *
 264      * @param f a <code>File</code> object
 265      * @param size width and height of the icon in pixels to be scaled(valid range: 1 to 256)
 266      * @return an icon as it would be displayed by a native file chooser
 267      * @see JFileChooser#getIcon
 268      * @since 12
 269      */
 270     protected Icon getSystemIcon(File f, int size) {
 271         if (f == null) {
 272             return null;
 273         }
 274 
 275         if(size > 256 || size < 1) {
 276             return null;
 277         }
 278 
 279         ShellFolder sf;
 280         try {
 281             sf = getShellFolder(f);
 282         } catch (FileNotFoundException e) {
 283             return null;
 284         }
 285 
 286         Image img = sf.getIcon(size);
 287         if (img != null) {
 288             return new ImageIcon(img, sf.getFolderType());
 289         } else {
 290             return UIManager.getIcon(f.isDirectory() ? "FileView.directoryIcon" : "FileView.fileIcon");
 291         }
 292     }
 293 
 294     /**
 295      * On Windows, a file can appear in multiple folders, other than its
 296      * parent directory in the filesystem. Folder could for example be the
 297      * "Desktop" folder which is not the same as file.getParentFile().
 298      *
 299      * @param folder a <code>File</code> object representing a directory or special folder
 300      * @param file a <code>File</code> object
 301      * @return <code>true</code> if <code>folder</code> is a directory or special folder and contains <code>file</code>.
 302      * @since 1.4
 303      */
 304     public boolean isParent(File folder, File file) {
 305         if (folder == null || file == null) {
 306             return false;
 307         } else if (folder instanceof ShellFolder) {
 308                 File parent = file.getParentFile();
 309                 if (parent != null && parent.equals(folder)) {
 310                     return true;
 311                 }
 312             File[] children = getFiles(folder, false);
 313             for (File child : children) {
 314                 if (file.equals(child)) {
 315                     return true;
 316                 }
 317             }
 318             return false;
 319         } else {
 320             return folder.equals(file.getParentFile());
 321         }
 322     }
 323 
 324     /**
 325      *
 326      * @param parent a <code>File</code> object representing a directory or special folder
 327      * @param fileName a name of a file or folder which exists in <code>parent</code>
 328      * @return a File object. This is normally constructed with <code>new
 329      * File(parent, fileName)</code> except when parent and child are both
 330      * special folders, in which case the <code>File</code> is a wrapper containing
 331      * a <code>ShellFolder</code> object.
 332      * @since 1.4
 333      */
 334     public File getChild(File parent, String fileName) {
 335         if (parent instanceof ShellFolder) {
 336             File[] children = getFiles(parent, false);
 337             for (File child : children) {
 338                 if (child.getName().equals(fileName)) {
 339                     return child;
 340                 }
 341             }
 342         }
 343         return createFileObject(parent, fileName);
 344     }
 345 
 346 
 347     /**
 348      * Checks if <code>f</code> represents a real directory or file as opposed to a
 349      * special folder such as <code>"Desktop"</code>. Used by UI classes to decide if
 350      * a folder is selectable when doing directory choosing.
 351      *
 352      * @param f a <code>File</code> object
 353      * @return <code>true</code> if <code>f</code> is a real file or directory.
 354      * @since 1.4
 355      */
 356     public boolean isFileSystem(File f) {
 357         if (f instanceof ShellFolder) {
 358             ShellFolder sf = (ShellFolder)f;
 359             // Shortcuts to directories are treated as not being file system objects,
 360             // so that they are never returned by JFileChooser.
 361             return sf.isFileSystem() && !(sf.isLink() && sf.isDirectory());
 362         } else {
 363             return true;
 364         }
 365     }
 366 
 367     /**
 368      * Creates a new folder with a default folder name.
 369      *
 370      * @param containingDir a {@code File} object denoting directory to contain the new folder
 371      * @return a {@code File} object denoting the newly created folder
 372      * @throws IOException if new folder could not be created
 373      */
 374     public abstract File createNewFolder(File containingDir) throws IOException;
 375 
 376     /**
 377      * Returns whether a file is hidden or not.
 378      *
 379      * @param f a {@code File} object
 380      * @return true if the given {@code File} denotes a hidden file
 381      */
 382     public boolean isHiddenFile(File f) {
 383         return f.isHidden();
 384     }
 385 
 386 
 387     /**
 388      * Is dir the root of a tree in the file system, such as a drive
 389      * or partition. Example: Returns true for "C:\" on Windows 98.
 390      *
 391      * @param dir a <code>File</code> object representing a directory
 392      * @return <code>true</code> if <code>f</code> is a root of a filesystem
 393      * @see #isRoot
 394      * @since 1.4
 395      */
 396     public boolean isFileSystemRoot(File dir) {
 397         return ShellFolder.isFileSystemRoot(dir);
 398     }
 399 
 400     /**
 401      * Used by UI classes to decide whether to display a special icon
 402      * for drives or partitions, e.g. a "hard disk" icon.
 403      *
 404      * The default implementation has no way of knowing, so always returns false.
 405      *
 406      * @param dir a directory
 407      * @return <code>false</code> always
 408      * @since 1.4
 409      */
 410     public boolean isDrive(File dir) {
 411         return false;
 412     }
 413 
 414     /**
 415      * Used by UI classes to decide whether to display a special icon
 416      * for a floppy disk. Implies isDrive(dir).
 417      *
 418      * The default implementation has no way of knowing, so always returns false.
 419      *
 420      * @param dir a directory
 421      * @return <code>false</code> always
 422      * @since 1.4
 423      */
 424     public boolean isFloppyDrive(File dir) {
 425         return false;
 426     }
 427 
 428     /**
 429      * Used by UI classes to decide whether to display a special icon
 430      * for a computer node, e.g. "My Computer" or a network server.
 431      *
 432      * The default implementation has no way of knowing, so always returns false.
 433      *
 434      * @param dir a directory
 435      * @return <code>false</code> always
 436      * @since 1.4
 437      */
 438     public boolean isComputerNode(File dir) {
 439         return ShellFolder.isComputerNode(dir);
 440     }
 441 
 442 
 443     /**
 444      * Returns all root partitions on this system. For example, on
 445      * Windows, this would be the "Desktop" folder, while on DOS this
 446      * would be the A: through Z: drives.
 447      *
 448      * @return an array of {@code File} objects representing all root partitions
 449      *         on this system
 450      */
 451     public File[] getRoots() {
 452         // Don't cache this array, because filesystem might change
 453         File[] roots = (File[])ShellFolder.get("roots");
 454 
 455         for (int i = 0; i < roots.length; i++) {
 456             if (isFileSystemRoot(roots[i])) {
 457                 roots[i] = createFileSystemRoot(roots[i]);
 458             }
 459         }
 460         return roots;
 461     }
 462 
 463 
 464     // Providing default implementations for the remaining methods
 465     // because most OS file systems will likely be able to use this
 466     // code. If a given OS can't, override these methods in its
 467     // implementation.
 468 
 469     /**
 470      * Returns the home directory.
 471      * @return the home directory
 472      */
 473     public File getHomeDirectory() {
 474         return createFileObject(System.getProperty("user.home"));
 475     }
 476 
 477     /**
 478      * Return the user's default starting directory for the file chooser.
 479      *
 480      * @return a <code>File</code> object representing the default
 481      *         starting folder
 482      * @since 1.4
 483      */
 484     public File getDefaultDirectory() {
 485         File f = (File)ShellFolder.get("fileChooserDefaultFolder");
 486         if (isFileSystemRoot(f)) {
 487             f = createFileSystemRoot(f);
 488         }
 489         return f;
 490     }
 491 
 492     /**
 493      * Returns a File object constructed in dir from the given filename.
 494      *
 495      * @param dir an abstract pathname denoting a directory
 496      * @param filename a {@code String} representation of a pathname
 497      * @return a {@code File} object created from {@code dir} and {@code filename}
 498      */
 499     public File createFileObject(File dir, String filename) {
 500         if(dir == null) {
 501             return new File(filename);
 502         } else {
 503             return new File(dir, filename);
 504         }
 505     }
 506 
 507     /**
 508      * Returns a File object constructed from the given path string.
 509      *
 510      * @param path {@code String} representation of path
 511      * @return a {@code File} object created from the given {@code path}
 512      */
 513     public File createFileObject(String path) {
 514         File f = new File(path);
 515         if (isFileSystemRoot(f)) {
 516             f = createFileSystemRoot(f);
 517         }
 518         return f;
 519     }
 520 
 521 
 522     /**
 523      * Gets the list of shown (i.e. not hidden) files.
 524      *
 525      * @param dir the root directory of files to be returned
 526      * @param useFileHiding determine if hidden files are returned
 527      * @return an array of {@code File} objects representing files and
 528      *         directories in the given {@code dir}. It includes hidden
 529      *         files if {@code useFileHiding} is false.
 530      */
 531     public File[] getFiles(File dir, boolean useFileHiding) {
 532         List<File> files = new ArrayList<File>();
 533 
 534         // add all files in dir
 535         if (!(dir instanceof ShellFolder)) {
 536             try {
 537                 dir = getShellFolder(dir);
 538             } catch (FileNotFoundException e) {
 539                 return new File[0];
 540             }
 541         }
 542 
 543         File[] names = ((ShellFolder) dir).listFiles(!useFileHiding);
 544 
 545         if (names == null) {
 546             return new File[0];
 547         }
 548 
 549         for (File f : names) {
 550             if (Thread.currentThread().isInterrupted()) {
 551                 break;
 552             }
 553 
 554             if (!(f instanceof ShellFolder)) {
 555                 if (isFileSystemRoot(f)) {
 556                     f = createFileSystemRoot(f);
 557                 }
 558                 try {
 559                     f = ShellFolder.getShellFolder(f);
 560                 } catch (FileNotFoundException e) {
 561                     // Not a valid file (wouldn't show in native file chooser)
 562                     // Example: C:\pagefile.sys
 563                     continue;
 564                 } catch (InternalError e) {
 565                     // Not a valid file (wouldn't show in native file chooser)
 566                     // Example C:\Winnt\Profiles\joe\history\History.IE5
 567                     continue;
 568                 }
 569             }
 570             if (!useFileHiding || !isHiddenFile(f)) {
 571                 files.add(f);
 572             }
 573         }
 574 
 575         return files.toArray(new File[files.size()]);
 576     }
 577 
 578 
 579 
 580     /**
 581      * Returns the parent directory of <code>dir</code>.
 582      * @param dir the <code>File</code> being queried
 583      * @return the parent directory of <code>dir</code>, or
 584      *   <code>null</code> if <code>dir</code> is <code>null</code>
 585      */
 586     public File getParentDirectory(File dir) {
 587         if (dir == null || !dir.exists()) {
 588             return null;
 589         }
 590 
 591         ShellFolder sf;
 592 
 593         try {
 594             sf = getShellFolder(dir);
 595         } catch (FileNotFoundException e) {
 596             return null;
 597         }
 598 
 599         File psf = sf.getParentFile();
 600 
 601         if (psf == null) {
 602             return null;
 603         }
 604 
 605         if (isFileSystem(psf)) {
 606             File f = psf;
 607             if (!f.exists()) {
 608                 // This could be a node under "Network Neighborhood".
 609                 File ppsf = psf.getParentFile();
 610                 if (ppsf == null || !isFileSystem(ppsf)) {
 611                     // We're mostly after the exists() override for windows below.
 612                     f = createFileSystemRoot(f);
 613                 }
 614             }
 615             return f;
 616         } else {
 617             return psf;
 618         }
 619     }
 620 
 621     /**
 622      * Returns an array of files representing the values to show by default in
 623      * the file chooser selector.
 624      *
 625      * @return an array of {@code File} objects.
 626      * @throws SecurityException if the caller does not have necessary
 627      *                           permissions
 628      * @since 9
 629      */
 630     public File[] getChooserComboBoxFiles() {
 631         return (File[]) ShellFolder.get("fileChooserComboBoxFolders");
 632     }
 633 
 634     /**
 635      * Returns whether the specified file denotes a shell interpreted link which
 636      * can be obtained by the {@link #getLinkLocation(File)}.
 637      *
 638      * @param file a file
 639      * @return whether this is a link
 640      * @throws NullPointerException if {@code file} equals {@code null}
 641      * @throws SecurityException if the caller does not have necessary
 642      *                           permissions
 643      * @see #getLinkLocation(File)
 644      * @since 9
 645      */
 646     public boolean isLink(File file) {
 647         if (file == null) {
 648             throw new NullPointerException("file is null");
 649         }
 650         try {
 651             return ShellFolder.getShellFolder(file).isLink();
 652         } catch (FileNotFoundException e) {
 653             return false;
 654         }
 655     }
 656 
 657     /**
 658      * Returns the regular file referenced by the specified link file if
 659      * the specified file is a shell interpreted link.
 660      * Returns {@code null} if the specified file is not
 661      * a shell interpreted link.
 662      *
 663      * @param file a file
 664      * @return the linked file or {@code null}.
 665      * @throws FileNotFoundException if the linked file does not exist
 666      * @throws NullPointerException if {@code file} equals {@code null}
 667      * @throws SecurityException if the caller does not have necessary
 668      *                           permissions
 669      * @since 9
 670      */
 671     public File getLinkLocation(File file) throws FileNotFoundException {
 672         if (file == null) {
 673             throw new NullPointerException("file is null");
 674         }
 675         ShellFolder shellFolder;
 676         try {
 677             shellFolder = ShellFolder.getShellFolder(file);
 678         } catch (FileNotFoundException e) {
 679             return null;
 680         }
 681         return shellFolder.isLink() ? shellFolder.getLinkLocation() : null;
 682     }
 683 
 684     /**
 685      * Throws {@code FileNotFoundException} if file not found or current thread was interrupted
 686      */
 687     ShellFolder getShellFolder(File f) throws FileNotFoundException {
 688         if (!(f instanceof ShellFolder) && !(f instanceof FileSystemRoot) && isFileSystemRoot(f)) {
 689             f = createFileSystemRoot(f);
 690         }
 691 
 692         try {
 693             return ShellFolder.getShellFolder(f);
 694         } catch (InternalError e) {
 695             System.err.println("FileSystemView.getShellFolder: f="+f);
 696             e.printStackTrace();
 697             return null;
 698         }
 699     }
 700 
 701     /**
 702      * Creates a new <code>File</code> object for <code>f</code> with correct
 703      * behavior for a file system root directory.
 704      *
 705      * @param f a <code>File</code> object representing a file system root
 706      *          directory, for example "/" on Unix or "C:\" on Windows.
 707      * @return a new <code>File</code> object
 708      * @since 1.4
 709      */
 710     protected File createFileSystemRoot(File f) {
 711         return new FileSystemRoot(f);
 712     }
 713 
 714     @SuppressWarnings("serial") // Same-version serialization only
 715     static class FileSystemRoot extends File {
 716         public FileSystemRoot(File f) {
 717             super(f,"");
 718         }
 719 
 720         public FileSystemRoot(String s) {
 721             super(s);
 722         }
 723 
 724         public boolean isDirectory() {
 725             return true;
 726         }
 727 
 728         public String getName() {
 729             return getPath();
 730         }
 731     }
 732 }
 733 
 734 /**
 735  * FileSystemView that handles some specific unix-isms.
 736  */
 737 class UnixFileSystemView extends FileSystemView {
 738 
 739     private static final String newFolderString =
 740             UIManager.getString("FileChooser.other.newFolder");
 741     private static final String newFolderNextString  =
 742             UIManager.getString("FileChooser.other.newFolder.subsequent");
 743 
 744     /**
 745      * Creates a new folder with a default folder name.
 746      */
 747     public File createNewFolder(File containingDir) throws IOException {
 748         if(containingDir == null) {
 749             throw new IOException("Containing directory is null:");
 750         }
 751         File newFolder;
 752         // Unix - using OpenWindows' default folder name. Can't find one for Motif/CDE.
 753         newFolder = createFileObject(containingDir, newFolderString);
 754         int i = 1;
 755         while (newFolder.exists() && i < 100) {
 756             newFolder = createFileObject(containingDir, MessageFormat.format(
 757                     newFolderNextString, i));
 758             i++;
 759         }
 760 
 761         if(newFolder.exists()) {
 762             throw new IOException("Directory already exists:" + newFolder.getAbsolutePath());
 763         } else {
 764             if(!newFolder.mkdirs()) {
 765                 throw new IOException(newFolder.getAbsolutePath());
 766             }
 767         }
 768 
 769         return newFolder;
 770     }
 771 
 772     public boolean isFileSystemRoot(File dir) {
 773         return dir != null && dir.getAbsolutePath().equals("/");
 774     }
 775 
 776     public boolean isDrive(File dir) {
 777         return isFloppyDrive(dir);
 778     }
 779 
 780     public boolean isFloppyDrive(File dir) {
 781         // Could be looking at the path for Solaris, but wouldn't be reliable.
 782         // For example:
 783         // return (dir != null && dir.getAbsolutePath().toLowerCase().startsWith("/floppy"));
 784         return false;
 785     }
 786 
 787     public boolean isComputerNode(File dir) {
 788         if (dir != null) {
 789             String parent = dir.getParent();
 790             if (parent != null && parent.equals("/net")) {
 791                 return true;
 792             }
 793         }
 794         return false;
 795     }
 796 }
 797 
 798 
 799 /**
 800  * FileSystemView that handles some specific windows concepts.
 801  */
 802 class WindowsFileSystemView extends FileSystemView {
 803 
 804     private static final String newFolderString =
 805             UIManager.getString("FileChooser.win32.newFolder");
 806     private static final String newFolderNextString  =
 807             UIManager.getString("FileChooser.win32.newFolder.subsequent");
 808 
 809     public Boolean isTraversable(File f) {
 810         return Boolean.valueOf(isFileSystemRoot(f) || isComputerNode(f) || f.isDirectory());
 811     }
 812 
 813     public File getChild(File parent, String fileName) {
 814         if (fileName.startsWith("\\")
 815             && !fileName.startsWith("\\\\")
 816             && isFileSystem(parent)) {
 817 
 818             //Path is relative to the root of parent's drive
 819             String path = parent.getAbsolutePath();
 820             if (path.length() >= 2
 821                 && path.charAt(1) == ':'
 822                 && Character.isLetter(path.charAt(0))) {
 823 
 824                 return createFileObject(path.substring(0, 2) + fileName);
 825             }
 826         }
 827         return super.getChild(parent, fileName);
 828     }
 829 
 830     /**
 831      * Type description for a file, directory, or folder as it would be displayed in
 832      * a system file browser. Example from Windows: the "Desktop" folder
 833      * is described as "Desktop".
 834      *
 835      * The Windows implementation gets information from the ShellFolder class.
 836      */
 837     public String getSystemTypeDescription(File f) {
 838         if (f == null) {
 839             return null;
 840         }
 841 
 842         try {
 843             return getShellFolder(f).getFolderType();
 844         } catch (FileNotFoundException e) {
 845             return null;
 846         }
 847     }
 848 
 849     /**
 850      * @return the Desktop folder.
 851      */
 852     public File getHomeDirectory() {
 853         File[] roots = getRoots();
 854         return (roots.length == 0) ? null : roots[0];
 855     }
 856 
 857     /**
 858      * Creates a new folder with a default folder name.
 859      */
 860     public File createNewFolder(File containingDir) throws IOException {
 861         if(containingDir == null) {
 862             throw new IOException("Containing directory is null:");
 863         }
 864         // Using NT's default folder name
 865         File newFolder = createFileObject(containingDir, newFolderString);
 866         int i = 2;
 867         while (newFolder.exists() && i < 100) {
 868             newFolder = createFileObject(containingDir, MessageFormat.format(
 869                 newFolderNextString, i));
 870             i++;
 871         }
 872 
 873         if(newFolder.exists()) {
 874             throw new IOException("Directory already exists:" + newFolder.getAbsolutePath());
 875         } else {
 876             if(!newFolder.mkdirs()) {
 877                 throw new IOException(newFolder.getAbsolutePath());
 878             }
 879         }
 880 
 881         return newFolder;
 882     }
 883 
 884     public boolean isDrive(File dir) {
 885         return isFileSystemRoot(dir);
 886     }
 887 
 888     public boolean isFloppyDrive(final File dir) {
 889         String path = AccessController.doPrivileged(new PrivilegedAction<String>() {
 890             public String run() {
 891                 return dir.getAbsolutePath();
 892             }
 893         });
 894 
 895         return path != null && (path.equals("A:\\") || path.equals("B:\\"));
 896     }
 897 
 898     /**
 899      * Returns a File object constructed from the given path string.
 900      */
 901     public File createFileObject(String path) {
 902         // Check for missing backslash after drive letter such as "C:" or "C:filename"
 903         if (path.length() >= 2 && path.charAt(1) == ':' && Character.isLetter(path.charAt(0))) {
 904             if (path.length() == 2) {
 905                 path += "\\";
 906             } else if (path.charAt(2) != '\\') {
 907                 path = path.substring(0, 2) + "\\" + path.substring(2);
 908             }
 909         }
 910         return super.createFileObject(path);
 911     }
 912 
 913     @SuppressWarnings("serial") // anonymous class
 914     protected File createFileSystemRoot(File f) {
 915         // Problem: Removable drives on Windows return false on f.exists()
 916         // Workaround: Override exists() to always return true.
 917         return new FileSystemRoot(f) {
 918             public boolean exists() {
 919                 return true;
 920             }
 921         };
 922     }
 923 
 924 }
 925 
 926 /**
 927  * Fallthrough FileSystemView in case we can't determine the OS.
 928  */
 929 class GenericFileSystemView extends FileSystemView {
 930 
 931     private static final String newFolderString =
 932             UIManager.getString("FileChooser.other.newFolder");
 933 
 934     /**
 935      * Creates a new folder with a default folder name.
 936      */
 937     public File createNewFolder(File containingDir) throws IOException {
 938         if(containingDir == null) {
 939             throw new IOException("Containing directory is null:");
 940         }
 941         // Using NT's default folder name
 942         File newFolder = createFileObject(containingDir, newFolderString);
 943 
 944         if(newFolder.exists()) {
 945             throw new IOException("Directory already exists:" + newFolder.getAbsolutePath());
 946         } else {
 947             if(!newFolder.mkdirs()) {
 948                 throw new IOException(newFolder.getAbsolutePath());
 949             }
 950         }
 951         return newFolder;
 952     }
 953 
 954 }