• 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.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static com.google.common.base.Preconditions.checkState;
22 import static com.google.common.jimfs.PathType.ParseResult;
23 import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
24 
25 import com.google.common.annotations.VisibleForTesting;
26 import com.google.common.base.Functions;
27 import com.google.common.base.Predicate;
28 import com.google.common.collect.ComparisonChain;
29 import com.google.common.collect.ImmutableList;
30 import com.google.common.collect.ImmutableSet;
31 import com.google.common.collect.Iterables;
32 import com.google.common.collect.Lists;
33 import com.google.common.collect.Ordering;
34 import java.net.URI;
35 import java.nio.file.FileSystem;
36 import java.nio.file.Files;
37 import java.nio.file.PathMatcher;
38 import java.util.ArrayList;
39 import java.util.Comparator;
40 import java.util.List;
41 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
42 
43 /**
44  * Service for creating {@link JimfsPath} instances and handling other path-related operations.
45  *
46  * @author Colin Decker
47  */
48 final class PathService implements Comparator<JimfsPath> {
49 
50   private static final Ordering<Name> DISPLAY_ROOT_ORDERING = Name.displayOrdering().nullsLast();
51   private static final Ordering<Iterable<Name>> DISPLAY_NAMES_ORDERING =
52       Name.displayOrdering().lexicographical();
53 
54   private static final Ordering<Name> CANONICAL_ROOT_ORDERING =
55       Name.canonicalOrdering().nullsLast();
56   private static final Ordering<Iterable<Name>> CANONICAL_NAMES_ORDERING =
57       Name.canonicalOrdering().lexicographical();
58 
59   private final PathType type;
60 
61   private final ImmutableSet<PathNormalization> displayNormalizations;
62   private final ImmutableSet<PathNormalization> canonicalNormalizations;
63   private final boolean equalityUsesCanonicalForm;
64 
65   private final Ordering<Name> rootOrdering;
66   private final Ordering<Iterable<Name>> namesOrdering;
67 
68   private volatile FileSystem fileSystem;
69   private volatile JimfsPath emptyPath;
70 
PathService(Configuration config)71   PathService(Configuration config) {
72     this(
73         config.pathType,
74         config.nameDisplayNormalization,
75         config.nameCanonicalNormalization,
76         config.pathEqualityUsesCanonicalForm);
77   }
78 
PathService( PathType type, Iterable<PathNormalization> displayNormalizations, Iterable<PathNormalization> canonicalNormalizations, boolean equalityUsesCanonicalForm)79   PathService(
80       PathType type,
81       Iterable<PathNormalization> displayNormalizations,
82       Iterable<PathNormalization> canonicalNormalizations,
83       boolean equalityUsesCanonicalForm) {
84     this.type = checkNotNull(type);
85     this.displayNormalizations = ImmutableSet.copyOf(displayNormalizations);
86     this.canonicalNormalizations = ImmutableSet.copyOf(canonicalNormalizations);
87     this.equalityUsesCanonicalForm = equalityUsesCanonicalForm;
88 
89     this.rootOrdering = equalityUsesCanonicalForm ? CANONICAL_ROOT_ORDERING : DISPLAY_ROOT_ORDERING;
90     this.namesOrdering =
91         equalityUsesCanonicalForm ? CANONICAL_NAMES_ORDERING : DISPLAY_NAMES_ORDERING;
92   }
93 
94   /** Sets the file system to use for created paths. */
setFileSystem(FileSystem fileSystem)95   public void setFileSystem(FileSystem fileSystem) {
96     // allowed to not be JimfsFileSystem for testing purposes only
97     checkState(this.fileSystem == null, "may not set fileSystem twice");
98     this.fileSystem = checkNotNull(fileSystem);
99   }
100 
101   /** Returns the file system this service is for. */
getFileSystem()102   public FileSystem getFileSystem() {
103     return fileSystem;
104   }
105 
106   /** Returns the default path separator. */
getSeparator()107   public String getSeparator() {
108     return type.getSeparator();
109   }
110 
111   /** Returns an empty path which has a single name, the empty string. */
emptyPath()112   public JimfsPath emptyPath() {
113     JimfsPath result = emptyPath;
114     if (result == null) {
115       // use createPathInternal to avoid recursive call from createPath()
116       result = createPathInternal(null, ImmutableList.of(Name.EMPTY));
117       emptyPath = result;
118       return result;
119     }
120     return result;
121   }
122 
123   /** Returns the {@link Name} form of the given string. */
name(String name)124   public Name name(String name) {
125     switch (name) {
126       case "":
127         return Name.EMPTY;
128       case ".":
129         return Name.SELF;
130       case "..":
131         return Name.PARENT;
132       default:
133         String display = PathNormalization.normalize(name, displayNormalizations);
134         String canonical = PathNormalization.normalize(name, canonicalNormalizations);
135         return Name.create(display, canonical);
136     }
137   }
138 
139   /** Returns the {@link Name} forms of the given strings. */
140   @VisibleForTesting
names(Iterable<String> names)141   List<Name> names(Iterable<String> names) {
142     List<Name> result = new ArrayList<>();
143     for (String name : names) {
144       result.add(name(name));
145     }
146     return result;
147   }
148 
149   /** Returns a root path with the given name. */
createRoot(Name root)150   public JimfsPath createRoot(Name root) {
151     return createPath(checkNotNull(root), ImmutableList.<Name>of());
152   }
153 
154   /** Returns a single filename path with the given name. */
createFileName(Name name)155   public JimfsPath createFileName(Name name) {
156     return createPath(null, ImmutableList.of(name));
157   }
158 
159   /** Returns a relative path with the given names. */
createRelativePath(Iterable<Name> names)160   public JimfsPath createRelativePath(Iterable<Name> names) {
161     return createPath(null, ImmutableList.copyOf(names));
162   }
163 
164   /** Returns a path with the given root (or no root, if null) and the given names. */
createPath(@ullableDecl Name root, Iterable<Name> names)165   public JimfsPath createPath(@NullableDecl Name root, Iterable<Name> names) {
166     ImmutableList<Name> nameList = ImmutableList.copyOf(Iterables.filter(names, NOT_EMPTY));
167     if (root == null && nameList.isEmpty()) {
168       // ensure the canonical empty path (one empty string name) is used rather than a path with
169       // no root and no names
170       return emptyPath();
171     }
172     return createPathInternal(root, nameList);
173   }
174 
175   /** Returns a path with the given root (or no root, if null) and the given names. */
createPathInternal(@ullableDecl Name root, Iterable<Name> names)176   protected final JimfsPath createPathInternal(@NullableDecl Name root, Iterable<Name> names) {
177     return new JimfsPath(this, root, names);
178   }
179 
180   /** Parses the given strings as a path. */
parsePath(String first, String... more)181   public JimfsPath parsePath(String first, String... more) {
182     String joined = type.joiner().join(Iterables.filter(Lists.asList(first, more), NOT_EMPTY));
183     return toPath(type.parsePath(joined));
184   }
185 
toPath(ParseResult parsed)186   private JimfsPath toPath(ParseResult parsed) {
187     Name root = parsed.root() == null ? null : name(parsed.root());
188     Iterable<Name> names = names(parsed.names());
189     return createPath(root, names);
190   }
191 
192   /** Returns the string form of the given path. */
toString(JimfsPath path)193   public String toString(JimfsPath path) {
194     Name root = path.root();
195     String rootString = root == null ? null : root.toString();
196     Iterable<String> names = Iterables.transform(path.names(), Functions.toStringFunction());
197     return type.toString(rootString, names);
198   }
199 
200   /** Creates a hash code for the given path. */
hash(JimfsPath path)201   public int hash(JimfsPath path) {
202     // Note: JimfsPath.equals() is implemented using the compare() method below;
203     // equalityUsesCanonicalForm is taken into account there via the namesOrdering, which is set
204     // at construction time.
205     int hash = 31;
206     hash = 31 * hash + getFileSystem().hashCode();
207 
208     final Name root = path.root();
209     final ImmutableList<Name> names = path.names();
210 
211     if (equalityUsesCanonicalForm) {
212       // use hash codes of names themselves, which are based on the canonical form
213       hash = 31 * hash + (root == null ? 0 : root.hashCode());
214       for (Name name : names) {
215         hash = 31 * hash + name.hashCode();
216       }
217     } else {
218       // use hash codes from toString() form of names
219       hash = 31 * hash + (root == null ? 0 : root.toString().hashCode());
220       for (Name name : names) {
221         hash = 31 * hash + name.toString().hashCode();
222       }
223     }
224     return hash;
225   }
226 
227   @Override
compare(JimfsPath a, JimfsPath b)228   public int compare(JimfsPath a, JimfsPath b) {
229     return ComparisonChain.start()
230         .compare(a.root(), b.root(), rootOrdering)
231         .compare(a.names(), b.names(), namesOrdering)
232         .result();
233   }
234 
235   /**
236    * Returns the URI for the given path. The given file system URI is the base against which the
237    * path is resolved to create the returned URI.
238    */
toUri(URI fileSystemUri, JimfsPath path)239   public URI toUri(URI fileSystemUri, JimfsPath path) {
240     checkArgument(path.isAbsolute(), "path (%s) must be absolute", path);
241     String root = String.valueOf(path.root());
242     Iterable<String> names = Iterables.transform(path.names(), Functions.toStringFunction());
243     return type.toUri(fileSystemUri, root, names, Files.isDirectory(path, NOFOLLOW_LINKS));
244   }
245 
246   /** Converts the path of the given URI into a path for this file system. */
fromUri(URI uri)247   public JimfsPath fromUri(URI uri) {
248     return toPath(type.fromUri(uri));
249   }
250 
251   /**
252    * Returns a {@link PathMatcher} for the given syntax and pattern as specified by {@link
253    * FileSystem#getPathMatcher(String)}.
254    */
createPathMatcher(String syntaxAndPattern)255   public PathMatcher createPathMatcher(String syntaxAndPattern) {
256     return PathMatchers.getPathMatcher(
257         syntaxAndPattern,
258         type.getSeparator() + type.getOtherSeparators(),
259         equalityUsesCanonicalForm ? canonicalNormalizations : displayNormalizations);
260   }
261 
262   private static final Predicate<Object> NOT_EMPTY =
263       new Predicate<Object>() {
264         @Override
265         public boolean apply(Object input) {
266           return !input.toString().isEmpty();
267         }
268       };
269 }
270