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