PageRenderTime 63ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/src/com/facebook/buck/io/MorePaths.java

https://gitlab.com/smartether/buck
Java | 304 lines | 188 code | 34 blank | 82 comment | 31 complexity | ed924ca904909598327c9692742eca36 MD5 | raw file
  1. /*
  2. * Copyright 2013-present Facebook, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. * not use this file except in compliance with the License. You may obtain
  6. * a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations
  14. * under the License.
  15. */
  16. package com.facebook.buck.io;
  17. import com.facebook.buck.util.HumanReadableException;
  18. import com.facebook.buck.util.environment.Platform;
  19. import com.google.common.base.Function;
  20. import com.google.common.base.Preconditions;
  21. import com.google.common.collect.FluentIterable;
  22. import com.google.common.collect.ImmutableSet;
  23. import com.google.common.io.ByteSource;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.nio.file.FileSystem;
  27. import java.nio.file.Files;
  28. import java.nio.file.Path;
  29. import java.nio.file.Paths;
  30. import java.util.Optional;
  31. import javax.annotation.Nullable;
  32. /**
  33. * Common functions that are done with a {@link Path}. If a function is going to take a
  34. * {@link ProjectFilesystem}, then it should be in {@link MoreProjectFilesystems} instead.
  35. */
  36. public class MorePaths {
  37. /** Utility class: do not instantiate. */
  38. private MorePaths() {}
  39. public static final Path EMPTY_PATH = Paths.get("");
  40. public static String pathWithUnixSeparators(String path) {
  41. return pathWithUnixSeparators(Paths.get(path));
  42. }
  43. public static String pathWithUnixSeparators(Path path) {
  44. return path.toString().replace('\\', '/');
  45. }
  46. public static String pathWithWindowsSeparators(Path path) {
  47. return path.toString().replace('/', '\\');
  48. }
  49. public static String pathWithPlatformSeparators(String path) {
  50. return pathWithPlatformSeparators(Paths.get(path));
  51. }
  52. public static String pathWithPlatformSeparators(Path path) {
  53. if (Platform.detect() == Platform.WINDOWS) {
  54. return pathWithWindowsSeparators(path);
  55. } else {
  56. return pathWithUnixSeparators(path);
  57. }
  58. }
  59. public static String pathWithUnixSeparatorsAndTrailingSlash(Path path) {
  60. return pathWithUnixSeparators(path) + "/";
  61. }
  62. public static Path getParentOrEmpty(Path path) {
  63. Path parent = path.getParent();
  64. if (parent == null) {
  65. parent = EMPTY_PATH;
  66. }
  67. return parent;
  68. }
  69. /**
  70. * Get the path of a file relative to a base directory.
  71. *
  72. * @param path must reference a file, not a directory.
  73. * @param baseDir must reference a directory that is relative to a common directory with the path.
  74. * may be null if referencing the same directory as the path.
  75. * @return the relative path of path from the directory baseDir.
  76. */
  77. public static Path getRelativePath(Path path, @Nullable Path baseDir) {
  78. if (baseDir == null) {
  79. // This allows callers to use this method with "file.parent()" for files from the project
  80. // root dir.
  81. baseDir = EMPTY_PATH;
  82. }
  83. Preconditions.checkArgument(!path.isAbsolute(),
  84. "Path must be relative: %s.", path);
  85. Preconditions.checkArgument(!baseDir.isAbsolute(),
  86. "Path must be relative: %s.", baseDir);
  87. return relativize(baseDir, path);
  88. }
  89. /**
  90. * Get a relative path from path1 to path2, first normalizing each path.
  91. *
  92. * This method is a workaround for JDK-6925169 (Path.relativize
  93. * returns incorrect result if path contains "." or "..").
  94. */
  95. public static Path relativize(Path path1, Path path2) {
  96. Preconditions.checkArgument(
  97. path1.isAbsolute() == path2.isAbsolute(),
  98. "Both paths must be absolute or both paths must be relative. (%s is %s, %s is %s)",
  99. path1,
  100. path1.isAbsolute() ? "absolute" : "relative",
  101. path2,
  102. path2.isAbsolute() ? "absolute" : "relative");
  103. path1 = normalize(path1);
  104. path2 = normalize(path2);
  105. // On Windows, if path1 is "" then Path.relativize returns ../path2 instead of path2 or ./path2
  106. if (EMPTY_PATH.equals(path1)) {
  107. return path2;
  108. }
  109. return path1.relativize(path2);
  110. }
  111. /**
  112. * Get a path without unnecessary path parts.
  113. *
  114. * This method is a workaround for JDK-8037945 (Paths.get("").normalize() throws
  115. * ArrayIndexOutOfBoundsException).
  116. */
  117. public static Path normalize(Path path) {
  118. if (!EMPTY_PATH.equals(path)) {
  119. path = path.normalize();
  120. }
  121. return path;
  122. }
  123. /**
  124. * Creates a symlink at {@code pathToProjectRoot.resolve(pathToDesiredLinkUnderProjectRoot)} that
  125. * points to {@code pathToProjectRoot.resolve(pathToExistingFileUnderProjectRoot)} using a
  126. * relative symlink. Both params must be relative to the project root.
  127. *
  128. * @param pathToDesiredLinkUnderProjectRoot must reference a file, not a directory.
  129. * @param pathToExistingFileUnderProjectRoot must reference a file, not a directory.
  130. * @return the relative path from the new symlink that was created to the existing file.
  131. */
  132. public static Path createRelativeSymlink(
  133. Path pathToDesiredLinkUnderProjectRoot,
  134. Path pathToExistingFileUnderProjectRoot,
  135. Path pathToProjectRoot) throws IOException {
  136. Path target = getRelativePath(
  137. pathToExistingFileUnderProjectRoot,
  138. pathToDesiredLinkUnderProjectRoot.getParent());
  139. Files.createSymbolicLink(pathToProjectRoot.resolve(pathToDesiredLinkUnderProjectRoot), target);
  140. return target;
  141. }
  142. /**
  143. * Filters out {@link Path} objects from {@code paths} that aren't a subpath of {@code root} and
  144. * returns a set of paths relative to {@code root}.
  145. */
  146. public static ImmutableSet<Path> filterForSubpaths(Iterable<Path> paths, final Path root) {
  147. final Path normalizedRoot = root.toAbsolutePath().normalize();
  148. return FluentIterable.from(paths)
  149. .filter(input -> {
  150. if (input.isAbsolute()) {
  151. return input.normalize().startsWith(normalizedRoot);
  152. } else {
  153. return true;
  154. }
  155. })
  156. .transform(input -> {
  157. if (input.isAbsolute()) {
  158. return relativize(normalizedRoot, input);
  159. } else {
  160. return input;
  161. }
  162. })
  163. .toSet();
  164. }
  165. /**
  166. * Expands "~/foo" into "/home/zuck/foo". Returns regular paths unmodified.
  167. */
  168. public static Path expandHomeDir(Path path) {
  169. if (!path.startsWith("~")) {
  170. return path;
  171. }
  172. Path homePath = path.getFileSystem().getPath(System.getProperty("user.home"));
  173. if (path.equals(path.getFileSystem().getPath("~"))) {
  174. return homePath;
  175. }
  176. return homePath.resolve(path.subpath(1, path.getNameCount()));
  177. }
  178. public static ByteSource asByteSource(final Path path) {
  179. return new ByteSource() {
  180. @Override
  181. public InputStream openStream() throws IOException {
  182. return Files.newInputStream(path);
  183. }
  184. };
  185. }
  186. public static String getFileExtension(Path path) {
  187. String name = path.getFileName().toString();
  188. int index = name.lastIndexOf('.');
  189. return index == -1 ? "" : name.substring(index + 1);
  190. }
  191. public static String getNameWithoutExtension(Path file) {
  192. String name = file.getFileName().toString();
  193. int index = name.lastIndexOf('.');
  194. return index == -1 ? name : name.substring(0, index);
  195. }
  196. public static String stripPathPrefixAndExtension(Path fileName, String prefix) {
  197. String nameWithoutExtension = getNameWithoutExtension(fileName);
  198. if (!nameWithoutExtension.startsWith(prefix) ||
  199. nameWithoutExtension.length() < prefix.length()) {
  200. throw new HumanReadableException(
  201. "Invalid prefix on filename in path %s (file %s) - expecting %s",
  202. fileName,
  203. nameWithoutExtension,
  204. prefix);
  205. }
  206. return nameWithoutExtension.substring(
  207. prefix.length(),
  208. nameWithoutExtension.length());
  209. }
  210. public static Optional<Path> stripPrefix(Path p, Path prefix) {
  211. if (prefix.getNameCount() > p.getNameCount()) {
  212. return Optional.empty();
  213. }
  214. for (int i = 0; i < prefix.getNameCount(); ++i) {
  215. if (!prefix.getName(i).equals(p.getName(i))) {
  216. return Optional.empty();
  217. }
  218. }
  219. return Optional.of(p.subpath(prefix.getNameCount(), p.getNameCount()));
  220. }
  221. public static Function<String, Path> toPathFn(final FileSystem fileSystem) {
  222. return input -> fileSystem.getPath(input);
  223. }
  224. private static Path dropPathPart(Path p, int i) {
  225. if (i == 0) {
  226. return p.subpath(1, p.getNameCount());
  227. } else if (i == p.getNameCount() - 1) {
  228. return p.subpath(0, p.getNameCount() - 1);
  229. } else {
  230. return p.subpath(0, i).resolve(p.subpath(i + 1, p.getNameCount()));
  231. }
  232. }
  233. /**
  234. * Drop any "." parts (useless). Do keep ".." parts; don't normalize them away.
  235. *
  236. * Note that while Path objects provide a {@link Path#normalize()} method for eliminating
  237. * redundant parts of paths like in {@code foo/a/../b/c}, changing its internal parts
  238. * (and actually using the filesystem), we don't use those methods to clean up the incoming paths;
  239. * we only strip empty parts, and those consisting only of {@code .} because doing so maps
  240. * exactly-same paths together, and can't influence where it may point to, whereas {@code ..} and
  241. * symbolic links might.
  242. */
  243. public static Path fixPath(Path p) {
  244. int i = 0;
  245. while (i < p.getNameCount()) {
  246. if (p.getName(i).toString().equals(".")) {
  247. p = dropPathPart(p, i);
  248. } else {
  249. i++;
  250. }
  251. }
  252. return p;
  253. }
  254. /**
  255. * Drop the cache in Path object.
  256. *
  257. * Path's implementation class {@code UnixPath}, will lazily initialize a String representation
  258. * and store it in the object when {@code #toString()} is called for the first time. This doubles
  259. * the memory requirement for the Path object.
  260. *
  261. * This hack constructs a new path, dropping the cached toString value.
  262. *
  263. * Due to the nature of what this function does, it's very sensitive to the implementation. Any
  264. * calls to {@code #toString()} on the returned object would also recreate the cached string
  265. * value.
  266. */
  267. public static Path dropInternalCaches(Path p) {
  268. return p.getFileSystem().getPath(p.toString());
  269. }
  270. }