我有一个JFileChooser,我需要以编程方式将其currentDirectory设置为包含多个SMB共享(例如\\blah)的网络主机。从技术上讲,这不是“目录”,而是代表可用共享列表的shell文件夹。
\\blah
JFileChooser导航到特定共享(例如\\blah\someShare)没有问题,但是不能处理主机“目录”本身(例如\\blah)。
\\blah\someShare
用户可以通过“网络”外壳文件夹,或找到特定的共享并导航至其父目录,来导航至JFileChooser中的此类“目录”。调试显示,该目录在后台显示为Win32ShellFolder2。到目前为止,我所有以编程方式设置currentDirectory的尝试都失败了。
Win32ShellFolder2
new File("\\\\blah") 可以创建,但从Java的角度来看实际上并不存在。
new File("\\\\blah")
chooser.setCurrentDirectory(new File("\\\\blah"));
失败,因为JFileChooser检查给定目录是否存在,并new File("\\\\blah").exists()返回false。
JFileChooser
new File("\\\\blah").exists()
File dir = new File("\\\\blah").getCanonicalFile();
失败,但例外:
java.io.IOException: Invalid argument at java.io.WinNTFileSystem.canonicalize0(Native Method) at java.io.WinNTFileSystem.canonicalize(WinNTFileSystem.java:428) at java.io.File.getCanonicalPath(File.java:618) at java.io.File.getCanonicalFile(File.java:643)
File dir = ShellFolder.getShellFolder(new File("\\\\blah"));
java.io.FileNotFoundException at sun.awt.shell.ShellFolder.getShellFolder(ShellFolder.java:247)
File dir = new Win32ShellFolderManager2().createShellFolder(new File("\\\\blah"));
java.io.FileNotFoundException: File \\blah not found at sun.awt.shell.Win32ShellFolderManager2.createShellFolder(Win32ShellFolderManager2.java:80) at sun.awt.shell.Win32ShellFolderManager2.createShellFolder(Win32ShellFolderManager2.java:64)
Path dir = Paths.get("\\\\blah");
java.nio.file.InvalidPathException: UNC path is missing sharename: \\blah at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:118) at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77) at sun.nio.fs.WindowsPath.parse(WindowsPath.java:94) at sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:255) at java.nio.file.Paths.get(Paths.java:84)
我找到了Windows特定的解决方案,该解决方案允许仅从名称(例如\\blah或\\blah\)导航到任何可访问的计算机节点,而无需枚举Network shell文件夹或给定节点上的网络共享的任何高级知识。
\\blah\
在调试此问题时,我发现ShellFolder必须为给定的计算机路径创建一个才能导航到它。Win32ShellFolderManager2.createShellFolder()将调用File.getCanonicalPath()给定的文件,该文件又将调用WinNTFileSystem.canonicalize()。最后一次调用在计算机路径上始终失败。经过大量的实验,ShellFolder通过将File对象包装在绕过的东西中,我能够为任何可访问的计算机路径创建一个WinNTFileSystem.canonicalize():
ShellFolder
Win32ShellFolderManager2.createShellFolder()
File.getCanonicalPath()
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(); } }
诚然,此解决方案有两个边缘情况(例如可行,\\blah\但\\blah\someShare\..\没有),理想情况下,OpenJDK应该在其末尾解决这些怪癖。这也是特定于操作系统和特定于实现的解决方案,在Windows上的OpenJDK安装程序之外无法使用。
\\blah\someShare\..\
与之集成的最简单方法JFileChooser是覆盖其approveSelection()方法。这样,用户可以在对话框中输入计算机路径(\\blah或\\blah\),然后按Enter键导航到该位置。当给出了不存在或不可访问的路径时,将显示一条警报消息。
approveSelection()
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);
或者,FileSystemView可以通过覆盖其createFileObject(String)方法来检查计算机路径来增强此功能。这允许将计算机路径传递给JFileChooser(String,FileSystemView)构造函数,并且仍然允许用户导航到可访问的计算机路径。但是,仍然没有一种简单的方法可以在不覆盖的情况下向用户发送有关不可访问的计算机路径的消息JFileChooser.approveSelection():
FileSystemView
createFileObject(String)
JFileChooser(String,FileSystemView)
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); } }
用法:
ComputerNodeFriendlyFileSystemView fsv = new ComputerNodeFriendlyFileSystemView(FileSystemView.getFileSystemView()); JFileChooser chooser = new JFileChooser("\\\\blah", fsv); chooser.showOpenDialog(null);