| /* |
| * 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<String, ?> 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<String, ?> 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<? extends OpenOption> 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<? extends OpenOption> options, FileAttribute<?>... attrs) |
| throws IOException { |
| ImmutableSet<OpenOption> 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<? extends OpenOption> 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<? extends OpenOption> 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<OpenOption> 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<OpenOption> 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<Path> newDirectoryStream( |
| Path dir, DirectoryStream.Filter<? super Path> 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<CopyOption> 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 extends FileAttributeView> V getFileAttributeView( |
| Path path, Class<V> type, LinkOption... options) { |
| JimfsPath checkedPath = checkPath(path); |
| return getDefaultView(checkedPath) |
| .getFileAttributeView(checkedPath, type, Options.getLinkOptions(options)); |
| } |
| |
| @Override |
| public <A extends BasicFileAttributes> A readAttributes( |
| Path path, Class<A> type, LinkOption... options) throws IOException { |
| JimfsPath checkedPath = checkPath(path); |
| return getDefaultView(checkedPath) |
| .readAttributes(checkedPath, type, Options.getLinkOptions(options)); |
| } |
| |
| @Override |
| public Map<String, Object> 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)); |
| } |
| } |