com.google.javascript.jscomp.deps.PathUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler-unshaded Show documentation
Show all versions of closure-compiler-unshaded Show documentation
Closure Compiler is a JavaScript optimizing compiler. It parses your
JavaScript, analyzes it, removes dead code and rewrites and minimizes
what's left. It also checks syntax, variable references, and types, and
warns about common JavaScript pitfalls. It is used in many of Google's
JavaScript apps, including Gmail, Google Web Search, Google Maps, and
Google Docs.
/*
* Copyright 2008 The Closure Compiler Authors.
*
* 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.google.javascript.jscomp.deps;
import static com.google.common.base.StandardSystemProperty.USER_DIR;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Utility methods for manipulation of UNIX-like paths.
* TODO(tbreisacher): We can probably nuke this entire class and replace it with
* https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html
*/
public final class PathUtil {
private static final CharMatcher SLASH_MATCHER = CharMatcher.is('/');
private static final CharMatcher NON_SLASH_MATCHER = CharMatcher.isNot('/');
private PathUtil() {
}
/**
* Removes all ../ and ./ entries from within the given path. If there are extra ..s that move
* beyond the first directory given, they are removed.
*
* Examples:
* "a/b/../c" results in "a/c"
* "./foo/./../bar" results in "bar"
* "a/.." results in ""
* "a/../../foo" results in "foo"
*
* @param path The path to remove dots from.
* @return The path with all dots collapsed.
*/
public static String collapseDots(String path) {
path = removeExtraneousSlashes(path);
// Optimization: Most paths don't contain dots.
if (!path.contains(".")) {
return path;
}
List dstFragments = new ArrayList<>();
for (String fragment : Splitter.on('/').split(path)) {
if (fragment.equals("..")) {
if (!dstFragments.isEmpty()) {
dstFragments.remove(dstFragments.size() - 1);
}
} else if (!fragment.equals(".")) {
dstFragments.add(fragment);
}
}
// Special case for Join.join([""]); -> "/"
if (dstFragments.size() == 1 && dstFragments.get(0).isEmpty()) {
return "/";
}
return Joiner.on("/").join(dstFragments);
}
/**
* Determines if a path is absolute or not by testing for the presence of "/"
* at the front of the string.
*
* @param path The path to test
* @return true if the path starts with DELIMITER, false otherwise.
*/
static boolean isAbsolute(String path) {
return path.startsWith("/");
}
/**
* Removes extra slashes from a path. Leading slash is preserved, trailing
* slash is stripped, and any runs of more than one slash in the middle is
* replaced by a single slash.
*/
static String removeExtraneousSlashes(String s) {
int lastNonSlash = NON_SLASH_MATCHER.lastIndexIn(s);
if (lastNonSlash != -1) {
s = s.substring(0, lastNonSlash + 1);
}
return SLASH_MATCHER.collapseFrom(s, '/');
}
/**
* Converts the given path into an absolute one. This prepends the current
* working directory and removes all .'s from the path. If an absolute path
* is given, it will not be prefixed.
*
* Unlike File.getAbsolutePath(), this function does remove .'s from the
* path and unlike File.getCanonicalPath(), this function does not resolve
* symlinks and does not use filesystem calls.
*
* @param path The path to make absolute.
* @return The path made absolute.
*/
public static String makeAbsolute(String path) {
return makeAbsolute(path, USER_DIR.value());
}
/**
* Converts the given path into an absolute one. This prepends the given
* rootPath and removes all .'s from the path. If an absolute path is given,
* it will not be prefixed.
*
* Unlike File.getAbsolutePath(), this function does remove .'s from the
* path and unlike File.getCanonicalPath(), this function does not resolve
* symlinks and does not use filesystem calls.
*
* @param rootPath The path to prefix to path if path is not already absolute.
* @param path The path to make absolute.
* @return The path made absolute.
*/
public static String makeAbsolute(String path, String rootPath) {
if (!isAbsolute(path)) {
path = rootPath + "/" + path;
}
return collapseDots(path);
}
/**
* Returns targetPath relative to basePath.
*
* basePath and targetPath must either both be relative, or both be
* absolute paths.
*
* This function is different from makeRelative
* in that it is able to add in ../ components and collapse existing ones as well.
*
* Examples:
* base="some/relative/path" target="some/relative/path/foo" return="foo"
* base="some/relative/path" target="some/relative" return=".."
* base="some/relative/path" target="foo/bar" return="../../../foo/bar"
* base="/some/abs/path" target="/foo/bar" return="../../../foo/bar"
*
* @param basePath The path to make targetPath relative to.
* @param targetPath The path to make relative.
* @return A path relative to targetPath. The returned value will never start
* with a slash.
*/
public static String makeRelative(String basePath, String targetPath) {
// Ensure the paths are both absolute or both relative.
if (isAbsolute(basePath) !=
isAbsolute(targetPath)) {
throw new IllegalArgumentException(
"Paths must both be relative or both absolute.\n" +
" basePath: " + basePath + "\n" +
" targetPath: " + targetPath);
}
basePath = collapseDots(basePath);
targetPath = collapseDots(targetPath);
String[] baseFragments = basePath.split("/");
String[] targetFragments = targetPath.split("/");
int i = -1;
do {
i += 1;
if (i == baseFragments.length && i == targetFragments.length) {
// Eg) base: /java/com/google
// target: /java/com/google
// result: . <-- . is better than "" since "" + "/path" = "/path"
return ".";
} else if (i == baseFragments.length) {
// Eg) base: /java/com/google
// target: /java/com/google/c/ui
// result: c/ui
return Joiner.on("/").join(
Lists.newArrayList(
Arrays.asList(targetFragments).listIterator(i)));
} else if (i == targetFragments.length) {
// Eg) base: /java/com/google/c/ui
// target: /java/com/google
// result: ../..
return Strings.repeat("../", baseFragments.length - i - 1) + "..";
}
} while (baseFragments[i].equals(targetFragments[i]));
// Eg) base: /java/com/google/c
// target: /java/com/google/common/base
// result: ../common/base
return Strings.repeat("../", baseFragments.length - i) +
Joiner.on("/").join(
Lists.newArrayList(Arrays.asList(targetFragments).listIterator(i)));
}
}