adjusted in-memory filesystem to comply with API (return files/folders when requested, even though the oposite kind exists for the given name)

This commit is contained in:
Sebastian Stenzel
2016-01-01 22:44:46 +01:00
parent 71face8091
commit 4e7f3503d9
5 changed files with 201 additions and 21 deletions

View File

@@ -44,6 +44,16 @@ public interface Folder extends Node {
*/
File file(String name) throws UncheckedIOException;
/**
* Returns a file by resolving a path relative to this folder.
*
* @param path A unix-style path, which is always relative to this folder, no matter if it starts with a slash or not
* @return File with the given path relative to this folder
*/
default File resolveFile(String relativePath) throws UncheckedIOException {
return PathResolver.resolveFile(this, relativePath);
}
/**
* <p>
* Returns the child {@link Node} in this directory of type {@link Folder}
@@ -54,6 +64,16 @@ public interface Folder extends Node {
*/
Folder folder(String name) throws UncheckedIOException;
/**
* Returns a folder by resolving a path relative to this folder.
*
* @param path A unix-style path, which is always relative to this folder, no matter if it starts with a slash or not
* @return Folder with the given path relative to this folder
*/
default Folder resolveFolder(String relativePath) throws UncheckedIOException {
return PathResolver.resolveFolder(this, relativePath);
}
/**
* Creates the directory including all parent directories, if it doesn't
* exist yet. No effect, if folder already exists.

View File

@@ -0,0 +1,100 @@
package org.cryptomator.filesystem;
import java.io.FileNotFoundException;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.Iterator;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
public final class PathResolver {
private static final String DOT = ".";
private static final String DOTDOT = "..";
private PathResolver() {
}
/**
* Resolves a relative path (separated by '/') to a folder, e.g.
* <!-- @formatter:off -->
* <table>
* <thead>
* <tr>
* <th>dir</th>
* <th>path</th>
* <th>result</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>/foo/bar</td>
* <td>foo/bar</td>
* <td>/foo/bar/foo/bar</td>
* </tr>
* <tr>
* <td>/foo/bar</td>
* <td>../baz</td>
* <td>/foo/baz</td>
* </tr>
* <tr>
* <td>/foo/bar</td>
* <td>./foo/..</td>
* <td>/foo/bar</td>
* </tr>
* <tr>
* <td>/foo/bar</td>
* <td>../../..</td>
* <td>Exception</td>
* </tr>
* </tbody>
* </table>
*
* @param dir The directory from which to resolve the path.
* @param relativePath The path relative to a given directory.
* @return The folder with the given path relative to the given dir.
*/
public static Folder resolveFolder(Folder dir, String relativePath) {
final String[] fragments = StringUtils.split(relativePath, '/');
if (ArrayUtils.isEmpty(fragments)) {
throw new IllegalArgumentException("Empty relativePath");
}
return resolveFolder(dir, Arrays.stream(fragments).iterator());
}
/**
* Resolves a relative path (separated by '/') to a file. Besides returning a File, this method is identical to {@link #resolveFile(Folder, String)}.
*
* @param dir The directory from which to resolve the path.
* @param relativePath The path relative to a given directory.
* @return The file with the given path relative to the given dir.
*/
public static File resolveFile(Folder dir, String relativePath) {
final String[] fragments = StringUtils.split(relativePath, '/');
if (ArrayUtils.isEmpty(fragments)) {
throw new IllegalArgumentException("Empty relativePath");
}
final Folder folder = resolveFolder(dir, Arrays.stream(fragments).limit(fragments.length - 1).iterator());
final String filename = fragments[fragments.length - 1];
return folder.file(filename);
}
private static Folder resolveFolder(Folder dir, Iterator<String> remainingPathFragments) {
if (!remainingPathFragments.hasNext()) {
return dir;
}
final String fragment = remainingPathFragments.next();
assert fragment.length() > 0 : "iterator must not contain empty fragments";
if (DOT.equals(fragment)) {
return resolveFolder(dir, remainingPathFragments);
} else if (DOTDOT.equals(fragment) && dir.parent().isPresent()) {
return resolveFolder(dir.parent().get(), remainingPathFragments);
} else if (DOTDOT.equals(fragment) && !dir.parent().isPresent()) {
throw new UncheckedIOException(new FileNotFoundException("Unresolvable path"));
} else {
return resolveFolder(dir.folder(fragment), remainingPathFragments);
}
}
}

