mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-25 06:01:31 +00:00
Changes to filesystem API and nio implementation
* Partial implementation of nio filesystem * Addded some tests * Added project for common test dependencies * Removed default implementation of Folder#delete ** reason: didn't work because empty folders were not deleted and this cannot be done in the default implementation
This commit is contained in:
1
main/commons-test/.gitignore
vendored
Normal file
1
main/commons-test/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target/
|
||||
23
main/commons-test/pom.xml
Normal file
23
main/commons-test/pom.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (c) 2015 Markus Kreusch This file is licensed under the terms
|
||||
of the MIT license. See the LICENSE.txt file for more info. -->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>commons-test</artifactId>
|
||||
<name>Cryptomator common test dependencies</name>
|
||||
<description>Shared utilities for tests</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.cryptomator.commons.test.matcher;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
/**
|
||||
* Wraps hamcrest contains and containsInAny order matcher factory methods to
|
||||
* avoid problems due to incorrect / inconsistent handling of generics by the
|
||||
* several java compilers.
|
||||
*
|
||||
* @author Markus Kreusch
|
||||
*/
|
||||
public class ContainsMatcher {
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@SafeVarargs
|
||||
public static <T> Matcher<Iterable<? super T>> containsInAnyOrder(Matcher<? extends T>... matchers) {
|
||||
return Matchers.containsInAnyOrder((Matcher[]) matchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@SafeVarargs
|
||||
public static <T> Matcher<Iterable<? super T>> contains(Matcher<? extends T>... matchers) {
|
||||
return Matchers.contains((Matcher[]) matchers);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
******************************************************************************/
|
||||
package org.cryptomator.commons.test.matcher;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeDiagnosingMatcher;
|
||||
|
||||
public class PropertyMatcher<T, P> extends TypeSafeDiagnosingMatcher<T> {
|
||||
|
||||
private final Class<T> expectedType;
|
||||
private final Function<T, P> getter;
|
||||
private final String name;
|
||||
private final Matcher<? super P> subMatcher;
|
||||
|
||||
public PropertyMatcher(Class<T> type, Function<T, P> getter, String name, Matcher<? super P> subMatcher) {
|
||||
super(type);
|
||||
this.expectedType = type;
|
||||
this.getter = getter;
|
||||
this.name = name;
|
||||
this.subMatcher = subMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("a ") //
|
||||
.appendText(expectedType.getSimpleName()) //
|
||||
.appendText(" with a ") //
|
||||
.appendText(name) //
|
||||
.appendText(" that is ") //
|
||||
.appendDescriptionOf(subMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(T item, Description mismatchDescription) {
|
||||
P propertyValue = getter.apply(item);
|
||||
if (subMatcher.matches(propertyValue)) {
|
||||
return true;
|
||||
} else {
|
||||
mismatchDescription.appendText("a ") //
|
||||
.appendText(expectedType.getSimpleName()) //
|
||||
.appendText(" with a ") //
|
||||
.appendText(name) //
|
||||
.appendText(" that was ");
|
||||
subMatcher.describeMismatch(propertyValue, mismatchDescription);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class FileSystemVisitor {
|
||||
|
||||
private final Consumer<Folder> beforeFolderVisitor;
|
||||
private final Consumer<Folder> afterFolderVisitor;
|
||||
private final Consumer<File> fileVisitor;
|
||||
|
||||
private FileSystemVisitor(Consumer<Folder> beforeFolderVisitor, Consumer<Folder> afterFolderVisitor, Consumer<File> fileVisitor) {
|
||||
this.beforeFolderVisitor = beforeFolderVisitor;
|
||||
this.afterFolderVisitor = afterFolderVisitor;
|
||||
this.fileVisitor = fileVisitor;
|
||||
}
|
||||
|
||||
public static FileSystemVisitorBuilder fileSystemVisitor() {
|
||||
return new FileSystemVisitorBuilder();
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(Folder folder) {
|
||||
beforeFolderVisitor.accept(folder);
|
||||
folder.folders().forEach(this::visit);
|
||||
folder.files().forEach(this::visit);
|
||||
afterFolderVisitor.accept(folder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(File file) {
|
||||
fileVisitor.accept(file);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class FileSystemVisitorBuilder {
|
||||
|
||||
private Consumer<Folder> beforeFolderVisitor = noOp();
|
||||
private Consumer<Folder> afterFolderVisitor = noOp();
|
||||
private Consumer<File> fileVisitor = noOp();
|
||||
|
||||
private FileSystemVisitorBuilder() {
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder beforeFolder(Consumer<Folder> beforeFolderVisitor) {
|
||||
if (beforeFolderVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
this.beforeFolderVisitor = beforeFolderVisitor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder afterFolder(Consumer<Folder> afterFolderVisitor) {
|
||||
if (afterFolderVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
this.afterFolderVisitor = afterFolderVisitor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder forEachFile(Consumer<File> fileVisitor) {
|
||||
if (fileVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
this.fileVisitor = fileVisitor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(Folder folder) {
|
||||
return build().visit(folder);
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(File file) {
|
||||
return build().visit(file);
|
||||
}
|
||||
|
||||
public FileSystemVisitor build() {
|
||||
return new FileSystemVisitor(beforeFolderVisitor, afterFolderVisitor, fileVisitor);
|
||||
}
|
||||
|
||||
private static <T> Consumer<T> noOp() {
|
||||
return ignoredParameter -> {
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,17 +88,7 @@ public interface Folder extends Node {
|
||||
* <p>
|
||||
* 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
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2015 Markus Kreusch
|
||||
This file is licensed under the terms of the MIT license.
|
||||
See the LICENSE.txt file for more info.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<!-- Copyright (c) 2015 Markus Kreusch This file is licensed under the terms
|
||||
of the MIT license. See the LICENSE.txt file for more info. -->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
@@ -38,6 +36,19 @@
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -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<NioFolder> parent, Path path) {
|
||||
return new NioFile(parent, path, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NioFolder folder(Optional<NioFolder> parent, Path path) {
|
||||
return new NioFolder(parent, path, this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<NioFolder> parent, Path path, NioNodeFactory nodeFactory) {
|
||||
super(parent, path, nodeFactory);
|
||||
public NioFile(Optional<NioFolder> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Path, NioFolder> folders = WeakValuedCache.usingLoader(this::folderFromPath);
|
||||
private final WeakValuedCache<Path, NioFile> files = WeakValuedCache.usingLoader(this::fileFromPath);
|
||||
|
||||
public NioFolder(Optional<NioFolder> parent, Path path, NioNodeFactory nodeFactory) {
|
||||
super(parent, path, nodeFactory);
|
||||
public NioFolder(Optional<NioFolder> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,17 +14,26 @@ class NioNode implements Node {
|
||||
|
||||
protected final Optional<NioFolder> parent;
|
||||
protected final Path path;
|
||||
protected final NioNodeFactory nodeFactory;
|
||||
|
||||
public NioNode(Optional<NioFolder> parent, Path path, NioNodeFactory nodeFactory) {
|
||||
private NioFileSystem fileSystem;
|
||||
|
||||
public NioNode(Optional<NioFolder> 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
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.cryptomator.filesystem.nio;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
interface NioNodeFactory {
|
||||
|
||||
NioFile file(Optional<NioFolder> parent, Path path);
|
||||
|
||||
NioFolder folder(Optional<NioFolder> parent, Path path);
|
||||
|
||||
}
|
||||
@@ -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<Key, Value> {
|
||||
|
||||
@@ -30,7 +32,11 @@ class WeakValuedCache<Key, Value> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<Folder> folderWithName(String name) {
|
||||
return new PropertyMatcher<>(Folder.class, Folder::name, "name", equalTo(name));
|
||||
}
|
||||
|
||||
public static Matcher<File> fileWithName(String name) {
|
||||
return new PropertyMatcher<>(File.class, File::name, "name", equalTo(name));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Path> isDirectory() {
|
||||
return new FeatureMatcher<Path, Boolean>(is(true), "a path for which Files.isDirectory", "Files.isDirectory") {
|
||||
@Override
|
||||
protected Boolean featureValueOf(Path actual) {
|
||||
return Files.isDirectory(actual);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String, Value> inTest;
|
||||
|
||||
private Function<String, Value> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
33
main/pom.xml
33
main/pom.xml
@@ -35,6 +35,7 @@
|
||||
<log4j.version>2.1</log4j.version>
|
||||
<slf4j.version>1.7.7</slf4j.version>
|
||||
<junit.version>4.12</junit.version>
|
||||
<hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
|
||||
<commons-io.version>2.4</commons-io.version>
|
||||
<commons-collections.version>4.0</commons-collections.version>
|
||||
<commons-lang3.version>3.3.2</commons-lang3.version>
|
||||
@@ -43,7 +44,7 @@
|
||||
<jackson-databind.version>2.4.4</jackson-databind.version>
|
||||
<mockito.version>1.10.19</mockito.version>
|
||||
</properties>
|
||||
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
@@ -54,6 +55,13 @@
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- modules -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons-test</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>filesystem-api</artifactId>
|
||||
@@ -74,7 +82,7 @@
|
||||
<artifactId>filesystem-crypto</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
@@ -173,18 +181,36 @@
|
||||
<version>${jackson-databind.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JUnit / Mockito -->
|
||||
<!-- JUnit / Mockito / Hamcrest -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<version>${hamcrest.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
@@ -223,6 +249,7 @@
|
||||
<module>core</module>
|
||||
<module>ui</module>
|
||||
<module>jackrabbit-filesystem-adapter</module>
|
||||
<module>commons-test</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
|
||||
Reference in New Issue
Block a user