first tests with refactored io layers

This commit is contained in:
Sebastian Stenzel
2015-12-14 04:37:29 +01:00
parent 3971d3afd5
commit e1b74ce312
19 changed files with 859 additions and 13 deletions
@@ -7,11 +7,12 @@ package org.cryptomator.filesystem;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public interface File extends Node {
ReadableFile openReadable(long timeout, TimeUnit unit) throws IOException;
ReadableFile openReadable(long timeout, TimeUnit unit) throws IOException, TimeoutException;
WritableFile openWritable(long timeout, TimeUnit unit) throws IOException;
WritableFile openWritable(long timeout, TimeUnit unit) throws IOException, TimeoutException;
}
@@ -15,7 +15,7 @@ import java.util.Optional;
public interface FileSystem extends Folder {
@Override
default Optional<Folder> parent() {
default Optional<? extends Folder> parent() {
return Optional.empty();
}
@@ -32,11 +32,11 @@ public interface Folder extends Node {
* if an {@link IOException} occurs while initializing the
* stream
*/
Stream<Node> children() throws IOException;
Stream<? extends Node> children() throws IOException;
File file(String name) throws IOException;
File file(String name) throws UncheckedIOException;
Folder folder(String name) throws IOException;
Folder folder(String name) throws UncheckedIOException;
void create(FolderCreateMode mode) throws IOException;
@@ -46,7 +46,7 @@ public interface Folder extends Node {
* @return the result of {@link #children()} filtered to contain only
* {@link File Files}
*/
default Stream<File> files() throws IOException {
default Stream<? extends File> files() throws IOException {
return children() //
.filter(File.class::isInstance) //
.map(File.class::cast);
@@ -56,7 +56,7 @@ public interface Folder extends Node {
* @return the result of {@link #children()} filtered to contain only
* {@link Folder Folders}
*/
default Stream<Folder> folders() throws IOException {
default Stream<? extends Folder> folders() throws IOException {
return children() //
.filter(Folder.class::isInstance) //
.map(Folder.class::cast);
@@ -5,7 +5,7 @@
******************************************************************************/
package org.cryptomator.filesystem;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.time.Instant;
import java.util.Optional;
@@ -19,12 +19,12 @@ import java.util.Optional;
*/
public interface Node {
String name() throws IOException;
String name() throws UncheckedIOException;
Optional<Folder> parent() throws IOException;
Optional<? extends Folder> parent() throws UncheckedIOException;
boolean exists() throws IOException;
boolean exists() throws UncheckedIOException;
Instant lastModified() throws IOException;
Instant lastModified() throws UncheckedIOException;
}
@@ -0,0 +1,141 @@
package org.cryptomator.filesystem.inmem;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.cryptomator.filesystem.ReadableFile;
import org.cryptomator.filesystem.WritableFile;
public class InMemoryFile extends InMemoryNode implements ReadableFile, WritableFile {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private ByteBuffer content = ByteBuffer.wrap(new byte[0]);
InMemoryFile(InMemoryFolder parent, String name, Instant lastModified) {
super(parent, name, lastModified);
}
@Override
public ReadableFile openReadable(long timeout, TimeUnit unit) throws IOException, TimeoutException {
if (!exists()) {
throw new FileNotFoundException(this.name() + " does not exist");
}
try {
if (!lock.readLock().tryLock(timeout, unit)) {
throw new TimeoutException("Failed to open " + name() + " for reading within time limit.");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return this;
}
@Override
public WritableFile openWritable(long timeout, TimeUnit unit) throws IOException, TimeoutException {
try {
if (!lock.writeLock().tryLock(timeout, unit)) {
throw new TimeoutException("Failed to open " + name() + " for writing within time limit.");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
final InMemoryFolder parent = parent().get();
try {
parent.children.compute(this.name(), (k, v) -> {
if (v != null && v != this) {
throw new IllegalStateException("More than one representation of same file");
}
return this;
});
} catch (UncheckedIOException e) {
throw e.getCause();
}
return this;
}
@Override
public void read(ByteBuffer target) throws IOException {
this.read(target, 0);
}
@Override
public void read(ByteBuffer target, int position) throws IOException {
content.rewind();
content.position(position);
target.put(content);
}
@Override
public void write(ByteBuffer source) throws IOException {
this.write(source, content.position());
}
@Override
public void write(ByteBuffer source, int position) throws IOException {
assert content != null;
if (position + source.remaining() > content.remaining()) {
// create bigger buffer
ByteBuffer tmp = ByteBuffer.allocate(Math.max(position, content.capacity()) + source.remaining());
tmp.put(content);
content = tmp;
}
content.position(position);
content.put(source);
}
@Override
public WritableFile moveTo(WritableFile other) throws IOException {
this.copyTo(other);
this.delete();
return other;
}
@Override
public void setLastModified(Instant instant) {
this.lastModified = instant;
}
@Override
public void delete() {
final InMemoryFolder parent = parent().get();
parent.children.computeIfPresent(this.name(), (k, v) -> {
truncate();
// returning null removes the entry.
return null;
});
}
@Override
public void truncate() {
content = ByteBuffer.wrap(new byte[0]);
}
@Override
public WritableFile copyTo(WritableFile other) throws IOException {
content.rewind();
other.truncate();
other.write(content);
return other;
}
@Override
public void close() throws IOException {
if (lock.isWriteLockedByCurrentThread()) {
lock.writeLock().unlock();
} else if (lock.getReadHoldCount() > 0) {
lock.readLock().unlock();
}
}
@Override
public String toString() {
return parent.toString() + name;
}
}
@@ -0,0 +1,34 @@
package org.cryptomator.filesystem.inmem;
import java.time.Instant;
import java.util.Optional;
import org.cryptomator.filesystem.FileSystem;
public class InMemoryFileSystem extends InMemoryFolder implements FileSystem {
public InMemoryFileSystem() {
super(null, "", Instant.now());
}
@Override
public Optional<InMemoryFolder> parent() {
return Optional.empty();
}
@Override
public boolean exists() {
return true;
}
@Override
public void delete() {
// no-op.
}
@Override
public String toString() {
return "/";
}
}
@@ -0,0 +1,99 @@
package org.cryptomator.filesystem.inmem;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileAlreadyExistsException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Stream;
import org.apache.commons.io.FileExistsException;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.FolderCreateMode;
public class InMemoryFolder extends InMemoryNode implements Folder {
final Map<String, InMemoryNode> children = new TreeMap<>();
final Map<String, InMemoryNode> volatileChildren = new HashMap<>();
InMemoryFolder(InMemoryFolder parent, String name, Instant lastModified) {
super(parent, name, lastModified);
}
@Override
public Stream<InMemoryNode> children() {
return children.values().stream();
}
@Override
public InMemoryFile file(String name) {
InMemoryNode node = children.get(name);
if (node == null) {
node = volatileChildren.computeIfAbsent(name, (k) -> {
return new InMemoryFile(this, name, Instant.MIN);
});
}
if (node instanceof InMemoryFile) {
return (InMemoryFile) node;
} else {
throw new UncheckedIOException(new FileAlreadyExistsException(name + " exists, but is not a file."));
}
}
@Override
public InMemoryFolder folder(String name) {
InMemoryNode node = children.get(name);
if (node == null) {
node = volatileChildren.computeIfAbsent(name, (k) -> {
return new InMemoryFolder(this, name, Instant.MIN);
});
}
if (node instanceof InMemoryFolder) {
return (InMemoryFolder) node;
} else {
throw new UncheckedIOException(new FileAlreadyExistsException(name + " exists, but is not a folder."));
}
}
@Override
public void create(FolderCreateMode mode) throws IOException {
if (exists()) {
return;
}
if (!parent.exists() && FolderCreateMode.FAIL_IF_PARENT_IS_MISSING.equals(mode)) {
throw new FileNotFoundException(parent.name);
} else if (!parent.exists() && FolderCreateMode.INCLUDING_PARENTS.equals(mode)) {
parent.create(mode);
}
assert parent.exists();
try {
parent.children.compute(this.name(), (k, v) -> {
if (v == null) {
this.lastModified = Instant.now();
return this;
} else {
throw new UncheckedIOException(new FileExistsException(k));
}
});
} catch (UncheckedIOException e) {
throw e.getCause();
}
}
@Override
public void delete() {
parent.children.computeIfPresent(name, (k, v) -> {
// returning null removes the entry.
return null;
});
}
@Override
public String toString() {
return parent.toString() + name + "/";
}
}
@@ -0,0 +1,61 @@
package org.cryptomator.filesystem.inmem;
import java.time.Instant;
import java.util.Optional;
import org.cryptomator.filesystem.Node;
public class InMemoryNode implements Node {
protected final InMemoryFolder parent;
protected final String name;
protected Instant lastModified;
InMemoryNode(InMemoryFolder parent, String name, Instant lastModified) {
this.parent = parent;
this.name = name;
this.lastModified = lastModified;
}
@Override
public String name() {
return name;
}
@Override
public Optional<InMemoryFolder> parent() {
return Optional.of(parent);
}
@Override
public boolean exists() {
return parent.children().anyMatch(node -> node.equals(this));
}
@Override
public Instant lastModified() {
return lastModified;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((parent == null) ? 0 : parent.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof InMemoryNode) {
InMemoryNode other = (InMemoryNode) obj;
return this.getClass() == other.getClass() //
&& (this.parent == null && other.parent == null || this.parent.equals(other.parent)) //
&& (this.name == null && other.name == null || this.name.equals(other.name));
} else {
return false;
}
}
}
@@ -0,0 +1,94 @@
package org.cryptomator.filesystem.inmem;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.FolderCreateMode;
import org.cryptomator.filesystem.ReadableFile;
import org.cryptomator.filesystem.WritableFile;
import org.junit.Assert;
import org.junit.Test;
public class InMemoryFileSystemTest {
@Test
public void testFolderCreation() throws IOException {
final FileSystem fs = new InMemoryFileSystem();
Folder fooFolder = fs.folder("foo");
// nothing happened yet:
Assert.assertFalse(fooFolder.exists());
Assert.assertEquals(0, fs.folders().count());
// create /foo
fooFolder.create(FolderCreateMode.FAIL_IF_PARENT_IS_MISSING);
Assert.assertTrue(fooFolder.exists());
Assert.assertEquals(1, fs.folders().count());
// delete /foo
fooFolder.delete();
Assert.assertFalse(fooFolder.exists());
Assert.assertEquals(0, fs.folders().count());
// create /foo/bar
Folder fooBarFolder = fooFolder.folder("bar");
Assert.assertFalse(fooBarFolder.exists());
fooBarFolder.create(FolderCreateMode.INCLUDING_PARENTS);
Assert.assertTrue(fooFolder.exists());
Assert.assertTrue(fooBarFolder.exists());
Assert.assertEquals(1, fs.folders().count());
Assert.assertEquals(1, fooFolder.folders().count());
}
@Test
public void testFileReadCopyMoveWrite() throws IOException, TimeoutException {
final FileSystem fs = new InMemoryFileSystem();
File fooFile = fs.file("foo.txt");
// nothing happened yet:
Assert.assertFalse(fooFile.exists());
Assert.assertEquals(0, fs.files().count());
// write "hello world" to foo
try (WritableFile writable = fooFile.openWritable(1, TimeUnit.SECONDS)) {
writable.write(ByteBuffer.wrap("hello".getBytes()));
writable.write(ByteBuffer.wrap(" ".getBytes()));
writable.write(ByteBuffer.wrap("world".getBytes()));
}
Assert.assertTrue(fooFile.exists());
// copy foo to bar
File barFile = fs.file("bar.txt");
try (WritableFile writable = barFile.openWritable(1, TimeUnit.SECONDS)) {
try (ReadableFile readable = fooFile.openReadable(1, TimeUnit.SECONDS)) {
readable.copyTo(writable);
}
}
Assert.assertTrue(fooFile.exists());
Assert.assertTrue(barFile.exists());
// move bar to baz
File bazFile = fs.file("baz.txt");
try (WritableFile src = barFile.openWritable(1, TimeUnit.SECONDS)) {
try (WritableFile dst = bazFile.openWritable(1, TimeUnit.SECONDS)) {
src.moveTo(dst);
}
}
Assert.assertFalse(barFile.exists());
Assert.assertTrue(bazFile.exists());
// read "hello world" from baz
final ByteBuffer readBuf = ByteBuffer.allocate(5);
try (ReadableFile readable = bazFile.openReadable(1, TimeUnit.SECONDS)) {
readable.read(readBuf, 6);
}
Assert.assertEquals("world", new String(readBuf.array()));
}
}