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