How to construct a relative path in Java from two absolute paths (or URLs)?

前端 未结 22 2264
小蘑菇
小蘑菇 2020-11-22 10:30

Given two absolute paths, e.g.

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

How can one create a relative path that uses the second path as its base?

相关标签:
22条回答
  • 2020-11-22 11:08

    org.apache.ant has a FileUtils class with a getRelativePath method. Haven't tried it myself yet, but could be worthwhile to check it out.

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

    0 讨论(0)
  • 2020-11-22 11:11

    Here a method that resolves a relative path from a base path regardless they are in the same or in a different root:

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 11:11

    If Paths is not available for JRE 1.5 runtime or maven plugin

    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();
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 11:12

    At the time of writing (June 2010), this was the only solution that passed my test cases. I can't guarantee that this solution is bug-free, but it does pass the included test cases. The method and tests I've written depend on the FilenameUtils class from Apache commons IO.

    The solution was tested with Java 1.4. If you're using Java 1.5 (or higher) you should consider replacing StringBuffer with StringBuilder (if you're still using Java 1.4 you should consider a change of employer instead).

    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);
            }
        }    
    }
    

    The test cases that this passes are

    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
        }
    }
    
    0 讨论(0)
提交回复
热议问题