blob: 49717bd4ee8221ab39f7887d3f8aa41259f65109 [file] [log] [blame]
/*
* Copyright 2013 Google 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.google.common.jimfs;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.jimfs.PathType.ParseResult;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
/**
* Service for creating {@link JimfsPath} instances and handling other path-related operations.
*
* @author Colin Decker
*/
final class PathService implements Comparator<JimfsPath> {
private static final Ordering<Name> DISPLAY_ROOT_ORDERING = Name.displayOrdering().nullsLast();
private static final Ordering<Iterable<Name>> DISPLAY_NAMES_ORDERING =
Name.displayOrdering().lexicographical();
private static final Ordering<Name> CANONICAL_ROOT_ORDERING =
Name.canonicalOrdering().nullsLast();
private static final Ordering<Iterable<Name>> CANONICAL_NAMES_ORDERING =
Name.canonicalOrdering().lexicographical();
private final PathType type;
private final ImmutableSet<PathNormalization> displayNormalizations;
private final ImmutableSet<PathNormalization> canonicalNormalizations;
private final boolean equalityUsesCanonicalForm;
private final Ordering<Name> rootOrdering;
private final Ordering<Iterable<Name>> namesOrdering;
private volatile FileSystem fileSystem;
private volatile JimfsPath emptyPath;
PathService(Configuration config) {
this(
config.pathType,
config.nameDisplayNormalization,
config.nameCanonicalNormalization,
config.pathEqualityUsesCanonicalForm);
}
PathService(
PathType type,
Iterable<PathNormalization> displayNormalizations,
Iterable<PathNormalization> canonicalNormalizations,
boolean equalityUsesCanonicalForm) {
this.type = checkNotNull(type);
this.displayNormalizations = ImmutableSet.copyOf(displayNormalizations);
this.canonicalNormalizations = ImmutableSet.copyOf(canonicalNormalizations);
this.equalityUsesCanonicalForm = equalityUsesCanonicalForm;
this.rootOrdering = equalityUsesCanonicalForm ? CANONICAL_ROOT_ORDERING : DISPLAY_ROOT_ORDERING;
this.namesOrdering =
equalityUsesCanonicalForm ? CANONICAL_NAMES_ORDERING : DISPLAY_NAMES_ORDERING;
}
/** Sets the file system to use for created paths. */
public void setFileSystem(FileSystem fileSystem) {
// allowed to not be JimfsFileSystem for testing purposes only
checkState(this.fileSystem == null, "may not set fileSystem twice");
this.fileSystem = checkNotNull(fileSystem);
}
/** Returns the file system this service is for. */
public FileSystem getFileSystem() {
return fileSystem;
}
/** Returns the default path separator. */
public String getSeparator() {
return type.getSeparator();
}
/** Returns an empty path which has a single name, the empty string. */
public JimfsPath emptyPath() {
JimfsPath result = emptyPath;
if (result == null) {
// use createPathInternal to avoid recursive call from createPath()
result = createPathInternal(null, ImmutableList.of(Name.EMPTY));
emptyPath = result;
return result;
}
return result;
}
/** Returns the {@link Name} form of the given string. */
public Name name(String name) {
switch (name) {
case "":
return Name.EMPTY;
case ".":
return Name.SELF;
case "..":
return Name.PARENT;
default:
String display = PathNormalization.normalize(name, displayNormalizations);
String canonical = PathNormalization.normalize(name, canonicalNormalizations);
return Name.create(display, canonical);
}
}
/** Returns the {@link Name} forms of the given strings. */
@VisibleForTesting
List<Name> names(Iterable<String> names) {
List<Name> result = new ArrayList<>();
for (String name : names) {
result.add(name(name));
}
return result;
}
/** Returns a root path with the given name. */
public JimfsPath createRoot(Name root) {
return createPath(checkNotNull(root), ImmutableList.<Name>of());
}
/** Returns a single filename path with the given name. */
public JimfsPath createFileName(Name name) {
return createPath(null, ImmutableList.of(name));
}
/** Returns a relative path with the given names. */
public JimfsPath createRelativePath(Iterable<Name> names) {
return createPath(null, ImmutableList.copyOf(names));
}
/** Returns a path with the given root (or no root, if null) and the given names. */
public JimfsPath createPath(@NullableDecl Name root, Iterable<Name> names) {
ImmutableList<Name> nameList = ImmutableList.copyOf(Iterables.filter(names, NOT_EMPTY));
if (root == null && nameList.isEmpty()) {
// ensure the canonical empty path (one empty string name) is used rather than a path with
// no root and no names
return emptyPath();
}
return createPathInternal(root, nameList);
}
/** Returns a path with the given root (or no root, if null) and the given names. */
protected final JimfsPath createPathInternal(@NullableDecl Name root, Iterable<Name> names) {
return new JimfsPath(this, root, names);
}
/** Parses the given strings as a path. */
public JimfsPath parsePath(String first, String... more) {
String joined = type.joiner().join(Iterables.filter(Lists.asList(first, more), NOT_EMPTY));
return toPath(type.parsePath(joined));
}
private JimfsPath toPath(ParseResult parsed) {
Name root = parsed.root() == null ? null : name(parsed.root());
Iterable<Name> names = names(parsed.names());
return createPath(root, names);
}
/** Returns the string form of the given path. */
public String toString(JimfsPath path) {
Name root = path.root();
String rootString = root == null ? null : root.toString();
Iterable<String> names = Iterables.transform(path.names(), Functions.toStringFunction());
return type.toString(rootString, names);
}
/** Creates a hash code for the given path. */
public int hash(JimfsPath path) {
// Note: JimfsPath.equals() is implemented using the compare() method below;
// equalityUsesCanonicalForm is taken into account there via the namesOrdering, which is set
// at construction time.
int hash = 31;
hash = 31 * hash + getFileSystem().hashCode();
final Name root = path.root();
final ImmutableList<Name> names = path.names();
if (equalityUsesCanonicalForm) {
// use hash codes of names themselves, which are based on the canonical form
hash = 31 * hash + (root == null ? 0 : root.hashCode());
for (Name name : names) {
hash = 31 * hash + name.hashCode();
}
} else {
// use hash codes from toString() form of names
hash = 31 * hash + (root == null ? 0 : root.toString().hashCode());
for (Name name : names) {
hash = 31 * hash + name.toString().hashCode();
}
}
return hash;
}
@Override
public int compare(JimfsPath a, JimfsPath b) {
return ComparisonChain.start()
.compare(a.root(), b.root(), rootOrdering)
.compare(a.names(), b.names(), namesOrdering)
.result();
}
/**
* Returns the URI for the given path. The given file system URI is the base against which the
* path is resolved to create the returned URI.
*/
public URI toUri(URI fileSystemUri, JimfsPath path) {
checkArgument(path.isAbsolute(), "path (%s) must be absolute", path);
String root = String.valueOf(path.root());
Iterable<String> names = Iterables.transform(path.names(), Functions.toStringFunction());
return type.toUri(fileSystemUri, root, names, Files.isDirectory(path, NOFOLLOW_LINKS));
}
/** Converts the path of the given URI into a path for this file system. */
public JimfsPath fromUri(URI uri) {
return toPath(type.fromUri(uri));
}
/**
* Returns a {@link PathMatcher} for the given syntax and pattern as specified by {@link
* FileSystem#getPathMatcher(String)}.
*/
public PathMatcher createPathMatcher(String syntaxAndPattern) {
return PathMatchers.getPathMatcher(
syntaxAndPattern,
type.getSeparator() + type.getOtherSeparators(),
equalityUsesCanonicalForm ? canonicalNormalizations : displayNormalizations);
}
private static final Predicate<Object> NOT_EMPTY =
new Predicate<Object>() {
@Override
public boolean apply(Object input) {
return !input.toString().isEmpty();
}
};
}