Como para construir um caminho relativo em Java a partir de dois caminhos absolutos (ou URL)?

StackOverflow https://stackoverflow.com/questions/204784

  •  03-07-2019
  •  | 
  •  

Pergunta

Dado dois caminhos absolutos, por exemplo.

/var/data/stuff/xyz.dat
/var/data

Como se pode criar um caminho relativo que usa o segundo caminho como sua base? No exemplo acima, o resultado deve ser: ./stuff/xyz.dat

Foi útil?

Solução

É um pouco rotunda, mas por que não usar URI? Tem um método relativizar o que faz todas as verificações necessárias para você.

String path = "/var/data/stuff/xyz.dat";
String base = "/var/data";
String relative = new File(base).toURI().relativize(new File(path).toURI()).getPath();
// relative == "stuff/xyz.dat"

Por favor note que para o caminho do arquivo há java.nio.file.Path#relativize desde Java 1.7, como fora apontado por @Jirka Meluzin em < a href = "https://stackoverflow.com/a/25743823/537554"> a outra resposta .

Outras dicas

Desde Java 7 você pode usar o relativizar método:

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

public class Test {

     public static void main(String[] args) {
        Path pathAbsolute = Paths.get("/var/data/stuff/xyz.dat");
        Path pathBase = Paths.get("/var/data");
        Path pathRelative = pathBase.relativize(pathAbsolute);
        System.out.println(pathRelative);
    }

}

Output:

stuff/xyz.dat

No momento da escrita (Junho de 2010), esta foi a única solução que passou os meus casos de teste. Não posso garantir que esta solução é livre de bugs, mas não passar os casos de teste incluídos. O método e os testes que eu escrevi depender do FilenameUtils classe de Apache Commons IO .

A solução foi testada com 1,4 Java. Se você estiver usando Java 1.5 (ou superior), você deve considerar substituir StringBuffer com StringBuilder (se você ainda está usando Java 1.4 você deve considerar uma mudança de empregador, em vez).

import java.io.File;
import java.util.regex.Pattern;

import org.apache.commons.io.FilenameUtils;

public class ResourceUtils {

    /**
     * Get the relative path from one file to another, specifying the directory separator. 
     * If one of the provided resources does not exist, it is assumed to be a file unless it ends with '/' or
     * '\'.
     * 
     * @param targetPath targetPath is calculated to this file
     * @param basePath basePath is calculated from this file
     * @param pathSeparator directory separator. The platform default is not assumed so that we can test Unix behaviour when running on Windows (for example)
     * @return
     */
    public static String getRelativePath(String targetPath, String basePath, String pathSeparator) {

        // Normalize the paths
        String normalizedTargetPath = FilenameUtils.normalizeNoEndSeparator(targetPath);
        String normalizedBasePath = FilenameUtils.normalizeNoEndSeparator(basePath);

        // Undo the changes to the separators made by normalization
        if (pathSeparator.equals("/")) {
            normalizedTargetPath = FilenameUtils.separatorsToUnix(normalizedTargetPath);
            normalizedBasePath = FilenameUtils.separatorsToUnix(normalizedBasePath);

        } else if (pathSeparator.equals("\\")) {
            normalizedTargetPath = FilenameUtils.separatorsToWindows(normalizedTargetPath);
            normalizedBasePath = FilenameUtils.separatorsToWindows(normalizedBasePath);

        } else {
            throw new IllegalArgumentException("Unrecognised dir separator '" + pathSeparator + "'");
        }

        String[] base = normalizedBasePath.split(Pattern.quote(pathSeparator));
        String[] target = normalizedTargetPath.split(Pattern.quote(pathSeparator));

        // First get all the common elements. Store them as a string,
        // and also count how many of them there are.
        StringBuffer common = new StringBuffer();

        int commonIndex = 0;
        while (commonIndex < target.length && commonIndex < base.length
                && target[commonIndex].equals(base[commonIndex])) {
            common.append(target[commonIndex] + pathSeparator);
            commonIndex++;
        }

        if (commonIndex == 0) {
            // No single common path element. This most
            // likely indicates differing drive letters, like C: and D:.
            // These paths cannot be relativized.
            throw new PathResolutionException("No common path element found for '" + normalizedTargetPath + "' and '" + normalizedBasePath
                    + "'");
        }   

        // The number of directories we have to backtrack depends on whether the base is a file or a dir
        // For example, the relative path from
        //
        // /foo/bar/baz/gg/ff to /foo/bar/baz
        // 
        // ".." if ff is a file
        // "../.." if ff is a directory
        //
        // The following is a heuristic to figure out if the base refers to a file or dir. It's not perfect, because
        // the resource referred to by this path may not actually exist, but it's the best I can do
        boolean baseIsFile = true;

        File baseResource = new File(normalizedBasePath);

        if (baseResource.exists()) {
            baseIsFile = baseResource.isFile();

        } else if (basePath.endsWith(pathSeparator)) {
            baseIsFile = false;
        }

        StringBuffer relative = new StringBuffer();

        if (base.length != commonIndex) {
            int numDirsUp = baseIsFile ? base.length - commonIndex - 1 : base.length - commonIndex;

            for (int i = 0; i < numDirsUp; i++) {
                relative.append(".." + pathSeparator);
            }
        }
        relative.append(normalizedTargetPath.substring(common.length()));
        return relative.toString();
    }


