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