1 /* 2 * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.filechooser; 27 28 29 import javax.swing.*; 30 31 import java.awt.Image; 32 import java.io.File; 33 import java.io.FileNotFoundException; 34 import java.io.IOException; 35 import java.text.MessageFormat; 36 import java.util.List; 37 import java.util.ArrayList; 38 import java.lang.ref.WeakReference; 39 import java.beans.PropertyChangeListener; 40 import java.beans.PropertyChangeEvent; 41 import java.security.AccessController; 42 import java.security.PrivilegedAction; 43 44 import sun.awt.shell.*; 45 46 /** 47 * FileSystemView is JFileChooser's gateway to the 48 * file system. Since the JDK1.1 File API doesn't allow 49 * access to such information as root partitions, file type 50 * information, or hidden file bits, this class is designed 51 * to intuit as much OS-specific file system information as 52 * possible. 53 * 54 * <p> 55 * 56 * Java Licensees may want to provide a different implementation of 57 * FileSystemView to better handle a given operating system. 58 * 59 * @author Jeff Dinkins 60 */ 61 62 // PENDING(jeff) - need to provide a specification for 63 // how Mac/OS2/BeOS/etc file systems can modify FileSystemView 64 // to handle their particular type of file system. 65 66 public abstract class FileSystemView { 67 68 static FileSystemView windowsFileSystemView = null; 69 static FileSystemView unixFileSystemView = null; 70 //static FileSystemView macFileSystemView = null; 71 static FileSystemView genericFileSystemView = null; 72 73 private boolean useSystemExtensionHiding = 74 UIManager.getDefaults().getBoolean("FileChooser.useSystemExtensionHiding"); 75 76 public static FileSystemView getFileSystemView() { 77 if(File.separatorChar == '\\') { 78 if(windowsFileSystemView == null) { 79 windowsFileSystemView = new WindowsFileSystemView(); 80 } 81 return windowsFileSystemView; 82 } 83 84 if(File.separatorChar == '/') { 85 if(unixFileSystemView == null) { 86 unixFileSystemView = new UnixFileSystemView(); 87 } 88 return unixFileSystemView; 89 } 90 91 // if(File.separatorChar == ':') { 92 // if(macFileSystemView == null) { 93 // macFileSystemView = new MacFileSystemView(); 94 // } 95 // return macFileSystemView; 96 //} 97 98 if(genericFileSystemView == null) { 99 genericFileSystemView = new GenericFileSystemView(); 100 } 101 return genericFileSystemView; 102 } 103 104 public FileSystemView() { 105 final WeakReference<FileSystemView> weakReference = new WeakReference<FileSystemView>(this); 106 107 UIManager.addPropertyChangeListener(new PropertyChangeListener() { 108 public void propertyChange(PropertyChangeEvent evt) { 109 FileSystemView fileSystemView = weakReference.get(); 110 111 if (fileSystemView == null) { 112 // FileSystemView was destroyed 113 UIManager.removePropertyChangeListener(this); 114 } else { 115 if (evt.getPropertyName().equals("lookAndFeel")) { 116 fileSystemView.useSystemExtensionHiding = 117 UIManager.getDefaults().getBoolean("FileChooser.useSystemExtensionHiding"); 118 } 119 } 120 } 121 }); 122 } 123 124 /** 125 * Determines if the given file is a root in the navigable tree(s). 126 * Examples: Windows 98 has one root, the Desktop folder. DOS has one root 127 * per drive letter, <code>C:\</code>, <code>D:\</code>, etc. Unix has one root, 128 * the <code>"/"</code> directory. 129 * 130 * The default implementation gets information from the <code>ShellFolder</code> class. 131 * 132 * @param f a <code>File</code> object representing a directory 133 * @return <code>true</code> if <code>f</code> is a root in the navigable tree. 134 * @see #isFileSystemRoot 135 */ 136 public boolean isRoot(File f) { 137 if (f == null || !f.isAbsolute()) { 138 return false; 139 } 140 141 File[] roots = getRoots(); 142 for (File root : roots) { 143 if (root.equals(f)) { 144 return true; 145 } 146 } 147 return false; 148 } 149 150 /** 151 * Returns true if the file (directory) can be visited. 152 * Returns false if the directory cannot be traversed. 153 * 154 * @param f the <code>File</code> 155 * @return <code>true</code> if the file/directory can be traversed, otherwise <code>false</code> 156 * @see JFileChooser#isTraversable 157 * @see FileView#isTraversable 158 * @since 1.4 159 */ 160 public Boolean isTraversable(File f) { 161 return Boolean.valueOf(f.isDirectory()); 162 } 163 164 /** 165 * Name of a file, directory, or folder as it would be displayed in 166 * a system file browser. Example from Windows: the "M:\" directory 167 * displays as "CD-ROM (M:)" 168 * 169 * The default implementation gets information from the ShellFolder class. 170 * 171 * @param f a <code>File</code> object 172 * @return the file name as it would be displayed by a native file chooser 173 * @see JFileChooser#getName 174 * @since 1.4 175 */ 176 public String getSystemDisplayName(File f) { 177 if (f == null) { 178 return null; 179 } 180 181 String name = f.getName(); 182 183 if (!name.equals("..") && !name.equals(".") && 184 (useSystemExtensionHiding || !isFileSystem(f) || isFileSystemRoot(f)) && 185 (f instanceof ShellFolder || f.exists())) { 186 187 try { 188 name = getShellFolder(f).getDisplayName(); 189 } catch (FileNotFoundException e) { 190 return null; 191 } 192 193 if (name == null || name.length() == 0) { 194 name = f.getPath(); // e.g. "/" 195 } 196 } 197 198 return name; 199 } 200 201 /** 202 * Type description for a file, directory, or folder as it would be displayed in 203 * a system file browser. Example from Windows: the "Desktop" folder 204 * is described as "Desktop". 205 * 206 * Override for platforms with native ShellFolder implementations. 207 * 208 * @param f a <code>File</code> object 209 * @return the file type description as it would be displayed by a native file chooser 210 * or null if no native information is available. 211 * @see JFileChooser#getTypeDescription 212 * @since 1.4 213 */ 214 public String getSystemTypeDescription(File f) { 215 return null; 216 } 217 218 /** 219 * Icon for a file, directory, or folder as it would be displayed in 220 * a system file browser. Example from Windows: the "M:\" directory 221 * displays a CD-ROM icon. 222 * 223 * The default implementation gets information from the ShellFolder class. 224 * 225 * @param f a <code>File</code> object 226 * @return an icon as it would be displayed by a native file chooser 227 * @see JFileChooser#getIcon 228 * @since 1.4 229 */ 230 public Icon getSystemIcon(File f) { 231 if (f == null) { 232 return null; 233 } 234 235 ShellFolder sf; 236 237 try { 238 sf = getShellFolder(f); 239 } catch (FileNotFoundException e) { 240 return null; 241 } 242 243 Image img = sf.getIcon(false); 244 245 if (img != null) { 246 return new ImageIcon(img, sf.getFolderType()); 247 } else { 248 return UIManager.getIcon(f.isDirectory() ? "FileView.directoryIcon" : "FileView.fileIcon"); 249 } 250 } 251 252 /** 253 * On Windows, a file can appear in multiple folders, other than its 254 * parent directory in the filesystem. Folder could for example be the 255 * "Desktop" folder which is not the same as file.getParentFile(). 256 * 257 * @param folder a <code>File</code> object representing a directory or special folder 258 * @param file a <code>File</code> object 259 * @return <code>true</code> if <code>folder</code> is a directory or special folder and contains <code>file</code>. 260 * @since 1.4 261 */ 262 public boolean isParent(File folder, File file) { 263 if (folder == null || file == null) { 264 return false; 265 } else if (folder instanceof ShellFolder) { 266 File parent = file.getParentFile(); 267 if (parent != null && parent.equals(folder)) { 268 return true; 269 } 270 File[] children = getFiles(folder, false); 271 for (File child : children) { 272 if (file.equals(child)) { 273 return true; 274 } 275 } 276 return false; 277 } else { 278 return folder.equals(file.getParentFile()); 279 } 280 } 281 282 /** 283 * 284 * @param parent a <code>File</code> object representing a directory or special folder 285 * @param fileName a name of a file or folder which exists in <code>parent</code> 286 * @return a File object. This is normally constructed with <code>new 287 * File(parent, fileName)</code> except when parent and child are both 288 * special folders, in which case the <code>File</code> is a wrapper containing 289 * a <code>ShellFolder</code> object. 290 * @since 1.4 291 */ 292 public File getChild(File parent, String fileName) { 293 if (parent instanceof ShellFolder) { 294 File[] children = getFiles(parent, false); 295 for (File child : children) { 296 if (child.getName().equals(fileName)) { 297 return child; 298 } 299 } 300 } 301 return createFileObject(parent, fileName); 302 } 303 304 305 /** 306 * Checks if <code>f</code> represents a real directory or file as opposed to a 307 * special folder such as <code>"Desktop"</code>. Used by UI classes to decide if 308 * a folder is selectable when doing directory choosing. 309 * 310 * @param f a <code>File</code> object 311 * @return <code>true</code> if <code>f</code> is a real file or directory. 312 * @since 1.4 313 */ 314 public boolean isFileSystem(File f) { 315 if (f instanceof ShellFolder) { 316 ShellFolder sf = (ShellFolder)f; 317 // Shortcuts to directories are treated as not being file system objects, 318 // so that they are never returned by JFileChooser. 319 return sf.isFileSystem() && !(sf.isLink() && sf.isDirectory()); 320 } else { 321 return true; 322 } 323 } 324 325 /** 326 * Creates a new folder with a default folder name. 327 */ 328 public abstract File createNewFolder(File containingDir) throws IOException; 329 330 /** 331 * Returns whether a file is hidden or not. 332 */ 333 public boolean isHiddenFile(File f) { 334 return f.isHidden(); 335 } 336 337 338 /** 339 * Is dir the root of a tree in the file system, such as a drive 340 * or partition. Example: Returns true for "C:\" on Windows 98. 341 * 342 * @param dir a <code>File</code> object representing a directory 343 * @return <code>true</code> if <code>f</code> is a root of a filesystem 344 * @see #isRoot 345 * @since 1.4 346 */ 347 public boolean isFileSystemRoot(File dir) { 348 return ShellFolder.isFileSystemRoot(dir); 349 } 350 351 /** 352 * Used by UI classes to decide whether to display a special icon 353 * for drives or partitions, e.g. a "hard disk" icon. 354 * 355 * The default implementation has no way of knowing, so always returns false. 356 * 357 * @param dir a directory 358 * @return <code>false</code> always 359 * @since 1.4 360 */ 361 public boolean isDrive(File dir) { 362 return false; 363 } 364 365 /** 366 * Used by UI classes to decide whether to display a special icon 367 * for a floppy disk. Implies isDrive(dir). 368 * 369 * The default implementation has no way of knowing, so always returns false. 370 * 371 * @param dir a directory 372 * @return <code>false</code> always 373 * @since 1.4 374 */ 375 public boolean isFloppyDrive(File dir) { 376 return false; 377 } 378 379 /** 380 * Used by UI classes to decide whether to display a special icon 381 * for a computer node, e.g. "My Computer" or a network server. 382 * 383 * The default implementation has no way of knowing, so always returns false. 384 * 385 * @param dir a directory 386 * @return <code>false</code> always 387 * @since 1.4 388 */ 389 public boolean isComputerNode(File dir) { 390 return ShellFolder.isComputerNode(dir); 391 } 392 393 394 /** 395 * Returns all root partitions on this system. For example, on 396 * Windows, this would be the "Desktop" folder, while on DOS this 397 * would be the A: through Z: drives. 398 */ 399 public File[] getRoots() { 400 // Don't cache this array, because filesystem might change 401 File[] roots = (File[])ShellFolder.get("roots"); 402 403 for (int i = 0; i < roots.length; i++) { 404 if (isFileSystemRoot(roots[i])) { 405 roots[i] = createFileSystemRoot(roots[i]); 406 } 407 } 408 return roots; 409 } 410 411 412 // Providing default implementations for the remaining methods 413 // because most OS file systems will likely be able to use this 414 // code. If a given OS can't, override these methods in its 415 // implementation. 416 417 public File getHomeDirectory() { 418 return createFileObject(System.getProperty("user.home")); 419 } 420 421 /** 422 * Return the user's default starting directory for the file chooser. 423 * 424 * @return a <code>File</code> object representing the default 425 * starting folder 426 * @since 1.4 427 */ 428 public File getDefaultDirectory() { 429 File f = (File)ShellFolder.get("fileChooserDefaultFolder"); 430 if (isFileSystemRoot(f)) { 431 f = createFileSystemRoot(f); 432 } 433 return f; 434 } 435 436 /** 437 * Returns a File object constructed in dir from the given filename. 438 */ 439 public File createFileObject(File dir, String filename) { 440 if(dir == null) { 441 return new File(filename); 442 } else { 443 return new File(dir, filename); 444 } 445 } 446 447 /** 448 * Returns a File object constructed from the given path string. 449 */ 450 public File createFileObject(String path) { 451 File f = new File(path); 452 if (isFileSystemRoot(f)) { 453 f = createFileSystemRoot(f); 454 } 455 return f; 456 } 457 458 459 /** 460 * Gets the list of shown (i.e. not hidden) files. 461 */ 462 public File[] getFiles(File dir, boolean useFileHiding) { 463 List<File> files = new ArrayList<File>(); 464 465 // add all files in dir 466 if (!(dir instanceof ShellFolder)) { 467 try { 468 dir = getShellFolder(dir); 469 } catch (FileNotFoundException e) { 470 return new File[0]; 471 } 472 } 473 474 File[] names = ((ShellFolder) dir).listFiles(!useFileHiding); 475 476 if (names == null) { 477 return new File[0]; 478 } 479 480 for (File f : names) { 481 if (Thread.currentThread().isInterrupted()) { 482 break; 483 } 484 485 if (!(f instanceof ShellFolder)) { 486 if (isFileSystemRoot(f)) { 487 f = createFileSystemRoot(f); 488 } 489 try { 490 f = ShellFolder.getShellFolder(f); 491 } catch (FileNotFoundException e) { 492 // Not a valid file (wouldn't show in native file chooser) 493 // Example: C:\pagefile.sys 494 continue; 495 } catch (InternalError e) { 496 // Not a valid file (wouldn't show in native file chooser) 497 // Example C:\Winnt\Profiles\joe\history\History.IE5 498 continue; 499 } 500 } 501 if (!useFileHiding || !isHiddenFile(f)) { 502 files.add(f); 503 } 504 } 505 506 return files.toArray(new File[files.size()]); 507 } 508 509 510 511 /** 512 * Returns the parent directory of <code>dir</code>. 513 * @param dir the <code>File</code> being queried 514 * @return the parent directory of <code>dir</code>, or 515 * <code>null</code> if <code>dir</code> is <code>null</code> 516 */ 517 public File getParentDirectory(File dir) { 518 if (dir == null || !dir.exists()) { 519 return null; 520 } 521 522 ShellFolder sf; 523 524 try { 525 sf = getShellFolder(dir); 526 } catch (FileNotFoundException e) { 527 return null; 528 } 529 530 File psf = sf.getParentFile(); 531 532 if (psf == null) { 533 return null; 534 } 535 536 if (isFileSystem(psf)) { 537 File f = psf; 538 if (!f.exists()) { 539 // This could be a node under "Network Neighborhood". 540 File ppsf = psf.getParentFile(); 541 if (ppsf == null || !isFileSystem(ppsf)) { 542 // We're mostly after the exists() override for windows below. 543 f = createFileSystemRoot(f); 544 } 545 } 546 return f; 547 } else { 548 return psf; 549 } 550 } 551 552 /** 553 * Throws {@code FileNotFoundException} if file not found or current thread was interrupted 554 */ 555 ShellFolder getShellFolder(File f) throws FileNotFoundException { 556 if (!(f instanceof ShellFolder) && !(f instanceof FileSystemRoot) && isFileSystemRoot(f)) { 557 f = createFileSystemRoot(f); 558 } 559 560 try { 561 return ShellFolder.getShellFolder(f); 562 } catch (InternalError e) { 563 System.err.println("FileSystemView.getShellFolder: f="+f); 564 e.printStackTrace(); 565 return null; 566 } 567 } 568 569 /** 570 * Creates a new <code>File</code> object for <code>f</code> with correct 571 * behavior for a file system root directory. 572 * 573 * @param f a <code>File</code> object representing a file system root 574 * directory, for example "/" on Unix or "C:\" on Windows. 575 * @return a new <code>File</code> object 576 * @since 1.4 577 */ 578 protected File createFileSystemRoot(File f) { 579 return new FileSystemRoot(f); 580 } 581 582 @SuppressWarnings("serial") // Same-version serialization only 583 static class FileSystemRoot extends File { 584 public FileSystemRoot(File f) { 585 super(f,""); 586 } 587 588 public FileSystemRoot(String s) { 589 super(s); 590 } 591 592 public boolean isDirectory() { 593 return true; 594 } 595 596 public String getName() { 597 return getPath(); 598 } 599 } 600 } 601 602 /** 603 * FileSystemView that handles some specific unix-isms. 604 */ 605 class UnixFileSystemView extends FileSystemView { 606 607 private static final String newFolderString = 608 UIManager.getString("FileChooser.other.newFolder"); 609 private static final String newFolderNextString = 610 UIManager.getString("FileChooser.other.newFolder.subsequent"); 611 612 /** 613 * Creates a new folder with a default folder name. 614 */ 615 public File createNewFolder(File containingDir) throws IOException { 616 if(containingDir == null) { 617 throw new IOException("Containing directory is null:"); 618 } 619 File newFolder; 620 // Unix - using OpenWindows' default folder name. Can't find one for Motif/CDE. 621 newFolder = createFileObject(containingDir, newFolderString); 622 int i = 1; 623 while (newFolder.exists() && i < 100) { 624 newFolder = createFileObject(containingDir, MessageFormat.format( 625 newFolderNextString, new Integer(i))); 626 i++; 627 } 628 629 if(newFolder.exists()) { 630 throw new IOException("Directory already exists:" + newFolder.getAbsolutePath()); 631 } else { 632 newFolder.mkdirs(); 633 } 634 635 return newFolder; 636 } 637 638 public boolean isFileSystemRoot(File dir) { 639 return dir != null && dir.getAbsolutePath().equals("/"); 640 } 641 642 public boolean isDrive(File dir) { 643 return isFloppyDrive(dir); 644 } 645 646 public boolean isFloppyDrive(File dir) { 647 // Could be looking at the path for Solaris, but wouldn't be reliable. 648 // For example: 649 // return (dir != null && dir.getAbsolutePath().toLowerCase().startsWith("/floppy")); 650 return false; 651 } 652 653 public boolean isComputerNode(File dir) { 654 if (dir != null) { 655 String parent = dir.getParent(); 656 if (parent != null && parent.equals("/net")) { 657 return true; 658 } 659 } 660 return false; 661 } 662 } 663 664 665 /** 666 * FileSystemView that handles some specific windows concepts. 667 */ 668 class WindowsFileSystemView extends FileSystemView { 669 670 private static final String newFolderString = 671 UIManager.getString("FileChooser.win32.newFolder"); 672 private static final String newFolderNextString = 673 UIManager.getString("FileChooser.win32.newFolder.subsequent"); 674 675 public Boolean isTraversable(File f) { 676 return Boolean.valueOf(isFileSystemRoot(f) || isComputerNode(f) || f.isDirectory()); 677 } 678 679 public File getChild(File parent, String fileName) { 680 if (fileName.startsWith("\\") 681 && !fileName.startsWith("\\\\") 682 && isFileSystem(parent)) { 683 684 //Path is relative to the root of parent's drive 685 String path = parent.getAbsolutePath(); 686 if (path.length() >= 2 687 && path.charAt(1) == ':' 688 && Character.isLetter(path.charAt(0))) { 689 690 return createFileObject(path.substring(0, 2) + fileName); 691 } 692 } 693 return super.getChild(parent, fileName); 694 } 695 696 /** 697 * Type description for a file, directory, or folder as it would be displayed in 698 * a system file browser. Example from Windows: the "Desktop" folder 699 * is described as "Desktop". 700 * 701 * The Windows implementation gets information from the ShellFolder class. 702 */ 703 public String getSystemTypeDescription(File f) { 704 if (f == null) { 705 return null; 706 } 707 708 try { 709 return getShellFolder(f).getFolderType(); 710 } catch (FileNotFoundException e) { 711 return null; 712 } 713 } 714 715 /** 716 * @return the Desktop folder. 717 */ 718 public File getHomeDirectory() { 719 return getRoots()[0]; 720 } 721 722 /** 723 * Creates a new folder with a default folder name. 724 */ 725 public File createNewFolder(File containingDir) throws IOException { 726 if(containingDir == null) { 727 throw new IOException("Containing directory is null:"); 728 } 729 // Using NT's default folder name 730 File newFolder = createFileObject(containingDir, newFolderString); 731 int i = 2; 732 while (newFolder.exists() && i < 100) { 733 newFolder = createFileObject(containingDir, MessageFormat.format( 734 newFolderNextString, new Integer(i))); 735 i++; 736 } 737 738 if(newFolder.exists()) { 739 throw new IOException("Directory already exists:" + newFolder.getAbsolutePath()); 740 } else { 741 newFolder.mkdirs(); 742 } 743 744 return newFolder; 745 } 746 747 public boolean isDrive(File dir) { 748 return isFileSystemRoot(dir); 749 } 750 751 public boolean isFloppyDrive(final File dir) { 752 String path = AccessController.doPrivileged(new PrivilegedAction<String>() { 753 public String run() { 754 return dir.getAbsolutePath(); 755 } 756 }); 757 758 return path != null && (path.equals("A:\\") || path.equals("B:\\")); 759 } 760 761 /** 762 * Returns a File object constructed from the given path string. 763 */ 764 public File createFileObject(String path) { 765 // Check for missing backslash after drive letter such as "C:" or "C:filename" 766 if (path.length() >= 2 && path.charAt(1) == ':' && Character.isLetter(path.charAt(0))) { 767 if (path.length() == 2) { 768 path += "\\"; 769 } else if (path.charAt(2) != '\\') { 770 path = path.substring(0, 2) + "\\" + path.substring(2); 771 } 772 } 773 return super.createFileObject(path); 774 } 775 776 @SuppressWarnings("serial") // anonymous class 777 protected File createFileSystemRoot(File f) { 778 // Problem: Removable drives on Windows return false on f.exists() 779 // Workaround: Override exists() to always return true. 780 return new FileSystemRoot(f) { 781 public boolean exists() { 782 return true; 783 } 784 }; 785 } 786 787 } 788 789 /** 790 * Fallthrough FileSystemView in case we can't determine the OS. 791 */ 792 class GenericFileSystemView extends FileSystemView { 793 794 private static final String newFolderString = 795 UIManager.getString("FileChooser.other.newFolder"); 796 797 /** 798 * Creates a new folder with a default folder name. 799 */ 800 public File createNewFolder(File containingDir) throws IOException { 801 if(containingDir == null) { 802 throw new IOException("Containing directory is null:"); 803 } 804 // Using NT's default folder name 805 File newFolder = createFileObject(containingDir, newFolderString); 806 807 if(newFolder.exists()) { 808 throw new IOException("Directory already exists:" + newFolder.getAbsolutePath()); 809 } else { 810 newFolder.mkdirs(); 811 } 812 813 return newFolder; 814 } 815 816 }