aboutsummaryrefslogtreecommitdiff
path: root/jimfs/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'jimfs/src/main')
-rw-r--r--jimfs/src/main/java/com/google/common/jimfs/Jimfs.java24
-rw-r--r--jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java121
-rw-r--r--jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystems.java51
-rw-r--r--jimfs/src/main/java/com/google/common/jimfs/SystemJimfsFileSystemProvider.java271
4 files changed, 357 insertions, 110 deletions
diff --git a/jimfs/src/main/java/com/google/common/jimfs/Jimfs.java b/jimfs/src/main/java/com/google/common/jimfs/Jimfs.java
index 3bafaa0..f6a65bd 100644
--- a/jimfs/src/main/java/com/google/common/jimfs/Jimfs.java
+++ b/jimfs/src/main/java/com/google/common/jimfs/Jimfs.java
@@ -17,6 +17,7 @@
package com.google.common.jimfs;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.jimfs.SystemJimfsFileSystemProvider.FILE_SYSTEM_KEY;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
@@ -149,14 +150,23 @@ public final class Jimfs {
checkArgument(URI_SCHEME.equals(uri.getScheme()),
"uri (%s) must have scheme %s", uri, URI_SCHEME);
- ImmutableMap<String, ?> env = ImmutableMap.of(CONFIG_KEY, config);
try {
- // Using FileSystems.newFileSystem so that we use the same FileSystemProvider that users will
- // get if they use FileSystems (or other methods like Paths.get(URI)) directly, if possible.
- // We pass in the ClassLoader that loaded this class to ensure that JimfsFileSystemProvider
- // will be found, though if that ClassLoader isn't the system ClassLoader, a new
- // JimfsFileSystemProvider will be created each time.
- return FileSystems.newFileSystem(uri, env, Jimfs.class.getClassLoader());
+ // Create the FileSystem. It uses JimfsFileSystemProvider as its provider, as that is
+ // the provider that actually implements the operations needed for Files methods to work.
+ JimfsFileSystem fileSystem = JimfsFileSystems.newFileSystem(
+ JimfsFileSystemProvider.instance(), uri, config);
+
+ // Now, call FileSystems.newFileSystem, passing it the FileSystem we just created. This
+ // allows the system-loaded SystemJimfsFileSystemProvider instance to cache the FileSystem
+ // so that methods like Paths.get(URI) work.
+ // We do it in this awkward way to avoid issues when the classes in the API (this class
+ // and Configuration, for example) are loaded by a different classloader than the one that
+ // loads SystemJimfsFileSystemProvider using ServiceLoader. See
+ // https://github.com/google/jimfs/issues/18 for gory details.
+ ImmutableMap<String, ?> env = ImmutableMap.of(FILE_SYSTEM_KEY, fileSystem);
+ FileSystems.newFileSystem(uri, env, SystemJimfsFileSystemProvider.class.getClassLoader());
+
+ return fileSystem;
} catch (IOException e) {
throw new AssertionError(e);
}
diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java
index 5c8c251..ae1f15d 100644
--- a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java
+++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystemProvider.java
@@ -18,21 +18,16 @@ 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.base.Strings.isNullOrEmpty;
import static com.google.common.jimfs.Feature.FILE_CHANNEL;
-import static com.google.common.jimfs.Jimfs.CONFIG_KEY;
import static com.google.common.jimfs.Jimfs.URI_SCHEME;
import static java.nio.file.StandardOpenOption.APPEND;
-import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.MapMaker;
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;
@@ -41,13 +36,10 @@ 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.Paths;
import java.nio.file.ProviderMismatchException;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributes;
@@ -56,20 +48,21 @@ import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
import java.util.Map;
import java.util.Set;
-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}.
+ * {@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
*/
-@AutoService(FileSystemProvider.class)
-public final class JimfsFileSystemProvider extends FileSystemProvider {
+final class JimfsFileSystemProvider extends FileSystemProvider {
+
+ private static final JimfsFileSystemProvider INSTANCE = new JimfsFileSystemProvider();
static {
// Register the URL stream handler implementation.
@@ -80,62 +73,28 @@ public final class JimfsFileSystemProvider extends FileSystemProvider {
}
}
+ /**
+ * Returns the singleton instance of this provider.
+ */
+ static JimfsFileSystemProvider instance() {
+ return INSTANCE;
+ }
+
@Override
public String getScheme() {
return URI_SCHEME;
}
- /**
- * Cache of file systems that have been created but not closed.
- *
- * <p>This cache is static to ensure that even when this provider isn't loaded by the system
- * class loader, meaning that a new instance of it must be created each time one of the methods
- * on {@link FileSystems} or {@link Paths#get(URI)} is called, cached file system instances are
- * still available.
- *
- * <p>The cache uses weak values so that it doesn't prevent file systems that are created but not
- * closed from being garbage collected if no references to them are held elsewhere. This is a
- * compromise between ensuring that any file URI continues to work as long as the file system
- * hasn't been closed (which is technically the correct thing to do but unlikely to be something
- * that most users care about) and ensuring that users don't get unexpected leaks of large
- * amounts of memory because they're creating many file systems in tests but forgetting to close
- * them (which seems likely to happen sometimes). Users that want to ensure that a file system
- * won't be garbage collected just need to ensure they hold a reference to it somewhere for as
- * long as they need it to stick around.
- */
- private static final ConcurrentMap<URI, JimfsFileSystem> fileSystems = new MapMaker()
- .weakValues()
- .makeMap();
-
@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;
+ throw new UnsupportedOperationException("This method should not be called directly;"
+ + "use an overload of Jimfs.newFileSystem() to create a 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;
+ throw new UnsupportedOperationException("This method should not be called directly; "
+ + "use FileSystems.getFileSystem(URI) instead.");
}
@Override
@@ -156,50 +115,10 @@ public final class JimfsFileSystemProvider extends FileSystemProvider {
}
}
- /**
- * Returns a runnable that, when run, removes the file system with the given URI from this
- * provider.
- */
- static Runnable removeFileSystemRunnable(final URI uri) {
- return new Runnable() {
- @Override
- public void run() {
- fileSystems.remove(uri);
- }
- };
- }
-
@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);
- }
+ throw new UnsupportedOperationException("This method should not be called directly; "
+ + "use Paths.get(URI) instead.");
}
private static JimfsPath checkPath(Path path) {
diff --git a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystems.java b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystems.java
index 7622889..fe23b58 100644
--- a/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystems.java
+++ b/jimfs/src/main/java/com/google/common/jimfs/JimfsFileSystems.java
@@ -16,8 +16,13 @@
package com.google.common.jimfs;
+import static com.google.common.jimfs.Jimfs.URI_SCHEME;
+
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.net.URI;
+import java.nio.file.spi.FileSystemProvider;
import java.util.HashMap;
import java.util.Map;
@@ -31,14 +36,56 @@ final class JimfsFileSystems {
private JimfsFileSystems() {}
/**
+ * The system-loaded {@code JimfsFileSystemProvider} that caches {@code JimfsFileSystem}
+ * instances.
+ */
+ private static final FileSystemProvider systemJimfsProvider = getSystemJimfsProvider();
+
+ private static FileSystemProvider getSystemJimfsProvider() {
+ for (FileSystemProvider provider : FileSystemProvider.installedProviders()) {
+ if (provider.getScheme().equals(URI_SCHEME)) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ private static final Runnable DO_NOTHING = new Runnable() {
+ @Override
+ public void run() {}
+ };
+
+ /**
+ * Returns a {@code Runnable} that will remove the file system with the given {@code URI} from
+ * the system provider's cache when called.
+ */
+ private static Runnable removeFileSystemRunnable(URI uri) {
+ if (systemJimfsProvider == null) {
+ // TODO(cgdecker): Use Runnables.doNothing() when it's out of @Beta
+ return DO_NOTHING;
+ }
+
+ // We have to invoke the SystemJimfsFileSystemProvider.removeFileSystemRunnable(URI)
+ // method reflectively since the system-loaded instance of it may be a different class
+ // than the one we'd get if we tried to cast it and call it like normal here.
+ try {
+ Method method = systemJimfsProvider.getClass()
+ .getDeclaredMethod("removeFileSystemRunnable", URI.class);
+ return (Runnable) method.invoke(systemJimfsProvider, uri);
+ } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+ throw new RuntimeException(
+ "Unable to get Runnable for removing the FileSystem from the cache when it is closed", e);
+ }
+ }
+
+ /**
* Initialize and configure a new file system with the given provider and URI, using the given
* configuration.
*/
public static JimfsFileSystem newFileSystem(
JimfsFileSystemProvider provider, URI uri, Configuration config) throws IOException {
PathService pathService = new PathService(config);
- FileSystemState state = new FileSystemState(
- JimfsFileSystemProvider.removeFileSystemRunnable(uri));
+ FileSystemState state = new FileSystemState(removeFileSystemRunnable(uri));
JimfsFileStore fileStore = createFileStore(config, pathService, state);
FileSystemView defaultView = createDefaultView(config, fileStore, pathService);
diff --git a/jimfs/src/main/java/com/google/common/jimfs/SystemJimfsFileSystemProvider.java b/jimfs/src/main/java/com/google/common/jimfs/SystemJimfsFileSystemProvider.java
new file mode 100644
index 0000000..1d1f2a0
--- /dev/null
+++ b/jimfs/src/main/java/com/google/common/jimfs/SystemJimfsFileSystemProvider.java
@@ -0,0 +1,271 @@
+/*
+ * 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.Strings.isNullOrEmpty;
+import static com.google.common.jimfs.Jimfs.URI_SCHEME;
+
+import com.google.auto.service.AutoService;
+import com.google.common.collect.MapMaker;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URISyntaxException;
+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.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+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.ConcurrentMap;
+
+/**
+ * {@link FileSystemProvider} implementation for Jimfs that is loaded by the system as a service.
+ * This implementation only serves as a cache for file system instances and does not implement
+ * actual file system operations.
+ *
+ * <p>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 class SystemJimfsFileSystemProvider extends FileSystemProvider {
+
+ /**
+ * Env map key that maps to the already-created {@code FileSystem} instance in
+ * {@code newFileSystem}.
+ */
+ static final String FILE_SYSTEM_KEY = "fileSystem";
+
+ /**
+ * Cache of file systems that have been created but not closed.
+ *
+ * <p>This cache is static to ensure that even when this provider isn't loaded by the system class
+ * loader, meaning that a new instance of it must be created each time one of the methods on
+ * {@link FileSystems} or {@link Paths#get(URI)} is called, cached file system instances are still
+ * available.
+ *
+ * <p>The cache uses weak values so that it doesn't prevent file systems that are created but not
+ * closed from being garbage collected if no references to them are held elsewhere. This is a
+ * compromise between ensuring that any file URI continues to work as long as the file system
+ * hasn't been closed (which is technically the correct thing to do but unlikely to be something
+ * that most users care about) and ensuring that users don't get unexpected leaks of large amounts
+ * of memory because they're creating many file systems in tests but forgetting to close them
+ * (which seems likely to happen sometimes). Users that want to ensure that a file system won't be
+ * garbage collected just need to ensure they hold a reference to it somewhere for as long as they
+ * need it to stick around.
+ */
+ private static final ConcurrentMap<URI, FileSystem> fileSystems = new MapMaker()
+ .weakValues()
+ .makeMap();
+
+ @Override
+ public String getScheme() {
+ return URI_SCHEME;
+ }
+
+ @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(FILE_SYSTEM_KEY) instanceof FileSystem,
+ "env map (%s) must contain key '%s' mapped to an instance of %s",
+ env, FILE_SYSTEM_KEY, FileSystem.class);
+
+ FileSystem fileSystem = (FileSystem) env.get(FILE_SYSTEM_KEY);
+ if (fileSystems.putIfAbsent(uri, fileSystem) != null) {
+ throw new FileSystemAlreadyExistsException(uri.toString());
+ }
+ return fileSystem;
+ }
+
+ @Override
+ public FileSystem getFileSystem(URI uri) {
+ FileSystem fileSystem = fileSystems.get(uri);
+ if (fileSystem == null) {
+ throw new FileSystemNotFoundException(uri.toString());
+ }
+ return fileSystem;
+ }
+
+ @Override
+ public Path getPath(URI uri) {
+ checkArgument(URI_SCHEME.equalsIgnoreCase(uri.getScheme()),
+ "uri scheme does not match this provider: %s", uri);
+
+ String path = uri.getPath();
+ checkArgument(!isNullOrEmpty(path), "uri must have a path: %s", uri);
+
+ return toPath(getFileSystem(toFileSystemUri(uri)), 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);
+ }
+ }
+
+ /**
+ * Invokes the {@code toPath(URI)} method on the given {@code FileSystem}.
+ */
+ private static Path toPath(FileSystem fileSystem, URI uri) {
+ // We have to invoke this method by reflection because while the file system should be
+ // an instance of JimfsFileSystem, it may be loaded by a different class loader and as
+ // such appear to be a totally different class.
+ try {
+ Method toPath = fileSystem.getClass().getDeclaredMethod("toPath", URI.class);
+ return (Path) toPath.invoke(fileSystem, uri);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("invalid file system: " + fileSystem);
+ } catch (InvocationTargetException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public FileSystem newFileSystem(Path path, Map<String, ?> env) throws IOException {
+ FileSystemProvider realProvider = path.getFileSystem().provider();
+ return realProvider.newFileSystem(path, env);
+ }
+
+ /**
+ * Returns a runnable that, when run, removes the file system with the given URI from this
+ * provider.
+ */
+ @SuppressWarnings("unused") // called via reflection
+ public static Runnable removeFileSystemRunnable(final URI uri) {
+ return new Runnable() {
+ @Override
+ public void run() {
+ fileSystems.remove(uri);
+ }
+ };
+ }
+
+ @Override
+ public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options,
+ FileAttribute<?>... attrs) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DirectoryStream<Path> newDirectoryStream(Path dir,
+ DirectoryStream.Filter<? super Path> filter) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void delete(Path path) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void copy(Path source, Path target, CopyOption... options) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void move(Path source, Path target, CopyOption... options) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isSameFile(Path path, Path path2) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isHidden(Path path) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public FileStore getFileStore(Path path) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void checkAccess(Path path, AccessMode... modes) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type,
+ LinkOption... options) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type,
+ LinkOption... options) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options)
+ throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setAttribute(Path path, String attribute, Object value, LinkOption... options)
+ throws IOException {
+ throw new UnsupportedOperationException();
+ }
+}