    static class PathResolutionException extends RuntimeException {
        PathResolutionException(String msg) {
            super(msg);
        }
    }    
}

Os casos de teste que este passes são

public void testGetRelativePathsUnix() {
    assertEquals("stuff/xyz.dat", ResourceUtils.getRelativePath("/var/data/stuff/xyz.dat", "/var/data/", "/"));
    assertEquals("../../b/c", ResourceUtils.getRelativePath("/a/b/c", "/a/x/y/", "/"));
    assertEquals("../../b/c", ResourceUtils.getRelativePath("/m/n/o/a/b/c", "/m/n/o/a/x/y/", "/"));
}

public void testGetRelativePathFileToFile() {
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common\\sapisvr.exe";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathDirectoryToFile() {
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common\\";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathFileToDirectory() {
    String target = "C:\\Windows\\Boot\\Fonts";
    String base = "C:\\Windows\\Speech\\Common\\foo.txt";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts", relPath);
}

public void testGetRelativePathDirectoryToDirectory() {
    String target = "C:\\Windows\\Boot\\";
    String base = "C:\\Windows\\Speech\\Common\\";
    String expected = "..\\..\\Boot";

    String relPath = ResourceUtils.getRelativePath(target, base, "\\");
    assertEquals(expected, relPath);
}

public void testGetRelativePathDifferentDriveLetters() {
    String target = "D:\\sources\\recovery\\RecEnv.exe";
    String base = "C:\\Java\\workspace\\AcceptanceTests\\Standard test data\\geo\\";

    try {
        ResourceUtils.getRelativePath(target, base, "\\");
        fail();

    } catch (PathResolutionException ex) {
        // expected exception
    }
}

Ao usar java.net.URI.relativize você deve estar ciente de bug Java: JDK-6226081 (URI deve ser capaz de relativar caminhos com raízes parciais)

De momento, o método de relativize() URI só relativizar URIs quando um é um prefixo da outra.

O que significa, essencialmente, java.net.URI.relativize não irá criar ".." é para você.

O bug referido no outra resposta é abordada por noreferrer URIUtils em Apache HttpComponents

public static URI resolve(URI baseURI,
                          String reference)

Resolve uma referência URI contra um URI base. Work-em torno de bug no java.net.URI ()

Em Java 8 Você pode fazer simples (e em contraste com URI, é livre de erros):

Path#relativize(Path)

Se você sabe que a segunda cadeia é parte da primeira:

String s1 = "/var/data/stuff/xyz.dat";
String s2 = "/var/data";
String s3 = s1.substring(s2.length());

