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