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