/src/com/facebook/buck/io/MorePaths.java
Java | 304 lines | 188 code | 34 blank | 82 comment | 31 complexity | ed924ca904909598327c9692742eca36 MD5 | raw file
- /*
- * Copyright 2013-present Facebook, Inc.
- *
- * 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.facebook.buck.io;
- import com.facebook.buck.util.HumanReadableException;
- import com.facebook.buck.util.environment.Platform;
- import com.google.common.base.Function;
- import com.google.common.base.Preconditions;
- import com.google.common.collect.FluentIterable;
- import com.google.common.collect.ImmutableSet;
- import com.google.common.io.ByteSource;
- import java.io.IOException;
- import java.io.InputStream;
- import java.nio.file.FileSystem;
- import java.nio.file.Files;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.util.Optional;
- import javax.annotation.Nullable;
- /**
- * Common functions that are done with a {@link Path}. If a function is going to take a
- * {@link ProjectFilesystem}, then it should be in {@link MoreProjectFilesystems} instead.
- */
- public class MorePaths {
- /** Utility class: do not instantiate. */
- private MorePaths() {}
- public static final Path EMPTY_PATH = Paths.get("");
- public static String pathWithUnixSeparators(String path) {
- return pathWithUnixSeparators(Paths.get(path));
- }
- public static String pathWithUnixSeparators(Path path) {
- return path.toString().replace('\\', '/');
- }
- public static String pathWithWindowsSeparators(Path path) {
- return path.toString().replace('/', '\\');
- }
- public static String pathWithPlatformSeparators(String path) {
- return pathWithPlatformSeparators(Paths.get(path));
- }
- public static String pathWithPlatformSeparators(Path path) {
- if (Platform.detect() == Platform.WINDOWS) {
- return pathWithWindowsSeparators(path);
- } else {
- return pathWithUnixSeparators(path);
- }
- }
- public static String pathWithUnixSeparatorsAndTrailingSlash(Path path) {
- return pathWithUnixSeparators(path) + "/";
- }
- public static Path getParentOrEmpty(Path path) {
- Path parent = path.getParent();
- if (parent == null) {
- parent = EMPTY_PATH;
- }
- return parent;
- }
- /**
- * Get the path of a file relative to a base directory.
- *
- * @param path must reference a file, not a directory.
- * @param baseDir must reference a directory that is relative to a common directory with the path.
- * may be null if referencing the same directory as the path.
- * @return the relative path of path from the directory baseDir.
- */
- public static Path getRelativePath(Path path, @Nullable Path baseDir) {
- if (baseDir == null) {
- // This allows callers to use this method with "file.parent()" for files from the project
- // root dir.
- baseDir = EMPTY_PATH;
- }
- Preconditions.checkArgument(!path.isAbsolute(),
- "Path must be relative: %s.", path);
- Preconditions.checkArgument(!baseDir.isAbsolute(),
- "Path must be relative: %s.", baseDir);
- return relativize(baseDir, path);
- }
- /**
- * Get a relative path from path1 to path2, first normalizing each path.
- *
- * This method is a workaround for JDK-6925169 (Path.relativize
- * returns incorrect result if path contains "." or "..").
- */
- public static Path relativize(Path path1, Path path2) {
- Preconditions.checkArgument(
- path1.isAbsolute() == path2.isAbsolute(),
- "Both paths must be absolute or both paths must be relative. (%s is %s, %s is %s)",
- path1,
- path1.isAbsolute() ? "absolute" : "relative",
- path2,
- path2.isAbsolute() ? "absolute" : "relative");
- path1 = normalize(path1);
- path2 = normalize(path2);
- // On Windows, if path1 is "" then Path.relativize returns ../path2 instead of path2 or ./path2
- if (EMPTY_PATH.equals(path1)) {
- return path2;
- }
- return path1.relativize(path2);
- }
- /**
- * Get a path without unnecessary path parts.
- *
- * This method is a workaround for JDK-8037945 (Paths.get("").normalize() throws
- * ArrayIndexOutOfBoundsException).
- */
- public static Path normalize(Path path) {
- if (!EMPTY_PATH.equals(path)) {
- path = path.normalize();
- }
- return path;
- }
- /**
- * Creates a symlink at {@code pathToProjectRoot.resolve(pathToDesiredLinkUnderProjectRoot)} that
- * points to {@code pathToProjectRoot.resolve(pathToExistingFileUnderProjectRoot)} using a
- * relative symlink. Both params must be relative to the project root.
- *
- * @param pathToDesiredLinkUnderProjectRoot must reference a file, not a directory.
- * @param pathToExistingFileUnderProjectRoot must reference a file, not a directory.
- * @return the relative path from the new symlink that was created to the existing file.
- */
- public static Path createRelativeSymlink(
- Path pathToDesiredLinkUnderProjectRoot,
- Path pathToExistingFileUnderProjectRoot,
- Path pathToProjectRoot) throws IOException {
- Path target = getRelativePath(
- pathToExistingFileUnderProjectRoot,
- pathToDesiredLinkUnderProjectRoot.getParent());
- Files.createSymbolicLink(pathToProjectRoot.resolve(pathToDesiredLinkUnderProjectRoot), target);
- return target;
- }
- /**
- * Filters out {@link Path} objects from {@code paths} that aren't a subpath of {@code root} and
- * returns a set of paths relative to {@code root}.
- */
- public static ImmutableSet<Path> filterForSubpaths(Iterable<Path> paths, final Path root) {
- final Path normalizedRoot = root.toAbsolutePath().normalize();
- return FluentIterable.from(paths)
- .filter(input -> {
- if (input.isAbsolute()) {
- return input.normalize().startsWith(normalizedRoot);
- } else {
- return true;
- }
- })
- .transform(input -> {
- if (input.isAbsolute()) {
- return relativize(normalizedRoot, input);
- } else {
- return input;
- }
- })
- .toSet();
- }
- /**
- * Expands "~/foo" into "/home/zuck/foo". Returns regular paths unmodified.
- */
- public static Path expandHomeDir(Path path) {
- if (!path.startsWith("~")) {
- return path;
- }
- Path homePath = path.getFileSystem().getPath(System.getProperty("user.home"));
- if (path.equals(path.getFileSystem().getPath("~"))) {
- return homePath;
- }
- return homePath.resolve(path.subpath(1, path.getNameCount()));
- }
- public static ByteSource asByteSource(final Path path) {
- return new ByteSource() {
- @Override
- public InputStream openStream() throws IOException {
- return Files.newInputStream(path);
- }
- };
- }
- public static String getFileExtension(Path path) {
- String name = path.getFileName().toString();
- int index = name.lastIndexOf('.');
- return index == -1 ? "" : name.substring(index + 1);
- }
- public static String getNameWithoutExtension(Path file) {
- String name = file.getFileName().toString();
- int index = name.lastIndexOf('.');
- return index == -1 ? name : name.substring(0, index);
- }
- public static String stripPathPrefixAndExtension(Path fileName, String prefix) {
- String nameWithoutExtension = getNameWithoutExtension(fileName);
- if (!nameWithoutExtension.startsWith(prefix) ||
- nameWithoutExtension.length() < prefix.length()) {
- throw new HumanReadableException(
- "Invalid prefix on filename in path %s (file %s) - expecting %s",
- fileName,
- nameWithoutExtension,
- prefix);
- }
- return nameWithoutExtension.substring(
- prefix.length(),
- nameWithoutExtension.length());
- }
- public static Optional<Path> stripPrefix(Path p, Path prefix) {
- if (prefix.getNameCount() > p.getNameCount()) {
- return Optional.empty();
- }
- for (int i = 0; i < prefix.getNameCount(); ++i) {
- if (!prefix.getName(i).equals(p.getName(i))) {
- return Optional.empty();
- }
- }
- return Optional.of(p.subpath(prefix.getNameCount(), p.getNameCount()));
- }
- public static Function<String, Path> toPathFn(final FileSystem fileSystem) {
- return input -> fileSystem.getPath(input);
- }
- private static Path dropPathPart(Path p, int i) {
- if (i == 0) {
- return p.subpath(1, p.getNameCount());
- } else if (i == p.getNameCount() - 1) {
- return p.subpath(0, p.getNameCount() - 1);
- } else {
- return p.subpath(0, i).resolve(p.subpath(i + 1, p.getNameCount()));
- }
- }
- /**
- * Drop any "." parts (useless). Do keep ".." parts; don't normalize them away.
- *
- * Note that while Path objects provide a {@link Path#normalize()} method for eliminating
- * redundant parts of paths like in {@code foo/a/../b/c}, changing its internal parts
- * (and actually using the filesystem), we don't use those methods to clean up the incoming paths;
- * we only strip empty parts, and those consisting only of {@code .} because doing so maps
- * exactly-same paths together, and can't influence where it may point to, whereas {@code ..} and
- * symbolic links might.
- */
- public static Path fixPath(Path p) {
- int i = 0;
- while (i < p.getNameCount()) {
- if (p.getName(i).toString().equals(".")) {
- p = dropPathPart(p, i);
- } else {
- i++;
- }
- }
- return p;
- }
- /**
- * Drop the cache in Path object.
- *
- * Path's implementation class {@code UnixPath}, will lazily initialize a String representation
- * and store it in the object when {@code #toString()} is called for the first time. This doubles
- * the memory requirement for the Path object.
- *
- * This hack constructs a new path, dropping the cached toString value.
- *
- * Due to the nature of what this function does, it's very sensitive to the implementation. Any
- * calls to {@code #toString()} on the returned object would also recreate the cached string
- * value.
- */
- public static Path dropInternalCaches(Path p) {
- return p.getFileSystem().getPath(p.toString());
- }
- }