aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/ClassLoaderTest.java121
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/JimfsFileSystemCloseTest.java7
6 files changed, 481 insertions, 114 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();
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/ClassLoaderTest.java b/jimfs/src/test/java/com/google/common/jimfs/ClassLoaderTest.java
new file mode 100644
index 0000000..306d3e1
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/ClassLoaderTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2015 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 java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URLClassLoader;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.List;
+
+/**
+ * Tests behavior when user code loads Jimfs in a separate class loader from the system class
+ * loader (which is what {@link FileSystemProvider#installedProviders()} uses to load
+ * {@link FileSystemProvider}s as services from the classpath).
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class ClassLoaderTest {
+
+ @Test
+ public void separateClassLoader() throws Exception {
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
+
+ ClassLoader loader = MoreObjects.firstNonNull(contextLoader, systemLoader);
+
+ if (loader instanceof URLClassLoader) {
+ // Anything we can do if it isn't a URLClassLoader?
+ URLClassLoader urlLoader = (URLClassLoader) loader;
+
+ ClassLoader separateLoader = new URLClassLoader(
+ urlLoader.getURLs(),
+ systemLoader.getParent()); // either null or the boostrap loader
+
+ Thread.currentThread().setContextClassLoader(separateLoader);
+ try {
+ Class<?> thisClass = separateLoader.loadClass(getClass().getName());
+ Method testMethod = thisClass.getDeclaredMethod("testMethod");
+
+ // First, the call to Jimfs.newFileSystem in testMethod needs to succeed
+ Object fs = testMethod.invoke(null);
+
+ // Next, some sanity checks:
+
+ // The file system is a JimfsFileSystem
+ assertEquals("com.google.common.jimfs.JimfsFileSystem", fs.getClass().getName());
+
+ // But it is not seen as an instance of JimfsFileSystem here because it was loaded by a
+ // different ClassLoader
+ assertFalse(fs instanceof JimfsFileSystem);
+
+ // But it should be an instance of FileSystem regardless, which is the important thing.
+ assertTrue(fs instanceof FileSystem);
+
+ // And normal file operations should work on it despite its provenance from a different
+ // ClassLoader
+ writeAndRead((FileSystem) fs, "bar.txt", "blah blah");
+
+ // And for the heck of it, test the contents of the file that was created in testMethod too
+ assertEquals("blah",
+ Files.readAllLines(((FileSystem) fs).getPath("foo.txt"), UTF_8).get(0));
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextLoader);
+ }
+ }
+ }
+
+ /**
+ * This method is really just testing that {@code Jimfs.newFileSystem()} succeeds. Without
+ * special handling, when the system class loader loads our {@code FileSystemProvider}
+ * implementation as a service and this code (the user code) is loaded in a separate class
+ * loader, the system-loaded provider won't see the instance of {@code Configuration} we give it
+ * as being an instance of the {@code Configuration} it's expecting (they're completely separate
+ * classes) and creation of the file system will fail.
+ */
+ public static FileSystem testMethod() throws IOException {
+ FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
+
+ // Just some random operations to verify that basic things work on the created file system.
+ writeAndRead(fs, "foo.txt", "blah");
+
+ return fs;
+ }
+
+ private static void writeAndRead(FileSystem fs, String path, String text) throws IOException {
+ Path p = fs.getPath(path);
+ Files.write(p, ImmutableList.of(text), UTF_8);
+ List<String> lines = Files.readAllLines(p, UTF_8);
+ assertEquals(text, lines.get(0));
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsFileSystemCloseTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsFileSystemCloseTest.java
index bd6ed80..9276567 100644
--- a/jimfs/src/test/java/com/google/common/jimfs/JimfsFileSystemCloseTest.java
+++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsFileSystemCloseTest.java
@@ -48,13 +48,13 @@ import java.nio.file.ClosedFileSystemException;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
-import java.nio.file.spi.FileSystemProvider;
/**
* Tests for what happens when a file system is closed.
@@ -75,14 +75,13 @@ public class JimfsFileSystemCloseTest {
@Test
public void testIsNotAvailableFromProvider() throws IOException {
- FileSystemProvider provider = fs.provider();
URI uri = fs.getUri();
- assertEquals(fs, provider.getFileSystem(uri));
+ assertEquals(fs, FileSystems.getFileSystem(uri));
fs.close();
try {
- provider.getFileSystem(uri);
+ FileSystems.getFileSystem(uri);
fail();
} catch (FileSystemNotFoundException expected) {
}