I am developing a graphical installer for our application. Since none of the available installer generators meet the requirements and constraints, I am building it from scratch.
The installer is supposed to run on several operating systems, and therefore the path handling needs to be OS-agnostic. I have written the following small utility for this purpose:
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();
}
}
Now my code is littered with Path.*Agnostic and Path.*Specific calls in many places. As is apparent, this is very error-prone and not transparent at all.
What approach should I take to make the path handling transparent and less error-prone? Do there exist any utilities/libraries that already address this problem? Any help would be greatly appreciated.
EDIT:
To exemplify what I mean, here is some code I wrote a while ago. (Offtopic: Forgive the long-ish method. The code is in initial stages, and will be undergoing some heavy refactoring soon.)
Some context: ApplicationContext is an object that stores the installation data. That includes several paths such as installationRootDirectory, installationDirectory etc. The defaults for these are specified when creating an installer, and hence are always stored in OS-agnostic formats.
@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()));
}
}
});
}
Or you could introduce 2 new classes:
and
each wrap a path however they need to.
each method could then have methods to convert to the other type of path, but instead of a “stringly-typed” solution where everything is a string and can be misused, you’d have 2 strongly typed classes that can’t be incorrectly passed around.
Anything that doesn’t care about the type of path would use
FilePathInterface, anything that needs to operate on specific kinds of paths would use those types specificly.FilePathInterfacecould hypothetically have bothtoAgnosticPathandtoOsSpecificPathin the interface if really necessary…