noOp() {
+ return ignoredParameter -> {
+ };
+ }
+
+ }
+
+}
diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java
index 3fc987143..f07cd4c5c 100644
--- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java
+++ b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java
@@ -88,17 +88,7 @@ public interface Folder extends Node {
*
* If the directory does not exist this method does nothing.
*/
- default void delete() throws UncheckedIOException {
- if (!exists()) {
- return;
- }
- folders().forEach(Folder::delete);
- files().forEach(file -> {
- try (WritableFile writableFile = file.openWritable()) {
- writableFile.delete();
- }
- });
- }
+ void delete();
/**
* Moves this directory and its contents to the given destination. If the
diff --git a/main/filesystem-nio/pom.xml b/main/filesystem-nio/pom.xml
index cdb933926..f732c68b5 100644
--- a/main/filesystem-nio/pom.xml
+++ b/main/filesystem-nio/pom.xml
@@ -1,10 +1,8 @@
-
-
+
+
4.0.0
org.cryptomator
@@ -38,6 +36,19 @@
com.google.guava
guava
+
+
+ org.cryptomator
+ commons-test
+
+
+ org.mockito
+ mockito-core
+
+
+ org.hamcrest
+ hamcrest-all
+
diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultNioNodeFactory.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultNioNodeFactory.java
deleted file mode 100644
index 6289a7065..000000000
--- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultNioNodeFactory.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.cryptomator.filesystem.nio;
-
-import java.nio.file.Path;
-import java.util.Optional;
-
-class DefaultNioNodeFactory implements NioNodeFactory {
-
- @Override
- public NioFile file(Optional parent, Path path) {
- return new NioFile(parent, path, this);
- }
-
- @Override
- public NioFolder folder(Optional parent, Path path) {
- return new NioFolder(parent, path, this);
- }
-
-}
diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java
index 722dff6da..ce520c782 100644
--- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java
+++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java
@@ -1,5 +1,7 @@
package org.cryptomator.filesystem.nio;
+import static java.lang.String.format;
+
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.file.Path;
@@ -15,8 +17,8 @@ class NioFile extends NioNode implements File {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
- public NioFile(Optional parent, Path path, NioNodeFactory nodeFactory) {
- super(parent, path, nodeFactory);
+ public NioFile(Optional parent, Path path) {
+ super(parent, path);
}
@Override
@@ -99,4 +101,9 @@ class NioFile extends NioNode implements File {
}
}
+ @Override
+ public String toString() {
+ return format("NioFile(%s)", path);
+ }
+
}
diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java
index fb00e6833..3ddd4476d 100644
--- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java
+++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFileSystem.java
@@ -1,5 +1,7 @@
package org.cryptomator.filesystem.nio;
+import static org.cryptomator.filesystem.FolderCreateMode.INCLUDING_PARENTS;
+
import java.nio.file.Path;
import java.util.Optional;
@@ -8,11 +10,12 @@ import org.cryptomator.filesystem.FileSystem;
public class NioFileSystem extends NioFolder implements FileSystem {
public static NioFileSystem rootedAt(Path root) {
- return new NioFileSystem(root, new DefaultNioNodeFactory());
+ return new NioFileSystem(root);
}
- NioFileSystem(Path root, NioNodeFactory nodeFactory) {
- super(Optional.empty(), root, nodeFactory);
+ private NioFileSystem(Path root) {
+ super(Optional.empty(), root);
+ create(INCLUDING_PARENTS);
}
}
diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java
index 48fd0eb0f..d4d3b22b9 100644
--- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java
+++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFolder.java
@@ -1,5 +1,7 @@
package org.cryptomator.filesystem.nio;
+import static java.lang.String.format;
+import static org.cryptomator.filesystem.FileSystemVisitor.fileSystemVisitor;
import static org.cryptomator.filesystem.FolderCreateMode.INCLUDING_PARENTS;
import java.io.IOException;
@@ -13,14 +15,15 @@ import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.FolderCreateMode;
import org.cryptomator.filesystem.Node;
+import org.cryptomator.filesystem.WritableFile;
class NioFolder extends NioNode implements Folder {
private final WeakValuedCache folders = WeakValuedCache.usingLoader(this::folderFromPath);
private final WeakValuedCache files = WeakValuedCache.usingLoader(this::fileFromPath);
- public NioFolder(Optional parent, Path path, NioNodeFactory nodeFactory) {
- super(parent, path, nodeFactory);
+ public NioFolder(Optional parent, Path path) {
+ super(parent, path);
}
@Override
@@ -41,11 +44,11 @@ class NioFolder extends NioNode implements Folder {
}
private NioFile fileFromPath(Path path) {
- return nodeFactory.file(Optional.of(this), path);
+ return new NioFile(Optional.of(this), path);
}
private NioFolder folderFromPath(Path path) {
- return nodeFactory.folder(Optional.of(this), path);
+ return new NioFolder(Optional.of(this), path);
}
@Override
@@ -82,4 +85,31 @@ class NioFolder extends NioNode implements Folder {
}
}
+ @Override
+ public String toString() {
+ return format("NioFolder(%s)", path);
+ }
+
+ @Override
+ public void delete() {
+ fileSystemVisitor() //
+ .forEachFile(NioFolder::deleteFile) //
+ .afterFolder(NioFolder::deleteEmptyFolder) //
+ .visit(this);
+ }
+
+ private static final void deleteFile(File file) {
+ try (WritableFile writableFile = file.openWritable()) {
+ writableFile.delete();
+ }
+ }
+
+ private static final void deleteEmptyFolder(Folder folder) {
+ try {
+ Files.delete(((NioFolder) folder).path);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
}
diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java
index 92c4ac5d0..9cdfb97ab 100644
--- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java
+++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNode.java
@@ -14,17 +14,26 @@ class NioNode implements Node {
protected final Optional parent;
protected final Path path;
- protected final NioNodeFactory nodeFactory;
- public NioNode(Optional parent, Path path, NioNodeFactory nodeFactory) {
+ private NioFileSystem fileSystem;
+
+ public NioNode(Optional parent, Path path) {
this.path = path.toAbsolutePath();
- this.nodeFactory = nodeFactory;
this.parent = parent;
}
+ NioFileSystem fileSystem() {
+ if (fileSystem == null) {
+ fileSystem = parent //
+ .map(NioNode::fileSystem) //
+ .orElseGet(() -> (NioFileSystem) this);
+ }
+ return fileSystem;
+ }
+
boolean belongsToSameFilesystem(Node other) {
return other instanceof NioNode //
- && ((NioNode) other).nodeFactory == nodeFactory;
+ && ((NioNode) other).fileSystem() == fileSystem();
}
@Override
diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNodeFactory.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNodeFactory.java
deleted file mode 100644
index 7bfeb49b6..000000000
--- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioNodeFactory.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.cryptomator.filesystem.nio;
-
-import java.nio.file.Path;
-import java.util.Optional;
-
-interface NioNodeFactory {
-
- NioFile file(Optional parent, Path path);
-
- NioFolder folder(Optional parent, Path path);
-
-}
diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WeakValuedCache.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WeakValuedCache.java
index 59f627c8a..a07040d7c 100644
--- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WeakValuedCache.java
+++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/WeakValuedCache.java
@@ -6,6 +6,8 @@ import java.util.function.Function;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
+import com.google.common.util.concurrent.ExecutionError;
+import com.google.common.util.concurrent.UncheckedExecutionException;
class WeakValuedCache {
@@ -30,7 +32,11 @@ class WeakValuedCache {
try {
return delegate.get(key);
} catch (ExecutionException e) {
- throw new RuntimeException(e);
+ throw new IllegalStateException("No checked exception can be thrown by loader", e);
+ } catch (UncheckedExecutionException e) {
+ throw (RuntimeException) e.getCause();
+ } catch (ExecutionError e) {
+ throw (Error) e.getCause();
}
}
diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/FilesystemSetupUtils.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/FilesystemSetupUtils.java
new file mode 100644
index 000000000..a580fa952
--- /dev/null
+++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/FilesystemSetupUtils.java
@@ -0,0 +1,89 @@
+package org.cryptomator.filesystem.nio;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.commons.io.IOUtils;
+
+class FilesystemSetupUtils {
+
+ public static Path emptyFilesystem() {
+ try {
+ return Files.createTempDirectory("test-filesystem");
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public static Path testFilesystem(Entry firstEntry, Entry... entries) {
+ try {
+ Path root = Files.createTempDirectory("test-filesystem");
+ firstEntry.create(root);
+ for (Entry entry : entries) {
+ entry.create(root);
+ }
+ return root;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static FileEntry file(String path) {
+ return new FileEntry(Paths.get(path));
+ }
+
+ public static FolderEntry folder(String path) {
+ return new FolderEntry(Paths.get(path));
+ }
+
+ interface Entry {
+
+ void create(Path root) throws IOException;
+
+ }
+
+ public static class FileEntry implements Entry {
+ private Path relativePath;
+ private byte[] data = new byte[0];
+
+ public FileEntry(Path relativePath) {
+ this.relativePath = relativePath;
+ }
+
+ public FileEntry withData(byte[] data) {
+ this.data = data;
+ return this;
+ }
+
+ public FileEntry withData(String data) {
+ return withData(data.getBytes());
+ }
+
+ @Override
+ public void create(Path root) throws IOException {
+ Path filePath = root.resolve(relativePath);
+ Files.createDirectories(filePath.getParent());
+ try (OutputStream out = Files.newOutputStream(filePath)) {
+ IOUtils.write(data, out);
+ }
+ }
+ }
+
+ public static class FolderEntry implements Entry {
+ private Path relativePath;
+
+ public FolderEntry(Path relativePath) {
+ this.relativePath = relativePath;
+ }
+
+ @Override
+ public void create(Path root) throws IOException {
+ Files.createDirectories(root.resolve(relativePath));
+ }
+ }
+
+}
diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java
new file mode 100644
index 000000000..6d151fd37
--- /dev/null
+++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileSystemTest.java
@@ -0,0 +1,52 @@
+package org.cryptomator.filesystem.nio;
+
+import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.emptyFilesystem;
+import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.file;
+import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.testFilesystem;
+import static org.cryptomator.filesystem.nio.PathMatcher.isDirectory;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class NioFileSystemTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testParentIsEmpty() throws IOException {
+ NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem());
+
+ assertThat(fileSystem.parent().isPresent(), is(false));
+ }
+
+ @Test
+ public void testObtainingAFileSystemWithNonExistingRootCreatesItIncludingAllParentFolders() throws IOException {
+ Path emptyFilesystem = emptyFilesystem();
+ Path nonExistingFilesystemRoot = emptyFilesystem.resolve("nonExistingRoot");
+ Files.delete(emptyFilesystem);
+
+ NioFileSystem.rootedAt(nonExistingFilesystemRoot);
+
+ assertThat(nonExistingFilesystemRoot, isDirectory());
+ }
+
+ @Test
+ public void testObtainingAFileSystemWhooseRootIsAFileFails() throws IOException {
+ Path emptyFilesystem = testFilesystem(file("rootWhichIsAFile"));
+ Path rootWhichIsAFile = emptyFilesystem.resolve("rootWhichIsAFile");
+
+ thrown.expect(UncheckedIOException.class);
+
+ NioFileSystem.rootedAt(rootWhichIsAFile);
+ }
+
+}
\ No newline at end of file
diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java
new file mode 100644
index 000000000..4234887bf
--- /dev/null
+++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFolderTest.java
@@ -0,0 +1,174 @@
+package org.cryptomator.filesystem.nio;
+
+import static java.util.stream.Collectors.toList;
+import static org.cryptomator.commons.test.matcher.ContainsMatcher.containsInAnyOrder;
+import static org.cryptomator.filesystem.FolderCreateMode.FAIL_IF_PARENT_IS_MISSING;
+import static org.cryptomator.filesystem.FolderCreateMode.INCLUDING_PARENTS;
+import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.emptyFilesystem;
+import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.file;
+import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.folder;
+import static org.cryptomator.filesystem.nio.FilesystemSetupUtils.testFilesystem;
+import static org.cryptomator.filesystem.nio.NioNodeMatcher.fileWithName;
+import static org.cryptomator.filesystem.nio.NioNodeMatcher.folderWithName;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.empty;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.cryptomator.filesystem.Folder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class NioFolderTest {
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testNameIsNameOfFolder() throws IOException {
+ final String folderName = "folderNameABC";
+ NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem());
+ Folder folder = fileSystem.folder(folderName);
+
+ assertThat(folder, folderWithName(folderName));
+ }
+
+ @Test
+ public void testCreateWithOptionFailIfParentIsMissingFailsIfParentIsMissing() throws IOException {
+ NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem());
+ Folder folderWithNonExistingParent = fileSystem.folder("a").folder("b");
+
+ thrown.expect(UncheckedIOException.class);
+ thrown.expectMessage(fileSystem.path.resolve("a/b").toString());
+
+ folderWithNonExistingParent.create(FAIL_IF_PARENT_IS_MISSING);
+ }
+
+ @Test
+ public void testCreateWithOptionIncludingParentsSucceedsIfParentIsMissing() throws IOException {
+ Path emptyFilesystemPath = emptyFilesystem();
+ NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystemPath);
+ Folder folderWithNonExistingParent = fileSystem.folder("a").folder("b");
+
+ folderWithNonExistingParent.create(INCLUDING_PARENTS);
+
+ assertThat(Files.isDirectory(emptyFilesystemPath.resolve("a/b")), is(true));
+ }
+
+ @Test
+ public void testCreateWithOptionFailIfParentIsMissingSucceedsIfParentIsPresent() throws IOException {
+ Path emptyFilesystemPath = emptyFilesystem();
+ NioFileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystemPath);
+ Folder nonExistingFolder = fileSystem.folder("a");
+
+ nonExistingFolder.create(FAIL_IF_PARENT_IS_MISSING);
+
+ assertThat(Files.isDirectory(emptyFilesystemPath.resolve("a")), is(true));
+ }
+
+ @Test
+ public void testChildrenOfEmptyNioFolderAreEmpty() throws IOException {
+ NioFolder folder = NioFileSystem.rootedAt(emptyFilesystem());
+
+ assertThat(folder.children().collect(toList()), is(empty()));
+ }
+
+ @Test
+ public void testChildrenOfNonExistingFolderThrowsUncheckedIOExceptionWithAbolutePathOfFolderInMessage() throws IOException {
+ Path emptyFolderPath = emptyFilesystem();
+ NioFolder folder = NioFileSystem.rootedAt(emptyFolderPath);
+ Files.delete(emptyFolderPath);
+
+ thrown.expect(UncheckedIOException.class);
+ thrown.expectMessage(emptyFolderPath.toString());
+
+ folder.children();
+ }
+
+ @Test
+ public void testChildrenOfFolderAreCorrect() throws IOException {
+ String folderName1 = "folder1";
+ String folderName2 = "folder2";
+ String fileName1 = "file1";
+ String fileName2 = "file2";
+
+ NioFolder folder = NioFileSystem.rootedAt(testFilesystem( //
+ folder(folderName1), //
+ folder(folderName2), //
+ file(fileName1), //
+ file(fileName2)));
+
+ assertThat(folder.children().collect(toList()),
+ containsInAnyOrder( //
+ folderWithName(folderName1), //
+ folderWithName(folderName2), //
+ fileWithName(fileName1), //
+ fileWithName(fileName2)));
+ }
+
+ @Test
+ public void testFilesDoesContainOnlyFileChildren() throws IOException {
+ String folderName1 = "folder1";
+ String folderName2 = "folder2";
+ String fileName1 = "file1";
+ String fileName2 = "file2";
+
+ NioFolder folder = NioFileSystem.rootedAt(testFilesystem( //
+ folder(folderName1), //
+ folder(folderName2), //
+ file(fileName1), //
+ file(fileName2)));
+
+ assertThat(folder.files().collect(toList()),
+ containsInAnyOrder( //
+ fileWithName(fileName1), //
+ fileWithName(fileName2)));
+ }
+
+ @Test
+ public void testFilesOfNonExistingFolderThrowsUncheckedIOExceptionWithAbolutePathOfFolderInMessage() throws IOException {
+ Path emptyFolderPath = emptyFilesystem();
+ NioFolder folder = NioFileSystem.rootedAt(emptyFolderPath);
+ Files.delete(emptyFolderPath);
+
+ thrown.expect(UncheckedIOException.class);
+ thrown.expectMessage(emptyFolderPath.toString());
+
+ folder.files();
+ }
+
+ @Test
+ public void testFoldersDoesContainOnlyFolderChildren() throws IOException {
+ String folderName1 = "folder1";
+ String folderName2 = "folder2";
+ String fileName1 = "file1";
+ String fileName2 = "file2";
+
+ NioFolder folder = NioFileSystem.rootedAt(testFilesystem( //
+ folder(folderName1), //
+ folder(folderName2), //
+ file(fileName1), //
+ file(fileName2)));
+
+ assertThat(folder.folders().collect(toList()),
+ containsInAnyOrder( //
+ folderWithName(folderName1), //
+ folderWithName(folderName2)));
+ }
+
+ @Test
+ public void testFoldersOfNonExistingFolderThrowsUncheckedIOExceptionWithAbolutePathOfFolderInMessage() throws IOException {
+ Path emptyFilesystemPath = emptyFilesystem();
+ NioFolder folder = NioFileSystem.rootedAt(emptyFilesystemPath);
+ Files.delete(emptyFilesystemPath);
+
+ thrown.expect(UncheckedIOException.class);
+ thrown.expectMessage(emptyFilesystemPath.toString());
+
+ folder.folders();
+ }
+}
diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioNodeMatcher.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioNodeMatcher.java
new file mode 100644
index 000000000..01cb4253d
--- /dev/null
+++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioNodeMatcher.java
@@ -0,0 +1,20 @@
+package org.cryptomator.filesystem.nio;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import org.cryptomator.commons.test.matcher.PropertyMatcher;
+import org.cryptomator.filesystem.File;
+import org.cryptomator.filesystem.Folder;
+import org.hamcrest.Matcher;
+
+class NioNodeMatcher {
+
+ public static Matcher folderWithName(String name) {
+ return new PropertyMatcher<>(Folder.class, Folder::name, "name", equalTo(name));
+ }
+
+ public static Matcher fileWithName(String name) {
+ return new PropertyMatcher<>(File.class, File::name, "name", equalTo(name));
+ }
+
+}
diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/PathMatcher.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/PathMatcher.java
new file mode 100644
index 000000000..a7b53ac4b
--- /dev/null
+++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/PathMatcher.java
@@ -0,0 +1,22 @@
+package org.cryptomator.filesystem.nio;
+
+import static org.hamcrest.CoreMatchers.is;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.hamcrest.FeatureMatcher;
+import org.hamcrest.Matcher;
+
+class PathMatcher {
+
+ public static Matcher isDirectory() {
+ return new FeatureMatcher(is(true), "a path for which Files.isDirectory", "Files.isDirectory") {
+ @Override
+ protected Boolean featureValueOf(Path actual) {
+ return Files.isDirectory(actual);
+ }
+ };
+ }
+
+}
diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SampleFilesystem.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SampleFilesystem.java
new file mode 100644
index 000000000..cc8e230d1
--- /dev/null
+++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SampleFilesystem.java
@@ -0,0 +1,15 @@
+package org.cryptomator.filesystem.nio;
+
+import org.apache.commons.lang3.StringUtils;
+
+public enum SampleFilesystem {
+
+ EMPTY_FILESYSTEM, SOME_FOLDERS_AND_FILES
+
+ ;
+
+ public String directoryName() {
+ return StringUtils.removeEnd(name(), "_FILESYSTEM").toLowerCase();
+ }
+
+}
diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WeakValuedCacheTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WeakValuedCacheTest.java
new file mode 100644
index 000000000..77b427847
--- /dev/null
+++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WeakValuedCacheTest.java
@@ -0,0 +1,138 @@
+package org.cryptomator.filesystem.nio;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.function.Function;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+
+public class WeakValuedCacheTest {
+
+ private final String A_KEY = "aKey";
+ private final String ANOTHER_KEY = "anotherKey";
+
+ private WeakValuedCache inTest;
+
+ private Function loader;
+
+ @SuppressWarnings("unchecked")
+ @Before
+ public void setup() {
+ loader = Mockito.mock(Function.class);
+ inTest = WeakValuedCache.usingLoader(loader);
+ }
+
+ @Test
+ public void testResultOfGetIsResultOfLoaderForTheSameKey() {
+ Value theValue = new Value();
+ Value theOtherValue = new Value();
+ when(loader.apply(A_KEY)).thenReturn(theValue);
+ when(loader.apply(ANOTHER_KEY)).thenReturn(theOtherValue);
+
+ Value result = inTest.get(A_KEY);
+ Value anotherResult = inTest.get(ANOTHER_KEY);
+
+ assertThat(result, is(sameInstance(theValue)));
+ assertThat(anotherResult, is(sameInstance(theOtherValue)));
+ }
+
+ @Test
+ public void testCachedResultIsResultOfLoaderForTheSameKey() {
+ Value theValue = new Value();
+ Value theOtherValue = new Value();
+ when(loader.apply(A_KEY)).thenReturn(theValue);
+ when(loader.apply(ANOTHER_KEY)).thenReturn(theOtherValue);
+
+ inTest.get(A_KEY);
+ inTest.get(ANOTHER_KEY);
+ Value result = inTest.get(A_KEY);
+ Value anotherResult = inTest.get(ANOTHER_KEY);
+
+ assertThat(result, is(sameInstance(theValue)));
+ assertThat(anotherResult, is(sameInstance(theOtherValue)));
+ }
+
+ @Test
+ public void testTwiceInvocationOfGetDoesNotInvokeLoaderTwice() {
+ Value theValue = new Value();
+ when(loader.apply(A_KEY)).thenReturn(theValue);
+
+ inTest.get(A_KEY);
+ inTest.get(A_KEY);
+
+ verify(loader).apply(A_KEY);
+ }
+
+ @Test
+ public void testSecondInvocationOfGetReturnsTheSameResult() {
+ Value theValue = new Value();
+ when(loader.apply(A_KEY)).thenReturn(theValue);
+
+ inTest.get(A_KEY);
+ Value result = inTest.get(A_KEY);
+
+ assertThat(result, is(sameInstance(theValue)));
+ }
+
+ @Test
+ public void testCacheDoesNotPreventGarbageCollectionOfValues() {
+ when(loader.apply(A_KEY)).thenAnswer(this::createValueUsingMoreThanHalfTheJvmMemory);
+
+ inTest.get(A_KEY);
+
+ // force garbage collection of previously created value by creating an
+ // object so large it can not coexist with the value
+ createByteArrayUsingMoreThanHalfTheJvmMemory();
+ }
+
+ @Test(expected = RuntimeExceptionThrownInLoader.class)
+ public void testCacheRethrowsRuntimeExceptionsFromLoader() {
+ when(loader.apply(A_KEY)).thenThrow(new RuntimeExceptionThrownInLoader());
+
+ inTest.get(A_KEY);
+ }
+
+ @Test(expected = ErrorThrownInLoader.class)
+ public void testCacheRethrowsErrorsFromLoader() {
+ when(loader.apply(A_KEY)).thenThrow(new ErrorThrownInLoader());
+
+ inTest.get(A_KEY);
+ }
+
+ private Value createValueUsingMoreThanHalfTheJvmMemory(InvocationOnMock invocation) {
+ byte[] data = createByteArrayUsingMoreThanHalfTheJvmMemory();
+ Value value = new Value();
+ value.setPayload(data);
+ return value;
+ }
+
+ private byte[] createByteArrayUsingMoreThanHalfTheJvmMemory() {
+ int max = (int) Runtime.getRuntime().maxMemory();
+ byte[] data = new byte[max / 2 + 1];
+ return data;
+ }
+
+ private static class RuntimeExceptionThrownInLoader extends RuntimeException {
+ }
+
+ private static class ErrorThrownInLoader extends Error {
+ }
+
+ private static class Value {
+
+ @SuppressWarnings("unused")
+ private Object payload;
+
+ public void setPayload(Object payload) {
+ this.payload = payload;
+ }
+ }
+
+}
diff --git a/main/pom.xml b/main/pom.xml
index 47f081266..308b2e008 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -35,6 +35,7 @@
2.1
1.7.7
4.12
+ 1.3
2.4
4.0
3.3.2
@@ -43,7 +44,7 @@
2.4.4
1.10.19
-
+
jitpack.io
@@ -54,6 +55,13 @@
+
+ org.cryptomator
+ commons-test
+ ${project.version}
+ test
+
+
org.cryptomator
filesystem-api
@@ -74,7 +82,7 @@
filesystem-crypto
${project.version}
-
+
org.cryptomator
core
@@ -173,18 +181,36 @@
${jackson-databind.version}
-
+
junit
junit
${junit.version}
test
+
+
+ hamcrest-core
+ org.hamcrest
+
+
org.mockito
mockito-core
${mockito.version}
test
+
+
+ hamcrest-core
+ org.hamcrest
+
+
+
+
+ org.hamcrest
+ hamcrest-all
+ ${hamcrest.version}
+ test
@@ -223,6 +249,7 @@
core
ui
jackrabbit-filesystem-adapter
+ commons-test