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