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 }