ou se você realmente quer o período no início como no seu exemplo:

String s3 = ".".concat(s1.substring(s2.length()));

A recursão produz uma solução menor. Isso gera uma exceção se o resultado for impossível (disco por exemplo diferente Windows) ou impraticável (raiz é único diretório comum.)

/**
 * Computes the path for a file relative to a given base, or fails if the only shared 
 * directory is the root and the absolute form is better.
 * 
 * @param base File that is the base for the result
 * @param name File to be "relativized"
 * @return the relative name
 * @throws IOException if files have no common sub-directories, i.e. at best share the
 *                     root prefix "/" or "C:\"
 */

public static String getRelativePath(File base, File name) throws IOException  {
    File parent = base.getParentFile();

    if (parent == null) {
        throw new IOException("No common directory");
    }

    String bpath = base.getCanonicalPath();
    String fpath = name.getCanonicalPath();

    if (fpath.startsWith(bpath)) {
        return fpath.substring(bpath.length() + 1);
    } else {
        return (".." + File.separator + getRelativePath(parent, name));
    }
}

Aqui está uma outra solução biblioteca livre:

Path sourceFile = Paths.get("some/common/path/example/a/b/c/f1.txt");
Path targetFile = Paths.get("some/common/path/example/d/e/f2.txt"); 
Path relativePath = sourceFile.relativize(targetFile);
System.out.println(relativePath);

Saídas

..\..\..\..\d\e\f2.txt

[EDIT], na verdade, ele produz em mais .. \ por causa da fonte não é um arquivo do diretório. solução correta para o meu caso é:

Path sourceFile = Paths.get(new File("some/common/path/example/a/b/c/f1.txt").parent());
Path targetFile = Paths.get("some/common/path/example/d/e/f2.txt"); 
Path relativePath = sourceFile.relativize(targetFile);
System.out.println(relativePath);

A minha versão é vagamente baseado em Matt e Steve versões 's:

/**
 * Returns the path of one File relative to another.
 *
 * @param target the target directory
 * @param base the base directory
 * @return target's path relative to the base directory
 * @throws IOException if an error occurs while resolving the files' canonical names
 */
 public static File getRelativeFile(File target, File base) throws IOException
 {
   String[] baseComponents = base.getCanonicalPath().split(Pattern.quote(File.separator));
   String[] targetComponents = target.getCanonicalPath().split(Pattern.quote(File.separator));

   // skip common components
   int index = 0;
   for (; index < targetComponents.length && index < baseComponents.length; ++index)
   {
     if (!targetComponents[index].equals(baseComponents[index]))
       break;
   }

   StringBuilder result = new StringBuilder();
   if (index != baseComponents.length)
   {
     // backtrack to base directory
     for (int i = index; i < baseComponents.length; ++i)
       result.append(".." + File.separator);
   }
   for (; index < targetComponents.length; ++index)
     result.append(targetComponents[index] + File.separator);
   if (!target.getPath().endsWith("/") && !target.getPath().endsWith("\\"))
   {
     // remove final path separator
     result.delete(result.length() - File.separator.length(), result.length());
   }
   return new File(result.toString());
 }

A solução de Matt B recebe o número de diretórios para errada BackTrack - deve ser o comprimento do caminho de base menos o número de elementos de caminho comuns, menos um (para o último elemento do caminho, que é um nome de arquivo ou uma fuga "" gerado por split). Acontece a trabalhar com /a/b/c/ e /a/x/y/, mas substituir os argumentos com /m/n/o/a/b/c/ e /m/n/o/a/x/y/ e você vai ver o problema.

Além disso, ele precisa de um else break dentro do primeiro loop, ou ele vai maltratar caminhos que acontecerá a ter nomes de diretório correspondente, como /a/b/c/d/ e /x/y/c/z - o c está na mesma ranhura em ambas as matrizes, mas não é uma jogo real.

Todas essas soluções têm a capacidade de caminhos punho que não pode ser relativizado um ao outro, porque eles têm raízes incompatíveis, como C:\foo\bar e D:\baz\quux. Provavelmente apenas um problema no Windows, mas digno de nota.

