• 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.jimfs.Feature.FILE_CHANNEL;
22 import static com.google.common.jimfs.Jimfs.URI_SCHEME;
23 import static java.nio.file.StandardOpenOption.APPEND;
24 
25 import com.google.common.collect.ImmutableSet;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.net.URI;
30 import java.nio.channels.AsynchronousFileChannel;
31 import java.nio.channels.FileChannel;
32 import java.nio.channels.SeekableByteChannel;
33 import java.nio.file.AccessMode;
34 import java.nio.file.CopyOption;
35 import java.nio.file.DirectoryStream;
36 import java.nio.file.FileStore;
37 import java.nio.file.FileSystem;
38 import java.nio.file.FileSystems;
39 import java.nio.file.LinkOption;
40 import java.nio.file.OpenOption;
41 import java.nio.file.Path;
42 import java.nio.file.ProviderMismatchException;
43 import java.nio.file.attribute.BasicFileAttributes;
44 import java.nio.file.attribute.DosFileAttributes;
45 import java.nio.file.attribute.FileAttribute;
46 import java.nio.file.attribute.FileAttributeView;
47 import java.nio.file.spi.FileSystemProvider;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.concurrent.ExecutorService;
51 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
52 
53 /**
54  * {@link FileSystemProvider} implementation for Jimfs. This provider implements the actual file
55  * system operations but does not handle creation, caching or lookup of file systems. See {@link
56  * SystemJimfsFileSystemProvider}, which is the {@code META-INF/services/} entry for Jimfs, for
57  * those operations.
58  *
59  * @author Colin Decker
60  */
61 final class JimfsFileSystemProvider extends FileSystemProvider {
62 
63   private static final JimfsFileSystemProvider INSTANCE = new JimfsFileSystemProvider();
64 
65   static {
66     // Register the URL stream handler implementation.
67     try {
Handler.register()68       Handler.register();
69     } catch (Throwable e) {
70       // Couldn't set the system property needed to register the handler. Nothing we can do really.
71     }
72   }
73 
74   /** Returns the singleton instance of this provider. */
instance()75   static JimfsFileSystemProvider instance() {
76     return INSTANCE;
77   }
78 
79   @Override
getScheme()80   public String getScheme() {
81     return URI_SCHEME;
82   }
83 
84   @Override
newFileSystem(URI uri, Map<String, ?> env)85   public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
86     throw new UnsupportedOperationException(
87         "This method should not be called directly;"
88             + "use an overload of Jimfs.newFileSystem() to create a FileSystem.");
89   }
90 
91   @Override
newFileSystem(Path path, Map<String, ?> env)92   public FileSystem newFileSystem(Path path, Map<String, ?> env) throws IOException {
93     JimfsPath checkedPath = checkPath(path);
94     checkNotNull(env);
95 
96     URI pathUri = checkedPath.toUri();
97     URI jarUri = URI.create("jar:" + pathUri);
98 
99     try {
100       // pass the new jar:jimfs://... URI to be handled by ZipFileSystemProvider
101       return FileSystems.newFileSystem(jarUri, env);
102     } catch (Exception e) {
103       // if any exception occurred, assume the file wasn't a zip file and that we don't support
104       // viewing it as a file system
105       throw new UnsupportedOperationException(e);
106     }
107   }
108 
109   @Override
getFileSystem(URI uri)110   public FileSystem getFileSystem(URI uri) {
111     throw new UnsupportedOperationException(
112         "This method should not be called directly; "
113             + "use FileSystems.getFileSystem(URI) instead.");
114   }
115 
116   /** Gets the file system for the given path. */
getFileSystem(Path path)117   private static JimfsFileSystem getFileSystem(Path path) {
118     return (JimfsFileSystem) checkPath(path).getFileSystem();
119   }
120 
121   @Override
getPath(URI uri)122   public Path getPath(URI uri) {
123     throw new UnsupportedOperationException(
124         "This method should not be called directly; " + "use Paths.get(URI) instead.");
125   }
126 
checkPath(Path path)127   private static JimfsPath checkPath(Path path) {
128     if (path instanceof JimfsPath) {
129       return (JimfsPath) path;
130     }
131     throw new ProviderMismatchException(
132         "path " + path + " is not associated with a Jimfs file system");
133   }
134 
135   /** Returns the default file system view for the given path. */
getDefaultView(JimfsPath path)136   private static FileSystemView getDefaultView(JimfsPath path) {
137     return getFileSystem(path).getDefaultView();
138   }
139 
140   @Override
newFileChannel( Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)141   public FileChannel newFileChannel(
142       Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
143     JimfsPath checkedPath = checkPath(path);
144     if (!checkedPath.getJimfsFileSystem().getFileStore().supportsFeature(FILE_CHANNEL)) {
145       throw new UnsupportedOperationException();
146     }
147     return newJimfsFileChannel(checkedPath, options, attrs);
148   }
149 
newJimfsFileChannel( JimfsPath path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)150   private JimfsFileChannel newJimfsFileChannel(
151       JimfsPath path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
152       throws IOException {
153     ImmutableSet<OpenOption> opts = Options.getOptionsForChannel(options);
154     FileSystemView view = getDefaultView(path);
155     RegularFile file = view.getOrCreateRegularFile(path, opts, attrs);
156     return new JimfsFileChannel(file, opts, view.state());
157   }
158 
159   @Override
newByteChannel( Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)160   public SeekableByteChannel newByteChannel(
161       Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
162     JimfsPath checkedPath = checkPath(path);
163     JimfsFileChannel channel = newJimfsFileChannel(checkedPath, options, attrs);
164     return checkedPath.getJimfsFileSystem().getFileStore().supportsFeature(FILE_CHANNEL)
165         ? channel
166         : new DowngradedSeekableByteChannel(channel);
167   }
168 
169   @Override
newAsynchronousFileChannel( Path path, Set<? extends OpenOption> options, @NullableDecl ExecutorService executor, FileAttribute<?>... attrs)170   public AsynchronousFileChannel newAsynchronousFileChannel(
171       Path path,
172       Set<? extends OpenOption> options,
173       @NullableDecl ExecutorService executor,
174       FileAttribute<?>... attrs)
175       throws IOException {
176     // call newFileChannel and cast so that FileChannel support is checked there
177     JimfsFileChannel channel = (JimfsFileChannel) newFileChannel(path, options, attrs);
178     if (executor == null) {
179       JimfsFileSystem fileSystem = (JimfsFileSystem) path.getFileSystem();
180       executor = fileSystem.getDefaultThreadPool();
181     }
182     return channel.asAsynchronousFileChannel(executor);
183   }
184 
185   @Override
newInputStream(Path path, OpenOption... options)186   public InputStream newInputStream(Path path, OpenOption... options) throws IOException {
187     JimfsPath checkedPath = checkPath(path);
188     ImmutableSet<OpenOption> opts = Options.getOptionsForInputStream(options);
189     FileSystemView view = getDefaultView(checkedPath);
190     RegularFile file = view.getOrCreateRegularFile(checkedPath, opts, NO_ATTRS);
191     return new JimfsInputStream(file, view.state());
192   }
193 
194   private static final FileAttribute<?>[] NO_ATTRS = {};
195 
196   @Override
newOutputStream(Path path, OpenOption... options)197   public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException {
198     JimfsPath checkedPath = checkPath(path);
199     ImmutableSet<OpenOption> opts = Options.getOptionsForOutputStream(options);
200     FileSystemView view = getDefaultView(checkedPath);
201     RegularFile file = view.getOrCreateRegularFile(checkedPath, opts, NO_ATTRS);
202     return new JimfsOutputStream(file, opts.contains(APPEND), view.state());
203   }
204 
205   @Override
newDirectoryStream( Path dir, DirectoryStream.Filter<? super Path> filter)206   public DirectoryStream<Path> newDirectoryStream(
207       Path dir, DirectoryStream.Filter<? super Path> filter) throws IOException {
208     JimfsPath checkedPath = checkPath(dir);
209     return getDefaultView(checkedPath)
210         .newDirectoryStream(checkedPath, filter, Options.FOLLOW_LINKS, checkedPath);
211   }
212 
213   @Override
createDirectory(Path dir, FileAttribute<?>... attrs)214   public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
215     JimfsPath checkedPath = checkPath(dir);
216     FileSystemView view = getDefaultView(checkedPath);
217     view.createDirectory(checkedPath, attrs);
218   }
219 
220   @Override
createLink(Path link, Path existing)221   public void createLink(Path link, Path existing) throws IOException {
222     JimfsPath linkPath = checkPath(link);
223     JimfsPath existingPath = checkPath(existing);
224     checkArgument(
225         linkPath.getFileSystem().equals(existingPath.getFileSystem()),
226         "link and existing paths must belong to the same file system instance");
227     FileSystemView view = getDefaultView(linkPath);
228     view.link(linkPath, getDefaultView(existingPath), existingPath);
229   }
230 
231   @Override
createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs)232   public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs)
233       throws IOException {
234     JimfsPath linkPath = checkPath(link);
235     JimfsPath targetPath = checkPath(target);
236     checkArgument(
237         linkPath.getFileSystem().equals(targetPath.getFileSystem()),
238         "link and target paths must belong to the same file system instance");
239     FileSystemView view = getDefaultView(linkPath);
240     view.createSymbolicLink(linkPath, targetPath, attrs);
241   }
242 
243   @Override
readSymbolicLink(Path link)244   public Path readSymbolicLink(Path link) throws IOException {
245     JimfsPath checkedPath = checkPath(link);
246     return getDefaultView(checkedPath).readSymbolicLink(checkedPath);
247   }
248 
249   @Override
delete(Path path)250   public void delete(Path path) throws IOException {
251     JimfsPath checkedPath = checkPath(path);
252     FileSystemView view = getDefaultView(checkedPath);
253     view.deleteFile(checkedPath, FileSystemView.DeleteMode.ANY);
254   }
255 
256   @Override
copy(Path source, Path target, CopyOption... options)257   public void copy(Path source, Path target, CopyOption... options) throws IOException {
258     copy(source, target, Options.getCopyOptions(options), false);
259   }
260 
copy(Path source, Path target, ImmutableSet<CopyOption> options, boolean move)261   private void copy(Path source, Path target, ImmutableSet<CopyOption> options, boolean move)
262       throws IOException {
263     JimfsPath sourcePath = checkPath(source);
264     JimfsPath targetPath = checkPath(target);
265 
266     FileSystemView sourceView = getDefaultView(sourcePath);
267     FileSystemView targetView = getDefaultView(targetPath);
268     sourceView.copy(sourcePath, targetView, targetPath, options, move);
269   }
270 
271   @Override
move(Path source, Path target, CopyOption... options)272   public void move(Path source, Path target, CopyOption... options) throws IOException {
273     copy(source, target, Options.getMoveOptions(options), true);
274   }
275 
276   @Override
isSameFile(Path path, Path path2)277   public boolean isSameFile(Path path, Path path2) throws IOException {
278     if (path.equals(path2)) {
279       return true;
280     }
281 
282     if (!(path instanceof JimfsPath && path2 instanceof JimfsPath)) {
283       return false;
284     }
285 
286     JimfsPath checkedPath = (JimfsPath) path;
287     JimfsPath checkedPath2 = (JimfsPath) path2;
288 
289     FileSystemView view = getDefaultView(checkedPath);
290     FileSystemView view2 = getDefaultView(checkedPath2);
291 
292     return view.isSameFile(checkedPath, view2, checkedPath2);
293   }
294 
295   @Override
isHidden(Path path)296   public boolean isHidden(Path path) throws IOException {
297     // TODO(cgdecker): This should probably be configurable, but this seems fine for now
298     /*
299      * If the DOS view is supported, use the Windows isHidden method (check the dos:hidden
300      * attribute). Otherwise, use the Unix isHidden method (just check if the file name starts with
301      * ".").
302      */
303     JimfsPath checkedPath = checkPath(path);
304     FileSystemView view = getDefaultView(checkedPath);
305     if (getFileStore(path).supportsFileAttributeView("dos")) {
306       return view.readAttributes(checkedPath, DosFileAttributes.class, Options.NOFOLLOW_LINKS)
307           .isHidden();
308     }
309     return path.getNameCount() > 0 && path.getFileName().toString().startsWith(".");
310   }
311 
312   @Override
getFileStore(Path path)313   public FileStore getFileStore(Path path) throws IOException {
314     return getFileSystem(path).getFileStore();
315   }
316 
317   @Override
checkAccess(Path path, AccessMode... modes)318   public void checkAccess(Path path, AccessMode... modes) throws IOException {
319     JimfsPath checkedPath = checkPath(path);
320     getDefaultView(checkedPath).checkAccess(checkedPath);
321   }
322 
323   @NullableDecl
324   @Override
getFileAttributeView( Path path, Class<V> type, LinkOption... options)325   public <V extends FileAttributeView> V getFileAttributeView(
326       Path path, Class<V> type, LinkOption... options) {
327     JimfsPath checkedPath = checkPath(path);
328     return getDefaultView(checkedPath)
329         .getFileAttributeView(checkedPath, type, Options.getLinkOptions(options));
330   }
331 
332   @Override
readAttributes( Path path, Class<A> type, LinkOption... options)333   public <A extends BasicFileAttributes> A readAttributes(
334       Path path, Class<A> type, LinkOption... options) throws IOException {
335     JimfsPath checkedPath = checkPath(path);
336     return getDefaultView(checkedPath)
337         .readAttributes(checkedPath, type, Options.getLinkOptions(options));
338   }
339 
340   @Override
readAttributes(Path path, String attributes, LinkOption... options)341   public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options)
342       throws IOException {
343     JimfsPath checkedPath = checkPath(path);
344     return getDefaultView(checkedPath)
345         .readAttributes(checkedPath, attributes, Options.getLinkOptions(options));
346   }
347 
348   @Override
setAttribute(Path path, String attribute, Object value, LinkOption... options)349   public void setAttribute(Path path, String attribute, Object value, LinkOption... options)
350       throws IOException {
351     JimfsPath checkedPath = checkPath(path);
352     getDefaultView(checkedPath)
353         .setAttribute(checkedPath, attribute, value, Options.getLinkOptions(options));
354   }
355 }
356