com.palantir.giraffe.file.UniformPath Maven / Gradle / Ivy
Show all versions of giraffe-fs-base Show documentation
/**
* Copyright 2015 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.palantir.giraffe.file;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.NoSuchElementException;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.UnmodifiableIterator;
import com.palantir.giraffe.file.base.ImmutableListPathCore;
import com.palantir.giraffe.file.base.ImmutableListPathCore.Parser.NamedRootFormat;
// @formatter:off
/**
* A {@link Path}-like object that defines a file system independent path syntax
* and is not associated with any {@code FileSystem} instance.
*
* Uniform paths use the following UNIX-like syntax:
*
* {@code [[//root]/]path/to/file/or/directory}
*
where {@code []} indicates an optional component and {@code /}
* serves as the path separator. All names are case-sensitive.
*
* If the root component is present, the path is absolute. For single-root file
* systems, the root prefix {@code //} and the root name are omitted and
* the path starts with a single forward slash. For multi-root file system, the
* path starts with the root prefix followed by the name of the root and a
* single forward slash. Root names that contain the path separator ({@code /})
* are not allowed.
*
* {@code UniformPath}s can be converted to standard {@code Path}s using the
* {@link #toPath(FileSystem) toPath} method.
*
* @author bkeyes
*/
// @formatter:on
public final class UniformPath implements Iterable, Comparable {
private static final String SEPARATOR = "/";
private static final String ROOT_PREFIX = "//";
private static final ImmutableListPathCore.Parser PARSER =
ImmutableListPathCore.namedRootParser(
SEPARATOR, NamedRootFormat.OPTIONAL_PREFIX, ROOT_PREFIX);
/**
* Converts a path string, or a sequence of strings that form a path string
* when joined, to a {@code UniformPath}.
*
* @param first the path string or initial part of the path string
* @param more additional strings to join to form the path string
*/
public static UniformPath get(String first, String... more) {
return get(Lists.asList(first, more));
}
/**
* Converts a sequence of strings that form a path string when joined to a
* {@code UniformPath}.
*
* @param components the components to join to form the path string
*/
public static UniformPath get(Iterable components) {
return fromString(Joiner.on(SEPARATOR).join(components));
}
/**
* Converts the specified {@link Path} into a {@code UniformPath}.
*
* For any path {@code p}, the following relationship will always hold:
*
*
* {@code UniformPath.fromPath(p).toPath(p.getFileSystem()).equals(p)}
*
*
* @param path the path to convert
*
* @return a {@code UniformPath} that is equivalent to the given path
*
* @throws IllegalArgumentException if any path segment contains the uniform
* path separator
*/
public static UniformPath fromPath(Path path) {
checkNotNull(path, "path must be non-null");
Iterable segments = Iterables.transform(path, new Function() {
@Override
public String apply(Path input) {
String seg = input.toString();
checkArgument(!seg.contains(SEPARATOR), "segment '%s' contains separator", seg);
return seg;
}
});
return new UniformPath(new ImmutableListPathCore(getRootString(path), segments));
}
private static String getRootString(Path p) {
Path root = p.getRoot();
if (root == null) {
return null;
} else {
String cleanRoot = cleanRoot(root.toString(), p.getFileSystem().getSeparator());
return cleanRoot.isEmpty() ? "" : ROOT_PREFIX + cleanRoot;
}
}
private static String cleanRoot(String root, String fsSeparator) {
String cleaned = root;
if (root.endsWith(fsSeparator)) {
cleaned = root.substring(0, root.length() - fsSeparator.length());
}
checkArgument(!cleaned.contains(SEPARATOR), "root '%s' contains separator", root);
return cleaned;
}
private static UniformPath fromString(String path) {
return new UniformPath(PARSER.parse(path));
}
private final ImmutableListPathCore core;
private UniformPath(ImmutableListPathCore core) {
this.core = core;
}
public boolean isAbsolute() {
return core.isAbsolute();
}
public UniformPath getRoot() {
ImmutableListPathCore root = core.getRoot();
if (root == null) {
return null;
} else {
return new UniformPath(root);
}
}
public UniformPath getFileName() {
ImmutableListPathCore name = core.getFileName();
if (name == null) {
return null;
} else {
return new UniformPath(name);
}
}
public UniformPath getParent() {
ImmutableListPathCore parent = core.getParent();
if (parent == null) {
return null;
} else {
return new UniformPath(parent);
}
}
public int getNameCount() {
return core.getNameCount();
}
public UniformPath getName(int index) {
return new UniformPath(core.getName(index));
}
public UniformPath subpath(int beginIndex, int endIndex) {
return new UniformPath(core.subpath(beginIndex, endIndex));
}
public boolean startsWith(UniformPath other) {
return core.startsWith(other.core);
}
public boolean startsWith(String other) {
return startsWith(fromString(other));
}
public boolean endsWith(UniformPath other) {
return core.endsWith(other.core);
}
public boolean endsWith(String other) {
return endsWith(fromString(other));
}
public UniformPath normalize() {
return new UniformPath(core.normalize());
}
public UniformPath resolve(UniformPath other) {
return new UniformPath(core.resolve(other.core));
}
public UniformPath resolve(String other) {
return resolve(fromString(other));
}
public UniformPath resolveSibling(UniformPath other) {
return new UniformPath(core.resolveSibling(other.core));
}
public UniformPath resolveSibling(String other) {
return resolveSibling(fromString(other));
}
public UniformPath relativize(UniformPath other) {
return new UniformPath(core.relativize(other.core));
}
@Override
public Iterator iterator() {
return new UnmodifiableIterator() {
private int i = 0;
@Override
public boolean hasNext() {
return i < getNameCount();
}
@Override
public UniformPath next() {
if (i < getNameCount()) {
UniformPath result = getName(i);
i++;
return result;
} else {
throw new NoSuchElementException();
}
}
};
}
/**
* Converts this uniform path to a real {@link Path} on the default file
* system.
*
* @see #toPath(FileSystem)
*/
public Path toPath() {
return toPath(FileSystems.getDefault());
}
/**
* Converts this uniform path to a real {@link Path} on the given file
* system.
*
* If this path is a relative path, each segment is passed as a separate
* argument to the file system's
* {@link FileSystem#getPath(String, String...) getPath} method. If this
* path is an absolute path, a relative path is constructed as described
* then resolved against a root path obtained using the following method:
*
* - Get all root directories on the file system
* - If this path does not have a named root and there is one
* root, use that root
* - If there are multiple roots or this path has a named root,
* compare this path's root to the string version of each file system root.
* If this path's root equals a file system root, ignoring any trailing path
* separator, use that root
* - If there are no matches, throw an {@code IllegalArgumentException}
*
*
* @param fs the {@link FileSystem} on which to create a path
*
* @throws IllegalArgumentException if this is an absolute path and the
* given file system does not have a matching root
*/
public Path toPath(FileSystem fs) {
checkNotNull(fs, "file system must be non-null");
if (isAbsolute()) {
return findRoot(fs, getRootName()).resolve(toRelativePath(fs));
} else {
return toRelativePath(fs);
}
}
private Path findRoot(FileSystem fs, String target) {
Iterable rootDirs = fs.getRootDirectories();
if (isSingleRoot() && Iterables.size(rootDirs) == 1) {
return Iterables.getOnlyElement(rootDirs);
} else {
for (Path dir : rootDirs) {
String root = dir.toString();
if (root.equals(target) || root.equals(target + fs.getSeparator())) {
return dir;
}
}
}
throw new IllegalArgumentException("no root directory matching '" + target + "'");
}
private Path toRelativePath(FileSystem fs) {
ImmutableList segments = core.getPathSegments();
if (segments.isEmpty()) {
return fs.getPath("");
} else {
String first = segments.get(0);
String[] more = segments.subList(1, segments.size()).toArray(new String[0]);
return fs.getPath(first, more);
}
}
private boolean isSingleRoot() {
return isAbsolute() && core.getRootString().isEmpty();
}
private String getRootName() {
String rootString = core.getRootString();
if (rootString.startsWith(ROOT_PREFIX)) {
return rootString.substring(ROOT_PREFIX.length());
} else {
return rootString;
}
}
@Override
public int compareTo(UniformPath other) {
return core.compareTo(other.core);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof UniformPath)) {
return false;
} else {
return core.equals(((UniformPath) obj).core);
}
}
@Override
public int hashCode() {
return core.hashCode();
}
@Override
public String toString() {
return core.toPathString(SEPARATOR);
}
}