Java中的透明和操作系统无关路径处理

Java中的透明和操作系统无关路径处理,java,cross-platform,file-handling,Java,Cross Platform,File Handling,我正在为我们的应用程序开发一个图形安装程序。因为没有一个可用的安装程序生成器满足要求和约束,所以我从头开始构建它 安装程序应该在多个操作系统上运行,因此路径处理需要与操作系统无关。为此,我编写了以下小实用程序: public class Path { private Path() { } public static String join(String... pathElements) { return ListEnhancer.wrap(Arrays.asList(pat

我正在为我们的应用程序开发一个图形安装程序。因为没有一个可用的安装程序生成器满足要求和约束,所以我从头开始构建它

安装程序应该在多个操作系统上运行,因此路径处理需要与操作系统无关。为此,我编写了以下小实用程序:

public class Path {
  private Path() {
  }

  public static String join(String... pathElements) {
    return ListEnhancer.wrap(Arrays.asList(pathElements)).
      mkString(File.separator);
  }

  public static String concatOsSpecific(String path, String element) {
    return path + File.separator + element;
  }

  public static String concatOsAgnostic(String path, String element) {
    return path + "/" + element;
  }

  public static String makeOsAgnostic(String path) {
    return path.replace(File.separator, "/");
  }

  public static String makeOsSpecific(String path) {
    return new File(path).getAbsolutePath();
  }

  public static String fileName(String path) {
    return new File(path).getName();
  }
}
现在我的代码到处都是
Path.*不可知的
Path.*在许多地方都有特定的
调用。很明显,这很容易出错,而且根本不透明

我应该采取什么方法来使路径处理透明和不易出错?是否存在已解决此问题的实用程序/库?任何帮助都将不胜感激

编辑:

为了举例说明我的意思,这里是我不久前写的一些代码。(主题外:请原谅这种冗长的方法。代码处于初始阶段,不久将经历一些繁重的重构。)

某些上下文:
ApplicationContext
是存储安装数据的对象。它包括多个路径,如
installationRootDirectory
installationDirectory
等。这些路径的默认值在创建安装程序时指定,因此始终以与操作系统无关的格式存储

@Override
protected void initializeComponents() {
  super.initializeComponents();
  choosePathLabel = new JLabel("Please select the installation path:");
  final ApplicationContext c = installer.getAppContext();
  pathTextField = new JTextField(
    Path.makeOsSpecific(c.getInstallationDirectory()));
  browseButton = new JButton("Browse", 
    new ImageIcon("resources/images/browse.png"));
  browseButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
      JFileChooser fileChooser = new JFileChooser();
      fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
      fileChooser.setAcceptAllFileFilterUsed(false);
      int choice = fileChooser.showOpenDialog(installer);
      String selectedInstallationRootDir = fileChooser.getSelectedFile().
        getPath();
      if (choice == JFileChooser.APPROVE_OPTION) {
        c.setInstallationRootDirectory(
          Path.makeOsAgnostic(selectedInstallationRootDir));
        pathTextField.setText(Path.makeOsSpecific(c.getInstallationDirectory()));
      }
    }
  });
}

不确定这是否是您想要的,但通常当我需要在独立于操作系统的Java程序中执行与路径相关的操作时,我总是使用字符串来传递路径,而不是文件,并且我总是做以下两件事:

每当我构建字符串路径时,我总是使用
/
作为文件分隔符

每当我使用字符串路径创建文件或将其保存为文本时,我总是在使用该路径之前进行以下调用:

String fSep = System.getProperty("file.separator);
String path = ... //might be built from scratch, might be passed in from somewhere
path = path.replace("/",fSep).replace("\\",fSep);
无论路径是在本地机器上构建的,还是从网络上具有不同操作系统的不同机器传入的,只要我打算在本地机器上使用该路径,这似乎都能很好地工作。如果您计划通过网络在不同操作系统之间传递路径,请注意您自己的代码是一致的

编辑


哇。。。不知何故,我的答案被弄乱了,代码格式也没有按照最初的预期工作…

我会创建自己的MyFile对象,扩展或包装java.util.File。然后确保所有代码都使用这个对象,而不是java.io.File。在这里,您将执行操作系统检查并调用方法来清理文件名。剩下的代码将是“干净的”。

或者您可以引入两个新类:

class OsSpecificPath implements FilePathInterface
{
      String path;

      OsAgnosticPath toAgnosticPath();

      OsSpecificPath concat( OsSpecificPath otherPath );

      // from IFilePath
      getFile();

     ... etc
}

每一个包装一条路径,但他们需要

然后,每个方法都可以有方法转换为其他类型的路径,但与“字符串类型”解决方案不同的是,在“字符串类型”解决方案中,所有内容都是字符串并且可能被误用,您将有两个不能错误传递的强类型类

任何不关心路径类型的内容都将使用
filepathcinterface
,任何需要在特定类型的路径上操作的内容都将具体使用这些类型
FilePathInterface
可以假设在接口中同时包含
ToAgonisticPath
toOsSpecificPath
,如果确实需要…

您永远不需要转换回操作系统不可知。以下是特定于操作系统的转换:

public class Path {
  private Path() {
  }

  public static String concat(String path, String element) {
    return new File(path, element).getPath();
  }

  public static String makeOsSpecific(String path) {
    return new File(path).getAbsolutePath();
  }

  public static String fileName(String path) {
    return new File(path).getName();
  }
}
您的样本:

@Override
protected void initializeComponents() {
  super.initializeComponents();
  choosePathLabel = new JLabel("Please select the installation path:");
  final ApplicationContext c = installer.getAppContext();
  pathTextField = new JTextField(
    Path.makeOsSpecific(c.getInstallationDirectory()));
  browseButton = new JButton("Browse", 
    new ImageIcon("resources/images/browse.png"));
  browseButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
      JFileChooser fileChooser = new JFileChooser();
      fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
      fileChooser.setAcceptAllFileFilterUsed(false);
      int choice = fileChooser.showOpenDialog(installer);
      String selectedInstallationRootDir = fileChooser.getSelectedFile().
        getPath();
      if (choice == JFileChooser.APPROVE_OPTION) {
        c.setInstallationRootDirectory(selectedInstallationRootDir);
        pathTextField.setText(Path.makeOsSpecific(c.getInstallationDirectory()));
      }
    }
  });
}