Eu passei muito mais tempo com isso do que eu pretendia, mas tudo bem. Na verdade, eu precisava disso para o trabalho, por isso agradeço a todos que entrou na conversa, e estou certo de que haverá correções para esta versão também!

public static String getRelativePath(String targetPath, String basePath, 
        String pathSeparator) {

    //  We need the -1 argument to split to make sure we get a trailing 
    //  "" token if the base ends in the path separator and is therefore
    //  a directory. We require directory paths to end in the path
    //  separator -- otherwise they are indistinguishable from files.
    String[] base = basePath.split(Pattern.quote(pathSeparator), -1);
    String[] target = targetPath.split(Pattern.quote(pathSeparator), 0);

    //  First get all the common elements. Store them as a string,
    //  and also count how many of them there are. 
    String common = "";
    int commonIndex = 0;
    for (int i = 0; i < target.length && i < base.length; i++) {
        if (target[i].equals(base[i])) {
            common += target[i] + pathSeparator;
            commonIndex++;
        }
        else break;
    }

    if (commonIndex == 0)
    {
        //  Whoops -- not even a single common path element. This most
        //  likely indicates differing drive letters, like C: and D:. 
        //  These paths cannot be relativized. Return the target path.
        return targetPath;
        //  This should never happen when all absolute paths
        //  begin with / as in *nix. 
    }

    String relative = "";
    if (base.length == commonIndex) {
        //  Comment this out if you prefer that a relative path not start with ./
        //relative = "." + pathSeparator;
    }
    else {
        int numDirsUp = base.length - commonIndex - 1;
        //  The number of directories we have to backtrack is the length of 
        //  the base path MINUS the number of common path elements, minus
        //  one because the last element in the path isn't a directory.
        for (int i = 1; i <= (numDirsUp); i++) {
            relative += ".." + pathSeparator;
        }
    }
    relative += targetPath.substring(common.length());

    return relative;
}

E aqui são testes para cobrir vários casos:

public void testGetRelativePathsUnixy() 
{        
    assertEquals("stuff/xyz.dat", FileUtils.getRelativePath(
            "/var/data/stuff/xyz.dat", "/var/data/", "/"));
    assertEquals("../../b/c", FileUtils.getRelativePath(
            "/a/b/c", "/a/x/y/", "/"));
    assertEquals("../../b/c", FileUtils.getRelativePath(
            "/m/n/o/a/b/c", "/m/n/o/a/x/y/", "/"));
}

