小编典典

如何在JFileChooser中导航到网络主机?

java

问题

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

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

  • 用户可以通过“网络”外壳文件夹,或找到特定的共享并导航至其父目录,来导航至JFileChooser中的此类“目录”。调试显示,该目录在后台显示为Win32ShellFolder2。到目前为止,我所有以编程方式设置currentDirectory的尝试都失败了。

  • new File("\\\\blah") 可以创建,但从Java的角度来看实际上并不存在。

解决方案尝试失败

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

阅读 223

收藏
2020-11-19

共1个答案

小编典典

我找到了Windows特定的解决方案,该解决方案允许仅从名称(例如\\blah\\blah\)导航到任何可访问的计算机节点,而无需枚举Network
shell文件夹或给定节点上的网络共享的任何高级知识。

为计算机路径创建ShellFolder

在调试此问题时,我发现ShellFolder必须为给定的计算机路径创建一个才能导航到它。Win32ShellFolderManager2.createShellFolder()将调用File.getCanonicalPath()给定的文件,该文件又将调用WinNTFileSystem.canonicalize()。最后一次调用在计算机路径上始终失败。经过大量的实验,ShellFolder通过将File对象包装在绕过的东西中,我能够为任何可访问的计算机路径创建一个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安装程序之外无法使用。

与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);
2020-11-19