4

Given two paths, how can I compute the relative path from one to another?

I thought about using split in fancy ways, but it seems kind of hacky, especially in cases like: "http://foo.com/bar/baz#header" or "http://foo.com/bar/baz"?param=value.

An example would be:

String url1 = "http://foo.com/bar/baz";
String url2 = "http://foo.com/bar/qux/quux/corge";

System.out.println(relative(url1, url2)); // -> "../qux/quux/corge"
Brian
  • 1,659
  • 12
  • 17

3 Answers3

5

Java already offers this functionality, so the safest option would be to go the "standard" way:

String url1 = "http://foo.com/bar/baz";
String url2 = "http://foo.com/bar/qux/quux/corge";

Path p1 = Paths.get(url1);
Path p2 = Paths.get(url2);
Path p  = p1.relativize(p2);

System.out.println("Relative path: " + p);

The print statement above shows the correct relative path - i.e., in this case,

../qux/quux/corge

If the protocol (e.g., http vs https) and host parts can be different, then converting url1 and url2 above, into URL objectsand using thegetPath()` method, should yield the correct relative path.

PNS
  • 19,295
  • 32
  • 96
  • 143
  • I tested this and why does this code show `Illegal char <:> at index 4` ? – SomeDude Aug 02 '16 at 19:13
  • For some reason, I get `java.nio.file.InvalidPathException: Illegal char <:> at index 4: http://foo.com/bar/baz`. I am using `import java.nio.file.*;`. – Brian Aug 02 '16 at 19:13
  • @Brian, I found that you need to process url1, url2 to remove `http:`, then the `relativize` works – SomeDude Aug 02 '16 at 19:18
  • 2
    I changed `Path p1 = Paths.get(url1);` into `p1 = Paths.get(new URL(url1).getPath());` and the same to`p2` and it works. – Brian Aug 02 '16 at 19:19
  • @svasa, you keep commenting right before I hit send ;) – Brian Aug 02 '16 at 19:19
  • It works exactly as I posted it in the answer, tested in Oracle JDK 1.7 for Linux. Otherwise yes, the reasonable thing to do is what Brian suggested. :-) – PNS Aug 02 '16 at 20:28
  • This is wrong. A URL is not a Path. And URL.getPath() definitely *does not* convert a URL to a valid file name. – VGR Aug 03 '16 at 14:47
  • What exactly is wrong? Assuming that the host part is the same, event URL.getPath() works as expected. This is what `Paths.get()` assumes. – PNS Aug 03 '16 at 19:35
1

You can do something like this:

public static String relative(String url1, String url2){
    String[] parts = url1.split("/");
    String similar = "";
    for(String part:parts){
        if(url2.contains(similar+part+"/")){
            similar+=part+"/";
        }
    }
    return "./"+url2.replace(similar, "");
}
Titus
  • 22,031
  • 1
  • 23
  • 33
1

No matter what hacky method you use, it's going to have to loop through your URLs under the hood anyway. IndexOf(), split(), and other functions which find characters still have O(n) runtime, because they need to hunt down those characters. So, you may as well write your own little "indexOf" style function. Just compare each character, one at a time, until you find a difference. That marks the end of your identical URLs, and since we can be sneaky and declare 'i' outside the loop, we can exit the loop with that index still stored. Then you just have to spit out what's left of the longer URL!

public String relative (String url1, String url2) {
    int i;
    for(i = 0; i < url1.length(); i++) {
        if(url1.charAt(i) != url2.charAt(i))
            break;
    }
    if(url1.length() > url2.length())
        return url1.substring(i);
    return url2.substring(i);
}
Lord Farquaad
  • 712
  • 1
  • 12
  • 33
  • This only works if you normalise the URLs first. The [`Path` API approach](http://stackoverflow.com/a/38728494/2071828) is far better. – Boris the Spider Aug 02 '16 at 19:10