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