JDK-4906607 : Can't select a file through symlink using a JFileChooser
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.4.2,6u10
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic,linux,solaris_8
  • CPU: generic,x86,sparc,itanium
  • Submitted: 2003-08-14
  • Updated: 2010-03-24
  • Resolved: 2006-05-31
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
6 b86Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
JFileChooser impl uses getCanonicalFile() internally during FS browsing.
If the user has a file and a symlink, e.g.:
/data -> /home/user/data,
and wants to select the file through that symlink, he can't browse there,
because the JFileChooser will convert /data to /home/user/data
There are legitimate uses for the need to get the user selected symlink
instead of the canonical path to the target, example:
The selected path may be persisted between app sessions and the user may need
to change the target of the symlink (different version of a library, some VCS uses symlinks for paths to latest valid bits, ...)

File.getCanonical* calls are not apparently made from JFileChooser itself, nor from FileSystemView, but are made from various FileChooserUI subclasses.
###@###.### 2004-07-21

SUGGESTED FIX I suggest to add a static utility method to ShellFolder that would call getCanonicalFile() for ordinar files, and treat symlinks separately, using the approach suggested by ###@###.###. In FileChooserUI descendant classes, should use this method instead of direct call to getCanonicalFile().

EVALUATION Two stack traces that will help in evaluation: I'm creating a JFileChooser with a currentDirectory parameter that is a symbolic link. MetalFileChooserUI.DirectoryComboboxModel canonicalize file ... at javax.swing.plaf.metal.MetalFileChooserUI$DirectoryComboBoxModel.addItem(MetalFileChooserUI.java:942) at javax.swing.plaf.metal.MetalFileChooserUI$DirectoryComboBoxModel.access$800(MetalFileChooserUI.java:900) at javax.swing.plaf.metal.MetalFileChooserUI.doDirectoryChanged(MetalFileChooserUI.java:651) at javax.swing.plaf.metal.MetalFileChooserUI.access$1100(MetalFileChooserUI.java:36) at javax.swing.plaf.metal.MetalFileChooserUI$5.propertyChange(MetalFileChooserUI.java:737) at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:338) at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:275) at java.awt.Component.firePropertyChange(Component.java:7688) at javax.swing.JFileChooser.setCurrentDirectory(JFileChooser.java:563) at javax.swing.JFileChooser.<init>(JFileChooser.java:333) at javax.swing.JFileChooser.<init>(JFileChooser.java:315) ... and sets it to JFileChooser instance: at javax.swing.JFileChooser.setCurrentDirectory(JFileChooser.java:563) at javax.swing.plaf.metal.MetalFileChooserUI$DirectoryComboBoxAction.actionPerformed(MetalFileChooserUI.java:1137) at javax.swing.JComboBox.fireActionEvent(JComboBox.java:1207) at javax.swing.JComboBox.contentsChanged(JComboBox.java:1278) at javax.swing.AbstractListModel.fireContentsChanged(AbstractListModel.java:100) at javax.swing.plaf.metal.MetalFileChooserUI$DirectoryComboBoxModel.setSelectedItem(MetalFileChooserUI.java:1000) at javax.swing.plaf.metal.MetalFileChooserUI$DirectoryComboBoxModel.addItem(MetalFileChooserUI.java:971)

CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: mustang

SUGGESTED FIX Browse and return the path the user really selected. ------ As far as I know there is no reason to call getCanonicalFile here. On Windows it is fine and may be necessary. On Unix it is unnecessary and harmful. Use new File(f.toURI().normalize()) to absolutize paths, normalize trailing '/', and remove '.' and '..' path components. I think this may deserve a higher severity than S4; for people with big symlink farms on Unix systems this causes a real problem and can be very confusing. ###@###.### 2004-02-24 Additional note re. using new File(f.toURI().normalize()): you may need to specially handle "/.." -> "/", as the sample code in the workaround section illustrates. ###@###.### 2004-07-21

