10

I am trying to familiarize myself with java.nio.file.Path.relativize() to no avail.

I have read the javadocs, and I have seen examples. However, I still cannot get my head around the following example(I use Linux, apologies to window users):

Working directory for program is: /home/userspace/workspace/java8.

With two files: /home/userspace/workspace/java8/zoo.txt and /home/userspace/temp/delete/dictionary.txt

The following program calls Path.relativize():

package certExam.java8.ch9NIO.paths;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Relativize 
{
    public static void main(String[] args) 
    {
        Path relativePathToZoo = Paths.get("zoo.txt");
        Path relativePathToDictionary = Paths.get("../../temp/delete/dictionary.txt");
        System.out.println("relativePathToZoo.relativize(relativePathToDictionary): "+relativePathToZoo.relativize(relativePathToDictionary));
        System.out.println("relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath(): "+relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath());
        System.out.println("relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath().normalize(): "+relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath().normalize());
        System.out.println("relativePathToDictionary.relativize(relativePathToZoo): "+relativePathToDictionary.relativize(relativePathToZoo));
        System.out.println("relativePathToDictionary.relativize(relativePathToZoo).toAbsolutePath().normalize(): "+relativePathToDictionary.relativize(relativePathToZoo).toAbsolutePath().normalize());
        System.out.println();
    }
}

The output is:

relativePathToZoo.relativize(relativePathToDictionary): ../../../temp/delete/dictionary.txt 
    relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath(): /home/userspace/workspace/java8/../../../temp/delete/dictionary.txt 
    relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath().normalize(): /home/temp/delete/dictionary.txt 
    relativePathToDictionary.relativize(relativePathToZoo): ../../../../../zoo.txt 
    relativePathToDictionary.relativize(relativePathToZoo).toAbsolutePath().normalize(): /zoo.txt

My question, the bit I cannot understand is: Why does relativePathToDictionary.relativize(relativePathToZoo) output ../../../../../zoo.txt?

When normalized, it would make you think that zoo.txt lives in the root directory.

How does relativize() work out such a deep path? I understand that relativize() works in relation to the current working directory, so it adds .. to every path. But I am cannot understand, how it worked out the path to zoo.txt in relation to dictionary.txt.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Lucas T
  • 3,011
  • 6
  • 29
  • 36
  • 2
    Remember that relativize returns something that can be passed to resolve, and [resolve assumes its Path represents a directory.](http://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html#resolve-java.nio.file.Path-) Therefore, relativize also assumes its Path is a directory. – VGR Jun 10 '16 at 13:45
  • I understood that after some time by just thinking this: I'm navigating on directories on terminal and I want to go from Path A to Path B with a single cd command. That's the output of relativize – Bruno Leite Mar 26 '20 at 17:20

2 Answers2

18

First of all, the current working directory is completely irrelevant. I could reproduce your problem even under Windows, not having any of these files and directories on my system, the only difference being the use of \\ instead of /.

What should relativize do? If you have a path like foo bar baz and ask for relativizing foo bar hello, you’ll get .. hello as that’s the path, relative to foo bar baz to get to foo bar hello, i.e Paths.get("foo", "bar", "baz").resolve(Paths.get("..", "hello")).normalize() produces the same path as Paths.get("foo", "bar", "hello"), regardless of any real file system structure.

Now you ran into the bug JDK-6925169, as suggested by the user Berger in a comment. The Path implementation does not handle . and .. components correctly in relativize, but treats them like any other path component.

So whether you use Paths.get("..", "..", "temp", "delete", "dictionary.txt") or Paths.get("a", "b", "c", "d", "e"), it makes no difference, in either case, the implementation treats it as five nonmatching path components that have to be removed to resolve to Paths.get("zoo.txt"). This applies to both, Windows and Linux. You may verify it with the following platform-independent code:

Path relative = Paths.get("zoo.txt");
Path base1 = Paths.get("..", "..", "temp", "delete", "dictionary.txt");
Path base2 = Paths.get("a",  "b",  "c",    "d",      "e");

Path relativized1 = base1.relativize(relative);
System.out.println("relativized1: "+relativized1);
Path relativized2 = base2.relativize(relative);
System.out.println("relativized2: "+relativized2);

Path resolved1 = base1.resolve(relativized1).normalize();
System.out.println("resolved1="+resolved1);
Path resolved2 = base2.resolve(relativized2).normalize();
System.out.println("resolved2="+resolved2);

Since relatize incorrectly treats all component the same, the relativized paths are the same, but since the normalize operation does handle the .. path components, the first resolved path will exhibit the problem whereas the second resolves to the expected zoo.txt.

It might be important for the understanding, that all path components, including dictionary.txt, are treated like directories. The documentation of relativize doesn’t mention that explicitly, but you can derive it from the documented relationship to resolve, whose documentation says “… this method considers this path to be a directory”.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
18

Given the example paths below:

Path p1 = Paths.get("java/temp/zoo.txt");
Path p2 = Paths.get("java/bin/elephant.bin");

Path p1Top2 = p1.relativize(p2);
System.out.println(p1Top2); 

We want to get from zoo.txt to elephant.bin in this example. So, let's start at zoo.txt and ask ourselves: how do I get from zoo.txt to elephant.bin. First I have to go up a directory, so I use ".." Now I'm in temp. (Trace the steps with your finger if it helps!). I have to go up one more to java so, I use ".." again. Now I'm in java. The directory bin is in java, so, I go down to it using "/bin". Once more I go down using "/elephant.bin". We have arrived at our destination.

Put all of the above steps we took together and you get the output:

../../bin/elephant.bin
Chidozie Nnachor
  • 872
  • 10
  • 21
ZackOfAllTrades
  • 567
  • 5
  • 12
  • 2
    Thanks for the example. I think `p1.relativize(p2)` returning a relative path of `p1` would be more logical, though... – Eric Duminil Feb 08 '19 at 10:39
  • 1
    This explanation really opened up the aha! moment for me. I've been struggling with this for days! Thanks! – Chidozie Nnachor Oct 28 '21 at 22:19
  • This is confusing because in the context of Linux, p2 relative to p1, would be just `elephant.bin` without `..` since both the files are in the same directory. – Gayan Weerakutti Mar 12 '22 at 12:30