diff options
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) { } |