/* * 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 static com.google.common.jimfs.Feature.FILE_CHANNEL; import static com.google.common.jimfs.Jimfs.URI_SCHEME; import static java.nio.file.StandardOpenOption.APPEND; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; import java.nio.file.AccessMode; import java.nio.file.CopyOption; import java.nio.file.DirectoryStream; import java.nio.file.FileStore; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.LinkOption; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.ProviderMismatchException; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.DosFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.nio.file.spi.FileSystemProvider; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import org.checkerframework.checker.nullness.compatqual.NullableDecl; /** * {@link FileSystemProvider} implementation for Jimfs. This provider implements the actual file * system operations but does not handle creation, caching or lookup of file systems. See {@link * SystemJimfsFileSystemProvider}, which is the {@code META-INF/services/} entry for Jimfs, for * those operations. * * @author Colin Decker */ final class JimfsFileSystemProvider extends FileSystemProvider { private static final JimfsFileSystemProvider INSTANCE = new JimfsFileSystemProvider(); static { // Register the URL stream handler implementation. try { Handler.register(); } catch (Throwable e) { // Couldn't set the system property needed to register the handler. Nothing we can do really. } } /** Returns the singleton instance of this provider. */ static JimfsFileSystemProvider instance() { return INSTANCE; } @Override public String getScheme() { return URI_SCHEME; } @Override public FileSystem newFileSystem(URI uri, Map env) throws IOException { throw new UnsupportedOperationException( "This method should not be called directly;" + "use an overload of Jimfs.newFileSystem() to create a FileSystem."); } @Override public FileSystem newFileSystem(Path path, Map env) throws IOException { JimfsPath checkedPath = checkPath(path); checkNotNull(env); URI pathUri = checkedPath.toUri(); URI jarUri = URI.create("jar:" + pathUri); try { // pass the new jar:jimfs://... URI to be handled by ZipFileSystemProvider return FileSystems.newFileSystem(jarUri, env); } catch (Exception e) { // if any exception occurred, assume the file wasn't a zip file and that we don't support // viewing it as a file system throw new UnsupportedOperationException(e); } } @Override public FileSystem getFileSystem(URI uri) { throw new UnsupportedOperationException( "This method should not be called directly; " + "use FileSystems.getFileSystem(URI) instead."); } /** Gets the file system for the given path. */ private static JimfsFileSystem getFileSystem(Path path) { return (JimfsFileSystem) checkPath(path).getFileSystem(); } @Override public Path getPath(URI uri) { throw new UnsupportedOperationException( "This method should not be called directly; " + "use Paths.get(URI) instead."); } private static JimfsPath checkPath(Path path) { if (path instanceof JimfsPath) { return (JimfsPath) path; } throw new ProviderMismatchException( "path " + path + " is not associated with a Jimfs file system"); } /** Returns the default file system view for the given path. */ private static FileSystemView getDefaultView(JimfsPath path) { return getFileSystem(path).getDefaultView(); } @Override public FileChannel newFileChannel( Path path, Set options, FileAttribute... attrs) throws IOException { JimfsPath checkedPath = checkPath(path); if (!checkedPath.getJimfsFileSystem().getFileStore().supportsFeature(FILE_CHANNEL)) { throw new UnsupportedOperationException(); } return newJimfsFileChannel(checkedPath, options, attrs); } private JimfsFileChannel newJimfsFileChannel( JimfsPath path, Set options, FileAttribute... attrs) throws IOException { ImmutableSet opts = Options.getOptionsForChannel(options); FileSystemView view = getDefaultView(path); RegularFile file = view.getOrCreateRegularFile(path, opts, attrs); return new JimfsFileChannel(file, opts, view.state()); } @Override public SeekableByteChannel newByteChannel( Path path, Set options, FileAttribute... attrs) throws IOException { JimfsPath checkedPath = checkPath(path); JimfsFileChannel channel = newJimfsFileChannel(checkedPath, options, attrs); return checkedPath.getJimfsFileSystem().getFileStore().supportsFeature(FILE_CHANNEL) ? channel : new DowngradedSeekableByteChannel(channel); } @Override public AsynchronousFileChannel newAsynchronousFileChannel( Path path, Set options, @NullableDecl ExecutorService executor, FileAttribute... attrs) throws IOException { // call newFileChannel and cast so that FileChannel support is checked there JimfsFileChannel channel = (JimfsFileChannel) newFileChannel(path, options, attrs); if (executor == null) { JimfsFileSystem fileSystem = (JimfsFileSystem) path.getFileSystem(); executor = fileSystem.getDefaultThreadPool(); } return channel.asAsynchronousFileChannel(executor); } @Override public InputStream newInputStream(Path path, OpenOption... options) throws IOException { JimfsPath checkedPath = checkPath(path); ImmutableSet opts = Options.getOptionsForInputStream(options); FileSystemView view = getDefaultView(checkedPath); RegularFile file = view.getOrCreateRegularFile(checkedPath, opts, NO_ATTRS); return new JimfsInputStream(file, view.state()); } private static final FileAttribute[] NO_ATTRS = {}; @Override public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException { JimfsPath checkedPath = checkPath(path); ImmutableSet opts = Options.getOptionsForOutputStream(options); FileSystemView view = getDefaultView(checkedPath); RegularFile file = view.getOrCreateRegularFile(checkedPath, opts, NO_ATTRS); return new JimfsOutputStream(file, opts.contains(APPEND), view.state()); } @Override public DirectoryStream newDirectoryStream( Path dir, DirectoryStream.Filter filter) throws IOException { JimfsPath checkedPath = checkPath(dir); return getDefaultView(checkedPath) .newDirectoryStream(checkedPath, filter, Options.FOLLOW_LINKS, checkedPath); } @Override public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { JimfsPath checkedPath = checkPath(dir); FileSystemView view = getDefaultView(checkedPath); view.createDirectory(checkedPath, attrs); } @Override public void createLink(Path link, Path existing) throws IOException { JimfsPath linkPath = checkPath(link); JimfsPath existingPath = checkPath(existing); checkArgument( linkPath.getFileSystem().equals(existingPath.getFileSystem()), "link and existing paths must belong to the same file system instance"); FileSystemView view = getDefaultView(linkPath); view.link(linkPath, getDefaultView(existingPath), existingPath); } @Override public void createSymbolicLink(Path link, Path target, FileAttribute... attrs) throws IOException { JimfsPath linkPath = checkPath(link); JimfsPath targetPath = checkPath(target); checkArgument( linkPath.getFileSystem().equals(targetPath.getFileSystem()), "link and target paths must belong to the same file system instance"); FileSystemView view = getDefaultView(linkPath); view.createSymbolicLink(linkPath, targetPath, attrs); } @Override public Path readSymbolicLink(Path link) throws IOException { JimfsPath checkedPath = checkPath(link); return getDefaultView(checkedPath).readSymbolicLink(checkedPath); } @Override public void delete(Path path) throws IOException { JimfsPath checkedPath = checkPath(path); FileSystemView view = getDefaultView(checkedPath); view.deleteFile(checkedPath, FileSystemView.DeleteMode.ANY); } @Override public void copy(Path source, Path target, CopyOption... options) throws IOException { copy(source, target, Options.getCopyOptions(options), false); } private void copy(Path source, Path target, ImmutableSet options, boolean move) throws IOException { JimfsPath sourcePath = checkPath(source); JimfsPath targetPath = checkPath(target); FileSystemView sourceView = getDefaultView(sourcePath); FileSystemView targetView = getDefaultView(targetPath); sourceView.copy(sourcePath, targetView, targetPath, options, move); } @Override public void move(Path source, Path target, CopyOption... options) throws IOException { copy(source, target, Options.getMoveOptions(options), true); } @Override public boolean isSameFile(Path path, Path path2) throws IOException { if (path.equals(path2)) { return true; } if (!(path instanceof JimfsPath && path2 instanceof JimfsPath)) { return false; } JimfsPath checkedPath = (JimfsPath) path; JimfsPath checkedPath2 = (JimfsPath) path2; FileSystemView view = getDefaultView(checkedPath); FileSystemView view2 = getDefaultView(checkedPath2); return view.isSameFile(checkedPath, view2, checkedPath2); } @Override public boolean isHidden(Path path) throws IOException { // TODO(cgdecker): This should probably be configurable, but this seems fine for now /* * If the DOS view is supported, use the Windows isHidden method (check the dos:hidden * attribute). Otherwise, use the Unix isHidden method (just check if the file name starts with * "."). */ JimfsPath checkedPath = checkPath(path); FileSystemView view = getDefaultView(checkedPath); if (getFileStore(path).supportsFileAttributeView("dos")) { return view.readAttributes(checkedPath, DosFileAttributes.class, Options.NOFOLLOW_LINKS) .isHidden(); } return path.getNameCount() > 0 && path.getFileName().toString().startsWith("."); } @Override public FileStore getFileStore(Path path) throws IOException { return getFileSystem(path).getFileStore(); } @Override public void checkAccess(Path path, AccessMode... modes) throws IOException { JimfsPath checkedPath = checkPath(path); getDefaultView(checkedPath).checkAccess(checkedPath); } @NullableDecl @Override public V getFileAttributeView( Path path, Class type, LinkOption... options) { JimfsPath checkedPath = checkPath(path); return getDefaultView(checkedPath) .getFileAttributeView(checkedPath, type, Options.getLinkOptions(options)); } @Override public A readAttributes( Path path, Class type, LinkOption... options) throws IOException { JimfsPath checkedPath = checkPath(path); return getDefaultView(checkedPath) .readAttributes(checkedPath, type, Options.getLinkOptions(options)); } @Override public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException { JimfsPath checkedPath = checkPath(path); return getDefaultView(checkedPath) .readAttributes(checkedPath, attributes, Options.getLinkOptions(options)); } @Override public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { JimfsPath checkedPath = checkPath(path); getDefaultView(checkedPath) .setAttribute(checkedPath, attribute, value, Options.getLinkOptions(options)); } }