Java tutorial
/* * 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.jimfs; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.jimfs.Feature.FILE_CHANNEL; import static com.google.jimfs.Jimfs.CONFIG_KEY; import static com.google.jimfs.Jimfs.URI_SCHEME; import static java.nio.file.StandardOpenOption.APPEND; import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; 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.FileSystemAlreadyExistsException; import java.nio.file.FileSystemNotFoundException; 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.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import javax.annotation.Nullable; /** * {@link FileSystemProvider} implementation for Jimfs. While this class is public, it should not * be used directly. To create a new file system instance, see {@link Jimfs}. For other operations, * use the public APIs in {@code java.nio.file}. * * @author Colin Decker */ @AutoService(FileSystemProvider.class) public final class JimfsFileSystemProvider extends FileSystemProvider { @Override public String getScheme() { return URI_SCHEME; } /** * Cache of file systems that have been created but not closed. */ private final ConcurrentMap<URI, JimfsFileSystem> fileSystems = new ConcurrentHashMap<>(); @Override public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException { checkArgument(uri.getScheme().equalsIgnoreCase(URI_SCHEME), "uri (%s) scheme must be '%s'", uri, URI_SCHEME); checkArgument(isValidFileSystemUri(uri), "uri (%s) may not have a path, query or fragment", uri); checkArgument(env.get(CONFIG_KEY) instanceof Configuration, "env map (%s) must contain key '%s' mapped to an instance of Jimfs.Configuration", env, CONFIG_KEY); Configuration config = (Configuration) env.get(CONFIG_KEY); JimfsFileSystem fileSystem = JimfsFileSystems.newFileSystem(this, uri, config); if (fileSystems.putIfAbsent(uri, fileSystem) != null) { throw new FileSystemAlreadyExistsException(uri.toString()); } return fileSystem; } @Override public FileSystem getFileSystem(URI uri) { return getJimfsFileSystem(uri); } private JimfsFileSystem getJimfsFileSystem(URI uri) { JimfsFileSystem fileSystem = fileSystems.get(uri); if (fileSystem == null) { throw new FileSystemNotFoundException(uri.toString()); } return 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); } } /** * Called when the given file system is closed to remove it from this provider. */ void remove(JimfsFileSystem fileSystem) { fileSystems.remove(fileSystem.getUri()); } @Override public Path getPath(URI uri) { checkArgument(URI_SCHEME.equalsIgnoreCase(uri.getScheme()), "uri scheme does not match this provider: %s", uri); checkArgument(!isNullOrEmpty(uri.getPath()), "uri must have a path: %s", uri); return getJimfsFileSystem(toFileSystemUri(uri)).toPath(uri); } /** * Returns whether or not the given URI is valid as a base file system URI. It must not have a * path, query or fragment. */ private static boolean isValidFileSystemUri(URI uri) { // would like to just check null, but fragment appears to be the empty string when not present return isNullOrEmpty(uri.getPath()) && isNullOrEmpty(uri.getQuery()) && isNullOrEmpty(uri.getFragment()); } /** * Returns the given URI with any path, query or fragment stripped off. */ private static URI toFileSystemUri(URI uri) { try { return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), null, null, null); } catch (URISyntaxException e) { throw new AssertionError(e); } } 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"); } /** * Gets the file system for the given path. */ private static JimfsFileSystem getFileSystem(Path path) { return (JimfsFileSystem) checkPath(path).getFileSystem(); } /** * 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); RegularFile file = getDefaultView(path).getOrCreateRegularFile(path, opts, attrs); return new JimfsFileChannel(file, opts); } @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, @Nullable 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); RegularFile file = getDefaultView(checkedPath).getOrCreateRegularFile(checkedPath, opts, NO_ATTRS); return new JimfsInputStream(file); } 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); RegularFile file = getDefaultView(checkedPath).getOrCreateRegularFile(checkedPath, opts, NO_ATTRS); return new JimfsOutputStream(file, opts.contains(APPEND)); } @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); } @Override public void move(Path source, Path target, CopyOption... options) throws IOException { copy(source, target, Options.getMoveOptions(options), true); } 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 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); } @Nullable @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)); } }