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