使用
/
分隔路径在Windows上的效果出奇地好。除此之外,你为什么需要不可知论版本呢?@Mat,我知道。以下是一些案例:1<代码>文件#getAbsolutePath返回一个带有操作系统特定分隔符的字符串。2.
System.getProperty()
返回带有操作系统特定分隔符的字符串。3.我需要读取和写入一些安装面板中的文本框,当呈现给用户时,路径需要采用操作系统特定的格式。确定。那么,“不可知事物”用于什么呢?@Mat,让我在我的问题中添加一个代码示例。是否需要显示相同的分隔符?(例如,对所有操作系统使用``)。如果不支持,只需使用一个小实用程序来确定不支持的分隔符,并在写入FS之前替换它。这样,您就不会强迫用户做任何“操作系统特定的”+1,以减少出错的可能性。不过,转换仍然是手动的,代码也会变得杂乱无章。不过,它们可能不需要手动。这两个类都可以实现一个接口,比如
IFilePath
之类的,然后每个类都知道如何基于自己的路径样式创建文件对象。然后,您只需到处传递IFilePath对象,并将所有这些转换隐藏在类本身中。是的,在某些情况下,比如在文本框中显示它们,你可能需要进行一些手动转换,但在其他地方你不应该…是的,这是有意义的。谢谢把你的答案标记为正确。旁注:这是一个Java问题,因此请遵循Java约定的方法和类型名称。(方法名称应该小写,Java接口不遵循
I-
惯例。)我想再次感谢您的回答。我按照您建议的想法重新编写了API,发现代码中有几个错误,否则我可能会遗漏。在写入文件系统之前,我还进行了替换。我假设他指的是java.io.file。您的示例中有一个错误
File#getPath
方法返回操作系统特定的路径字符串,而我需要在
ApplicationContext
中以操作系统无关的形式存储路径。因此
c.setInstallationDirectory(selectedInstallationRootDir)
是错误的。@缺少Faktor-为什么需要以操作系统无关的格式存储它们?这是我一直在问的问题,你还没有回答。我已经在这篇文章的评论中回答了这个问题。不管怎样,我们又来了:
安装程序
对象是使用我正在设计的API创建的
@Override
protected void initializeComponents() {
  super.initializeComponents();
  choosePathLabel = new JLabel("Please select the installation path:");
  final ApplicationContext c = installer.getAppContext();
  pathTextField = new JTextField(
    Path.makeOsSpecific(c.getInstallationDirectory()));
  browseButton = new JButton("Browse", 
    new ImageIcon("resources/images/browse.png"));
  browseButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
      JFileChooser fileChooser = new JFileChooser();
      fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
      fileChooser.setAcceptAllFileFilterUsed(false);
      int choice = fileChooser.showOpenDialog(installer);
      String selectedInstallationRootDir = fileChooser.getSelectedFile().
        getPath();
      if (choice == JFileChooser.APPROVE_OPTION) {
        c.setInstallationRootDirectory(selectedInstallationRootDir);
        pathTextField.setText(Path.makeOsSpecific(c.getInstallationDirectory()));
      }
    }
  });
}