public void testGetRelativePathFileToFile() 
{
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common\\sapisvr.exe";

    String relPath = FileUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathDirectoryToFile() 
{
    String target = "C:\\Windows\\Boot\\Fonts\\chs_boot.ttf";
    String base = "C:\\Windows\\Speech\\Common";

    String relPath = FileUtils.getRelativePath(target, base, "\\");
    assertEquals("..\\..\\Boot\\Fonts\\chs_boot.ttf", relPath);
}

public void testGetRelativePathDifferentDriveLetters() 
{
    String target = "D:\\sources\\recovery\\RecEnv.exe";
    String base   = "C:\\Java\\workspace\\AcceptanceTests\\Standard test data\\geo\\";

    //  Should just return the target path because of the incompatible roots.
    String relPath = FileUtils.getRelativePath(target, base, "\\");
    assertEquals(target, relPath);
}

Na verdade, minha outra resposta fez não funcionar se o caminho de destino não era uma criança do caminho de base.

Isso deve funcionar.

public class RelativePathFinder {

    public static String getRelativePath(String targetPath, String basePath, 
       String pathSeparator) {

        // find common path
        String[] target = targetPath.split(pathSeparator);
        String[] base = basePath.split(pathSeparator);

        String common = "";
        int commonIndex = 0;
        for (int i = 0; i < target.length && i < base.length; i++) {

            if (target[i].equals(base[i])) {
                common += target[i] + pathSeparator;
                commonIndex++;
            }
        }


        String relative = "";
        // is the target a child directory of the base directory?
        // i.e., target = /a/b/c/d, base = /a/b/
        if (commonIndex == base.length) {
            relative = "." + pathSeparator + targetPath.substring(common.length());
        }
        else {
            // determine how many directories we have to backtrack
            for (int i = 1; i <= commonIndex; i++) {
                relative += ".." + pathSeparator;
            }
            relative += targetPath.substring(common.length());
        }

        return relative;
    }

    public static String getRelativePath(String targetPath, String basePath) {
        return getRelativePath(targetPath, basePath, File.pathSeparator);
    }
}

public class RelativePathFinderTest extends TestCase {

    public void testGetRelativePath() {
        assertEquals("./stuff/xyz.dat", RelativePathFinder.getRelativePath(
                "/var/data/stuff/xyz.dat", "/var/data/", "/"));
        assertEquals("../../b/c", RelativePathFinder.getRelativePath("/a/b/c",
                "/a/x/y/", "/"));
    }

}

Cool !! Eu preciso de um pouco de código como este, mas para a comparação de caminhos de diretório em máquinas Linux. Descobri que isso não estava trabalhando em situações onde um diretório pai era o alvo.

Aqui está uma versão amigável diretório do método:

 public static String getRelativePath(String targetPath, String basePath, 
     String pathSeparator) {

 boolean isDir = false;
 {
   File f = new File(targetPath);
   isDir = f.isDirectory();
 }
 //  We need the -1 argument to split to make sure we get a trailing 
 //  "" token if the base ends in the path separator and is therefore
 //  a directory. We require directory paths to end in the path
 //  separator -- otherwise they are indistinguishable from files.
 String[] base = basePath.split(Pattern.quote(pathSeparator), -1);
 String[] target = targetPath.split(Pattern.quote(pathSeparator), 0);

 //  First get all the common elements. Store them as a string,
 //  and also count how many of them there are. 
 String common = "";
 int commonIndex = 0;
 for (int i = 0; i < target.length && i < base.length; i++) {
     if (target[i].equals(base[i])) {
         common += target[i] + pathSeparator;
         commonIndex++;
     }
     else break;
 }

 if (commonIndex == 0)
 {
     //  Whoops -- not even a single common path element. This most
     //  likely indicates differing drive letters, like C: and D:. 
     //  These paths cannot be relativized. Return the target path.
     return targetPath;
     //  This should never happen when all absolute paths
     //  begin with / as in *nix. 
 }

 String relative = "";
 if (base.length == commonIndex) {
     //  Comment this out if you prefer that a relative path not start with ./
     relative = "." + pathSeparator;
 }
 else {
     int numDirsUp = base.length - commonIndex - (isDir?0:1); /* only subtract 1 if it  is a file. */
     //  The number of directories we have to backtrack is the length of 
     //  the base path MINUS the number of common path elements, minus
     //  one because the last element in the path isn't a directory.
     for (int i = 1; i <= (numDirsUp); i++) {
         relative += ".." + pathSeparator;
     }
 }
 //if we are comparing directories then we 
 if (targetPath.length() > common.length()) {
  //it's OK, it isn't a directory
  relative += targetPath.substring(common.length());
 }

 return relative;
}

Eu estou supondo que você tem fromPath (um caminho absoluto para uma pasta), e toPath (um caminho absoluto para uma pasta / arquivo), e seu está procurando um caminho que, com representar o arquivo / pasta em toPath como um caminho relativo de fromPath (seu diretório de trabalho atual é fromPath ) então algo como isto deve funcionar:

public static String getRelativePath(String fromPath, String toPath) {

  // This weirdness is because a separator of '/' messes with String.split()
  String regexCharacter = File.separator;
  if (File.separatorChar == '\\') {
    regexCharacter = "\\\\";
  }

  String[] fromSplit = fromPath.split(regexCharacter);
  String[] toSplit = toPath.split(regexCharacter);

  // Find the common path
  int common = 0;
  while (fromSplit[common].equals(toSplit[common])) {
    common++;
  }

  StringBuffer result = new StringBuffer(".");

  // Work your way up the FROM path to common ground
  for (int i = common; i < fromSplit.length; i++) {
    result.append(File.separatorChar).append("..");
  }

  // Work your way down the TO path
  for (int i = common; i < toSplit.length; i++) {
    result.append(File.separatorChar).append(toSplit[i]);
  }

  return result.toString();
}

Muitas das respostas já aqui, mas eu achei que eles não lidar com todos os casos, tais como a base e alvo sendo o mesmo. Esta função tem um diretório base e um caminho de destino e retorna o caminho relativo. Se não existir qualquer caminho relativo, o caminho de destino é devolvido. File.separator é desnecessário.

public static String getRelativePath (String baseDir, String targetPath) {
    String[] base = baseDir.replace('\\', '/').split("\\/");
    targetPath = targetPath.replace('\\', '/');
    String[] target = targetPath.split("\\/");

    // Count common elements and their length.
    int commonCount = 0, commonLength = 0, maxCount = Math.min(target.length, base.length);
    while (commonCount < maxCount) {
        String targetElement = target[commonCount];
        if (!targetElement.equals(base[commonCount])) break;
        commonCount++;
        commonLength += targetElement.length() + 1; // Directory name length plus slash.
    }
    if (commonCount == 0) return targetPath; // No common path element.

    int targetLength = targetPath.length();
    int dirsUp = base.length - commonCount;
    StringBuffer relative = new StringBuffer(dirsUp * 3 + targetLength - commonLength + 1);
    for (int i = 0; i < dirsUp; i++)
        relative.append("../");
    if (commonLength < targetLength) relative.append(targetPath.substring(commonLength));
    return relative.toString();
}

Aqui um método que resolve um caminho relativo a partir de um caminho de base, independentemente eles estão na mesma ou em uma raiz diferente:

public static String GetRelativePath(String path, String base){

    final String SEP = "/";

    // if base is not a directory -> return empty
    if (!base.endsWith(SEP)){
        return "";
    }

    // check if path is a file -> remove last "/" at the end of the method
    boolean isfile = !path.endsWith(SEP);

    // get URIs and split them by using the separator
    String a = "";
    String b = "";
    try {
        a = new File(base).getCanonicalFile().toURI().getPath();
        b = new File(path).getCanonicalFile().toURI().getPath();
    } catch (IOException e) {
        e.printStackTrace();
    }
    String[] basePaths = a.split(SEP);
    String[] otherPaths = b.split(SEP);

    // check common part
    int n = 0;
    for(; n < basePaths.length && n < otherPaths.length; n ++)
    {
        if( basePaths[n].equals(otherPaths[n]) == false )
            break;
    }

    // compose the new path
    StringBuffer tmp = new StringBuffer("");
    for(int m = n; m < basePaths.length; m ++)
        tmp.append(".."+SEP);
    for(int m = n; m < otherPaths.length; m ++)
    {
        tmp.append(otherPaths[m]);
        tmp.append(SEP);
    }

    // get path string
    String result = tmp.toString();

    // remove last "/" if path is a file
    if (isfile && result.endsWith(SEP)){
        result = result.substring(0,result.length()-1);
    }

    return result;
}

Passes testes de Donal, a única mudança - se nenhuma raiz comum ele retorna caminho de destino (que poderia já estar relativa)

import static java.util.Arrays.asList;
import static java.util.Collections.nCopies;
import static org.apache.commons.io.FilenameUtils.normalizeNoEndSeparator;
import static org.apache.commons.io.FilenameUtils.separatorsToUnix;
import static org.apache.commons.lang3.StringUtils.getCommonPrefix;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import static org.apache.commons.lang3.StringUtils.join;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class ResourceUtils {

    public static String getRelativePath(String targetPath, String basePath, String pathSeparator) {
        File baseFile = new File(basePath);
        if (baseFile.isFile() || !baseFile.exists() && !basePath.endsWith("/") && !basePath.endsWith("\\"))
            basePath = baseFile.getParent();

        String target = separatorsToUnix(normalizeNoEndSeparator(targetPath));
        String base = separatorsToUnix(normalizeNoEndSeparator(basePath));

        String commonPrefix = getCommonPrefix(target, base);
        if (isBlank(commonPrefix))
            return targetPath.replaceAll("/", pathSeparator);

        target = target.replaceFirst(commonPrefix, "");
        base = base.replaceFirst(commonPrefix, "");

        List<String> result = new ArrayList<>();
        if (isNotEmpty(base))
            result.addAll(nCopies(base.split("/").length, ".."));
        result.addAll(asList(target.replaceFirst("^/", "").split("/")));

        return join(result, pathSeparator);
    }
}

Se você estiver escrevendo um plugin Maven, você pode usar Plexus' PathTool :

import org.codehaus.plexus.util.PathTool;

String relativeFilePath = PathTool.getRelativeFilePath(file1, file2);

Se os caminhos não está disponível para JRE 1.5 tempo de execução ou Maven plug-in

package org.afc.util;

import java.io.File;
import java.util.LinkedList;
import java.util.List;

public class FileUtil {

    public static String getRelativePath(String basePath, String filePath)  {
        return getRelativePath(new File(basePath), new File(filePath));
    }

    public static String getRelativePath(File base, File file)  {

        List<String> bases = new LinkedList<String>();
        bases.add(0, base.getName());
        for (File parent = base.getParentFile(); parent != null; parent = parent.getParentFile()) {
            bases.add(0, parent.getName());
        }

        List<String> files = new LinkedList<String>();
        files.add(0, file.getName());
        for (File parent = file.getParentFile(); parent != null; parent = parent.getParentFile()) {
            files.add(0, parent.getName());
        }

        int overlapIndex = 0;
        while (overlapIndex < bases.size() && overlapIndex < files.size() && bases.get(overlapIndex).equals(files.get(overlapIndex))) {
            overlapIndex++;
        }

        StringBuilder relativePath = new StringBuilder();
        for (int i = overlapIndex; i < bases.size(); i++) {
            relativePath.append("..").append(File.separatorChar);
        }

        for (int i = overlapIndex; i < files.size(); i++) {
            relativePath.append(files.get(i)).append(File.separatorChar);
        }

        relativePath.deleteCharAt(relativePath.length() - 1);
        return relativePath.toString();
    }

}

org.apache.ant tem uma classe FileUtils com um método getRelativePath. Já não tentei me ainda, mas pode valer a pena dar uma olhada.

http://javadoc.haefelinger.it/org.apache.ant/1.7.1/org/apache/tools/ant/util/FileUtils.html#getRelativePath (java.io.File , java.io.File)

private String relative(String left, String right){
    String[] lefts = left.split("/");
    String[] rights = right.split("/");
    int min = Math.min(lefts.length, rights.length);
    int commonIdx = -1;
    for(int i = 0; i < min; i++){
        if(commonIdx < 0 && !lefts[i].equals(rights[i])){
            commonIdx = i - 1;
            break;
        }
    }
    if(commonIdx < 0){
        return null;
    }
    StringBuilder sb = new StringBuilder(Math.max(left.length(), right.length()));
    sb.append(left).append("/");
    for(int i = commonIdx + 1; i < lefts.length;i++){
        sb.append("../");
    }
    for(int i = commonIdx + 1; i < rights.length;i++){
        sb.append(rights[i]).append("/");
    }

    return sb.deleteCharAt(sb.length() -1).toString();
}

Psuedo-código:

  1. Split as cordas pelo separador de caminho ( "/")
  2. Encontre o maior caminho comum por iteração através do resultado da string split (assim você iria acabar com "/ var / dados" ou "/ A" em seus dois exemplos)
  3. return "." + whicheverPathIsLonger.substring(commonPath.length);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top