diff options
Diffstat (limited to 'jimfs/src/main/java/com/google/common/jimfs/PathService.java')
-rw-r--r-- | jimfs/src/main/java/com/google/common/jimfs/PathService.java | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/jimfs/src/main/java/com/google/common/jimfs/PathService.java b/jimfs/src/main/java/com/google/common/jimfs/PathService.java new file mode 100644 index 0000000..49717bd --- /dev/null +++ b/jimfs/src/main/java/com/google/common/jimfs/PathService.java @@ -0,0 +1,269 @@ +/* + * 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(); + } + }; +} |