blob: aac68ee8b04ee8f00514d694fd089a750a7a7a80 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.vfs;
15
16import com.google.common.base.Preconditions;
17import com.google.common.base.Predicate;
18import com.google.common.collect.ImmutableList;
19import com.google.common.collect.Iterables;
20import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
21import com.google.devtools.build.lib.util.OS;
22import com.google.devtools.build.lib.util.StringCanonicalizer;
23
24import java.io.File;
25import java.io.FileNotFoundException;
26import java.io.IOException;
27import java.io.InputStream;
28import java.io.ObjectInputStream;
29import java.io.ObjectOutputStream;
30import java.io.OutputStream;
31import java.io.Serializable;
32import java.lang.ref.Reference;
33import java.lang.ref.ReferenceQueue;
34import java.lang.ref.WeakReference;
35import java.util.Arrays;
36import java.util.Collection;
37import java.util.IdentityHashMap;
38import java.util.Objects;
39
40/**
41 * <p>Instances of this class represent pathnames, forming a tree
42 * structure to implement sharing of common prefixes (parent directory names).
43 * A node in these trees is something like foo, bar, .., ., or /. If the
44 * instance is not a root path, it will have a parent path. A path can also
45 * have children, which are indexed by name in a map.
46 *
47 * <p>There is some limited support for Windows-style paths. Most importantly, drive identifiers
48 * in front of a path (c:/abc) are supported. However, Windows-style backslash separators
49 * (C:\\foo\\bar) and drive-relative paths ("C:foo") are explicitly not supported, same with
50 * advanced features like \\\\network\\paths and \\\\?\\unc\\paths.
51 *
52 * <p>{@link FileSystem} implementations maintain pointers into this graph.
53 */
54@ThreadSafe
55public class Path implements Comparable<Path>, Serializable {
56
57 private static FileSystem fileSystemForSerialization;
58
59 /**
60 * We need to specify used FileSystem. In this case we can save memory during the serialization.
61 */
62 public static void setFileSystemForSerialization(FileSystem fileSystem) {
63 fileSystemForSerialization = fileSystem;
64 }
65
66 /**
67 * Returns FileSystem that we are using.
68 */
69 public static FileSystem getFileSystemForSerialization() {
70 return fileSystemForSerialization;
71 }
72
73 // These are basically final, but can't be marked as such in order to support serialization.
74 private FileSystem fileSystem;
75 private String name;
76 private Path parent;
77 private int depth;
78 private int hashCode;
79
80 private static final ReferenceQueue<Path> REFERENCE_QUEUE = new ReferenceQueue<>();
81
82 private static class PathWeakReferenceForCleanup extends WeakReference<Path> {
83 final Path parent;
84 final String baseName;
85
86 PathWeakReferenceForCleanup(Path referent, ReferenceQueue<Path> referenceQueue) {
87 super(referent, referenceQueue);
88 parent = referent.getParentDirectory();
89 baseName = referent.getBaseName();
90 }
91 }
92
93 private static final Thread PATH_CHILD_CACHE_CLEANUP_THREAD = new Thread("Path cache cleanup") {
94 @Override
95 public void run() {
96 while (true) {
97 try {
98 PathWeakReferenceForCleanup ref = (PathWeakReferenceForCleanup) REFERENCE_QUEUE.remove();
99 Path parent = ref.parent;
100 synchronized (parent) {
101 // It's possible that since this reference was enqueued for deletion, the Path was
102 // recreated with a new entry in the map. We definitely shouldn't delete that entry, so
103 // check to make sure they're the same.
104 Reference<Path> currentRef = ref.parent.children.get(ref.baseName);
105 if (currentRef == ref) {
106 ref.parent.children.remove(ref.baseName);
107 }
108 }
109 } catch (InterruptedException e) {
110 // Ignored.
111 }
112 }
113 }
114 };
115
116 static {
117 PATH_CHILD_CACHE_CLEANUP_THREAD.setDaemon(true);
118 PATH_CHILD_CACHE_CLEANUP_THREAD.start();
119 }
120
121 /**
122 * A mapping from a child file name to the {@link Path} representing it.
123 *
124 * <p>File names must be a single path segment. The strings must be
125 * canonical. We use IdentityHashMap instead of HashMap for reasons of space
126 * efficiency: instances are smaller by a single word. Also, since all path
127 * segments are interned, the universe of Paths holds a minimal number of
128 * references to strings. (It's doubtful that there's any time gain from use
129 * of an IdentityHashMap, since the time saved by avoiding string equality
130 * tests during hash lookups is probably equal to the time spent eagerly
131 * interning strings, unless the collision rate is high.)
132 *
133 * <p>The Paths are stored as weak references to ensure that a live
134 * Path for a directory does not hold a strong reference to all of its
135 * descendants, which would prevent collection of paths we never intend to
136 * use again. Stale references in the map must be treated as absent.
137 *
138 * <p>A Path may be recycled once there is no Path that refers to it or
139 * to one of its descendants. This means that any data stored in the
140 * Path instance by one of its subclasses must be recoverable: it's ok to
141 * store data in Paths as an optimization, but there must be another
142 * source for that data in case the Path is recycled.
143 *
144 * <p>We intentionally avoid using the existing library classes for reasons of
145 * space efficiency: while ConcurrentHashMap would reduce our locking
146 * overhead, and ReferenceMap would simplify the code a little, both of those
147 * classes have much higher per-instance overheads than IdentityHashMap.
148 *
149 * <p>The Path object must be synchronized while children is being
150 * accessed.
151 */
Nathan Harmataa65fcf92015-08-20 21:14:44 +0000152 private volatile IdentityHashMap<String, Reference<Path>> children;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100153
154 /**
155 * Create a path instance. Should only be called by {@link #createChildPath}.
156 *
157 * @param name the name of this path; it must be canonicalized with {@link
158 * StringCanonicalizer#intern}
159 * @param parent this path's parent
160 */
161 protected Path(FileSystem fileSystem, String name, Path parent) {
162 this.fileSystem = fileSystem;
163 this.name = name;
164 this.parent = parent;
165 this.depth = parent == null ? 0 : parent.depth + 1;
166 this.hashCode = Objects.hash(parent, name);
167 }
168
169 /**
170 * Create the root path. Should only be called by
171 * {@link FileSystem#createRootPath()}.
172 */
173 protected Path(FileSystem fileSystem) {
174 this(fileSystem, StringCanonicalizer.intern("/"), null);
175 }
176
177 private void writeObject(ObjectOutputStream out) throws IOException {
178 Preconditions.checkState(fileSystem == fileSystemForSerialization, fileSystem);
179 out.writeUTF(getPathString());
180 }
181
182 private void readObject(ObjectInputStream in) throws IOException {
183 fileSystem = fileSystemForSerialization;
184 String p = in.readUTF();
185 PathFragment pf = new PathFragment(p);
186 PathFragment parentDir = pf.getParentDirectory();
187 if (parentDir == null) {
188 this.name = "/";
189 this.parent = null;
190 this.depth = 0;
191 } else {
192 this.name = pf.getBaseName();
193 this.parent = fileSystem.getPath(parentDir);
194 this.depth = this.parent.depth + 1;
195 }
196 this.hashCode = Objects.hash(parent, name);
197 }
198
199 /**
200 * Returns the filesystem instance to which this path belongs.
201 */
202 public FileSystem getFileSystem() {
203 return fileSystem;
204 }
205
206 public boolean isRootDirectory() {
207 return parent == null;
208 }
209
210 protected Path createChildPath(String childName) {
211 return new Path(fileSystem, childName, this);
212 }
213
214 /**
215 * Returns the child path named name, or creates such a path (and caches it)
216 * if it doesn't already exist.
217 */
218 private Path getCachedChildPath(String childName) {
Nathan Harmataa65fcf92015-08-20 21:14:44 +0000219 // We get a canonical instance since 'children' is an IdentityHashMap.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100220 childName = StringCanonicalizer.intern(childName);
Nathan Harmatafee390d2015-09-02 22:46:53 +0000221 // We use double-checked locking so that we only hold the lock when we might need to mutate the
222 // 'children' variable. 'children' will never become null if it's already non-null, so we only
223 // need to worry about the case where it's currently null and we race with another thread
224 // executing getCachedChildPath(<doesn't matter>) trying to set 'children' to a non-null value.
Nathan Harmataa65fcf92015-08-20 21:14:44 +0000225 if (children == null) {
226 synchronized (this) {
227 if (children == null) {
228 // 66% of Paths have size == 1, 80% <= 2
229 children = new IdentityHashMap<>(1);
230 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100231 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100232 }
Nathan Harmatafee390d2015-09-02 22:46:53 +0000233 synchronized (this) {
234 Reference<Path> childRef = children.get(childName);
235 Path child;
236 if (childRef == null || (child = childRef.get()) == null) {
237 child = createChildPath(childName);
238 children.put(childName, new PathWeakReferenceForCleanup(child, REFERENCE_QUEUE));
Nathan Harmataa65fcf92015-08-20 21:14:44 +0000239 }
Nathan Harmatafee390d2015-09-02 22:46:53 +0000240 return child;
Nathan Harmataa65fcf92015-08-20 21:14:44 +0000241 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100242 }
243
244 /**
245 * Applies the specified function to each {@link Path} that is an existing direct
246 * descendant of this one. The Predicate is evaluated only for its
247 * side-effects.
248 *
249 * <p>This function exists to hide the "children" field, whose complex
250 * synchronization and identity requirements are too unsafe to be exposed to
251 * subclasses. For example, the "children" field must be synchronized for
252 * the duration of any iteration over it; it may be null; and references
253 * within it may be stale, and must be ignored.
254 */
255 protected synchronized void applyToChildren(Predicate<Path> function) {
256 if (children != null) {
257 for (Reference<Path> childRef : children.values()) {
258 Path child = childRef.get();
259 if (child != null) {
260 function.apply(child);
261 }
262 }
263 }
264 }
265
266 /**
267 * Returns whether this path is recursively "under" {@code prefix} - that is,
268 * whether {@code path} is a prefix of this path.
269 *
270 * <p>This method returns {@code true} when called with this path itself. This
271 * method acts independently of the existence of files or folders.
272 *
273 * @param prefix a path which may or may not be a prefix of this path
274 */
275 public boolean startsWith(Path prefix) {
276 Path n = this;
277 for (int i = 0, len = depth - prefix.depth; i < len; i++) {
278 n = n.getParentDirectory();
279 }
280 return prefix.equals(n);
281 }
282
283 /**
284 * Computes a string representation of this path, and writes it to the
285 * given string builder. Only called locally with a new instance.
286 */
287 private void buildPathString(StringBuilder result) {
288 if (isRootDirectory()) {
289 result.append('/');
290 } else {
291 if (parent.isWindowsVolumeName()) {
292 result.append(parent.name);
293 } else {
294 parent.buildPathString(result);
295 }
296 if (!parent.isRootDirectory()) {
297 result.append('/');
298 }
299 result.append(name);
300 }
301 }
302
303 /**
304 * Returns true if the current path represents a Windows volume name (such as "c:" or "d:").
305 *
306 * <p>Paths such as '\\\\vol\\foo' are not supported.
307 */
308 private boolean isWindowsVolumeName() {
309 return OS.getCurrent() == OS.WINDOWS
310 && parent != null && parent.isRootDirectory() && name.length() == 2
311 && PathFragment.getWindowsDriveLetter(name) != '\0';
312 }
313
314 /**
315 * Returns the path as a string.
316 */
317 public String getPathString() {
318 // Profile driven optimization:
319 // Preallocate a size determined by the depth, so that
320 // we do not have to expand the capacity of the StringBuilder
321 StringBuilder builder = new StringBuilder(depth * 20);
322 buildPathString(builder);
323 return builder.toString();
324 }
325
326 /**
327 * Returns the path as a string.
328 */
329 @Override
330 public String toString() {
331 return getPathString();
332 }
333
334 @Override
335 public int hashCode() {
336 return hashCode;
337 }
338
339 @Override
340 public boolean equals(Object other) {
341 if (this == other) {
342 return true;
343 }
344 if (!(other instanceof Path)) {
345 return false;
346 }
347 Path otherPath = (Path) other;
348 return fileSystem.equals(otherPath.fileSystem) && name.equals(otherPath.name)
349 && Objects.equals(parent, otherPath.parent);
350 }
351
352 /**
353 * Returns a string of debugging information associated with this path.
354 * The results are unspecified and MUST NOT be interpreted programmatically.
355 */
356 protected String toDebugString() {
357 return "";
358 }
359
360 /**
361 * Returns a path representing the parent directory of this path,
362 * or null iff this Path represents the root of the filesystem.
363 *
364 * <p>Note: This method normalises ".." and "." path segments by string
365 * processing, not by directory lookups.
366 */
367 public Path getParentDirectory() {
368 return parent;
369 }
370
371 /**
372 * Returns true iff this path denotes an existing file of any kind. Follows
373 * symbolic links.
374 */
375 public boolean exists() {
376 return fileSystem.exists(this, true);
377 }
378
379 /**
380 * Returns true iff this path denotes an existing file of any kind.
381 *
382 * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a
383 * symbolic link, the link is dereferenced until a file other than a
384 * symbolic link is found
385 */
386 public boolean exists(Symlinks followSymlinks) {
387 return fileSystem.exists(this, followSymlinks.toBoolean());
388 }
389
390 /**
391 * Returns a new, immutable collection containing the names of all entities
392 * within the directory denoted by the current path. Follows symbolic links.
393 *
394 * @throws FileNotFoundException If the directory is not found
395 * @throws IOException If the path does not denote a directory
396 */
397 public Collection<Path> getDirectoryEntries() throws IOException, FileNotFoundException {
398 return fileSystem.getDirectoryEntries(this);
399 }
400
401 /**
402 * Returns a collection of the names and types of all entries within the directory
403 * denoted by the current path. Follows symbolic links if {@code followSymlinks} is true.
404 * Note that the order of the returned entries is not guaranteed.
405 *
406 * @param followSymlinks whether to follow symlinks or not
407 *
408 * @throws FileNotFoundException If the directory is not found
409 * @throws IOException If the path does not denote a directory
410 */
411 public Collection<Dirent> readdir(Symlinks followSymlinks) throws IOException {
412 return fileSystem.readdir(this, followSymlinks.toBoolean());
413 }
414
415 /**
416 * Returns a new, immutable collection containing the names of all entities
417 * within the directory denoted by the current path, for which the given
418 * predicate is true.
419 *
420 * @throws FileNotFoundException If the directory is not found
421 * @throws IOException If the path does not denote a directory
422 */
423 public Collection<Path> getDirectoryEntries(Predicate<? super Path> predicate)
424 throws IOException, FileNotFoundException {
425 return ImmutableList.<Path>copyOf(Iterables.filter(getDirectoryEntries(), predicate));
426 }
427
428 /**
429 * Returns the status of a file, following symbolic links.
430 *
431 * @throws IOException if there was an error obtaining the file status. Note,
432 * some implementations may defer the I/O, and hence the throwing of
433 * the exception, until the accessor methods of {@code FileStatus} are
434 * called.
435 */
436 public FileStatus stat() throws IOException {
437 return fileSystem.stat(this, true);
438 }
439
440 /**
441 * Like stat(), but returns null on file-nonexistence instead of throwing.
442 */
443 public FileStatus statNullable() {
444 return statNullable(Symlinks.FOLLOW);
445 }
446
447 /**
448 * Like stat(), but returns null on file-nonexistence instead of throwing.
449 */
450 public FileStatus statNullable(Symlinks symlinks) {
451 return fileSystem.statNullable(this, symlinks.toBoolean());
452 }
453
454 /**
455 * Returns the status of a file, optionally following symbolic links.
456 *
457 * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a
458 * symbolic link, the link is dereferenced until a file other than a
459 * symbolic link is found
460 * @throws IOException if there was an error obtaining the file status. Note,
461 * some implementations may defer the I/O, and hence the throwing of
462 * the exception, until the accessor methods of {@code FileStatus} are
463 * called
464 */
465 public FileStatus stat(Symlinks followSymlinks) throws IOException {
466 return fileSystem.stat(this, followSymlinks.toBoolean());
467 }
468
469 /**
470 * Like {@link #stat}, but may return null if the file is not found (corresponding to
471 * {@code ENOENT} and {@code ENOTDIR} in Unix's stat(2) function) instead of throwing. Follows
472 * symbolic links.
473 */
474 public FileStatus statIfFound() throws IOException {
475 return fileSystem.statIfFound(this, true);
476 }
477
478 /**
479 * Like {@link #stat}, but may return null if the file is not found (corresponding to
480 * {@code ENOENT} and {@code ENOTDIR} in Unix's stat(2) function) instead of throwing.
481 *
482 * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a
483 * symbolic link, the link is dereferenced until a file other than a
484 * symbolic link is found
485 */
486 public FileStatus statIfFound(Symlinks followSymlinks) throws IOException {
487 return fileSystem.statIfFound(this, followSymlinks.toBoolean());
488 }
489
490
491 /**
492 * Returns true iff this path denotes an existing directory. Follows symbolic
493 * links.
494 */
495 public boolean isDirectory() {
496 return fileSystem.isDirectory(this, true);
497 }
498
499 /**
500 * Returns true iff this path denotes an existing directory.
501 *
502 * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a
503 * symbolic link, the link is dereferenced until a file other than a
504 * symbolic link is found
505 */
506 public boolean isDirectory(Symlinks followSymlinks) {
507 return fileSystem.isDirectory(this, followSymlinks.toBoolean());
508 }
509
510 /**
511 * Returns true iff this path denotes an existing regular or special file.
512 * Follows symbolic links.
513 *
514 * <p>For our purposes, "file" includes special files (socket, fifo, block or
515 * char devices) too; it excludes symbolic links and directories.
516 */
517 public boolean isFile() {
518 return fileSystem.isFile(this, true);
519 }
520
521 /**
522 * Returns true iff this path denotes an existing regular or special file.
523 *
524 * <p>For our purposes, a "file" includes special files (socket, fifo, block
525 * or char devices) too; it excludes symbolic links and directories.
526 *
527 * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a
528 * symbolic link, the link is dereferenced until a file other than a
529 * symbolic link is found.
530 */
531 public boolean isFile(Symlinks followSymlinks) {
532 return fileSystem.isFile(this, followSymlinks.toBoolean());
533 }
534
535 /**
536 * Returns true iff this path denotes an existing symbolic link. Does not
537 * follow symbolic links.
538 */
539 public boolean isSymbolicLink() {
540 return fileSystem.isSymbolicLink(this);
541 }
542
543 /**
544 * Returns the last segment of this path, or "/" for the root directory.
545 */
546 public String getBaseName() {
547 return name;
548 }
549
550 /**
551 * Interprets the name of a path segment relative to the current path and
552 * returns the result.
553 *
554 * <p>This is a purely syntactic operation, i.e. it does no I/O, it does not
555 * validate the existence of any path, nor resolve symbolic links. If 'prefix'
556 * is not canonical, then a 'name' of '..' will be interpreted incorrectly.
557 *
558 * @precondition segment contains no slashes.
559 */
560 private Path getCanonicalPath(String segment) {
Ulf Adams07dba942015-03-05 14:47:37 +0000561 if (segment.equals(".") || segment.isEmpty()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100562 return this; // that's a noop
563 } else if (segment.equals("..")) {
564 // root's parent is root, when canonicalising:
565 return parent == null || isWindowsVolumeName() ? this : parent;
566 } else {
567 return getCachedChildPath(segment);
568 }
569 }
570
571 /**
572 * Returns the path formed by appending the single non-special segment
573 * "baseName" to this path.
574 *
575 * <p>You should almost always use {@link #getRelative} instead, which has
576 * the same performance characteristics if the given name is a valid base
577 * name, and which also works for '.', '..', and strings containing '/'.
578 *
579 * @throws IllegalArgumentException if {@code baseName} is not a valid base
580 * name according to {@link FileSystemUtils#checkBaseName}
581 */
582 public Path getChild(String baseName) {
583 FileSystemUtils.checkBaseName(baseName);
584 return getCachedChildPath(baseName);
585 }
586
587 /**
588 * Returns the path formed by appending the relative or absolute path fragment
589 * {@code suffix} to this path.
590 *
591 * <p>If suffix is absolute, the current path will be ignored; otherwise, they
592 * will be combined. Up-level references ("..") cause the preceding path
593 * segment to be elided; this interpretation is only correct if the base path
594 * is canonical.
595 */
596 public Path getRelative(PathFragment suffix) {
597 Path result = suffix.isAbsolute() ? fileSystem.getRootDirectory() : this;
598 if (!suffix.windowsVolume().isEmpty()) {
599 result = result.getCanonicalPath(suffix.windowsVolume());
600 }
601 for (String segment : suffix.segments()) {
602 result = result.getCanonicalPath(segment);
603 }
604 return result;
605 }
606
607 /**
608 * Returns the path formed by appending the relative or absolute string
609 * {@code path} to this path.
610 *
611 * <p>If the given path string is absolute, the current path will be ignored;
612 * otherwise, they will be combined. Up-level references ("..") cause the
613 * preceding path segment to be elided.
614 *
615 * <p>This is a purely syntactic operation, i.e. it does no I/O, it does not
616 * validate the existence of any path, nor resolve symbolic links.
617 */
618 public Path getRelative(String path) {
619 // Fast path for valid base names.
620 if ((path.length() == 0) || (path.equals("."))) {
621 return this;
622 } else if (path.equals("..")) {
623 return parent == null ? this : parent;
624 } else if ((path.indexOf('/') != -1)) {
625 return getRelative(new PathFragment(path));
626 } else {
627 return getCachedChildPath(path);
628 }
629 }
630
631 /**
632 * Returns an absolute PathFragment representing this path.
633 */
634 public PathFragment asFragment() {
635 String[] resultSegments = new String[depth];
636 Path currentPath = this;
637 for (int pos = depth - 1; pos >= 0; pos--) {
638 resultSegments[pos] = currentPath.getBaseName();
639 currentPath = currentPath.getParentDirectory();
640 }
641
642 char driveLetter = '\0';
643 if (resultSegments.length > 0) {
644 driveLetter = PathFragment.getWindowsDriveLetter(resultSegments[0]);
645 if (driveLetter != '\0') {
646 // Strip off the first segment that contains the volume name.
647 resultSegments = Arrays.copyOfRange(resultSegments, 1, resultSegments.length);
648 }
649 }
650
651 return new PathFragment(driveLetter, true, resultSegments);
652 }
653
654
655 /**
656 * Returns a relative path fragment to this path, relative to {@code
657 * ancestorDirectory}. {@code ancestorDirectory} must be on the same
658 * filesystem as this path. (Currently, both this path and "ancestorDirectory"
659 * must be absolute, though this restriction could be loosened.)
660 * <p>
661 * <code>x.relativeTo(z) == y</code> implies
662 * <code>z.getRelative(y.getPathString()) == x</code>.
663 * <p>
664 * For example, <code>"/foo/bar/wiz".relativeTo("/foo")</code> returns
665 * <code>"bar/wiz"</code>.
666 *
667 * @throws IllegalArgumentException if this path is not beneath {@code
668 * ancestorDirectory} or if they are not part of the same filesystem
669 */
670 public PathFragment relativeTo(Path ancestorPath) {
671 checkSameFilesystem(ancestorPath);
672
673 // Fast path: when otherPath is the ancestor of this path
674 int resultSegmentCount = depth - ancestorPath.depth;
675 if (resultSegmentCount >= 0) {
676 String[] resultSegments = new String[resultSegmentCount];
677 Path currentPath = this;
678 for (int pos = resultSegmentCount - 1; pos >= 0; pos--) {
679 resultSegments[pos] = currentPath.getBaseName();
680 currentPath = currentPath.getParentDirectory();
681 }
682 if (ancestorPath.equals(currentPath)) {
683 return new PathFragment('\0', false, resultSegments);
684 }
685 }
686
687 throw new IllegalArgumentException("Path " + this + " is not beneath " + ancestorPath);
688 }
689
690 /**
691 * Checks that "this" and "that" are paths on the same filesystem.
692 */
693 protected void checkSameFilesystem(Path that) {
694 if (this.fileSystem != that.fileSystem) {
695 throw new IllegalArgumentException("Files are on different filesystems: "
696 + this + ", " + that);
697 }
698 }
699
700 /**
701 * Returns an output stream to the file denoted by the current path, creating
702 * it and truncating it if necessary. The stream is opened for writing.
703 *
704 * @throws FileNotFoundException If the file cannot be found or created.
705 * @throws IOException If a different error occurs.
706 */
707 public OutputStream getOutputStream() throws IOException, FileNotFoundException {
708 return getOutputStream(false);
709 }
710
711 /**
712 * Returns an output stream to the file denoted by the current path, creating
713 * it and truncating it if necessary. The stream is opened for writing.
714 *
715 * @param append whether to open the file in append mode.
716 * @throws FileNotFoundException If the file cannot be found or created.
717 * @throws IOException If a different error occurs.
718 */
719 public OutputStream getOutputStream(boolean append) throws IOException, FileNotFoundException {
720 return fileSystem.getOutputStream(this, append);
721 }
722
723 /**
724 * Creates a directory with the name of the current path, not following
725 * symbolic links. Returns normally iff the directory exists after the call:
726 * true if the directory was created by this call, false if the directory was
727 * already in existence. Throws an exception if the directory could not be
728 * created for any reason.
729 *
730 * @throws IOException if the directory creation failed for any reason
731 */
732 public boolean createDirectory() throws IOException {
733 return fileSystem.createDirectory(this);
734 }
735
736 /**
737 * Creates a symbolic link with the name of the current path, following
738 * symbolic links. The referent of the created symlink is is the absolute path
739 * "target"; it is not possible to create relative symbolic links via this
740 * method.
741 *
742 * @throws IOException if the creation of the symbolic link was unsuccessful
743 * for any reason
744 */
745 public void createSymbolicLink(Path target) throws IOException {
746 checkSameFilesystem(target);
747 fileSystem.createSymbolicLink(this, target.asFragment());
748 }
749
750 /**
751 * Creates a symbolic link with the name of the current path, following
752 * symbolic links. The referent of the created symlink is is the path fragment
753 * "target", which may be absolute or relative.
754 *
755 * @throws IOException if the creation of the symbolic link was unsuccessful
756 * for any reason
757 */
758 public void createSymbolicLink(PathFragment target) throws IOException {
759 fileSystem.createSymbolicLink(this, target);
760 }
Nathan Harmata215974e52015-09-16 21:31:49 +0000761
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100762 /**
763 * Returns the target of the current path, which must be a symbolic link. The
764 * link contents are returned exactly, and may contain an absolute or relative
765 * path. Analogous to readlink(2).
766 *
767 * @return the content (i.e. target) of the symbolic link
768 * @throws IOException if the current path is not a symbolic link, or the
769 * contents of the link could not be read for any reason
770 */
771 public PathFragment readSymbolicLink() throws IOException {
772 return fileSystem.readSymbolicLink(this);
773 }
774
775 /**
Nathan Harmata215974e52015-09-16 21:31:49 +0000776 * If the current path is a symbolic link, returns the target of this symbolic link. The
777 * semantics are intentionally left underspecified otherwise to permit efficient implementations.
778 *
779 * @return the content (i.e. target) of the symbolic link
780 * @throws IOException if the current path is not a symbolic link, or the
781 * contents of the link could not be read for any reason
782 */
783 public PathFragment readSymbolicLinkUnchecked() throws IOException {
784 return fileSystem.readSymbolicLinkUnchecked(this);
785 }
786
787 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100788 * Returns the canonical path for this path, by repeatedly replacing symbolic
789 * links with their referents. Analogous to realpath(3).
790 *
791 * @return the canonical path for this path
792 * @throws IOException if any symbolic link could not be resolved, or other
793 * error occurred (for example, the path does not exist)
794 */
795 public Path resolveSymbolicLinks() throws IOException {
796 return fileSystem.resolveSymbolicLinks(this);
797 }
798
799 /**
800 * Renames the file denoted by the current path to the location "target", not
801 * following symbolic links.
802 *
803 * <p>Files cannot be atomically renamed across devices; copying is required.
804 * Use {@link FileSystemUtils#copyFile} followed by {@link Path#delete}.
805 *
806 * @throws IOException if the rename failed for any reason
807 */
808 public void renameTo(Path target) throws IOException {
809 checkSameFilesystem(target);
810 fileSystem.renameTo(this, target);
811 }
812
813 /**
814 * Returns the size in bytes of the file denoted by the current path,
815 * following symbolic links.
816 *
817 * <p>The size of directory or special file is undefined.
818 *
819 * @throws FileNotFoundException if the file denoted by the current path does
820 * not exist
821 * @throws IOException if the file's metadata could not be read, or some other
822 * error occurred
823 */
824 public long getFileSize() throws IOException, FileNotFoundException {
825 return fileSystem.getFileSize(this, true);
826 }
827
828 /**
829 * Returns the size in bytes of the file denoted by the current path.
830 *
831 * <p>The size of directory or special file is undefined. The size of a symbolic
832 * link is the length of the name of its referent.
833 *
834 * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a
835 * symbolic link, the link is deferenced until a file other than a
836 * symbol link is found
837 * @throws FileNotFoundException if the file denoted by the current path does
838 * not exist
839 * @throws IOException if the file's metadata could not be read, or some other
840 * error occurred
841 */
842 public long getFileSize(Symlinks followSymlinks) throws IOException, FileNotFoundException {
843 return fileSystem.getFileSize(this, followSymlinks.toBoolean());
844 }
845
846 /**
847 * Deletes the file denoted by this path, not following symbolic links.
848 * Returns normally iff the file doesn't exist after the call: true if this
849 * call deleted the file, false if the file already didn't exist. Throws an
850 * exception if the file could not be deleted for any reason.
851 *
852 * @return true iff the file was actually deleted by this call
853 * @throws IOException if the deletion failed but the file was present prior
854 * to the call
855 */
856 public boolean delete() throws IOException {
857 return fileSystem.delete(this);
858 }
859
860 /**
861 * Returns the last modification time of the file, in milliseconds since the
862 * UNIX epoch, of the file denoted by the current path, following symbolic
863 * links.
864 *
865 * <p>Caveat: many filesystems store file times in seconds, so do not rely on
866 * the millisecond precision.
867 *
868 * @throws IOException if the operation failed for any reason
869 */
870 public long getLastModifiedTime() throws IOException {
871 return fileSystem.getLastModifiedTime(this, true);
872 }
873
874 /**
875 * Returns the last modification time of the file, in milliseconds since the
876 * UNIX epoch, of the file denoted by the current path.
877 *
878 * <p>Caveat: many filesystems store file times in seconds, so do not rely on
879 * the millisecond precision.
880 *
881 * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a
882 * symbolic link, the link is dereferenced until a file other than a
883 * symbolic link is found
884 * @throws IOException if the modification time for the file could not be
885 * obtained for any reason
886 */
887 public long getLastModifiedTime(Symlinks followSymlinks) throws IOException {
888 return fileSystem.getLastModifiedTime(this, followSymlinks.toBoolean());
889 }
890
891 /**
892 * Sets the modification time of the file denoted by the current path. Follows
893 * symbolic links. If newTime is -1, the current time according to the kernel
894 * is used; this may differ from the JVM's clock.
895 *
896 * <p>Caveat: many filesystems store file times in seconds, so do not rely on
897 * the millisecond precision.
898 *
899 * @param newTime time, in milliseconds since the UNIX epoch, or -1L, meaning
900 * use the kernel's current time
901 * @throws IOException if the modification time for the file could not be set
902 * for any reason
903 */
904 public void setLastModifiedTime(long newTime) throws IOException {
905 fileSystem.setLastModifiedTime(this, newTime);
906 }
907
908 /**
909 * Returns value of the given extended attribute name or null if attribute does not exist or
910 * file system does not support extended attributes. Follows symlinks.
911 */
912 public byte[] getxattr(String name) throws IOException {
Nathan Harmatae1f187a2015-09-16 20:03:08 +0000913 return fileSystem.getxattr(this, name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100914 }
915
916 /**
917 * Returns the type of digest that may be returned by {@link #getFastDigest}, or {@code null}
918 * if the filesystem doesn't support them.
919 */
920 public String getFastDigestFunctionType() {
921 return fileSystem.getFastDigestFunctionType(this);
922 }
923
924 /**
925 * Gets a fast digest for the given path, or {@code null} if there isn't one available. The
926 * digest should be suitable for detecting changes to the file.
927 */
928 public byte[] getFastDigest() throws IOException {
929 return fileSystem.getFastDigest(this);
930 }
931
932 /**
933 * Returns the MD5 digest of the file denoted by the current path, following
934 * symbolic links.
935 *
936 * <p>This method runs in O(n) time where n is the length of the file, but
937 * certain implementations may be much faster than the worst case.
938 *
939 * @return a new 16-byte array containing the file's MD5 digest
940 * @throws IOException if the MD5 digest could not be computed for any reason
941 */
942 public byte[] getMD5Digest() throws IOException {
943 return fileSystem.getMD5Digest(this);
944 }
945
946 /**
947 * Opens the file denoted by this path, following symbolic links, for reading,
948 * and returns an input stream to it.
949 *
950 * @throws IOException if the file was not found or could not be opened for
951 * reading
952 */
953 public InputStream getInputStream() throws IOException {
954 return fileSystem.getInputStream(this);
955 }
956
957 /**
958 * Returns a java.io.File representation of this path.
959 *
960 * <p>Caveat: the result may be useless if this path's getFileSystem() is not
961 * the UNIX filesystem.
962 */
963 public File getPathFile() {
964 return new File(getPathString());
965 }
966
967 /**
968 * Returns true if the file denoted by the current path, following symbolic
969 * links, is writable for the current user.
970 *
971 * @throws FileNotFoundException if the file does not exist, a dangling
972 * symbolic link was encountered, or the file's metadata could not be
973 * read
974 */
975 public boolean isWritable() throws IOException, FileNotFoundException {
976 return fileSystem.isWritable(this);
977 }
978
979 /**
980 * Sets the read permissions of the file denoted by the current path,
981 * following symbolic links. Permissions apply to the current user.
982 *
983 * @param readable if true, the file is set to readable; otherwise the file is
984 * made non-readable
985 * @throws FileNotFoundException if the file does not exist
986 * @throws IOException If the action cannot be taken (ie. permissions)
987 */
988 public void setReadable(boolean readable) throws IOException, FileNotFoundException {
989 fileSystem.setReadable(this, readable);
990 }
991
992 /**
993 * Sets the write permissions of the file denoted by the current path,
994 * following symbolic links. Permissions apply to the current user.
995 *
996 * <p>TODO(bazel-team): (2009) what about owner/group/others?
997 *
998 * @param writable if true, the file is set to writable; otherwise the file is
999 * made non-writable
1000 * @throws FileNotFoundException if the file does not exist
1001 * @throws IOException If the action cannot be taken (ie. permissions)
1002 */
1003 public void setWritable(boolean writable) throws IOException, FileNotFoundException {
1004 fileSystem.setWritable(this, writable);
1005 }
1006
1007 /**
1008 * Returns true iff the file specified by the current path, following symbolic
1009 * links, is executable by the current user.
1010 *
1011 * @throws FileNotFoundException if the file does not exist or a dangling
1012 * symbolic link was encountered
1013 * @throws IOException if some other I/O error occurred
1014 */
1015 public boolean isExecutable() throws IOException, FileNotFoundException {
1016 return fileSystem.isExecutable(this);
1017 }
1018
1019 /**
1020 * Returns true iff the file specified by the current path, following symbolic
1021 * links, is readable by the current user.
1022 *
1023 * @throws FileNotFoundException if the file does not exist or a dangling
1024 * symbolic link was encountered
1025 * @throws IOException if some other I/O error occurred
1026 */
1027 public boolean isReadable() throws IOException, FileNotFoundException {
1028 return fileSystem.isReadable(this);
1029 }
1030
1031 /**
1032 * Sets the execute permission on the file specified by the current path,
1033 * following symbolic links. Permissions apply to the current user.
1034 *
1035 * @throws FileNotFoundException if the file does not exist or a dangling
1036 * symbolic link was encountered
1037 * @throws IOException if the metadata change failed, for example because of
1038 * permissions
1039 */
1040 public void setExecutable(boolean executable) throws IOException, FileNotFoundException {
1041 fileSystem.setExecutable(this, executable);
1042 }
1043
1044 /**
1045 * Sets the permissions on the file specified by the current path, following
1046 * symbolic links. If permission changes on this path's {@link FileSystem} are
1047 * slow (e.g. one syscall per change), this method should aim to be faster
1048 * than setting each permission individually. If this path's
1049 * {@link FileSystem} does not support group and others permissions, those
1050 * bits will be ignored.
1051 *
1052 * @throws FileNotFoundException if the file does not exist or a dangling
1053 * symbolic link was encountered
1054 * @throws IOException if the metadata change failed, for example because of
1055 * permissions
1056 */
1057 public void chmod(int mode) throws IOException {
1058 fileSystem.chmod(this, mode);
1059 }
1060
1061 /**
1062 * Compare Paths of the same file system using their PathFragments.
1063 *
1064 * <p>Paths from different filesystems will be compared using the identity
1065 * hash code of their respective filesystems.
1066 */
1067 @Override
1068 public int compareTo(Path o) {
1069 // Fast-path.
1070 if (equals(o)) {
1071 return 0;
1072 }
1073
1074 // If they are on different file systems, the file system decides the ordering.
1075 FileSystem otherFs = o.getFileSystem();
1076 if (!fileSystem.equals(otherFs)) {
1077 int thisFileSystemHash = System.identityHashCode(fileSystem);
1078 int otherFileSystemHash = System.identityHashCode(otherFs);
1079 if (thisFileSystemHash < otherFileSystemHash) {
1080 return -1;
1081 } else if (thisFileSystemHash > otherFileSystemHash) {
1082 return 1;
1083 } else {
1084 // TODO(bazel-team): Add a name to every file system to be used here.
1085 return 0;
1086 }
1087 }
1088
1089 // Equal file system, but different paths, because of the canonicalization.
1090 // We expect to often compare Paths that are very similar, for example for files in the same
1091 // directory. This can be done efficiently by going up segment by segment until we get the
1092 // identical path (canonicalization again), and then just compare the immediate child segments.
1093 // Overall this is much faster than creating PathFragment instances, and comparing those, which
1094 // requires us to always go up to the top-level directory and copy all segments into a new
1095 // string array.
1096 // This was previously showing up as a hotspot in a profile of globbing a large directory.
1097 Path a = this, b = o;
1098 int maxDepth = Math.min(a.depth, b.depth);
1099 while (a.depth > maxDepth) {
1100 a = a.getParentDirectory();
1101 }
1102 while (b.depth > maxDepth) {
1103 b = b.getParentDirectory();
1104 }
1105 // One is the child of the other.
1106 if (a.equals(b)) {
1107 // If a is the same as this, this.depth must be less than o.depth.
1108 return equals(a) ? -1 : 1;
1109 }
1110 Path previousa, previousb;
1111 do {
1112 previousa = a;
1113 previousb = b;
1114 a = a.getParentDirectory();
1115 b = b.getParentDirectory();
Nathan Harmata3c74af02015-10-09 13:16:08 +00001116 } while (!a.equals(b)); // This has to happen eventually.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001117 return previousa.name.compareTo(previousb.name);
1118 }
1119}