如何导航到JFileChooser中的networking主机?

问题

我有一个JFileChooser,我需要以编程方式将其currentDirectory设置为包含多个SMB共享(例如\\blah )的networking主机。 从技术上讲,这不是一个“目录”,而是一个表示可用共享列表的shell文件夹。

  • JFileChooser在导航到特定共享(例如\\blah\someShare )时没有问题,但不能处理主机“目录”本身(例如\\blah )。

  • 用户可以通过“Network”shell文件夹,或通过查找特定共享并导航到其父目录来导航到JFileChooser中的这些“目录”。 debugging显示,这个目录下的这个目录被表示为一个Win32ShellFolder2 。 我所有以编程方式设置currentDirectory的尝试都失败了。

  • 可以创buildnew File("\\\\blah") ,但从Java的angular度来看并不存在。

失败的解决scheme尝试

  • chooser.setCurrentDirectory(new File("\\\\blah"));

    因为JFileChooser检查给定的目录是否存在而失败,并且new File("\\\\blah").exists()返回false。

  • 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) 

曾几何时,我面对这样一个任务,我可以说这真的很烦人。 首先听起来很简单,但是当你开始挖掘和尝试时,会出现越来越多的问题。 我想谈谈我的旅程。
从我所了解的情况来看,这里的事情是\\ComputerName\并不是真正的文件系统。 内容取决于您的身份验证凭据,这是一个abstaction层。 只有windows机器才会这么做,这将破坏Java的系统独立性。 总结起来,它不是一个File对象可以指向的东西。 您可以使用Samba库jcifs,但在它们的实现中, SmbFile类需要用户身份验证,并且与java File类不兼容。 所以你不能在jFileChooser使用它。 可悲的是,他们没有改变它的意图,因为你可以在这里阅读。
我试图自己开发一个文件包装器,充当FileSmbFile Class的混合。 但是因为它给我带来了噩梦,所以我放弃了。
然后,我有了写一个简单的对话框的想法,列出了以前用jcifs扫描的网络共享,并让用户选择其中之一。 然后一个jFileChooser与选定的份额应该显示出来。

当我实现这个想法的时候,整个问题的超级简单的解决方案把我踢到了一边。

因为指向\\ComputerName\ShareName并单击One level higher按钮绝对没有问题,所以必须可以重现此步骤。 它是。 实际上,在jFileChooser的引擎下,我发现像MyComputerNetwork这样的地方是ShellFolders ,它们是File Objects的特例。 但是这些Shell文件夹受到保护,而不是Java API的一部分。
所以我不能直接实例化这些。 但是我可以访问FileSystemView来处理文件系统上的系统相关视图,例如为特殊位置创建这些Shell文件夹。
所以很长的文字为一个简短的答案。 如果您知道一个共享名,请为此共享名创建一个文件。 然后使用FileSystemView来获取其父文件。 瞧,你可以使用生成的File对象来扩展ShellFolderjFileChooser

 File f = new File("\\\\ComputerName\\ShareFolder"); FileSystemView fsv = FileSystemView.getFileSystemView(); f = fsv.getParentDirectory(f); JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(f); 

最后一个注意:这个解决方案不会要求你提供登录信息。 因此,在这里使用它们之前,必须在Windows中访问这些共享。

编辑:对不起,长文本。 除夕和我喝醉了。 现在我想补充一点,我发现了另一种方式。

 FileSystemView fsv = FileSystemView.getFileSystemView(); File Desktop = fsv.getRoots()[0]; 

在Windows系统上,这应该给你的桌面文件夹。 如果你在这里列出所有的文件:

 for(File file : Desktop.listFiles()) System.out.println(file.getName()); 

你会注意到一些奇怪的名字:

 ::{20D04FE0-3AEA-1069-A2D8-08002B30309D} // My Computer ::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C} // Network ::{031E4825-7B94-4DC3-B131-E946B44C8DD5} // User Directory 

我不知道这些代码对于所有的Windows版本都是一样的,但似乎是针对Windows7的。 所以你可以使用这个来获得网络外壳文件夹,然后在这台计算机上分享。

 File Network = fsv.getChild(Desktop, "::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}"); File Host = fsv.getChild(Network, "COMPUTERNAME"); // Must be in Capital Letters 

这里的问题是,这将需要大约10秒,因为网络文件夹被扫描的内容。

我发现了一个特定于Windows的解决方案,它允许从单独的名称(例如\\blah\\blah\ )导航到任何可访问的计算机节点,而不需要枚举Network shell文件夹,也不需要预先知道给定的网络共享节点。

为计算机路径创建ShellFolder

在调试这个问题的时候,我发现一个ShellFolder必须为给定的计算机路径创建才能导航到它。 Win32ShellFolderManager2.createShellFolder()将调用给定文件上的File.getCanonicalPath() ,该文件将依次调用WinNTFileSystem.canonicalize() 。 计算机路径上最后一次调用总是失败。 经过大量的实验,我能够通过将File对象包装在绕过WinNTFileSystem.canonicalize()东西中,为任何可访问的计算机路径创建ShellFolder

 /** * 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 (eg "\\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\ works,但\\blah\someShare\..\不会),理想情况下OpenJDK应该在这些问题上解决这些问题。 这也是一个特定于操作系统和特定实现的解决方案,不能在OpenJDK-on-Windows安装程序之外使用。

与JFileChooser集成:选项1

将它与JFileChooser集成的最简单方法是覆盖它的approveSelection()方法。 这允许用户在对话框中键入一个计算机路径( \\blah\\blah\ ),然后按Enter键在那里导航。 当给出不存在或不可访问的路径时显示警报消息。

 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); 

与JFileChooser集成:选项2

或者, 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);