/* * Copyright (c) 2002 Cunningham & Cunningham, Inc. * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence * * This file is part of FitGoodies. * * FitGoodies 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. * * FitGoodies 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 FitGoodies. If not, see <http://www.gnu.org/licenses/>. */ package de.cologneintelligence.fitgoodies.file; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** * Helper class which is used to process paths and directories. */ public class FileSystemDirectoryHelper { /** * Removes a prefix from a path. If the path does not start with the prefix, * the unchanged path is returned. * * @param path the full path * @param prefix prefix to remove * @return {@code path} without prefix, or {@code path} itself * unless it doesn't start with the prefix. */ public String removePrefix(final String path, final String prefix) { String pathWithoutPrefix = path; if (pathWithoutPrefix.startsWith(prefix)) { pathWithoutPrefix = pathWithoutPrefix.substring(prefix.length()); while (pathWithoutPrefix.startsWith("/") || pathWithoutPrefix.startsWith("\\")) { pathWithoutPrefix = pathWithoutPrefix.substring(1); } } return pathWithoutPrefix; } /** * Checks whether {@code subDir} is a sub directory of {@code parentDir}. * Note: the comparison is case sensitive. * * @param subDir potential sub directory * @param parentDir parent directory * @return {@code true} if {@code subDir} is a subDirectory of * {@code parentDir}, false otherwise. * @throws java.io.IOException Thrown when paths cannot be normalized. */ public boolean isSubDir(final File subDir, final File parentDir) throws IOException { int parentDirLength = parentDir.getCanonicalFile().getAbsolutePath().length(); File currentDir = subDir.getCanonicalFile().getAbsoluteFile(); while (currentDir.getAbsolutePath().length() > parentDirLength) { currentDir = currentDir.getParentFile(); } return currentDir.equals(parentDir.getAbsoluteFile()); } /** * Returns all directory paths between {@code fromDir} and {@code toDir}. * * @param fromDir directory to start from * @param toDir last directory path * @return an array of all generated paths * @throws java.io.IOException Thrown when paths cannot be normalized. */ public File[] getParentDirs(final File fromDir, final File toDir) throws IOException { List<File> result = new LinkedList<>(); final File fromDirCanonical = fromDir.getCanonicalFile(); for (File current = toDir.getCanonicalFile().getAbsoluteFile(); !current.equals(fromDirCanonical); current = current.getParentFile()) { result.add(0, current); } return result.toArray(new File[result.size()]); } /** * Gets the longest common parent directory path of two paths. * * @param dir1 first path * @param dir2 second path * @return longest common path in {@code dir1} and {@code dir2} * @throws java.io.IOException Exception which is thrown if paths cannot be normalized */ public File getCommonDir(final File dir1, final File dir2) throws IOException { List<File> parts1 = getParentDirs(dir1); List<File> parts2 = getParentDirs(dir2); File matched = null; final int maxCommonSize = Math.min(parts1.size(), parts2.size()); for (int i = 0; i < maxCommonSize; ++i) { if (parts1.get(i).equals(parts2.get(i))) { matched = parts1.get(i); } else { break; } } return matched; } private List<File> getParentDirs(File dir) throws IOException { List<File> dirs = new LinkedList<>(); File currentDir = dir.getCanonicalFile(); File lastDir; do { dirs.add(0, currentDir); lastDir = currentDir; currentDir = currentDir.getParentFile(); } while (currentDir != null && !currentDir.equals(lastDir)); return dirs; } /** * Converts an absolute path into a relative one. * * @param basePath path to start from * @param absPath path to convert * @return {@code absPath} as a relative path starting from {@code basePath} */ public String abs2rel(final String basePath, final String absPath) { if (!isAbsolutePath(absPath)) { return absPath; } if (isWindowsDrive(absPath) && isWindowsDrive(basePath) && absPath.charAt(0) != basePath.charAt(0)) { return absPath; } StringBuilder result = new StringBuilder(); String[] baseParts = getParts(basePath); String[] absParts = getParts(absPath); // extract common prefix int start = 0; for (int i = 0; i < Math.min(baseParts.length, absParts.length); ++i) { if (baseParts[i].equals(absParts[i])) { start = i + 1; } } for (int i = start; i < baseParts.length; ++i) { if (result.length() > 0) { result.append(File.separator); } result.append(".."); } for (int i = start; i < absParts.length; ++i) { if (result.length() > 0) { result.append(File.separator); } result.append(absParts[i]); } return result.toString(); } private boolean isAbsolutePath(String path) { return isWindowsDrive(path) || path.startsWith("\\") || path.startsWith("/"); } private String[] getParts(final String path) { String newPath = path; if (newPath.startsWith("/") || newPath.startsWith("\\")) { newPath = newPath.substring(1); } if (newPath.endsWith("/") || newPath.endsWith("\\")) { newPath = newPath.substring(0, newPath.length() - 1); } return newPath.split("[/\\\\]"); } /** * Converts an relative path into an absolute one. * * @param basePath path to start from * @param relPath path to convert * @return {@code relPath} as a absolute path */ public File rel2abs(final String basePath, final String relPath) { String[] baseParts = getParts(basePath); String[] relParts = getParts(relPath); if (isAbsolutePath(relPath)) { return new File(relPath); } List<String> parts = new ArrayList<>(); for (int i = 0; i < baseParts.length; ++i) { if (i > 0 || !isWindowsDrive(basePath)) { parts.add(baseParts[i]); } } for (String part : relParts) { if (part.equals("..") && parts.size() > 0) { parts.remove(parts.size() - 1); } else if (!part.equals(".") && !part.equals("..")) { parts.add(part); } } StringBuilder result = new StringBuilder(); if (isWindowsDrive(basePath)) { result.append(baseParts[0]); } for (String part : parts) { result.append(File.separator); result.append(part); } return new File(result.toString()); } private boolean isWindowsDrive(final String pathElement) { return pathElement.length() > 1 && pathElement.charAt(1) == ':'; } /** * Counts the number of directories in a given path. * * @param path the path to analyze * @return number of directories */ public int dirDepth(final File path) { final String stringPath = path.getPath(); return stringPath.length() - stringPath.replaceAll("[/\\\\]", "").length(); } // FIXME: this method only exists for mocking in tests public File subdir(File baseDir, String file) { return new File(baseDir, file); } }