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