com.aspectran.utils.PathUtils Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2024 The Aspectran Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.aspectran.utils;
import java.util.ArrayDeque;
import java.util.Deque;
public abstract class PathUtils {
public static final String REGULAR_FILE_SEPARATOR = "/";
public static final char REGULAR_FILE_SEPARATOR_CHAR = '/';
public static final String WINDOWS_FILE_SEPARATOR = "\\";
private static final String TOP_PATH = "..";
private static final String CURRENT_PATH = ".";
/**
* Apply the given relative path to the given Java resource path,
* assuming standard Java folder separation (i.e. "/" separators).
* @param path the path to start from (usually a full file path)
* @param relativePath the relative path to apply
* (relative to the full file path above)
* @return the full file path that results from applying the relative path
*/
public static String applyRelativePath(String path, String relativePath) {
Assert.notNull(path, "path must not be null");
Assert.notNull(relativePath, "relativePath must not be null");
int separatorIndex = path.lastIndexOf(REGULAR_FILE_SEPARATOR_CHAR);
if (separatorIndex != -1) {
String newPath = path.substring(0, separatorIndex);
if (!relativePath.startsWith(REGULAR_FILE_SEPARATOR)) {
newPath += REGULAR_FILE_SEPARATOR_CHAR;
}
return newPath + relativePath;
} else {
return relativePath;
}
}
/**
* Normalize the path by suppressing sequences like "path/.." and
* inner simple dots.
* The result is convenient for path comparison. For other uses,
* notice that Windows separators ("\") are replaced by simple slashes.
*
NOTE that {@code cleanPath} should not be depended
* upon in a security context. Other mechanisms should be used to prevent
* path-traversal issues.
* @param path the original path
* @return the normalized path
*/
public static String cleanPath(String path) {
if (!StringUtils.hasLength(path)) {
return path;
}
String normalizedPath = StringUtils.replace(path, WINDOWS_FILE_SEPARATOR, REGULAR_FILE_SEPARATOR);
String pathToUse = normalizedPath;
// Shortcut if there is no work to do
if (pathToUse.indexOf('.') == -1) {
return pathToUse;
}
// Strip prefix from path to analyze, to not treat it as part of the
// first path element. This is necessary to correctly parse paths like
// "file:core/../core/io/Resource.class", where the ".." should just
// strip the first "core" directory while keeping the "file:" prefix.
int prefixIndex = pathToUse.indexOf(':');
String prefix = "";
if (prefixIndex != -1) {
prefix = pathToUse.substring(0, prefixIndex + 1);
if (prefix.contains(REGULAR_FILE_SEPARATOR)) {
prefix = "";
} else {
pathToUse = pathToUse.substring(prefixIndex + 1);
}
}
if (pathToUse.startsWith(REGULAR_FILE_SEPARATOR)) {
prefix = prefix + REGULAR_FILE_SEPARATOR;
pathToUse = pathToUse.substring(1);
}
String[] pathArray = StringUtils.split(pathToUse, REGULAR_FILE_SEPARATOR);
// we never require more elements than pathArray and in the common case the same number
Deque pathElements = new ArrayDeque<>(pathArray.length);
int tops = 0;
for (int i = pathArray.length - 1; i >= 0; i--) {
String element = pathArray[i];
if (CURRENT_PATH.equals(element)) {
// Points to current directory - drop it.
} else if (TOP_PATH.equals(element)) {
// Registering top path found.
tops++;
} else {
if (tops > 0) {
// Merging path element with element corresponding to top path.
tops--;
} else {
// Normal path element found.
pathElements.addFirst(element);
}
}
}
// All path elements stayed the same - shortcut
if (pathArray.length == pathElements.size()) {
return normalizedPath;
}
// Remaining top paths need to be retained.
for (int i = 0; i < tops; i++) {
pathElements.addFirst(TOP_PATH);
}
// If nothing else left, at least explicitly point to current path.
if (pathElements.size() == 1 && pathElements.getLast().isEmpty() && !prefix.endsWith(REGULAR_FILE_SEPARATOR)) {
pathElements.addFirst(CURRENT_PATH);
}
final String joined = StringUtils.toDelimitedString(pathElements, REGULAR_FILE_SEPARATOR);
// avoid string concatenation with empty prefix
return prefix.isEmpty() ? joined : prefix + joined;
}
/**
* Compare two paths after normalization of them.
* @param path1 first path for comparison
* @param path2 second path for comparison
* @return whether the two paths are equivalent after normalization
*/
public static boolean pathEquals(String path1, String path2) {
return cleanPath(path1).equals(cleanPath(path2));
}
}