I found a Windows-specific solution that allows navigating to any accessible computer node from its name alone (e.g. \\blah
or \\blah\
), without requiring enumeration of the Network shell folder, or any advance knowledge of network shares on the given node.
Creating a ShellFolder for a computer path
While debugging this issue I discovered that a ShellFolder
has to be created for the given computer path to be able to navigate to it. Win32ShellFolderManager2.createShellFolder()
will call File.getCanonicalPath()
on the given file, which will in turn call WinNTFileSystem.canonicalize()
. This last call always fails on computer paths. After much experimentation, I was able to create a ShellFolder
for any accessible computer path by wrapping the File object in something that bypasses WinNTFileSystem.canonicalize()
:
/**
* Create a shell folder for a given network path.
*
* @param path File to test for existence.
* @return ShellFolder representing the given computer node.
* @throws IllegalArgumentException given path is not a computer node.
* @throws FileNotFoundException given path could not be found.
*/
public static ShellFolder getComputerNodeFolder(String path)
throws FileNotFoundException {
File file = new NonCanonicalizingFile(path);
if (ShellFolder.isComputerNode(file)) {
return new Win32ShellFolderManager2().createShellFolder(file);
} else {
throw new IllegalArgumentException("Given path is not a computer node.");
}
}
private static final class NonCanonicalizingFile extends File {
public NonCanonicalizingFile(String path) {
super(path);
}
@Override
public String getCanonicalPath() throws IOException {
// Win32ShellFolderManager2.createShellFolder() will call getCanonicalPath() on this file.
// Base implementation of getCanonicalPath() calls WinNTFileSystem.canonicalize() which fails on
// computer nodes (e.g. "\\blah"). We skip the canonicalize call, which is safe at this point because we've
// confirmed (in approveSelection()) that this file represents a computer node.
return getAbsolutePath();
}
}
Admittedly this solution has a couple edge-cases (e.g. \\blah\
works but \\blah\someShare\..\
does not), and ideally OpenJDK should fix these quirks on their end. This is also an OS-specific and implementation-specific solution, and will not work outside OpenJDK-on-Windows setup.
Integrating with JFileChooser: Option 1
The simplest way to integrate this with JFileChooser
is to override its approveSelection()
method. This allows user to type in a computer path (\\blah
or \\blah\
) in the dialog and press Enter to navigate there. An alert message is shown when a non-existent or non-accessible path was given.
JFileChooser chooser = new JFileChooser() {
@Override
public void approveSelection() {
File selectedFile = getSelectedFile();
if (selectedFile != null && ShellFolder.isComputerNode(selectedFile)) {
try {
// Resolve path and try to navigate to it
setCurrentDirectory(getComputerNodeFolder(selectedFile.getPath()));
} catch (FileNotFoundException ex) {
// Alert user if given computer node cannot be accessed
JOptionPane.showMessageDialog(this, "Cannot access " + selectedFile.getPath());
}
} else {
super.approveSelection();
}
}
};
chooser.showOpenDialog(null);
Integrating with JFileChooser: Option 2
Alternatively, FileSystemView
can be augmented by overriding its createFileObject(String)
method to check for computer paths. This allows passing a computer path to JFileChooser(String,FileSystemView)
constructor and still allows user to navigate to accessible computer paths. However, there is still no easy way to message the user about non-accessible computer paths without overriding JFileChooser.approveSelection()
:
public static class ComputerNodeFriendlyFileSystemView extends FileSystemView {
private final FileSystemView delegate;
public ComputerNodeFriendlyFileSystemView(FileSystemView delegate) {
this.delegate = delegate;
}
@Override
public File createFileObject(String path) {
File placeholderFile = new File(path);
if (ShellFolder.isComputerNode(placeholderFile)) {
try {
return getComputerNodeFolder(path);
} catch (FileNotFoundException ex) {
return placeholderFile;
}
} else {
return delegate.createFileObject(path);
}
}
// All code below simply delegates everything to the "delegate"
@Override
public File createNewFolder(File containingDir) throws IOException {
return delegate.createNewFolder(containingDir);
}
@Override
public boolean isRoot(File f) {
return delegate.isRoot(f);
}
@Override
public Boolean isTraversable(File f) {
return delegate.isTraversable(f);
}
@Override
public String getSystemDisplayName(File f) {
return delegate.getSystemDisplayName(f);
}
@Override
public String getSystemTypeDescription(File f) {
return delegate.getSystemTypeDescription(f);
}
@Override
public Icon getSystemIcon(File f) {
return delegate.getSystemIcon(f);
}
@Override
public boolean isParent(File folder, File file) {
return delegate.isParent(folder, file);
}
@Override
public File getChild(File parent, String fileName) {
return delegate.getChild(parent, fileName);
}
@Override
public boolean isFileSystem(File f) {
return delegate.isFileSystem(f);
}
@Override
public boolean isHiddenFile(File f) {
return delegate.isHiddenFile(f);
}
@Override
public boolean isFileSystemRoot(File dir) {
return delegate.isFileSystemRoot(dir);
}
@Override
public boolean isDrive(File dir) {
return delegate.isDrive(dir);
}
@Override
public boolean isFloppyDrive(File dir) {
return delegate.isFloppyDrive(dir);
}
@Override
public boolean isComputerNode(File dir) {
return delegate.isComputerNode(dir);
}
@Override
public File[] getRoots() {
return delegate.getRoots();
}
@Override
public File getHomeDirectory() {
return delegate.getHomeDirectory();
}
@Override
public File getDefaultDirectory() {
return delegate.getDefaultDirectory();
}
@Override
public File createFileObject(File dir, String filename) {
return delegate.createFileObject(dir, filename);
}
@Override
public File[] getFiles(File dir, boolean useFileHiding) {
return delegate.getFiles(dir, useFileHiding);
}
@Override
public File getParentDirectory(File dir) {
return delegate.getParentDirectory(dir);
}
}
Usage:
ComputerNodeFriendlyFileSystemView fsv
= new ComputerNodeFriendlyFileSystemView(FileSystemView.getFileSystemView());
JFileChooser chooser = new JFileChooser("\\\\blah", fsv);
chooser.showOpenDialog(null);