• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.jimfs;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 
21 import com.google.common.base.Joiner;
22 import com.google.common.base.Splitter;
23 import com.google.common.collect.ImmutableList;
24 import com.google.common.collect.Iterables;
25 import java.net.URI;
26 import java.net.URISyntaxException;
27 import java.nio.file.InvalidPathException;
28 import java.util.Arrays;
29 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
30 
31 /**
32  * An object defining a specific type of path. Knows how to parse strings to a path and how to
33  * render a path as a string as well as what the path separator is and what other separators are
34  * recognized when parsing paths.
35  *
36  * @author Colin Decker
37  */
38 public abstract class PathType {
39 
40   /**
41    * Returns a Unix-style path type. "/" is both the root and the only separator. Any path starting
42    * with "/" is considered absolute. The nul character ('\0') is disallowed in paths.
43    */
unix()44   public static PathType unix() {
45     return UnixPathType.INSTANCE;
46   }
47 
48   /**
49    * Returns a Windows-style path type. The canonical separator character is "\". "/" is also
50    * treated as a separator when parsing paths.
51    *
52    * <p>As much as possible, this implementation follows the information provided in <a
53    * href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx">this
54    * article</a>. Paths with drive-letter roots (e.g. "C:\") and paths with UNC roots (e.g.
55    * "\\host\share\") are supported.
56    *
57    * <p>Two Windows path features are not currently supported as they are too Windows-specific:
58    *
59    * <ul>
60    *   <li>Relative paths containing a drive-letter root, for example "C:" or "C:foo\bar". Such
61    *       paths have a root component and optionally have names, but are <i>relative</i> paths,
62    *       relative to the working directory of the drive identified by the root.
63    *   <li>Absolute paths with no root, for example "\foo\bar". Such paths are absolute paths on the
64    *       current drive.
65    * </ul>
66    */
windows()67   public static PathType windows() {
68     return WindowsPathType.INSTANCE;
69   }
70 
71   private final boolean allowsMultipleRoots;
72   private final String separator;
73   private final String otherSeparators;
74   private final Joiner joiner;
75   private final Splitter splitter;
76 
PathType(boolean allowsMultipleRoots, char separator, char... otherSeparators)77   protected PathType(boolean allowsMultipleRoots, char separator, char... otherSeparators) {
78     this.separator = String.valueOf(separator);
79     this.allowsMultipleRoots = allowsMultipleRoots;
80     this.otherSeparators = String.valueOf(otherSeparators);
81     this.joiner = Joiner.on(separator);
82     this.splitter = createSplitter(separator, otherSeparators);
83   }
84 
85   private static final char[] regexReservedChars = "^$.?+*\\[]{}()".toCharArray();
86 
87   static {
88     Arrays.sort(regexReservedChars);
89   }
90 
isRegexReserved(char c)91   private static boolean isRegexReserved(char c) {
92     return Arrays.binarySearch(regexReservedChars, c) >= 0;
93   }
94 
createSplitter(char separator, char... otherSeparators)95   private static Splitter createSplitter(char separator, char... otherSeparators) {
96     if (otherSeparators.length == 0) {
97       return Splitter.on(separator).omitEmptyStrings();
98     }
99 
100     // TODO(cgdecker): When CharMatcher is out of @Beta, us Splitter.on(CharMatcher)
101     StringBuilder patternBuilder = new StringBuilder();
102     patternBuilder.append("[");
103     appendToRegex(separator, patternBuilder);
104     for (char other : otherSeparators) {
105       appendToRegex(other, patternBuilder);
106     }
107     patternBuilder.append("]");
108     return Splitter.onPattern(patternBuilder.toString()).omitEmptyStrings();
109   }
110 
appendToRegex(char separator, StringBuilder patternBuilder)111   private static void appendToRegex(char separator, StringBuilder patternBuilder) {
112     if (isRegexReserved(separator)) {
113       patternBuilder.append("\\");
114     }
115     patternBuilder.append(separator);
116   }
117 
118   /** Returns whether or not this type of path allows multiple root directories. */
allowsMultipleRoots()119   public final boolean allowsMultipleRoots() {
120     return allowsMultipleRoots;
121   }
122 
123   /**
124    * Returns the canonical separator for this path type. The returned string always has a length of
125    * one.
126    */
getSeparator()127   public final String getSeparator() {
128     return separator;
129   }
130 
131   /**
132    * Returns the other separators that are recognized when parsing a path. If no other separators
133    * are recognized, the empty string is returned.
134    */
getOtherSeparators()135   public final String getOtherSeparators() {
136     return otherSeparators;
137   }
138 
139   /** Returns the path joiner for this path type. */
joiner()140   public final Joiner joiner() {
141     return joiner;
142   }
143 
144   /** Returns the path splitter for this path type. */
splitter()145   public final Splitter splitter() {
146     return splitter;
147   }
148 
149   /** Returns an empty path. */
emptyPath()150   protected final ParseResult emptyPath() {
151     return new ParseResult(null, ImmutableList.of(""));
152   }
153 
154   /**
155    * Parses the given strings as a path.
156    *
157    * @throws InvalidPathException if the path isn't valid for this path type
158    */
parsePath(String path)159   public abstract ParseResult parsePath(String path);
160 
161   @Override
toString()162   public String toString() {
163     return getClass().getSimpleName();
164   }
165 
166   /** Returns the string form of the given path. */
toString(@ullableDecl String root, Iterable<String> names)167   public abstract String toString(@NullableDecl String root, Iterable<String> names);
168 
169   /**
170    * Returns the string form of the given path for use in the path part of a URI. The root element
171    * is not nullable as the path must be absolute. The elements of the returned path <i>do not</i>
172    * need to be escaped. The {@code directory} boolean indicates whether the file the URI is for is
173    * known to be a directory.
174    */
toUriPath(String root, Iterable<String> names, boolean directory)175   protected abstract String toUriPath(String root, Iterable<String> names, boolean directory);
176 
177   /**
178    * Parses a path from the given URI path.
179    *
180    * @throws InvalidPathException if the given path isn't valid for this path type
181    */
parseUriPath(String uriPath)182   protected abstract ParseResult parseUriPath(String uriPath);
183 
184   /**
185    * Creates a URI for the path with the given root and names in the file system with the given URI.
186    */
toUri( URI fileSystemUri, String root, Iterable<String> names, boolean directory)187   public final URI toUri(
188       URI fileSystemUri, String root, Iterable<String> names, boolean directory) {
189     String path = toUriPath(root, names, directory);
190     try {
191       // it should not suck this much to create a new URI that's the same except with a path set =(
192       // need to do it this way for automatic path escaping
193       return new URI(
194           fileSystemUri.getScheme(),
195           fileSystemUri.getUserInfo(),
196           fileSystemUri.getHost(),
197           fileSystemUri.getPort(),
198           path,
199           null,
200           null);
201     } catch (URISyntaxException e) {
202       throw new AssertionError(e);
203     }
204   }
205 
206   /** Parses a path from the given URI. */
fromUri(URI uri)207   public final ParseResult fromUri(URI uri) {
208     return parseUriPath(uri.getPath());
209   }
210 
211   /** Simple result of parsing a path. */
212   public static final class ParseResult {
213 
214     @NullableDecl private final String root;
215     private final Iterable<String> names;
216 
ParseResult(@ullableDecl String root, Iterable<String> names)217     public ParseResult(@NullableDecl String root, Iterable<String> names) {
218       this.root = root;
219       this.names = checkNotNull(names);
220     }
221 
222     /** Returns whether or not this result is an absolute path. */
isAbsolute()223     public boolean isAbsolute() {
224       return root != null;
225     }
226 
227     /** Returns whether or not this result represents a root path. */
isRoot()228     public boolean isRoot() {
229       return root != null && Iterables.isEmpty(names);
230     }
231 
232     /** Returns the parsed root element, or null if there was no root. */
233     @NullableDecl
root()234     public String root() {
235       return root;
236     }
237 
238     /** Returns the parsed name elements. */
names()239     public Iterable<String> names() {
240       return names;
241     }
242   }
243 }
244