blob: 6fe6118b9ae789844ba5762c7e8027a1d5704fdc [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// 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.devtools.build.lib.vfs;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrintable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.util.StringCanonicalizer;
import com.google.devtools.build.lib.vfs.FileSystem.HashFunction;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Objects;
/**
* Instances of this class represent pathnames, forming a tree structure to implement sharing of
* common prefixes (parent directory names). A node in these trees is something like foo, bar, ..,
* ., or /. If the instance is not a root path, it will have a parent path. A path can also have
* children, which are indexed by name in a map.
*
* <p>There is some limited support for Windows-style paths. Most importantly, drive identifiers in
* front of a path (c:/abc) are supported. However, Windows-style backslash separators
* (C:\\foo\\bar) and drive-relative paths ("C:foo") are explicitly not supported, same with
* advanced features like \\\\network\\paths and \\\\?\\unc\\paths.
*
* <p>{@link FileSystem} implementations maintain pointers into this graph.
*/
@ThreadSafe
public class Path implements Comparable<Path>, Serializable, SkylarkPrintable {
/** Filesystem-specific factory for {@link Path} objects. */
public static interface PathFactory {
/**
* Creates the root of all paths used by a filesystem.
*
* <p>All other paths are instantiated via {@link Path#createChildPath(String)} which calls
* {@link #createChildPath(Path, String)}.
*
* <p>Beware: this is called during the FileSystem constructor which may occur before subclasses
* are completely initialized.
*/
Path createRootPath(FileSystem filesystem);
/**
* Create a child path of the given parent.
*
* <p>All {@link Path} objects are instantiated via this method, with the sole exception of the
* filesystem root, which is created by {@link #createRootPath(FileSystem)}.
*/
Path createChildPath(Path parent, String childName);
/**
* Makes the proper invocation of {@link FileSystem#getCachedChildPathInternal}, doing
* filesystem-specific logic if necessary.
*
* <p>On Unix filesystems this method merely calls through to {@code
* FileSystem.getCachedChildPathInternal(parent, child)}, but on Windows this can be used to
* handle the translatation of absolute Unix paths to absolute Windows paths, e.g. "/c" to "C:/"
* or "/usr" to "C:/tools/msys64/usr".
*/
Path getCachedChildPathInternal(Path path, String childName);
}
private static FileSystem fileSystemForSerialization;
/**
* We need to specify used FileSystem. In this case we can save memory during the serialization.
*/
public static void setFileSystemForSerialization(FileSystem fileSystem) {
fileSystemForSerialization = fileSystem;
}
/**
* Returns FileSystem that we are using.
*/
public static FileSystem getFileSystemForSerialization() {
return fileSystemForSerialization;
}
// These are basically final, but can't be marked as such in order to support serialization.
private FileSystem fileSystem;
private String name;
private Path parent;
private int depth;
private int hashCode;
private static final ReferenceQueue<Path> REFERENCE_QUEUE = new ReferenceQueue<>();
private static class PathWeakReferenceForCleanup extends WeakReference<Path> {
final Path parent;
final String baseName;
PathWeakReferenceForCleanup(Path referent, ReferenceQueue<Path> referenceQueue) {
super(referent, referenceQueue);
parent = referent.getParentDirectory();
baseName = referent.getBaseName();
}
}
private static final Thread PATH_CHILD_CACHE_CLEANUP_THREAD = new Thread("Path cache cleanup") {
@Override
public void run() {
while (true) {
try {
PathWeakReferenceForCleanup ref = (PathWeakReferenceForCleanup) REFERENCE_QUEUE.remove();
Path parent = ref.parent;
synchronized (parent) {
// It's possible that since this reference was enqueued for deletion, the Path was
// recreated with a new entry in the map. We definitely shouldn't delete that entry, so
// check to make sure they're the same.
Reference<Path> currentRef = ref.parent.children.get(ref.baseName);
if (currentRef == ref) {
ref.parent.children.remove(ref.baseName);
}
}
} catch (InterruptedException e) {
// Ignored.
}
}
}
};
static {
PATH_CHILD_CACHE_CLEANUP_THREAD.setDaemon(true);
PATH_CHILD_CACHE_CLEANUP_THREAD.start();
}
/**
* A mapping from a child file name to the {@link Path} representing it.
*
* <p>File names must be a single path segment. The strings must be
* canonical. We use IdentityHashMap instead of HashMap for reasons of space
* efficiency: instances are smaller by a single word. Also, since all path
* segments are interned, the universe of Paths holds a minimal number of
* references to strings. (It's doubtful that there's any time gain from use
* of an IdentityHashMap, since the time saved by avoiding string equality
* tests during hash lookups is probably equal to the time spent eagerly
* interning strings, unless the collision rate is high.)
*
* <p>The Paths are stored as weak references to ensure that a live
* Path for a directory does not hold a strong reference to all of its
* descendants, which would prevent collection of paths we never intend to
* use again. Stale references in the map must be treated as absent.
*
* <p>A Path may be recycled once there is no Path that refers to it or
* to one of its descendants. This means that any data stored in the
* Path instance by one of its subclasses must be recoverable: it's ok to
* store data in Paths as an optimization, but there must be another
* source for that data in case the Path is recycled.
*
* <p>We intentionally avoid using the existing library classes for reasons of
* space efficiency: while ConcurrentHashMap would reduce our locking
* overhead, and ReferenceMap would simplify the code a little, both of those
* classes have much higher per-instance overheads than IdentityHashMap.
*
* <p>The Path object must be synchronized while children is being
* accessed.
*/
private volatile IdentityHashMap<String, Reference<Path>> children;
/**
* Create a path instance.
*
* <p>Should only be called by {@link PathFactory#createChildPath(Path, String)}.
*
* @param name the name of this path; it must be canonicalized with {@link
* StringCanonicalizer#intern}
* @param parent this path's parent
*/
protected Path(FileSystem fileSystem, String name, Path parent) {
this.fileSystem = fileSystem;
this.name = name;
this.parent = parent;
this.depth = parent == null ? 0 : parent.depth + 1;
// No need to include the drive letter in the hash code, because it's derived from the parent
// and/or the name.
if (fileSystem == null || fileSystem.isFilePathCaseSensitive()) {
this.hashCode = Objects.hash(parent, name);
} else {
this.hashCode = Objects.hash(parent, name.toLowerCase());
}
}
/**
* Create the root path.
*
* <p>Should only be called by {@link PathFactory#createRootPath(FileSystem)}.
*/
protected Path(FileSystem fileSystem) {
this(fileSystem, StringCanonicalizer.intern("/"), null);
}
private void writeObject(ObjectOutputStream out) throws IOException {
Preconditions.checkState(
fileSystem == fileSystemForSerialization, "%s %s", fileSystem, fileSystemForSerialization);
out.writeUTF(getPathString());
}
private void readObject(ObjectInputStream in) throws IOException {
fileSystem = fileSystemForSerialization;
String p = in.readUTF();
PathFragment pf = PathFragment.create(p);
PathFragment parentDir = pf.getParentDirectory();
if (parentDir == null) {
this.name = "/";
this.parent = null;
this.depth = 0;
} else {
this.name = pf.getBaseName();
this.parent = fileSystem.getPath(parentDir);
this.depth = this.parent.depth + 1;
}
this.hashCode = Objects.hash(parent, name);
reinitializeAfterDeserialization();
}
/**
* Returns the filesystem instance to which this path belongs.
*/
public FileSystem getFileSystem() {
return fileSystem;
}
public boolean isRootDirectory() {
return parent == null;
}
protected Path createChildPath(String childName) {
return fileSystem.getPathFactory().createChildPath(this, childName);
}
/**
* Reinitializes this object after deserialization.
*
* <p>Derived classes should use this hook to initialize additional state.
*/
protected void reinitializeAfterDeserialization() {}
/**
* Returns true if {@code ancestorPath} may be an ancestor of {@code path}.
*
* <p>The return value may be a false positive, but it cannot be a false negative. This means that
* a true return value doesn't mean the ancestor candidate is really an ancestor, however a false
* return value means it's guaranteed that {@code ancestorCandidate} is not an ancestor of this
* path.
*
* <p>Subclasses may override this method with filesystem-specific logic, e.g. a Windows
* filesystem may return false if the ancestor path is on a different drive than this one, because
* it is then guaranteed that the ancestor candidate cannot be an ancestor of this path.
*
* @param ancestorCandidate the path that may or may not be an ancestor of this one
*/
protected boolean isMaybeRelativeTo(Path ancestorCandidate) {
return true;
}
/**
* Returns true if this directory is top-level, i.e. it is its own parent.
*
* <p>When canonicalizing paths the ".." segment of a top-level directory always resolves to the
* directory itself.
*
* <p>On Unix, a top-level directory would be just the filesystem root ("/), on Windows it would
* be the filesystem root and the volume roots.
*/
protected boolean isTopLevelDirectory() {
return isRootDirectory();
}
/**
* Returns the child path named name, or creates such a path (and caches it)
* if it doesn't already exist.
*/
private Path getCachedChildPath(String childName) {
return fileSystem.getPathFactory().getCachedChildPathInternal(this, childName);
}
/**
* Internal method only intended to be called by {@link PathFactory#getCachedChildPathInternal}.
*/
public static Path getCachedChildPathInternal(Path parent, String childName, boolean cacheable) {
// We get a canonical instance since 'children' is an IdentityHashMap.
childName = StringCanonicalizer.intern(childName);
if (!cacheable) {
// Non-cacheable children won't show up in `children` so applyToChildren won't run for these.
return parent.createChildPath(childName);
}
synchronized (parent) {
if (parent.children == null) {
// 66% of Paths have size == 1, 80% <= 2
parent.children = new IdentityHashMap<>(1);
}
Reference<Path> childRef = parent.children.get(childName);
Path child;
if (childRef == null || (child = childRef.get()) == null) {
child = parent.createChildPath(childName);
parent.children.put(childName, new PathWeakReferenceForCleanup(child, REFERENCE_QUEUE));
}
return child;
}
}
/**
* Applies the specified function to each {@link Path} that is an existing direct
* descendant of this one. The Predicate is evaluated only for its
* side-effects.
*
* <p>This function exists to hide the "children" field, whose complex
* synchronization and identity requirements are too unsafe to be exposed to
* subclasses. For example, the "children" field must be synchronized for
* the duration of any iteration over it; it may be null; and references
* within it may be stale, and must be ignored.
*/
protected synchronized void applyToChildren(Predicate<Path> function) {
if (children != null) {
for (Reference<Path> childRef : children.values()) {
Path child = childRef.get();
if (child != null) {
function.apply(child);
}
}
}
}
/**
* Returns whether this path is recursively "under" {@code prefix} - that is,
* whether {@code path} is a prefix of this path.
*
* <p>This method returns {@code true} when called with this path itself. This
* method acts independently of the existence of files or folders.
*
* @param prefix a path which may or may not be a prefix of this path
*/
public boolean startsWith(Path prefix) {
Path n = this;
for (int i = 0, len = depth - prefix.depth; i < len; i++) {
n = n.getParentDirectory();
}
return prefix.equals(n);
}
/**
* Computes a string representation of this path, and writes it to the given string builder. Only
* called locally with a new instance.
*/
protected void buildPathString(StringBuilder result) {
if (isRootDirectory()) {
result.append(PathFragment.ROOT_DIR);
} else {
parent.buildPathString(result);
if (!parent.isRootDirectory()) {
result.append(PathFragment.SEPARATOR_CHAR);
}
result.append(name);
}
}
/**
* Returns the path encoded as an {@link URI}.
*
* <p>This concrete implementation returns URIs with "file" as the scheme.
* For Example:
* - On Unix the path "/tmp/foo bar.txt" will be encoded as
* "file:///tmp/foo%20bar.txt".
* - On Windows the path "C:\Temp\Foo Bar.txt" will be encoded as
* "file:///C:/Temp/Foo%20Bar.txt"
*
* <p>Implementors extending this class for special filesystems will likely need to override
* this method.
*
* @throws URISyntaxException if the URI cannot be constructed.
*/
public URI toURI() {
String ps = getPathString();
if (!ps.startsWith("/")) {
// On Windows URI's need to start with a '/'. i.e. C:\Foo\Bar would be file:///C:/Foo/Bar
ps = "/" + ps;
}
try {
return new URI("file",
// Needs to be "" instead of null, so that toString() will append "//" after the scheme.
// We need this for backwards compatibility reasons as some consumers of the BEP are
// broken.
"",
ps, null, null);
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
}
/**
* Returns the path as a string.
*/
public String getPathString() {
// Profile driven optimization:
// Preallocate a size determined by the depth, so that
// we do not have to expand the capacity of the StringBuilder
StringBuilder builder = new StringBuilder(depth * 20);
buildPathString(builder);
return builder.toString();
}
@Override
public void repr(SkylarkPrinter printer) {
printer.append(getPathString());
}
/**
* Returns the path as a string.
*/
@Override
public String toString() {
return getPathString();
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Path)) {
return false;
}
Path otherPath = (Path) other;
if (hashCode != otherPath.hashCode) {
return false;
}
if (!fileSystem.equals(otherPath.fileSystem)) {
return false;
}
if (fileSystem.isFilePathCaseSensitive()) {
return name.equals(otherPath.name)
&& Objects.equals(parent, otherPath.parent);
} else {
return name.toLowerCase().equals(otherPath.name.toLowerCase())
&& Objects.equals(parent, otherPath.parent);
}
}
/**
* Returns a string of debugging information associated with this path.
* The results are unspecified and MUST NOT be interpreted programmatically.
*/
protected String toDebugString() {
return "";
}
/**
* Returns a path representing the parent directory of this path,
* or null iff this Path represents the root of the filesystem.
*
* <p>Note: This method normalises ".." and "." path segments by string
* processing, not by directory lookups.
*/
public Path getParentDirectory() {
return parent;
}
/** Prefer to use {@link #exists(FileSystem)}. */
@Deprecated
public boolean exists() {
return exists(fileSystem);
}
/**
* Returns true iff this path denotes an existing file of any kind. Follows symbolic links.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*/
public boolean exists(FileSystem fileSystem) {
return fileSystem.exists(this, true);
}
/** Prefer to use {@link #exists(FileSystem, Symlinks)}. */
@Deprecated
public boolean exists(Symlinks followSymlinks) {
return exists(fileSystem, followSymlinks);
}
/**
* Returns true iff this path denotes an existing file of any kind.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the
* link is dereferenced until a file other than a symbolic link is found
*/
public boolean exists(FileSystem fileSystem, Symlinks followSymlinks) {
return fileSystem.exists(this, followSymlinks.toBoolean());
}
/** Prefer to use {@link #getDirectoryEntries(FileSystem)}. */
@Deprecated
public Collection<Path> getDirectoryEntries() throws IOException, FileNotFoundException {
return getDirectoryEntries(fileSystem);
}
/**
* Returns a new, immutable collection containing the names of all entities within the directory
* denoted by the current path. Follows symbolic links.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws FileNotFoundException If the directory is not found
* @throws IOException If the path does not denote a directory
*/
public Collection<Path> getDirectoryEntries(FileSystem fileSystem)
throws IOException, FileNotFoundException {
Collection<String> entries = fileSystem.getDirectoryEntries(this);
Collection<Path> result = new ArrayList<>(entries.size());
for (String entry : entries) {
result.add(getChild(entry));
}
return result;
}
/** Prefer to use {@link #readdir(FileSystem, Symlinks)}. */
@Deprecated
public Collection<Dirent> readdir(Symlinks followSymlinks) throws IOException {
return readdir(fileSystem, followSymlinks);
}
/**
* Returns a collection of the names and types of all entries within the directory denoted by the
* current path. Follows symbolic links if {@code followSymlinks} is true. Note that the order of
* the returned entries is not guaranteed.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param followSymlinks whether to follow symlinks or not
* @throws FileNotFoundException If the directory is not found
* @throws IOException If the path does not denote a directory
*/
public Collection<Dirent> readdir(FileSystem fileSystem, Symlinks followSymlinks)
throws IOException {
return fileSystem.readdir(this, followSymlinks.toBoolean());
}
/** Prefer to use {@link #stat(FileSystem)}. */
@Deprecated
public FileStatus stat() throws IOException {
return stat(fileSystem);
}
/**
* Returns the status of a file, following symbolic links.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws IOException if there was an error obtaining the file status. Note, some implementations
* may defer the I/O, and hence the throwing of the exception, until the accessor methods of
* {@code FileStatus} are called.
*/
public FileStatus stat(FileSystem fileSystem) throws IOException {
return fileSystem.stat(this, true);
}
/** Prefer to use {@link #statNullable(FileSystem)}. */
@Deprecated
public FileStatus statNullable() {
return statNullable(fileSystem);
}
/**
* Like stat(), but returns null on file-nonexistence instead of throwing.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*/
public FileStatus statNullable(FileSystem fileSystem) {
return statNullable(fileSystem, Symlinks.FOLLOW);
}
/** Prefer to use {@link #statNullable(FileSystem, Symlinks)}. */
@Deprecated
public FileStatus statNullable(Symlinks symlinks) {
return statNullable(fileSystem, symlinks);
}
/**
* Like stat(), but returns null on file-nonexistence instead of throwing.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*/
public FileStatus statNullable(FileSystem fileSystem, Symlinks symlinks) {
return fileSystem.statNullable(this, symlinks.toBoolean());
}
/** Prefer to use {@link #stat(FileSystem, Symlinks)}. */
@Deprecated
public FileStatus stat(Symlinks followSymlinks) throws IOException {
return stat(fileSystem, followSymlinks);
}
/**
* Returns the status of a file, optionally following symbolic links.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the
* link is dereferenced until a file other than a symbolic link is found
* @throws IOException if there was an error obtaining the file status. Note, some implementations
* may defer the I/O, and hence the throwing of the exception, until the accessor methods of
* {@code FileStatus} are called
*/
public FileStatus stat(FileSystem fileSystem, Symlinks followSymlinks) throws IOException {
return fileSystem.stat(this, followSymlinks.toBoolean());
}
/** Prefer to use {@link #statIfFound(FileSystem)}. */
@Deprecated
public FileStatus statIfFound() throws IOException {
return statIfFound(fileSystem);
}
/**
* Like {@link #stat}, but may return null if the file is not found (corresponding to {@code
* ENOENT} and {@code ENOTDIR} in Unix's stat(2) function) instead of throwing. Follows symbolic
* links.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*/
public FileStatus statIfFound(FileSystem fileSystem) throws IOException {
return fileSystem.statIfFound(this, true);
}
/** Prefer to use {@link #statIfFound(FileSystem, Symlinks)}. */
@Deprecated
public FileStatus statIfFound(Symlinks followSymlinks) throws IOException {
return statIfFound(fileSystem, followSymlinks);
}
/**
* Like {@link #stat}, but may return null if the file is not found (corresponding to {@code
* ENOENT} and {@code ENOTDIR} in Unix's stat(2) function) instead of throwing.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the
* link is dereferenced until a file other than a symbolic link is found
*/
public FileStatus statIfFound(FileSystem fileSystem, Symlinks followSymlinks) throws IOException {
return fileSystem.statIfFound(this, followSymlinks.toBoolean());
}
/** Prefer to use {@link #isDirectory()} (FileSystem)}. */
@Deprecated
public boolean isDirectory() {
return isDirectory(fileSystem);
}
/**
* Returns true iff this path denotes an existing directory. Follows symbolic links.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*/
public boolean isDirectory(FileSystem fileSystem) {
return fileSystem.isDirectory(this, true);
}
/** Prefer to use {@link #isDirectory(FileSystem, Symlinks)}. */
@Deprecated
public boolean isDirectory(Symlinks followSymlinks) {
return isDirectory(fileSystem, followSymlinks);
}
/**
* Returns true iff this path denotes an existing directory.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the
* link is dereferenced until a file other than a symbolic link is found
*/
public boolean isDirectory(FileSystem fileSystem, Symlinks followSymlinks) {
return fileSystem.isDirectory(this, followSymlinks.toBoolean());
}
/** Prefer to use {@link #isFile(FileSystem)}. */
@Deprecated
public boolean isFile() {
return isFile(fileSystem);
}
/**
* Returns true iff this path denotes an existing regular or special file. Follows symbolic links.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* <p>For our purposes, "file" includes special files (socket, fifo, block or char devices) too;
* it excludes symbolic links and directories.
*/
public boolean isFile(FileSystem fileSystem) {
return fileSystem.isFile(this, true);
}
/** Prefer to use {@link #isFile(FileSystem, Symlinks)}. */
@Deprecated
public boolean isFile(Symlinks followSymlinks) {
return isFile(fileSystem, followSymlinks);
}
/**
* Returns true iff this path denotes an existing regular or special file.
*
* <p>For our purposes, a "file" includes special files (socket, fifo, block or char devices) too;
* it excludes symbolic links and directories.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the
* link is dereferenced until a file other than a symbolic link is found.
*/
public boolean isFile(FileSystem fileSystem, Symlinks followSymlinks) {
return fileSystem.isFile(this, followSymlinks.toBoolean());
}
/** Prefer to use {@link #isSpecialFile(FileSystem)}. */
@Deprecated
public boolean isSpecialFile() {
return isSpecialFile(fileSystem);
}
/**
* Returns true iff this path denotes an existing special file (e.g. fifo). Follows symbolic
* links.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*/
public boolean isSpecialFile(FileSystem fileSystem) {
return fileSystem.isSpecialFile(this, true);
}
/** Prefer to use {@link #isSpecialFile(FileSystem, Symlinks)}. */
@Deprecated
public boolean isSpecialFile(Symlinks followSymlinks) {
return isSpecialFile(fileSystem, followSymlinks);
}
/**
* Returns true iff this path denotes an existing special file (e.g. fifo).
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the
* link is dereferenced until a path other than a symbolic link is found.
*/
public boolean isSpecialFile(FileSystem fileSystem, Symlinks followSymlinks) {
return fileSystem.isSpecialFile(this, followSymlinks.toBoolean());
}
/** Prefer to use {@link #isSymbolicLink(FileSystem)}. */
@Deprecated
public boolean isSymbolicLink() {
return isSymbolicLink(fileSystem);
}
/**
* Returns true iff this path denotes an existing symbolic link. Does not follow symbolic links.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*/
public boolean isSymbolicLink(FileSystem fileSystem) {
return fileSystem.isSymbolicLink(this);
}
/**
* Returns the last segment of this path, or "/" for the root directory.
*/
public String getBaseName() {
return name;
}
/**
* Interprets the name of a path segment relative to the current path and
* returns the result.
*
* <p>This is a purely syntactic operation, i.e. it does no I/O, it does not
* validate the existence of any path, nor resolve symbolic links. If 'prefix'
* is not canonical, then a 'name' of '..' will be interpreted incorrectly.
*
* @precondition segment contains no slashes.
*/
private Path getCanonicalPath(String segment) {
if (segment.equals(".") || segment.isEmpty()) {
return this; // that's a noop
} else if (segment.equals("..")) {
// top-level directory's parent is root, when canonicalising:
return isTopLevelDirectory() ? this : parent;
} else {
return getCachedChildPath(segment);
}
}
/**
* Returns the path formed by appending the single non-special segment
* "baseName" to this path.
*
* <p>You should almost always use {@link #getRelative} instead, which has
* the same performance characteristics if the given name is a valid base
* name, and which also works for '.', '..', and strings containing '/'.
*
* @throws IllegalArgumentException if {@code baseName} is not a valid base
* name according to {@link FileSystemUtils#checkBaseName}
*/
public Path getChild(String baseName) {
FileSystemUtils.checkBaseName(baseName);
return getCachedChildPath(baseName);
}
protected Path getRootForRelativePathComputation(PathFragment suffix) {
return suffix.isAbsolute() ? fileSystem.getRootDirectory() : this;
}
/**
* Returns the path formed by appending the relative or absolute path fragment
* {@code suffix} to this path.
*
* <p>If suffix is absolute, the current path will be ignored; otherwise, they
* will be combined. Up-level references ("..") cause the preceding path
* segment to be elided; this interpretation is only correct if the base path
* is canonical.
*/
public Path getRelative(PathFragment suffix) {
Path result = getRootForRelativePathComputation(suffix);
for (String segment : suffix.segments()) {
result = result.getCanonicalPath(segment);
}
return result;
}
/**
* Returns the path formed by appending the relative or absolute string
* {@code path} to this path.
*
* <p>If the given path string is absolute, the current path will be ignored;
* otherwise, they will be combined. Up-level references ("..") cause the
* preceding path segment to be elided.
*
* <p>This is a purely syntactic operation, i.e. it does no I/O, it does not
* validate the existence of any path, nor resolve symbolic links.
*/
public Path getRelative(String path) {
// Fast path for valid base names.
if ((path.length() == 0) || (path.equals("."))) {
return this;
} else if (path.equals("..")) {
return isTopLevelDirectory() ? this : parent;
} else if (PathFragment.containsSeparator(path)) {
return getRelative(PathFragment.create(path));
} else {
return getCachedChildPath(path);
}
}
protected final String[] getSegments() {
String[] resultSegments = new String[depth];
Path currentPath = this;
for (int pos = depth - 1; pos >= 0; pos--) {
resultSegments[pos] = currentPath.getBaseName();
currentPath = currentPath.getParentDirectory();
}
return resultSegments;
}
/** Returns an absolute PathFragment representing this path. */
public PathFragment asFragment() {
return PathFragment.createAlreadyInterned('\0', true, getSegments());
}
/**
* Returns a relative path fragment to this path, relative to {@code
* ancestorDirectory}. {@code ancestorDirectory} must be on the same
* filesystem as this path. (Currently, both this path and "ancestorDirectory"
* must be absolute, though this restriction could be loosened.)
* <p>
* <code>x.relativeTo(z) == y</code> implies
* <code>z.getRelative(y.getPathString()) == x</code>.
* <p>
* For example, <code>"/foo/bar/wiz".relativeTo("/foo")</code> returns
* <code>"bar/wiz"</code>.
*
* @throws IllegalArgumentException if this path is not beneath {@code
* ancestorDirectory} or if they are not part of the same filesystem
*/
public PathFragment relativeTo(Path ancestorPath) {
checkSameFilesystem(ancestorPath);
if (isMaybeRelativeTo(ancestorPath)) {
// Fast path: when otherPath is the ancestor of this path
int resultSegmentCount = depth - ancestorPath.depth;
if (resultSegmentCount >= 0) {
String[] resultSegments = new String[resultSegmentCount];
Path currentPath = this;
for (int pos = resultSegmentCount - 1; pos >= 0; pos--) {
resultSegments[pos] = currentPath.getBaseName();
currentPath = currentPath.getParentDirectory();
}
if (ancestorPath.equals(currentPath)) {
return PathFragment.createAlreadyInterned('\0', false, resultSegments);
}
}
}
throw new IllegalArgumentException("Path " + this + " is not beneath " + ancestorPath);
}
/**
* Checks that "this" and "that" are paths on the same filesystem.
*/
protected void checkSameFilesystem(Path that) {
if (this.fileSystem != that.fileSystem) {
throw new IllegalArgumentException("Files are on different filesystems: "
+ this + ", " + that);
}
}
/** Prefer to use {@link #getOutputStream(FileSystem)}. */
@Deprecated
public OutputStream getOutputStream() throws IOException, FileNotFoundException {
return getOutputStream(fileSystem);
}
/**
* Returns an output stream to the file denoted by the current path, creating it and truncating it
* if necessary. The stream is opened for writing.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws FileNotFoundException If the file cannot be found or created.
* @throws IOException If a different error occurs.
*/
public OutputStream getOutputStream(FileSystem fileSystem)
throws IOException, FileNotFoundException {
return getOutputStream(fileSystem, false);
}
/** Prefer to use {@link #getOutputStream(FileSystem, boolean)}. */
@Deprecated
public OutputStream getOutputStream(boolean append) throws IOException, FileNotFoundException {
return getOutputStream(fileSystem, append);
}
/**
* Returns an output stream to the file denoted by the current path, creating it and truncating it
* if necessary. The stream is opened for writing.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param append whether to open the file in append mode.
* @throws FileNotFoundException If the file cannot be found or created.
* @throws IOException If a different error occurs.
*/
public OutputStream getOutputStream(FileSystem fileSystem, boolean append)
throws IOException, FileNotFoundException {
return fileSystem.getOutputStream(this, append);
}
/** Prefer to use {@link #createDirectory(FileSystem)}. */
@Deprecated
public boolean createDirectory() throws IOException {
return createDirectory(fileSystem);
}
/**
* Creates a directory with the name of the current path, not following symbolic links. Returns
* normally iff the directory exists after the call: true if the directory was created by this
* call, false if the directory was already in existence. Throws an exception if the directory
* could not be created for any reason.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws IOException if the directory creation failed for any reason
*/
public boolean createDirectory(FileSystem fileSystem) throws IOException {
return fileSystem.createDirectory(this);
}
/** Prefer to use {@link #createSymbolicLink(FileSystem, Path)}. */
@Deprecated
public void createSymbolicLink(Path target) throws IOException {
createSymbolicLink(fileSystem, target);
}
/**
* Creates a symbolic link with the name of the current path, following symbolic links. The
* referent of the created symlink is is the absolute path "target"; it is not possible to create
* relative symbolic links via this method.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws IOException if the creation of the symbolic link was unsuccessful for any reason
*/
public void createSymbolicLink(FileSystem fileSystem, Path target) throws IOException {
checkSameFilesystem(target);
fileSystem.createSymbolicLink(this, target.asFragment());
}
/** Prefer to use {@link #createSymbolicLink(FileSystem, PathFragment)}. */
@Deprecated
public void createSymbolicLink(PathFragment target) throws IOException {
createSymbolicLink(fileSystem, target);
}
/**
* Creates a symbolic link with the name of the current path, following symbolic links. The
* referent of the created symlink is is the path fragment "target", which may be absolute or
* relative.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws IOException if the creation of the symbolic link was unsuccessful for any reason
*/
public void createSymbolicLink(FileSystem fileSystem, PathFragment target) throws IOException {
fileSystem.createSymbolicLink(this, target);
}
/** Prefer to use {@link #readSymbolicLink(FileSystem)}. */
@Deprecated
public PathFragment readSymbolicLink() throws IOException {
return readSymbolicLink(fileSystem);
}
/**
* Returns the target of the current path, which must be a symbolic link. The link contents are
* returned exactly, and may contain an absolute or relative path. Analogous to readlink(2).
*
* <p>Note: for {@link FileSystem}s where {@link FileSystem#supportsSymbolicLinksNatively(Path)}
* returns false, this method will throw an {@link UnsupportedOperationException} if the link
* points to a non-existent file.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @return the content (i.e. target) of the symbolic link
* @throws IOException if the current path is not a symbolic link, or the contents of the link
* could not be read for any reason
*/
public PathFragment readSymbolicLink(FileSystem fileSystem) throws IOException {
return fileSystem.readSymbolicLink(this);
}
/** Prefer to use {@link #readSymbolicLinkUnchecked(FileSystem)}. */
@Deprecated
public PathFragment readSymbolicLinkUnchecked() throws IOException {
return readSymbolicLinkUnchecked(fileSystem);
}
/**
* If the current path is a symbolic link, returns the target of this symbolic link. The semantics
* are intentionally left underspecified otherwise to permit efficient implementations.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @return the content (i.e. target) of the symbolic link
* @throws IOException if the current path is not a symbolic link, or the contents of the link
* could not be read for any reason
*/
public PathFragment readSymbolicLinkUnchecked(FileSystem fileSystem) throws IOException {
return fileSystem.readSymbolicLinkUnchecked(this);
}
/** Prefer to use {@link #createHardLink(FileSystem, Path)}. */
@Deprecated
public void createHardLink(Path link) throws IOException {
createHardLink(fileSystem, link);
}
/**
* Create a hard link for the current path.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param link the path of the new link
* @throws IOException if there was an error executing {@link FileSystem#createHardLink}
*/
public void createHardLink(FileSystem fileSystem, Path link) throws IOException {
fileSystem.createHardLink(link, this);
}
/** Prefer to use {@link #resolveSymbolicLinks(FileSystem)}. */
@Deprecated
public Path resolveSymbolicLinks() throws IOException {
return resolveSymbolicLinks(fileSystem);
}
/**
* Returns the canonical path for this path, by repeatedly replacing symbolic links with their
* referents. Analogous to realpath(3).
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @return the canonical path for this path
* @throws IOException if any symbolic link could not be resolved, or other error occurred (for
* example, the path does not exist)
*/
public Path resolveSymbolicLinks(FileSystem fileSystem) throws IOException {
return fileSystem.resolveSymbolicLinks(this);
}
/** Prefer to use {@link #renameTo(FileSystem, Path)}. */
@Deprecated
public void renameTo(Path target) throws IOException {
renameTo(fileSystem, target);
}
/**
* Renames the file denoted by the current path to the location "target", not following symbolic
* links.
*
* <p>Files cannot be atomically renamed across devices; copying is required. Use {@link
* FileSystemUtils#copyFile} followed by {@link Path#delete}.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws IOException if the rename failed for any reason
*/
public void renameTo(FileSystem fileSystem, Path target) throws IOException {
checkSameFilesystem(target);
fileSystem.renameTo(this, target);
}
/** Prefer to use {@link #getFileSize(FileSystem)}. */
@Deprecated
public long getFileSize() throws IOException, FileNotFoundException {
return getFileSize(fileSystem);
}
/**
* Returns the size in bytes of the file denoted by the current path, following symbolic links.
*
* <p>The size of a directory or special file is undefined and should not be used.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws FileNotFoundException if the file denoted by the current path does not exist
* @throws IOException if the file's metadata could not be read, or some other error occurred
*/
public long getFileSize(FileSystem fileSystem) throws IOException, FileNotFoundException {
return fileSystem.getFileSize(this, true);
}
/** Prefer to use {@link #getFileSize(FileSystem, Symlinks)}. */
@Deprecated
public long getFileSize(Symlinks followSymlinks) throws IOException, FileNotFoundException {
return getFileSize(fileSystem, followSymlinks);
}
/**
* Returns the size in bytes of the file denoted by the current path.
*
* <p>The size of directory or special file is undefined. The size of a symbolic link is the
* length of the name of its referent.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the
* link is deferenced until a file other than a symbol link is found
* @throws FileNotFoundException if the file denoted by the current path does not exist
* @throws IOException if the file's metadata could not be read, or some other error occurred
*/
public long getFileSize(FileSystem fileSystem, Symlinks followSymlinks)
throws IOException, FileNotFoundException {
return fileSystem.getFileSize(this, followSymlinks.toBoolean());
}
/** Prefer to use {@link #delete(FileSystem)}. */
@Deprecated
public boolean delete() throws IOException {
return delete(fileSystem);
}
/**
* Deletes the file denoted by this path, not following symbolic links. Returns normally iff the
* file doesn't exist after the call: true if this call deleted the file, false if the file
* already didn't exist. Throws an exception if the file could not be deleted for any reason.
*
* <p>This is a migration method. The method (and its FileSystem-less counterpart) will be deleted
* once the FileSystem instance is removed from Path.
*
* @return true iff the file was actually deleted by this call
* @throws IOException if the deletion failed but the file was present prior to the call
*/
public boolean delete(FileSystem fileSystem) throws IOException {
return fileSystem.delete(this);
}
/** Prefer to use {@link #getLastModifiedTime(FileSystem)}. */
@Deprecated
public long getLastModifiedTime() throws IOException {
return getLastModifiedTime(fileSystem);
}
/**
* Returns the last modification time of the file, in milliseconds since the UNIX epoch, of the
* file denoted by the current path, following symbolic links.
*
* <p>Caveat: many filesystems store file times in seconds, so do not rely on the millisecond
* precision.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws IOException if the operation failed for any reason
*/
public long getLastModifiedTime(FileSystem fileSystem) throws IOException {
return fileSystem.getLastModifiedTime(this, true);
}
/** Prefer to use {@link #getLastModifiedTime(FileSystem, Symlinks)}. */
@Deprecated
public long getLastModifiedTime(Symlinks followSymlinks) throws IOException {
return getLastModifiedTime(fileSystem, followSymlinks);
}
/**
* Returns the last modification time of the file, in milliseconds since the UNIX epoch, of the
* file denoted by the current path.
*
* <p>Caveat: many filesystems store file times in seconds, so do not rely on the millisecond
* precision.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the
* link is dereferenced until a file other than a symbolic link is found
* @throws IOException if the modification time for the file could not be obtained for any reason
*/
public long getLastModifiedTime(FileSystem fileSystem, Symlinks followSymlinks)
throws IOException {
return fileSystem.getLastModifiedTime(this, followSymlinks.toBoolean());
}
/** Prefer to use {@link #setLastModifiedTime(FileSystem, long)}. */
@Deprecated
public void setLastModifiedTime(long newTime) throws IOException {
setLastModifiedTime(fileSystem, newTime);
}
/**
* Sets the modification time of the file denoted by the current path. Follows symbolic links. If
* newTime is -1, the current time according to the kernel is used; this may differ from the JVM's
* clock.
*
* <p>Caveat: many filesystems store file times in seconds, so do not rely on the millisecond
* precision.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param newTime time, in milliseconds since the UNIX epoch, or -1L, meaning use the kernel's
* current time
* @throws IOException if the modification time for the file could not be set for any reason
*/
public void setLastModifiedTime(FileSystem fileSystem, long newTime) throws IOException {
fileSystem.setLastModifiedTime(this, newTime);
}
/** Prefer to use {@link #getxattr(FileSystem, String)}. */
@Deprecated
public byte[] getxattr(String name) throws IOException {
return getxattr(fileSystem, name);
}
/**
* Returns value of the given extended attribute name or null if attribute does not exist or file
* system does not support extended attributes. Follows symlinks.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*/
public byte[] getxattr(FileSystem fileSystem, String name) throws IOException {
return fileSystem.getxattr(this, name);
}
/** Prefer to use {@link #getFastDigest(FileSystem)}. */
@Deprecated
public byte[] getFastDigest() throws IOException {
return getFastDigest(fileSystem);
}
/**
* Gets a fast digest for the given path, or {@code null} if there isn't one available. The digest
* should be suitable for detecting changes to the file.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*/
public byte[] getFastDigest(FileSystem fileSystem) throws IOException {
return fileSystem.getFastDigest(this);
}
/** Prefer to use {@link #isValidDigest(FileSystem, byte[])}. */
@Deprecated
public boolean isValidDigest(byte[] digest) {
return isValidDigest(fileSystem, digest);
}
/**
* Returns whether the given digest is a valid digest for the default system digest function.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*/
public boolean isValidDigest(FileSystem fileSystem, byte[] digest) {
return fileSystem.isValidDigest(digest);
}
/** Prefer to use {@link #getDigest(FileSystem)}. */
@Deprecated
public byte[] getDigest() throws IOException {
return getDigest(fileSystem);
}
/**
* Returns the digest of the file denoted by the current path, following symbolic links.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @return a new byte array containing the file's digest
* @throws IOException if the digest could not be computed for any reason
*/
public byte[] getDigest(FileSystem fileSystem) throws IOException {
return fileSystem.getDigest(this);
}
/** Prefer to use {@link #getDigest(FileSystem, HashFunction)}. */
@Deprecated
public byte[] getDigest(HashFunction hashFunction) throws IOException {
return getDigest(fileSystem, hashFunction);
}
/**
* Returns the digest of the file denoted by the current path and digest function, following
* symbolic links.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @return a new byte array containing the file's digest
* @throws IOException if the digest could not be computed for any reason
*/
public byte[] getDigest(FileSystem fileSystem, HashFunction hashFunction) throws IOException {
return fileSystem.getDigest(this, hashFunction);
}
/** Prefer to use {@link #getInputStream(FileSystem)}. */
@Deprecated
public InputStream getInputStream() throws IOException {
return getInputStream(fileSystem);
}
/**
* Opens the file denoted by this path, following symbolic links, for reading, and returns an
* input stream to it.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws IOException if the file was not found or could not be opened for reading
*/
public InputStream getInputStream(FileSystem fileSystem) throws IOException {
return fileSystem.getInputStream(this);
}
/**
* Returns a java.io.File representation of this path.
*
* <p>Caveat: the result may be useless if this path's getFileSystem() is not
* the UNIX filesystem.
*/
public File getPathFile() {
return new File(getPathString());
}
/** Prefer to use {@link #isWritable(FileSystem)}. */
@Deprecated
public boolean isWritable() throws IOException, FileNotFoundException {
return isWritable(fileSystem);
}
/**
* Returns true if the file denoted by the current path, following symbolic links, is writable for
* the current user.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws FileNotFoundException if the file does not exist, a dangling symbolic link was
* encountered, or the file's metadata could not be read
*/
public boolean isWritable(FileSystem fileSystem) throws IOException, FileNotFoundException {
return fileSystem.isWritable(this);
}
/** Prefer to use {@link #setReadable(FileSystem, boolean)}. */
@Deprecated
public void setReadable(boolean readable) throws IOException, FileNotFoundException {
setReadable(fileSystem, readable);
}
/**
* Sets the read permissions of the file denoted by the current path, following symbolic links.
* Permissions apply to the current user.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param readable if true, the file is set to readable; otherwise the file is made non-readable
* @throws FileNotFoundException if the file does not exist
* @throws IOException If the action cannot be taken (ie. permissions)
*/
public void setReadable(FileSystem fileSystem, boolean readable)
throws IOException, FileNotFoundException {
fileSystem.setReadable(this, readable);
}
/** Prefer to use {@link #setWritable(FileSystem, boolean)}. */
@Deprecated
public void setWritable(boolean writable) throws IOException, FileNotFoundException {
setWritable(fileSystem, writable);
}
/**
* Sets the write permissions of the file denoted by the current path, following symbolic links.
* Permissions apply to the current user.
*
* <p>TODO(bazel-team): (2009) what about owner/group/others?
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @param writable if true, the file is set to writable; otherwise the file is made non-writable
* @throws FileNotFoundException if the file does not exist
* @throws IOException If the action cannot be taken (ie. permissions)
*/
public void setWritable(FileSystem fileSystem, boolean writable)
throws IOException, FileNotFoundException {
fileSystem.setWritable(this, writable);
}
/** Prefer to use {@link #isExecutable(FileSystem)}. */
@Deprecated
public boolean isExecutable() throws IOException, FileNotFoundException {
return isExecutable(fileSystem);
}
/**
* Returns true iff the file specified by the current path, following symbolic links, is
* executable by the current user.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws FileNotFoundException if the file does not exist or a dangling symbolic link was
* encountered
* @throws IOException if some other I/O error occurred
*/
public boolean isExecutable(FileSystem fileSystem) throws IOException, FileNotFoundException {
return fileSystem.isExecutable(this);
}
/** Prefer to use {@link #isReadable(FileSystem)}. */
@Deprecated
public boolean isReadable() throws IOException, FileNotFoundException {
return isReadable(fileSystem);
}
/**
* Returns true iff the file specified by the current path, following symbolic links, is readable
* by the current user.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws FileNotFoundException if the file does not exist or a dangling symbolic link was
* encountered
* @throws IOException if some other I/O error occurred
*/
public boolean isReadable(FileSystem fileSystem) throws IOException, FileNotFoundException {
return fileSystem.isReadable(this);
}
/** Prefer to use {@link #setExecutable(FileSystem, boolean)}. */
@Deprecated
public void setExecutable(boolean executable) throws IOException, FileNotFoundException {
setExecutable(fileSystem, executable);
}
/**
* Sets the execute permission on the file specified by the current path, following symbolic
* links. Permissions apply to the current user.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws FileNotFoundException if the file does not exist or a dangling symbolic link was
* encountered
* @throws IOException if the metadata change failed, for example because of permissions
*/
public void setExecutable(FileSystem fileSystem, boolean executable)
throws IOException, FileNotFoundException {
fileSystem.setExecutable(this, executable);
}
/** Prefer to use {@link #chmod(FileSystem, int)}. */
@Deprecated
public void chmod(int mode) throws IOException {
chmod(fileSystem, mode);
}
/**
* Sets the permissions on the file specified by the current path, following symbolic links. If
* permission changes on this path's {@link FileSystem} are slow (e.g. one syscall per change),
* this method should aim to be faster than setting each permission individually. If this path's
* {@link FileSystem} does not support group and others permissions, those bits will be ignored.
*
* <p>This is a migration method. It will be deleted once the file system instance is deleted from
* Path.
*
* @throws FileNotFoundException if the file does not exist or a dangling symbolic link was
* encountered
* @throws IOException if the metadata change failed, for example because of permissions
*/
public void chmod(FileSystem fileSystem, int mode) throws IOException {
fileSystem.chmod(this, mode);
}
/** Prefer to use {@link #prefetchPackageAsync(FileSystem, int)}. */
@Deprecated
public void prefetchPackageAsync(int maxDirs) {
prefetchPackageAsync(fileSystem, maxDirs);
}
public void prefetchPackageAsync(FileSystem fileSystem, int maxDirs) {
fileSystem.prefetchPackageAsync(this, maxDirs);
}
/**
* Compare Paths of the same file system using their PathFragments.
*
* <p>Paths from different filesystems will be compared using the identity
* hash code of their respective filesystems.
*/
@Override
public int compareTo(Path o) {
// Fast-path.
if (equals(o)) {
return 0;
}
// If they are on different file systems, the file system decides the ordering.
FileSystem otherFs = o.getFileSystem();
if (!fileSystem.equals(otherFs)) {
int thisFileSystemHash = System.identityHashCode(fileSystem);
int otherFileSystemHash = System.identityHashCode(otherFs);
if (thisFileSystemHash < otherFileSystemHash) {
return -1;
} else if (thisFileSystemHash > otherFileSystemHash) {
return 1;
} else {
// TODO(bazel-team): Add a name to every file system to be used here.
return 0;
}
}
// Equal file system, but different paths, because of the canonicalization.
// We expect to often compare Paths that are very similar, for example for files in the same
// directory. This can be done efficiently by going up segment by segment until we get the
// identical path (canonicalization again), and then just compare the immediate child segments.
// Overall this is much faster than creating PathFragment instances, and comparing those, which
// requires us to always go up to the top-level directory and copy all segments into a new
// string array.
// This was previously showing up as a hotspot in a profile of globbing a large directory.
Path a = this;
Path b = o;
int maxDepth = Math.min(a.depth, b.depth);
while (a.depth > maxDepth) {
a = a.getParentDirectory();
}
while (b.depth > maxDepth) {
b = b.getParentDirectory();
}
// One is the child of the other.
if (a.equals(b)) {
// If a is the same as this, this.depth must be less than o.depth.
return equals(a) ? -1 : 1;
}
Path previousa;
Path previousb;
do {
previousa = a;
previousb = b;
a = a.getParentDirectory();
b = b.getParentDirectory();
} while (!a.equals(b)); // This has to happen eventually.
return previousa.name.compareTo(previousb.name);
}
}