diff options
Diffstat (limited to 'jimfs/src/main/java/com/google/common/jimfs/PathType.java')
-rw-r--r-- | jimfs/src/main/java/com/google/common/jimfs/PathType.java | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/jimfs/src/main/java/com/google/common/jimfs/PathType.java b/jimfs/src/main/java/com/google/common/jimfs/PathType.java new file mode 100644 index 0000000..4e4d30e --- /dev/null +++ b/jimfs/src/main/java/com/google/common/jimfs/PathType.java @@ -0,0 +1,243 @@ +/* + * 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.checkNotNull; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.InvalidPathException; +import java.util.Arrays; +import org.checkerframework.checker.nullness.compatqual.NullableDecl; + +/** + * An object defining a specific type of path. Knows how to parse strings to a path and how to + * render a path as a string as well as what the path separator is and what other separators are + * recognized when parsing paths. + * + * @author Colin Decker + */ +public abstract class PathType { + + /** + * Returns a Unix-style path type. "/" is both the root and the only separator. Any path starting + * with "/" is considered absolute. The nul character ('\0') is disallowed in paths. + */ + public static PathType unix() { + return UnixPathType.INSTANCE; + } + + /** + * Returns a Windows-style path type. The canonical separator character is "\". "/" is also + * treated as a separator when parsing paths. + * + * <p>As much as possible, this implementation follows the information provided in <a + * href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx">this + * article</a>. Paths with drive-letter roots (e.g. "C:\") and paths with UNC roots (e.g. + * "\\host\share\") are supported. + * + * <p>Two Windows path features are not currently supported as they are too Windows-specific: + * + * <ul> + * <li>Relative paths containing a drive-letter root, for example "C:" or "C:foo\bar". Such + * paths have a root component and optionally have names, but are <i>relative</i> paths, + * relative to the working directory of the drive identified by the root. + * <li>Absolute paths with no root, for example "\foo\bar". Such paths are absolute paths on the + * current drive. + * </ul> + */ + public static PathType windows() { + return WindowsPathType.INSTANCE; + } + + private final boolean allowsMultipleRoots; + private final String separator; + private final String otherSeparators; + private final Joiner joiner; + private final Splitter splitter; + + protected PathType(boolean allowsMultipleRoots, char separator, char... otherSeparators) { + this.separator = String.valueOf(separator); + this.allowsMultipleRoots = allowsMultipleRoots; + this.otherSeparators = String.valueOf(otherSeparators); + this.joiner = Joiner.on(separator); + this.splitter = createSplitter(separator, otherSeparators); + } + + private static final char[] regexReservedChars = "^$.?+*\\[]{}()".toCharArray(); + + static { + Arrays.sort(regexReservedChars); + } + + private static boolean isRegexReserved(char c) { + return Arrays.binarySearch(regexReservedChars, c) >= 0; + } + + private static Splitter createSplitter(char separator, char... otherSeparators) { + if (otherSeparators.length == 0) { + return Splitter.on(separator).omitEmptyStrings(); + } + + // TODO(cgdecker): When CharMatcher is out of @Beta, us Splitter.on(CharMatcher) + StringBuilder patternBuilder = new StringBuilder(); + patternBuilder.append("["); + appendToRegex(separator, patternBuilder); + for (char other : otherSeparators) { + appendToRegex(other, patternBuilder); + } + patternBuilder.append("]"); + return Splitter.onPattern(patternBuilder.toString()).omitEmptyStrings(); + } + + private static void appendToRegex(char separator, StringBuilder patternBuilder) { + if (isRegexReserved(separator)) { + patternBuilder.append("\\"); + } + patternBuilder.append(separator); + } + + /** Returns whether or not this type of path allows multiple root directories. */ + public final boolean allowsMultipleRoots() { + return allowsMultipleRoots; + } + + /** + * Returns the canonical separator for this path type. The returned string always has a length of + * one. + */ + public final String getSeparator() { + return separator; + } + + /** + * Returns the other separators that are recognized when parsing a path. If no other separators + * are recognized, the empty string is returned. + */ + public final String getOtherSeparators() { + return otherSeparators; + } + + /** Returns the path joiner for this path type. */ + public final Joiner joiner() { + return joiner; + } + + /** Returns the path splitter for this path type. */ + public final Splitter splitter() { + return splitter; + } + + /** Returns an empty path. */ + protected final ParseResult emptyPath() { + return new ParseResult(null, ImmutableList.of("")); + } + + /** + * Parses the given strings as a path. + * + * @throws InvalidPathException if the path isn't valid for this path type + */ + public abstract ParseResult parsePath(String path); + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + /** Returns the string form of the given path. */ + public abstract String toString(@NullableDecl String root, Iterable<String> names); + + /** + * Returns the string form of the given path for use in the path part of a URI. The root element + * is not nullable as the path must be absolute. The elements of the returned path <i>do not</i> + * need to be escaped. The {@code directory} boolean indicates whether the file the URI is for is + * known to be a directory. + */ + protected abstract String toUriPath(String root, Iterable<String> names, boolean directory); + + /** + * Parses a path from the given URI path. + * + * @throws InvalidPathException if the given path isn't valid for this path type + */ + protected abstract ParseResult parseUriPath(String uriPath); + + /** + * Creates a URI for the path with the given root and names in the file system with the given URI. + */ + public final URI toUri( + URI fileSystemUri, String root, Iterable<String> names, boolean directory) { + String path = toUriPath(root, names, directory); + try { + // it should not suck this much to create a new URI that's the same except with a path set =( + // need to do it this way for automatic path escaping + return new URI( + fileSystemUri.getScheme(), + fileSystemUri.getUserInfo(), + fileSystemUri.getHost(), + fileSystemUri.getPort(), + path, + null, + null); + } catch (URISyntaxException e) { + throw new AssertionError(e); + } + } + + /** Parses a path from the given URI. */ + public final ParseResult fromUri(URI uri) { + return parseUriPath(uri.getPath()); + } + + /** Simple result of parsing a path. */ + public static final class ParseResult { + + @NullableDecl private final String root; + private final Iterable<String> names; + + public ParseResult(@NullableDecl String root, Iterable<String> names) { + this.root = root; + this.names = checkNotNull(names); + } + + /** Returns whether or not this result is an absolute path. */ + public boolean isAbsolute() { + return root != null; + } + + /** Returns whether or not this result represents a root path. */ + public boolean isRoot() { + return root != null && Iterables.isEmpty(names); + } + + /** Returns the parsed root element, or null if there was no root. */ + @NullableDecl + public String root() { + return root; + } + + /** Returns the parsed name elements. */ + public Iterable<String> names() { + return names; + } + } +} |