jeplus.util.RelativeDirUtil.java Source code

Java tutorial

Introduction

Here is the source code for jeplus.util.RelativeDirUtil.java

Source

/***************************************************************************
 *   jEPlus - EnergyPlus shell for parametric studies                      *
 *   Copyright (C) 2010  Yi Zhang <yi@jeplus.org>                          *
 *                                                                         *
 *   This program is free software: you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation, either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 *                                                                         *
 ***************************************************************************/
package jeplus.util;

import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.LoggerFactory;

/**
 * this class provides functions used to generate a relative path from two absolute paths
 *
 * @author David M. Howard
 */
public class RelativeDirUtil {

    /** Logger */
    final static org.slf4j.Logger logger = LoggerFactory.getLogger(RelativeDirUtil.class);

    /**
     * break a path down into individual elements and add to a list. example : if a path is /a/b/c/d.txt, the breakdown will be
     * [d.txt,c,b,a]
     *
     * @param f input file
     * @return a List collection with the individual elements of the path in reverse order
     */
    private static List getPathList(File f) {
        List l = new ArrayList();
        File r;
        try {
            r = f.getCanonicalFile();
            while (r != null) {
                l.add(r.getName());
                r = r.getParentFile();
            }
        } catch (IOException e) {
            logger.error("", e);
            l = null;
        }
        return l;
    }

    /**
     * figure out a string representing the relative path of 'f' with respect to 'r'
     *
     * @param r home path
     * @param f path of file
     */
    private static String matchPathLists(List r, List f) {
        int i;
        int j;
        String s;
        // start at the beginning of the lists
        // iterate while both lists are equal
        s = "";
        i = r.size() - 1;
        j = f.size() - 1;

        // first eliminate common root
        while ((i >= 0) && (j >= 0) && (r.get(i).equals(f.get(j)))) {
            i--;
            j--;
        }

        // If two lists are identical, i==-1, j==-1
        if (i < 0 && j < 0) {
            s += "." + File.separator;
        } else { // Otherwise
            // for each remaining level in the home path, add a ..
            for (; i >= 0; i--) {
                s += ".." + File.separator;
            }
            // for each level in the file path, add the path
            for (; j >= 0; j--) {
                s += f.get(j) + File.separator;
            }
        }
        return s;
    }

    /**
     * get relative path of File 'f' with respect to 'home' directory example : home = /a/b/c/ path = /a/d/e/ s = getRelativePath(home,
     * path) = ../../d/e/
     *
     * @param home base path, should be a directory, not a file, or it doesn't make sense
     * @param path the path (to a file) to generate path for
     * @return path from home to path as a string
     */
    public static String getRelativePath(File home, File path) {
        File r;
        List homelist;
        List filelist;
        String s;

        homelist = getPathList(home);
        filelist = getPathList(path);
        s = matchPathLists(homelist, filelist);

        return s;
    }

    /**
     * Check the path is absolute or not. If it is not, use the specified BaseDir and calculate absolute path
     *
     * @param thispath The Path to check
     * @param BaseDir The Base Directory to which a relative path is associated
     * @return The absolute (canonical where possible) path
     */
    public static String checkAbsolutePath(String thispath, String BaseDir) {
        String abspath;
        File path = new File(thispath);
        if (!path.isAbsolute()) {
            path = new File(BaseDir + thispath);
        }
        try {
            abspath = path.getCanonicalPath();
        } catch (IOException ex) {
            Logger.getLogger(RelativeDirUtil.class.getName()).log(Level.WARNING, null, ex);
            abspath = path.getAbsolutePath();
        }
        return abspath;
    }

    /**
     * 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
        switch (pathSeparator) {
        case "/":
            normalizedTargetPath = FilenameUtils.separatorsToUnix(normalizedTargetPath);
            normalizedBasePath = FilenameUtils.separatorsToUnix(normalizedBasePath);
            break;
        case "\\":
            normalizedTargetPath = FilenameUtils.separatorsToWindows(normalizedTargetPath);
            normalizedBasePath = FilenameUtils.separatorsToWindows(normalizedBasePath);
            break;
        default:
            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.
        StringBuilder common = new StringBuilder();

        int commonIndex = 0;
        while (commonIndex < target.length && commonIndex < base.length
                && target[commonIndex].equals(base[commonIndex])) {
            common.append(target[commonIndex]).append(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;
        }

        StringBuilder relative = new StringBuilder();

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

            for (int i = 0; i < numDirsUp; i++) {
                relative.append("..").append(pathSeparator);
            }
        }
        // deal with current folder (targetPath and basePath are the same)
        if (normalizedTargetPath.length() <= common.length()) {
            relative.append(".");
        } else {
            relative.append(normalizedTargetPath.substring(common.length()));
        }
        return relative.append(pathSeparator).toString();
    }

    static class PathResolutionException extends RuntimeException {

        PathResolutionException(String msg) {
            super(msg);
        }
    }

    /**
     * test the function
     */
    public static void main(String args[]) {
        if (args.length != 2) {
            System.out.println("RelativePath <home> <file>");
            return;
        }
        System.out.println("home = " + args[0]);
        System.out.println("file = " + args[1]);
        System.out.println("path = " + getRelativePath(new File(args[0]), new File(args[1]).getParentFile())
                + new File(args[1]).getName());
    }
}