diff options
author | Colin Decker <cgdecker@google.com> | 2014-06-10 10:21:31 -0400 |
---|---|---|
committer | Colin Decker <cgdecker@google.com> | 2014-06-10 10:24:54 -0400 |
commit | 4739aee7e8e6ee9c66801541921f42f9af93387d (patch) | |
tree | c38f2facff1ed2c757bb72aeea10d4ec241009cf /jimfs/src/main/java/com/google/common/jimfs/JimfsPath.java | |
parent | 357d446ed22d70b86993c43bb360cff26123d740 (diff) | |
download | jimfs-4739aee7e8e6ee9c66801541921f42f9af93387d.tar.gz |
Move all classes to com.google.common.jimfs.
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=68905401
Diffstat (limited to 'jimfs/src/main/java/com/google/common/jimfs/JimfsPath.java')
-rw-r--r-- | jimfs/src/main/java/com/google/common/jimfs/JimfsPath.java | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsPath.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsPath.java new file mode 100644 index 0000000..e157efa --- /dev/null +++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsPath.java @@ -0,0 +1,452 @@ +/* + * 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 com.google.common.base.Objects; +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.ProviderMismatchException; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.AbstractList; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; + +import javax.annotation.Nullable; + +/** + * Jimfs implementation of {@link Path}. Creation of new {@code Path} objects is delegated to the + * file system's {@link PathService}. + * + * @author Colin Decker + */ +final class JimfsPath implements Path { + + @Nullable + private final Name root; + private final ImmutableList<Name> names; + private final PathService pathService; + + public JimfsPath(PathService pathService, @Nullable Name root, Iterable<Name> names) { + this.pathService = checkNotNull(pathService); + this.root = root; + this.names = ImmutableList.copyOf(names); + } + + /** + * Returns the root name, or null if there is no root. + */ + @Nullable + public Name root() { + return root; + } + + /** + * Returns the list of name elements. + */ + public ImmutableList<Name> names() { + return names; + } + + /** + * Returns the file name of this path. Unlike {@link #getFileName()}, this may return the name of + * the root if this is a root path. + */ + @Nullable + public Name name() { + if (!names.isEmpty()) { + return Iterables.getLast(names); + } + return root; + } + + /** + * Returns whether or not this is the empty path, with no root and a single, empty string, name. + */ + public boolean isEmptyPath() { + return root == null && names.size() == 1 && names.get(0).toString().isEmpty(); + } + + @Override + public FileSystem getFileSystem() { + return pathService.getFileSystem(); + } + + /** + * Equivalent to {@link #getFileSystem()} but with a return type of {@code JimfsFileSystem}. + * {@code getFileSystem()}'s return type is left as {@code FileSystem} to make testing paths + * easier (as long as methods that access the file system in some way are not called, the file + * system can be a fake file system instance). + */ + public JimfsFileSystem getJimfsFileSystem() { + return (JimfsFileSystem) pathService.getFileSystem(); + } + + @Override + public boolean isAbsolute() { + return root != null; + } + + @Override + public JimfsPath getRoot() { + if (root == null) { + return null; + } + return pathService.createRoot(root); + } + + @Override + public JimfsPath getFileName() { + return names.isEmpty() ? null : getName(names.size() - 1); + } + + @Override + public JimfsPath getParent() { + if (names.isEmpty() || names.size() == 1 && root == null) { + return null; + } + + return pathService.createPath(root, names.subList(0, names.size() - 1)); + } + + @Override + public int getNameCount() { + return names.size(); + } + + @Override + public JimfsPath getName(int index) { + checkArgument(index >= 0 && index < names.size(), + "index (%s) must be >= 0 and < name count (%s)", index, names.size()); + return pathService.createFileName(names.get(index)); + } + + @Override + public JimfsPath subpath(int beginIndex, int endIndex) { + checkArgument(beginIndex >= 0 && endIndex <= names.size() && endIndex > beginIndex, + "beginIndex (%s) must be >= 0; endIndex (%s) must be <= name count (%s) and > beginIndex", + beginIndex, endIndex, names.size()); + return pathService.createRelativePath(names.subList(beginIndex, endIndex)); + } + + /** + * Returns true if list starts with all elements of other in the same order. + */ + private static boolean startsWith(List<?> list, List<?> other) { + return list.size() >= other.size() && list.subList(0, other.size()).equals(other); + } + + @Override + public boolean startsWith(Path other) { + JimfsPath otherPath = checkPath(other); + return otherPath != null + && getFileSystem().equals(otherPath.getFileSystem()) + && Objects.equal(root, otherPath.root) + && startsWith(names, otherPath.names); + } + + @Override + public boolean startsWith(String other) { + return startsWith(pathService.parsePath(other)); + } + + @Override + public boolean endsWith(Path other) { + JimfsPath otherPath = checkPath(other); + if (otherPath == null) { + return false; + } + + if (otherPath.isAbsolute()) { + return compareTo(otherPath) == 0; + } + return startsWith(names.reverse(), otherPath.names.reverse()); + } + + @Override + public boolean endsWith(String other) { + return endsWith(pathService.parsePath(other)); + } + + @Override + public JimfsPath normalize() { + if (isNormal()) { + return this; + } + + Deque<Name> newNames = new ArrayDeque<>(); + for (Name name : names) { + if (name.equals(Name.PARENT)) { + Name lastName = newNames.peekLast(); + if (lastName != null && !lastName.equals(Name.PARENT)) { + newNames.removeLast(); + } else if (!isAbsolute()) { + // if there's a root and we have an extra ".." that would go up above the root, ignore it + newNames.add(name); + } + } else if (!name.equals(Name.SELF)) { + newNames.add(name); + } + } + + return newNames.equals(names) ? this : pathService.createPath(root, newNames); + } + + /** + * Returns whether or not this path is in a normalized form. It's normal if it both contains no + * "." names and contains no ".." names in a location other than the start of the path. + */ + private boolean isNormal() { + if (getNameCount() == 0 || getNameCount() == 1 && !isAbsolute()) { + return true; + } + + boolean foundNonParentName = isAbsolute(); // if there's a root, the path doesn't start with .. + boolean normal = true; + for (Name name : names) { + if (name.equals(Name.PARENT)) { + if (foundNonParentName) { + normal = false; + break; + } + } else { + if (name.equals(Name.SELF)) { + normal = false; + break; + } + + foundNonParentName = true; + } + } + return normal; + } + + /** + * Resolves the given name against this path. The name is assumed not to be a root name. + */ + JimfsPath resolve(Name name) { + if (name.toString().isEmpty()) { + return this; + } + return pathService.createPathInternal(root, ImmutableList.<Name>builder() + .addAll(names) + .add(name) + .build()); + } + + @Override + public JimfsPath resolve(Path other) { + JimfsPath otherPath = checkPath(other); + if (otherPath == null) { + throw new ProviderMismatchException(other.toString()); + } + + if (isEmptyPath() || otherPath.isAbsolute()) { + return otherPath; + } + if (otherPath.isEmptyPath()) { + return this; + } + return pathService.createPath(root, ImmutableList.<Name>builder() + .addAll(names) + .addAll(otherPath.names) + .build()); + } + + @Override + public JimfsPath resolve(String other) { + return resolve(pathService.parsePath(other)); + } + + @Override + public JimfsPath resolveSibling(Path other) { + JimfsPath otherPath = checkPath(other); + if (otherPath == null) { + throw new ProviderMismatchException(other.toString()); + } + + if (otherPath.isAbsolute()) { + return otherPath; + } + JimfsPath parent = getParent(); + if (parent == null) { + return otherPath; + } + return parent.resolve(other); + } + + @Override + public JimfsPath resolveSibling(String other) { + return resolveSibling(pathService.parsePath(other)); + } + + @Override + public JimfsPath relativize(Path other) { + JimfsPath otherPath = checkPath(other); + if (otherPath == null) { + throw new ProviderMismatchException(other.toString()); + } + + checkArgument(Objects.equal(root, otherPath.root), + "Paths have different roots: %s, %s", this, other); + + if (equals(other)) { + return pathService.emptyPath(); + } + + if (isEmptyPath()) { + return otherPath; + } + + ImmutableList<Name> otherNames = otherPath.names; + int sharedSubsequenceLength = 0; + for (int i = 0; i < Math.min(getNameCount(), otherNames.size()); i++) { + if (names.get(i).equals(otherNames.get(i))) { + sharedSubsequenceLength++; + } else { + break; + } + } + + int extraNamesInThis = Math.max(0, getNameCount() - sharedSubsequenceLength); + + ImmutableList<Name> extraNamesInOther = (otherNames.size() <= sharedSubsequenceLength) + ? ImmutableList.<Name>of() + : otherNames.subList(sharedSubsequenceLength, otherNames.size()); + + List<Name> parts = new ArrayList<>(extraNamesInThis + extraNamesInOther.size()); + + // add .. for each extra name in this path + parts.addAll(Collections.nCopies(extraNamesInThis, Name.PARENT)); + // add each extra name in the other path + parts.addAll(extraNamesInOther); + + return pathService.createRelativePath(parts); + } + + @Override + public JimfsPath toAbsolutePath() { + return isAbsolute() ? this : getJimfsFileSystem().getWorkingDirectory().resolve(this); + } + + @Override + public JimfsPath toRealPath(LinkOption... options) throws IOException { + return getJimfsFileSystem().getDefaultView() + .toRealPath(this, pathService, Options.getLinkOptions(options)); + } + + @Override + public WatchKey register(WatchService watcher, WatchEvent.Kind<?>[] events, + WatchEvent.Modifier... modifiers) throws IOException { + checkNotNull(modifiers); + return register(watcher, events); + } + + @Override + public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) throws IOException { + checkNotNull(watcher); + checkNotNull(events); + if (!(watcher instanceof AbstractWatchService)) { + throw new IllegalArgumentException( + "watcher (" + watcher + ") is not associated with this file system"); + } + + AbstractWatchService service = (AbstractWatchService) watcher; + return service.register(this, Arrays.asList(events)); + } + + @Override + public URI toUri() { + return getJimfsFileSystem().toUri(this); + } + + @Override + public File toFile() { + // documented as unsupported for anything but the default file system + throw new UnsupportedOperationException(); + } + + @Override + public Iterator<Path> iterator() { + return asList().iterator(); + } + + private List<Path> asList() { + return new AbstractList<Path>() { + @Override + public Path get(int index) { + return getName(index); + } + + @Override + public int size() { + return getNameCount(); + } + }; + } + + @Override + public int compareTo(Path other) { + // documented to throw CCE if other is associated with a different FileSystemProvider + JimfsPath otherPath = (JimfsPath) other; + return ComparisonChain.start() + .compare(getJimfsFileSystem().getUri(), ((JimfsPath) other).getJimfsFileSystem().getUri()) + .compare(this, otherPath, pathService) + .result(); + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof JimfsPath && compareTo((JimfsPath) obj) == 0; + } + + @Override + public int hashCode() { + return pathService.hash(this); + } + + @Override + public String toString() { + return pathService.toString(this); + } + + @Nullable + private JimfsPath checkPath(Path other) { + if (checkNotNull(other) instanceof JimfsPath + && other.getFileSystem().equals(getFileSystem())) { + return (JimfsPath) other; + } + return null; + } +} |