Search code examples
javaniofilepath

What is the best way to ignore a leading slash when resolving a java.nio.files.Path


I'm trying to refactor some legacy code using java.io.File to use java.nio.file.Path instead.

I'm bitten by the fact that Path has better support for absolute filepaths, because a lot of the values I receive have a leading slash (/) while they are supposed to represent relative paths. String concatenation is easier that way but I'm trying to move away from String/File to represent filepaths.

The old behaviour was that new File(parent, child) returned a new File representing

  • a child file/directory under the parent directory, regardless of whether child started with /.

The new behaviour was that parent.resolve(child) returned a new Path representing either

  • a child file/directory under the parent directory
  • child as the root (if it started with /)

I think the new way can allow for cleaner code, but when refactoring a legacy application it can introduce subtle bugs.

What is the best/cleanest way to get back the old (File) behaviour while using Path?


import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;

class Main {
  public static void main(String[] args) {
    file();
    path();
  }

  public static void file(){
    File root = new File("/root");
    File relative = new File(root, "relative");
    File absolute = new File(root, "/absolute");

    System.out.println("File:");
    System.out.println(relative.getAbsolutePath()); // prints "/root/relative"
    System.out.println(absolute.getAbsolutePath()); // prints "/root/absolute"
    System.out.println();
  }

  public static void path(){
    Path root = Paths.get("/root");
    Path relative = root.resolve("relative");
    Path absolute = root.resolve("/absolute");

    System.out.println("Path:");
    System.out.println(relative.toAbsolutePath()); // prints "/root/relative"
    System.out.println(absolute.toAbsolutePath()); // prints "/absolute" but should print "/root/absolute"
    System.out.println();
  }
}

Basically what I want is a method that takes a parent Path, and a child String that returns me the parent+child Path regardless of whether the child had a leading /.
Something like this, but without the String manipulations that depend on me knowing that the configuration will use / (and not \):

private static Path resolveSafely(Path parent, String child) {
    child = child.startsWith("/")
            ? child.substring(1)
            : child;
    parent.resolve(child);
}

Solution

  • The best way I could find is this:

        Path root = Paths.get("/root");
        Path relative = root.resolve("relative");
        Path absolute = Paths.get(root.toString(), "/absolute");
    
        System.out.println("Path:");
        System.out.println(relative.toAbsolutePath()); // prints "/root/relative"
        System.out.println(absolute.toAbsolutePath()); // prints "/root/absolute"
        System.out.println();
    

    Hopefully that's all you need.

    Edit: Since Java 11, Path.of() is available and is the recommended way of obtaining Path objects instead of Paths.get(). Check javadoc which also states that Paths class may be deprecated in a future release.