View File

@@ -0,0 +1,68 @@
package org.cryptomator.filesystem;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Optional;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
public class PathResolverTest {
private final Folder root = Mockito.mock(Folder.class);
private final Folder foo = Mockito.mock(Folder.class);
private final Folder bar = Mockito.mock(Folder.class);
private final File baz = Mockito.mock(File.class);
@Before
public void configureMocks() throws IOException {
Mockito.doReturn(Optional.empty()).when(root).parent();
Mockito.doReturn(Optional.of(root)).when(foo).parent();
Mockito.doReturn(Optional.of(foo)).when(bar).parent();
Mockito.doReturn(foo).when(root).folder("foo");
Mockito.doReturn(bar).when(foo).folder("bar");
Mockito.doReturn(baz).when(bar).file("baz");
}
@Test
public void testResolveChildFolder() {
Assert.assertEquals(bar, PathResolver.resolveFolder(root, "foo/bar"));
Assert.assertEquals(bar, PathResolver.resolveFolder(root, "foo/./bar"));
Assert.assertEquals(bar, PathResolver.resolveFolder(root, "./foo/././bar"));
}
@Test
public void testResolveParentFolder() {
Assert.assertEquals(foo, PathResolver.resolveFolder(bar, ".."));
Assert.assertEquals(root, PathResolver.resolveFolder(bar, "../.."));
}
@Test
public void testResolveSiblingFolder() {
Assert.assertEquals(foo, PathResolver.resolveFolder(bar, "../../foo"));
}
@Test(expected = UncheckedIOException.class)
public void testResolveUnresolvableFolder() {
PathResolver.resolveFolder(root, "..");
}
@Test(expected = IllegalArgumentException.class)
public void testResolveFolderWithEmptyPath() {
PathResolver.resolveFolder(root, "");
}
@Test(expected = IllegalArgumentException.class)
public void testResolveFileWithEmptyPath() {
PathResolver.resolveFile(root, "");
}
@Test
public void testResolveFile() {
Assert.assertEquals(baz, PathResolver.resolveFile(foo, "../foo/bar/./baz"));
}
}

View File

@@ -16,6 +16,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import org.apache.commons.io.FileExistsException;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.ReadableFile;
import org.cryptomator.filesystem.WritableFile;
@@ -45,10 +46,12 @@ class InMemoryFile extends InMemoryNode implements File {
writeLock.lock();
final InMemoryFolder parent = parent().get();
parent.children.compute(this.name(), (k, v) -> {
if (v != null && v != this) {
throw new IllegalStateException("More than one representation of same file");
if (v == null || v == this) {
this.lastModified = Instant.now();
return this;
} else {
throw new UncheckedIOException(new FileExistsException(k));
}
return this;
});
return new InMemoryWritableFile(this::setLastModified, this::getContent, this::setContent, this::delete, writeLock);
}

View File

@@ -9,7 +9,6 @@
package org.cryptomator.filesystem.inmem;
import java.io.UncheckedIOException;
import java.nio.file.FileAlreadyExistsException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Iterator;
@@ -36,31 +35,21 @@ class InMemoryFolder extends InMemoryNode implements Folder {
@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);
});
}
final InMemoryNode node = children.get(name);
if (node instanceof InMemoryFile) {
return (InMemoryFile) node;
} else {
throw new UncheckedIOException(new FileAlreadyExistsException(name + " exists, but is not a file."));
return new InMemoryFile(this, name, Instant.MIN);
}
}
@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);
});
}
final InMemoryNode node = children.get(name);
if (node instanceof InMemoryFolder) {
return (InMemoryFolder) node;
} else {
throw new UncheckedIOException(new FileAlreadyExistsException(name + " exists, but is not a folder."));
return new InMemoryFolder(this, name, Instant.MIN);
}
}
@@ -86,11 +75,11 @@ class InMemoryFolder extends InMemoryNode implements Folder {
if (target.exists()) {
target.delete();
}
assert !target.exists();
assert!target.exists();
target.create();
this.copyTo(target);
this.delete();
assert !this.exists();
assert!this.exists();
}
@Override
@@ -112,7 +101,7 @@ class InMemoryFolder extends InMemoryNode implements Folder {
subFolder.delete();
}
}
assert !this.exists();
assert!this.exists();
}
@Override