WORK AROUND User can type the full path to the name field, but this is highly uncomfortable. ----- May be possible to work around this in client code using extreme hacks with subclassing java.io.File etc. By no means straightforward. ###@###.### 2004-02-24 Have found a workaround which seems to work reliably in Metal on 1.4.2_04 and GTK on 1.5.0 b58, and nearly reliably in Ocean (Metal) on 1.5.0 b58. (In the latter case, sometimes - not always - the first time you traverse a symlink to a dir it will be canonicalized, but you can back up and do it again and it will not be. Failed completely to track down why this happens; when I add a diagnostic patch to java/io/File.java, compile that, and add that using -Xbootclasspath/p:$pathdir, the problem mysteriously does not occur at all.) Try the following class: ---%<--- package fileChooserCanonicalPath; import java.io.File; import java.io.FileFilter; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.net.URI; import javax.swing.Icon; import javax.swing.JFileChooser; import javax.swing.UIManager; import javax.swing.filechooser.FileSystemView; public class Main { public static void main(String[] args) throws Exception { File tmp = new File(System.getProperty("java.io.tmpdir"), "fileChooserCanonicalPathTest"); del(tmp); File phys = new File(tmp, "physdir"); File file = new File(phys, "file"); File link = new File(tmp, "link"); phys.mkdirs(); new FileOutputStream(file).close(); Runtime.getRuntime().exec(new String[] {"ln", "-s", "physdir", link.getAbsolutePath()}); //UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel"); JFileChooser jfc = new JFileChooser(); //File currdir = tmp; File currdir = translate(tmp); jfc.setCurrentDirectory(currdir); jfc.setFileSystemView(new WrapperFileSystemView()); System.out.println("Try to select " + new File(link, "file").getAbsolutePath()); jfc.showOpenDialog(null); System.out.println(jfc.getSelectedFile()); System.exit(0); } private static void del(File f) throws IOException { if (f.isDirectory()) { File[] ks = f.listFiles(); for (int i = 0; i < ks.length; i++) { del(ks[i]); } } f.delete(); } private static File translate(File f) { if (f instanceof WrapperFile) { return f; } else if (f != null) { return new WrapperFile(f); } else { return null; } } private static File[] translate(File[] fs) { if (fs != null) { for (int i = 0; i < fs.length; i++) { fs[i] = translate(fs[i]); } } return fs; } private static File normalize(File f) { if (f.getAbsolutePath().equals("/..")) { return new File("/"); } else { return new File(f.toURI().normalize()); } } private static final class WrapperFile extends File { public WrapperFile(File orig) { this(orig.getPath()); } private WrapperFile(String path) { super(path); } private WrapperFile(URI uri) { super(uri); } public File getCanonicalFile() throws IOException { return translate(normalize(this)); } public String getCanonicalPath() throws IOException { return normalize(this).getAbsolutePath(); } public File getParentFile() { return translate(super.getParentFile()); } public File getAbsoluteFile() { return translate(super.getAbsoluteFile()); } public File[] listFiles() { return translate(super.listFiles()); } public File[] listFiles(FileFilter filter) { return translate(super.listFiles(filter)); } public File[] listFiles(FilenameFilter filter) { return translate(super.listFiles(filter)); } } private static final class WrapperFileSystemView extends FileSystemView { private final FileSystemView delegate = FileSystemView.getFileSystemView(); public WrapperFileSystemView() {} public boolean isFloppyDrive(File dir) { return delegate.isFloppyDrive(dir); } public boolean isComputerNode(File dir) { return delegate.isComputerNode(dir); } public File createNewFolder(File containingDir) throws IOException { return translate(delegate.createNewFolder(containingDir)); } public boolean isDrive(File dir) { return delegate.isDrive(dir); } public boolean isFileSystemRoot(File dir) { return delegate.isFileSystemRoot(dir); } public File getHomeDirectory() { return translate(delegate.getHomeDirectory()); } public File createFileObject(File dir, String filename) { return translate(delegate.createFileObject(dir, filename)); } public Boolean isTraversable(File f) { return delegate.isTraversable(f); } public boolean isFileSystem(File f) { return delegate.isFileSystem(f); } /* protected File createFileSystemRoot(File f) { return translate(delegate.createFileSystemRoot(f)); } */ public File getChild(File parent, String fileName) { return translate(delegate.getChild(parent, fileName)); } public File getParentDirectory(File dir) { return translate(delegate.getParentDirectory(dir)); } public Icon getSystemIcon(File f) { return delegate.getSystemIcon(f); } public boolean isParent(File folder, File file) { return delegate.isParent(folder, file); } public String getSystemTypeDescription(File f) { return delegate.getSystemTypeDescription(f); } public File getDefaultDirectory() { return translate(delegate.getDefaultDirectory()); } public String getSystemDisplayName(File f) { return delegate.getSystemDisplayName(f); } public File[] getRoots() { return translate(delegate.getRoots()); } public boolean isHiddenFile(File f) { return delegate.isHiddenFile(f); } public File[] getFiles(File dir, boolean useFileHiding) { return translate(delegate.getFiles(dir, useFileHiding)); } public boolean isRoot(File f) { return delegate.isRoot(f); } public File createFileObject(String path) { return translate(delegate.createFileObject(path)); } } } ---%<--- If you comment out the call to setFileSystemView (and use "File currdir = tmp;") the bug is clearly manifested on Linux: though you try to select .../link/file you wind up selecting .../physdir/file. With the code as written it usually works; you can select .../link/file. You can uncomment the call to UIManager to test on GTK as well. ###@###.### 2004-07-21

EVALUATION Will investigate to see if the getCanonicalPath() method can be modified for JFileChooser. ###@###.### 2003-09-30