1 /*
2 * Copyright (c) 1998, 2016, 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.beans.BeanProperty;
33 import java.beans.JavaBean;
34 import java.io.File;
35 import java.io.FileNotFoundException;
36 import java.io.IOException;
37 import java.text.MessageFormat;
38 import java.util.List;
39 import java.util.ArrayList;
40 import java.lang.ref.WeakReference;
41 import java.beans.PropertyChangeListener;
42 import java.beans.PropertyChangeEvent;
43 import java.beans.PropertyChangeSupport;
44 import java.security.AccessController;
45 import java.security.PrivilegedAction;
46 import java.util.Arrays;
47 import java.util.Collections;
48 import java.util.Objects;
49 import sun.awt.OSInfo;
50
51 import sun.awt.shell.*;
52
53 /**
54 * FileSystemView is JFileChooser's gateway to the
55 * file system. Since the JDK1.1 File API doesn't allow
56 * access to such information as root partitions, file type
57 * information, or hidden file bits, this class is designed
58 * to intuit as much OS-specific file system information as
59 * possible.
60 *
61 * <p>
62 *
63 * Java Licensees may want to provide a different implementation of
64 * FileSystemView to better handle a given operating system.
65 *
66 * @author Jeff Dinkins
67 */
68
69 // PENDING(jeff) - need to provide a specification for
70 // how Mac/OS2/BeOS/etc file systems can modify FileSystemView
71 // to handle their particular type of file system.
72
73 public abstract class FileSystemView {
74
75 static FileSystemView windowsFileSystemView = null;
76 static FileSystemView unixFileSystemView = null;
77 //static FileSystemView macFileSystemView = null;
78 static FileSystemView genericFileSystemView = null;
79
80 private boolean useSystemExtensionHiding =
81 UIManager.getDefaults().getBoolean("FileChooser.useSystemExtensionHiding");
82
83 /**
84 * Returns the file system view.
85 * @return the file system view
86 */
87 public static FileSystemView getFileSystemView() {
88 if(File.separatorChar == '\\') {
89 if(windowsFileSystemView == null) {
90 windowsFileSystemView = new WindowsFileSystemView();
91 }
92 return windowsFileSystemView;
93 }
94
95 if(File.separatorChar == '/') {
96 if(unixFileSystemView == null) {
97 unixFileSystemView = new UnixFileSystemView();
98 }
99 return unixFileSystemView;
100 }
101
102 // if(File.separatorChar == ':') {
103 // if(macFileSystemView == null) {
104 // macFileSystemView = new MacFileSystemView();
105 // }
106 // return macFileSystemView;
107 //}
108
109 if(genericFileSystemView == null) {
110 genericFileSystemView = new GenericFileSystemView();
111 }
112 return genericFileSystemView;
113 }
114
115 /**
116 * Constructs a FileSystemView.
117 */
118 public FileSystemView() {
119 final WeakReference<FileSystemView> weakReference = new WeakReference<FileSystemView>(this);
120
121 UIManager.addPropertyChangeListener(new PropertyChangeListener() {
122 public void propertyChange(PropertyChangeEvent evt) {
123 FileSystemView fileSystemView = weakReference.get();
124
125 if (fileSystemView == null) {
126 // FileSystemView was destroyed
127 UIManager.removePropertyChangeListener(this);
128 } else {
129 if (evt.getPropertyName().equals("lookAndFeel")) {
130 fileSystemView.useSystemExtensionHiding =
131 UIManager.getDefaults().getBoolean("FileChooser.useSystemExtensionHiding");
132 }
133 }
134 }
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 * On Windows, a file can appear in multiple folders, other than its
268 * parent directory in the filesystem. Folder could for example be the
269 * "Desktop" folder which is not the same as file.getParentFile().
270 *
271 * @param folder a <code>File</code> object representing a directory or special folder
272 * @param file a <code>File</code> object
273 * @return <code>true</code> if <code>folder</code> is a directory or special folder and contains <code>file</code>.
274 * @since 1.4
275 */
276 public boolean isParent(File folder, File file) {
277 if (folder == null || file == null) {
278 return false;
279 } else if (folder instanceof ShellFolder) {
280 File parent = file.getParentFile();
281 if (parent != null && parent.equals(folder)) {
282 return true;
283 }
284 File[] children = getFiles(folder, false);
285 for (File child : children) {
286 if (file.equals(child)) {
287 return true;
288 }
289 }
290 return false;
291 } else {
292 return folder.equals(file.getParentFile());
293 }
294 }
295
296 /**
297 *
298 * @param parent a <code>File</code> object representing a directory or special folder
299 * @param fileName a name of a file or folder which exists in <code>parent</code>
300 * @return a File object. This is normally constructed with <code>new
301 * File(parent, fileName)</code> except when parent and child are both
302 * special folders, in which case the <code>File</code> is a wrapper containing
303 * a <code>ShellFolder</code> object.
304 * @since 1.4
305 */
306 public File getChild(File parent, String fileName) {
307 if (parent instanceof ShellFolder) {
308 File[] children = getFiles(parent, false);
309 for (File child : children) {
310 if (child.getName().equals(fileName)) {
311 return child;
312 }
313 }
314 }
315 return createFileObject(parent, fileName);
316 }
317
318
319 /**
320 * Checks if <code>f</code> represents a real directory or file as opposed to a
321 * special folder such as <code>"Desktop"</code>. Used by UI classes to decide if
322 * a folder is selectable when doing directory choosing.
323 *
324 * @param f a <code>File</code> object
325 * @return <code>true</code> if <code>f</code> is a real file or directory.
326 * @since 1.4
327 */
328 public boolean isFileSystem(File f) {
329 if (f instanceof ShellFolder) {
330 ShellFolder sf = (ShellFolder)f;
331 // Shortcuts to directories are treated as not being file system objects,
332 // so that they are never returned by JFileChooser.
333 return sf.isFileSystem() && !(sf.isLink() && sf.isDirectory());
334 } else {
335 return true;
336 }
337 }
338
339 /**
340 * Creates a new folder with a default folder name.
341 *
342 * @param containingDir a {@code File} object denoting directory to contain the new folder
343 * @return a {@code File} object denoting the newly created folder
344 * @throws IOException if new folder could not be created
345 */
346 public abstract File createNewFolder(File containingDir) throws IOException;
347
348 /**
349 * Returns whether a file is hidden or not.
350 *
351 * @param f a {@code File} object
352 * @return true if the given {@code File} denotes a hidden file
353 */
354 public boolean isHiddenFile(File f) {
355 return f.isHidden();
356 }
357
358
359 /**
360 * Is dir the root of a tree in the file system, such as a drive
361 * or partition. Example: Returns true for "C:\" on Windows 98.
362 *
363 * @param dir a <code>File</code> object representing a directory
364 * @return <code>true</code> if <code>f</code> is a root of a filesystem
365 * @see #isRoot
366 * @since 1.4
367 */
368 public boolean isFileSystemRoot(File dir) {
369 return ShellFolder.isFileSystemRoot(dir);
370 }
371
372 /**
373 * Used by UI classes to decide whether to display a special icon
374 * for drives or partitions, e.g. a "hard disk" icon.
375 *
376 * The default implementation has no way of knowing, so always returns false.
377 *
378 * @param dir a directory
379 * @return <code>false</code> always
380 * @since 1.4
381 */
382 public boolean isDrive(File dir) {
383 return false;
384 }
385
386 /**
387 * Used by UI classes to decide whether to display a special icon
388 * for a floppy disk. Implies isDrive(dir).
389 *
390 * The default implementation has no way of knowing, so always returns false.
391 *
392 * @param dir a directory
393 * @return <code>false</code> always
394 * @since 1.4
395 */
396 public boolean isFloppyDrive(File dir) {
397 return false;
398 }
399
400 /**
401 * Used by UI classes to decide whether to display a special icon
402 * for a computer node, e.g. "My Computer" or a network server.
403 *
404 * The default implementation has no way of knowing, so always returns false.
405 *
406 * @param dir a directory
407 * @return <code>false</code> always
408 * @since 1.4
409 */
410 public boolean isComputerNode(File dir) {
411 return ShellFolder.isComputerNode(dir);
412 }
413
414
415 /**
416 * Returns all root partitions on this system. For example, on
417 * Windows, this would be the "Desktop" folder, while on DOS this
418 * would be the A: through Z: drives.
419 *
420 * @return an array of {@code File} objects representing all root partitions
421 * on this system
422 */
423 public File[] getRoots() {
424 // Don't cache this array, because filesystem might change
425 File[] roots = (File[])ShellFolder.get("roots");
426
427 for (int i = 0; i < roots.length; i++) {
428 if (isFileSystemRoot(roots[i])) {
429 roots[i] = createFileSystemRoot(roots[i]);
430 }
431 }
432 return roots;
433 }
434
435
436 // Providing default implementations for the remaining methods
437 // because most OS file systems will likely be able to use this
438 // code. If a given OS can't, override these methods in its
439 // implementation.
440
441 /**
442 * Returns the home directory.
443 * @return the home directory
444 */
445 public File getHomeDirectory() {
446 return createFileObject(System.getProperty("user.home"));
447 }
448
449 /**
450 * Return the user's default starting directory for the file chooser.
451 *
452 * @return a <code>File</code> object representing the default
453 * starting folder
454 * @since 1.4
455 */
456 public File getDefaultDirectory() {
457 File f = (File)ShellFolder.get("fileChooserDefaultFolder");
458 if (isFileSystemRoot(f)) {
459 f = createFileSystemRoot(f);
460 }
461 return f;
462 }
463
464 /**
465 * Returns a File object constructed in dir from the given filename.
466 *
467 * @param dir an abstract pathname denoting a directory
468 * @param filename a {@code String} representation of a pathname
469 * @return a {@code File} object created from {@code dir} and {@code filename}
470 */
471 public File createFileObject(File dir, String filename) {
472 if(dir == null) {
473 return new File(filename);
474 } else {
475 return new File(dir, filename);
476 }
477 }
478
479 /**
480 * Returns a File object constructed from the given path string.
481 *
482 * @param path {@code String} representation of path
483 * @return a {@code File} object created from the given {@code path}
484 */
485 public File createFileObject(String path) {
486 File f = new File(path);
487 if (isFileSystemRoot(f)) {
488 f = createFileSystemRoot(f);
489 }
490 return f;
491 }
492
493
494 /**
495 * Gets the list of shown (i.e. not hidden) files.
496 *
497 * @param dir the root directory of files to be returned
498 * @param useFileHiding determine if hidden files are returned
499 * @return an array of {@code File} objects representing files and
500 * directories in the given {@code dir}. It includes hidden
501 * files if {@code useFileHiding} is false.
502 */
503 public File[] getFiles(File dir, boolean useFileHiding) {
504 List<File> files = new ArrayList<File>();
505
506 // add all files in dir
507 if (!(dir instanceof ShellFolder)) {
508 try {
509 dir = getShellFolder(dir);
510 } catch (FileNotFoundException e) {
511 return new File[0];
512 }
513 }
514
515 File[] names = ((ShellFolder) dir).listFiles(!useFileHiding);
516
517 if (names == null) {
518 return new File[0];
519 }
520
521 for (File f : names) {
522 if (Thread.currentThread().isInterrupted()) {
523 break;
524 }
525
526 if (!(f instanceof ShellFolder)) {
527 if (isFileSystemRoot(f)) {
528 f = createFileSystemRoot(f);
529 }
530 try {
531 f = ShellFolder.getShellFolder(f);
532 } catch (FileNotFoundException e) {
533 // Not a valid file (wouldn't show in native file chooser)
534 // Example: C:\pagefile.sys
535 continue;
536 } catch (InternalError e) {
537 // Not a valid file (wouldn't show in native file chooser)
538 // Example C:\Winnt\Profiles\joe\history\History.IE5
539 continue;
540 }
541 }
542 if (!useFileHiding || !isHiddenFile(f)) {
543 files.add(f);
544 }
545 }
546
547 return files.toArray(new File[files.size()]);
548 }
549
550
551
552 /**
553 * Returns the parent directory of <code>dir</code>.
554 * @param dir the <code>File</code> being queried
555 * @return the parent directory of <code>dir</code>, or
556 * <code>null</code> if <code>dir</code> is <code>null</code>
557 */
558 public File getParentDirectory(File dir) {
559 if (dir == null || !dir.exists()) {
560 return null;
561 }
562
563 ShellFolder sf;
564
565 try {
566 sf = getShellFolder(dir);
567 } catch (FileNotFoundException e) {
568 return null;
569 }
570
571 File psf = sf.getParentFile();
572
573 if (psf == null) {
574 return null;
575 }
576
577 if (isFileSystem(psf)) {
578 File f = psf;
579 if (!f.exists()) {
580 // This could be a node under "Network Neighborhood".
581 File ppsf = psf.getParentFile();
582 if (ppsf == null || !isFileSystem(ppsf)) {
583 // We're mostly after the exists() override for windows below.
584 f = createFileSystemRoot(f);
585 }
586 }
587 return f;
588 } else {
589 return psf;
590 }
591 }
592
593 /**
594 * Returns an array of files representing the values to show by default in
595 * the file chooser selector.
596 *
597 * @return an array of {@code File} objects.
598 * @throws SecurityException if the caller does not have necessary
599 * permissions
600 * @since 9
601 */
602 public File[] getChooserComboBoxFiles() {
603 return (File[]) ShellFolder.get("fileChooserComboBoxFolders");
604 }
605
606 /**
607 * Returns whether the specified file denotes a shell interpreted link which
608 * can be obtained by the {@link #getLinkLocation(File)}.
609 *
610 * @param file a file
611 * @return whether this is a link
612 * @throws NullPointerException if {@code file} equals {@code null}
613 * @throws SecurityException if the caller does not have necessary
614 * permissions
615 * @see #getLinkLocation(File)
616 * @since 9
617 */
618 public boolean isLink(File file) {
619 if (file == null) {
620 throw new NullPointerException("file is null");
621 }
622 try {
623 return ShellFolder.getShellFolder(file).isLink();
624 } catch (FileNotFoundException e) {
625 return false;
626 }
627 }
628
629 /**
630 * Returns the regular file referenced by the specified link file if
631 * the specified file is a shell interpreted link.
632 * Returns {@code null} if the specified file is not
633 * a shell interpreted link.
634 *
635 * @param file a file
636 * @return the linked file or {@code null}.
637 * @throws FileNotFoundException if the linked file does not exist
638 * @throws NullPointerException if {@code file} equals {@code null}
639 * @throws SecurityException if the caller does not have necessary
640 * permissions
641 * @since 9
642 */
643 public File getLinkLocation(File file) throws FileNotFoundException {
644 if (file == null) {
645 throw new NullPointerException("file is null");
646 }
647 ShellFolder shellFolder;
648 try {
649 shellFolder = ShellFolder.getShellFolder(file);
650 } catch (FileNotFoundException e) {
651 return null;
652 }
653 return shellFolder.isLink() ? shellFolder.getLinkLocation() : null;
654 }
655
656 /**
657 * A group of essential folder that are intented to appear in a left pane of
658 * a file chooser.
659 */
660 @JavaBean(description = "A group of essential folder that are intented to "
661 + "appear in a left pane of a file chooser. ")
662 public static class EssentialFolderSection {
663
664 public static final String FOLDERS_PROPERTY = "folders";
665
666 private final PropertyChangeSupport propertyChangeSupport =
667 new PropertyChangeSupport(this);
668
669 private List<File> folders = Collections.emptyList();
670
671 /**
672 * Creates a new {@code EssentialFolderSection} with no folders.
673 */
674 public EssentialFolderSection() {
675 }
676
677 /**
678 * Creates a new {@code EssentialFolderSection} with the specified
679 * folders.
680 *
681 * @param folders
682 */
683 public EssentialFolderSection(File... folders) {
684 setFolders(Arrays.asList(folders));
685 }
686
687
688 /**
689 * Gets a list of the essential folders in this section.
690 *
691 * @return the value of folders
692 */
693 @SuppressWarnings("ReturnOfCollectionOrArrayField") // already an unmodifiableList or emptyList
694 public List<File> getFolders() {
695 return folders;
696 }
697
698 /**
699 * Updates the list of the essential folders in this section to the specified list.
700 *
701 * @param folders
702 * @throws NullPointerException if the argument is null
703 */
704 @BeanProperty(description = "The folders that are in ", bound = true, visualUpdate =true)
705 public void setFolders(List<File> folders) {
706 Objects.requireNonNull(folders);
707 folders = Collections.unmodifiableList(new ArrayList<>(folders));
708
709 List<File> oldFolders = this.folders;
710 this.folders = folders;
711 propertyChangeSupport.firePropertyChange(FOLDERS_PROPERTY, oldFolders, folders);
712 }
713
714 /**
715 * Adds a PropertyChangeListener to the listener list. Currently the
716 * listener will be fired only when the
717 * {@link #setFolders(java.util.List) folders} property is changed,
718 * but in the future more properties will be added.
719 * @param listener the PropertyChangeListener to be added
720 */
721 public void addPropertyChangeListener(PropertyChangeListener listener) {
722 propertyChangeSupport.addPropertyChangeListener(listener);
723 }
724
725 /**
726 * Removes a PropertyChangeListener from the listener list. If listener is null,
727 * or was never added, no exception is thrown and no action is taken.
728 *
729 * @param listener the PropertyChangeListener to be removed (registered using
730 * {@link #addPropertyChangeListener(java.beans.PropertyChangeListener)})
731 */
732 public void removePropertyChangeListener(PropertyChangeListener listener) {
733 propertyChangeSupport.removePropertyChangeListener(listener);
734 }
735
736 }
737
738 public EssentialFolderSection[] getEssentialFolders() {
739 return FileSystemView.getFileSystemView().getEssentialFolders();
740 }
741
742 /**
743 * Throws {@code FileNotFoundException} if file not found or current thread was interrupted
744 */
745 ShellFolder getShellFolder(File f) throws FileNotFoundException {
746 if (!(f instanceof ShellFolder) && !(f instanceof FileSystemRoot) && isFileSystemRoot(f)) {
747 f = createFileSystemRoot(f);
748 }
749
750 try {
751 return ShellFolder.getShellFolder(f);
752 } catch (InternalError e) {
753 System.err.println("FileSystemView.getShellFolder: f="+f);
754 e.printStackTrace();
755 return null;
756 }
757 }
758
759 /**
760 * Creates a new <code>File</code> object for <code>f</code> with correct
761 * behavior for a file system root directory.
762 *
763 * @param f a <code>File</code> object representing a file system root
764 * directory, for example "/" on Unix or "C:\" on Windows.
765 * @return a new <code>File</code> object
766 * @since 1.4
767 */
768 protected File createFileSystemRoot(File f) {
769 return new FileSystemRoot(f);
770 }
771
772 @SuppressWarnings("serial") // Same-version serialization only
773 static class FileSystemRoot extends File {
774 public FileSystemRoot(File f) {
775 super(f,"");
776 }
777
778 public FileSystemRoot(String s) {
779 super(s);
780 }
781
782 public boolean isDirectory() {
783 return true;
784 }
785
786 public String getName() {
787 return getPath();
788 }
789 }
790 }
791
792 /**
793 * FileSystemView that handles some specific unix-isms.
794 */
795 class UnixFileSystemView extends FileSystemView {
796
797 private static final String newFolderString =
798 UIManager.getString("FileChooser.other.newFolder");
799 private static final String newFolderNextString =
800 UIManager.getString("FileChooser.other.newFolder.subsequent");
801
802 /**
803 * Creates a new folder with a default folder name.
804 */
805 public File createNewFolder(File containingDir) throws IOException {
806 if(containingDir == null) {
807 throw new IOException("Containing directory is null:");
808 }
809 File newFolder;
810 // Unix - using OpenWindows' default folder name. Can't find one for Motif/CDE.
811 newFolder = createFileObject(containingDir, newFolderString);
812 int i = 1;
813 while (newFolder.exists() && i < 100) {
814 newFolder = createFileObject(containingDir, MessageFormat.format(
815 newFolderNextString, i));
816 i++;
817 }
818
819 if(newFolder.exists()) {
820 throw new IOException("Directory already exists:" + newFolder.getAbsolutePath());
821 } else {
822 if(!newFolder.mkdirs()) {
823 throw new IOException(newFolder.getAbsolutePath());
824 }
825 }
826
827 return newFolder;
828 }
829
830 public boolean isFileSystemRoot(File dir) {
831 return dir != null && dir.getAbsolutePath().equals("/");
832 }
833
834 public boolean isDrive(File dir) {
835 return isFloppyDrive(dir);
836 }
837
838 public boolean isFloppyDrive(File dir) {
839 // Could be looking at the path for Solaris, but wouldn't be reliable.
840 // For example:
841 // return (dir != null && dir.getAbsolutePath().toLowerCase().startsWith("/floppy"));
842 return false;
843 }
844
845 public boolean isComputerNode(File dir) {
846 if (dir != null) {
847 String parent = dir.getParent();
848 if (parent != null && parent.equals("/net")) {
849 return true;
850 }
851 }
852 return false;
853 }
854
855 @Override
856 public EssentialFolderSection[] getEssentialFolders() {
857 return new EssentialFolderSection[]{
858 new EssentialFolderSection(getChooserComboBoxFiles())
859 };
860 }
861 }
862
863
864 /**
865 * FileSystemView that handles some specific windows concepts.
866 */
867 class WindowsFileSystemView extends FileSystemView {
868
869 private static final String newFolderString =
870 UIManager.getString("FileChooser.win32.newFolder");
871 private static final String newFolderNextString =
872 UIManager.getString("FileChooser.win32.newFolder.subsequent");
873
874 public Boolean isTraversable(File f) {
875 return Boolean.valueOf(isFileSystemRoot(f) || isComputerNode(f) || f.isDirectory());
876 }
877
878 public File getChild(File parent, String fileName) {
879 if (fileName.startsWith("\\")
880 && !fileName.startsWith("\\\\")
881 && isFileSystem(parent)) {
882
883 //Path is relative to the root of parent's drive
884 String path = parent.getAbsolutePath();
885 if (path.length() >= 2
886 && path.charAt(1) == ':'
887 && Character.isLetter(path.charAt(0))) {
888
889 return createFileObject(path.substring(0, 2) + fileName);
890 }
891 }
892 return super.getChild(parent, fileName);
893 }
894
895 /**
896 * Type description for a file, directory, or folder as it would be displayed in
897 * a system file browser. Example from Windows: the "Desktop" folder
898 * is described as "Desktop".
899 *
900 * The Windows implementation gets information from the ShellFolder class.
901 */
902 public String getSystemTypeDescription(File f) {
903 if (f == null) {
904 return null;
905 }
906
907 try {
908 return getShellFolder(f).getFolderType();
909 } catch (FileNotFoundException e) {
910 return null;
911 }
912 }
913
914 /**
915 * @return the Desktop folder.
916 */
917 public File getHomeDirectory() {
918 File[] roots = getRoots();
919 return (roots.length == 0) ? null : roots[0];
920 }
921
922 /**
923 * Creates a new folder with a default folder name.
924 */
925 public File createNewFolder(File containingDir) throws IOException {
926 if(containingDir == null) {
927 throw new IOException("Containing directory is null:");
928 }
929 // Using NT's default folder name
930 File newFolder = createFileObject(containingDir, newFolderString);
931 int i = 2;
932 while (newFolder.exists() && i < 100) {
933 newFolder = createFileObject(containingDir, MessageFormat.format(
934 newFolderNextString, i));
935 i++;
936 }
937
938 if(newFolder.exists()) {
939 throw new IOException("Directory already exists:" + newFolder.getAbsolutePath());
940 } else {
941 if(!newFolder.mkdirs()) {
942 throw new IOException(newFolder.getAbsolutePath());
943 }
944 }
945
946 return newFolder;
947 }
948
949 public boolean isDrive(File dir) {
950 return isFileSystemRoot(dir);
951 }
952
953 public boolean isFloppyDrive(final File dir) {
954 String path = AccessController.doPrivileged(new PrivilegedAction<String>() {
955 public String run() {
956 return dir.getAbsolutePath();
957 }
958 });
959
960 return path != null && (path.equals("A:\\") || path.equals("B:\\"));
961 }
962
963 /**
964 * Returns a File object constructed from the given path string.
965 */
966 public File createFileObject(String path) {
967 // Check for missing backslash after drive letter such as "C:" or "C:filename"
968 if (path.length() >= 2 && path.charAt(1) == ':' && Character.isLetter(path.charAt(0))) {
969 if (path.length() == 2) {
970 path += "\\";
971 } else if (path.charAt(2) != '\\') {
972 path = path.substring(0, 2) + "\\" + path.substring(2);
973 }
974 }
975 return (File) ShellFolder.get("parseDisplayName " + path);
976 }
977
978 private static final String CLSID_NETWORK = "{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}";
979 private static final String CLSID_COMPUTER = "{20d04fe0-3aea-1069-a2d8-08002b30309d}";
980 private static final String CLSID_HOMEGROUP = "{67CA7650-96E6-4FDD-BB43-A8E774F73A57}";
981 private static final String CLSID_FAVORITES = "{323CA680-C24D-4099-B94D-446DD2D7249E}";
982 private static final String CLSID_LIBRARIES = "{031E4825-7B94-4dc3-B131-E946B44C8DD5}";
983 private static final String CLSID_QUICK_ACCESS = "{679f85cb-0220-4080-b29b-5540cc05aab6}";
984 private static final String CLSID_HOMEGROUP_WIN10 = "{B4FB3F98-C1EA-428d-A78A-D1F5659CBA93}";
985
986 private static final EssentialFolderSection[] ESSENTIAL_FOLDERS = createEssentialFolders();
987
988 private static EssentialFolderSection[] createEssentialFolders() {
989 List<File> folders = new ArrayList<>();
990 if (OSInfo.getWindowsVersion().compareTo(OSInfo.WINDOWS_7) <= 0) {
991 folders.add((File) ShellFolder.get(CLSID_FAVORITES));
992 folders.add((File) ShellFolder.get(CLSID_LIBRARIES));
993 folders.add((File) ShellFolder.get(CLSID_HOMEGROUP));
994 folders.add((File) ShellFolder.get(CLSID_COMPUTER));
995 folders.add((File) ShellFolder.get(CLSID_NETWORK));
996 } else {
997 folders.add((File) ShellFolder.get(CLSID_QUICK_ACCESS));
998 folders.add((File) ShellFolder.get(CLSID_COMPUTER));
999 folders.add((File) ShellFolder.get(CLSID_NETWORK));
1000
1001 // for some unknown reason, adding both Quick Access and Home Group
1002 // on Windows 10 results in all folders in the FilePane (!) with
1003 // default icon switched their icon to a green person icon.
1004 }
1005
1006 return new EssentialFolderSection[]{
1007 new EssentialFolderSection(folders.toArray(new File[0]))
1008 };
1009 }
1010
1011
1012 @Override
1013 public EssentialFolderSection[] getEssentialFolders() {
1014 return Arrays.copyOf(ESSENTIAL_FOLDERS, ESSENTIAL_FOLDERS.length);
1015 }
1016
1017 @SuppressWarnings("serial") // anonymous class
1018 protected File createFileSystemRoot(File f) {
1019 // Problem: Removable drives on Windows return false on f.exists()
1020 // Workaround: Override exists() to always return true.
1021 return new FileSystemRoot(f) {
1022 public boolean exists() {
1023 return true;
1024 }
1025 };
1026 }
1027
1028 }
1029
1030 /**
1031 * Fallthrough FileSystemView in case we can't determine the OS.
1032 */
1033 class GenericFileSystemView extends FileSystemView {
1034
1035 private static final String newFolderString =
1036 UIManager.getString("FileChooser.other.newFolder");
1037
1038 /**
1039 * Creates a new folder with a default folder name.
1040 */
1041 public File createNewFolder(File containingDir) throws IOException {
1042 if(containingDir == null) {
1043 throw new IOException("Containing directory is null:");
1044 }
1045 // Using NT's default folder name
1046 File newFolder = createFileObject(containingDir, newFolderString);
1047
1048 if(newFolder.exists()) {
1049 throw new IOException("Directory already exists:" + newFolder.getAbsolutePath());
1050 } else {
1051 if(!newFolder.mkdirs()) {
1052 throw new IOException(newFolder.getAbsolutePath());
1053 }
1054 }
1055 return newFolder;
1056 }
1057
1058 @Override
1059 public EssentialFolderSection[] getEssentialFolders() {
1060 return new EssentialFolderSection[]{
1061 new EssentialFolderSection(getChooserComboBoxFiles())
1062 };
1063 }
1064
1065 }