Implemented NioFile

* Implementation of NioFile methods
* Extracted Readable/WritableNioFile into separate classes
** Created SharedFileChannel to allow Readable/WritableNioFile for the
same NioFile to use a single, shared FileChannel
* Added tests for NioFile
* Tests for Readable/WritableNioFile pending
This commit is contained in:
Markus Kreusch
2015-12-31 16:48:25 +01:00
parent 806e366a72
commit 39535d08e7
10 changed files with 838 additions and 72 deletions

View File

@@ -11,6 +11,9 @@ package org.cryptomator.filesystem;
class Mover {
public static void move(File source, File destination) {
if (source == destination) {
return;
}
try (OpenFiles openFiles = DeadlockSafeFileOpener.withWritable(source).andWritable(destination).open()) {
openFiles.writable(source).moveTo(openFiles.writable(destination));
}

View File

@@ -13,6 +13,13 @@ import java.time.Instant;
public interface WritableFile extends WritableByteChannel {
/**
* <p>
* Moves this file including content to another.
* <p>
* Moving a file causes itself and the target to be
* {@link WritableFile#close() closed}.
*/
void moveTo(WritableFile other) throws UncheckedIOException;
void setLastModified(Instant instant) throws UncheckedIOException;

View File

@@ -4,7 +4,6 @@ import static java.lang.String.format;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
@@ -18,101 +17,56 @@ import org.cryptomator.filesystem.WritableFile;
class NioFile extends NioNode implements File {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
private SharedFileChannel sharedChannel;
public NioFile(Optional<NioFolder> parent, Path path) {
super(parent, path);
sharedChannel = new SharedFileChannel(path);
}
SharedFileChannel channel() {
return sharedChannel;
}
public ReentrantReadWriteLock lock() {
return lock;
}
@Override
public ReadableFile openReadable() throws UncheckedIOException {
if (lock.getWriteHoldCount() > 0) {
throw new IllegalStateException("Current thread is currently reading this file");
throw new IllegalStateException("Current thread is currently writing this file");
}
if (lock.getReadHoldCount() > 0) {
throw new IllegalStateException("Current thread is already reading this file");
}
lock.readLock().lock();
return new ReadableView();
return new ReadableNioFile(this);
}
@Override
public WritableFile openWritable() throws UncheckedIOException {
if (lock.getWriteHoldCount() > 0) {
throw new IllegalStateException("Current thread is already writing this file");
}
if (lock.getReadHoldCount() > 0) {
throw new IllegalStateException("Current thread is currently reading this file");
}
lock.readLock().lock();
return new WritableView();
lock.writeLock().lock();
return new WritableNioFile(this);
}
@Override
public boolean exists() throws UncheckedIOException {
return false;
return Files.isRegularFile(path);
}
private class ReadableView implements ReadableFile {
@Override
public int read(ByteBuffer target) throws UncheckedIOException {
return -1;
@Override
public Instant lastModified() throws UncheckedIOException {
if (Files.exists(path) && !exists()) {
throw new UncheckedIOException(new IOException(format("%s is a folder", path)));
}
@Override
public boolean isOpen() {
return false;
}
@Override
public void position(long position) throws UncheckedIOException {
}
@Override
public void copyTo(WritableFile other) throws UncheckedIOException {
}
@Override
public void close() throws UncheckedIOException {
}
}
private class WritableView implements WritableFile {
@Override
public int write(ByteBuffer source) throws UncheckedIOException {
return -1;
}
@Override
public boolean isOpen() {
return false;
}
@Override
public void position(long position) throws UncheckedIOException {
}
@Override
public void moveTo(WritableFile other) throws UncheckedIOException {
}
@Override
public void setLastModified(Instant instant) throws UncheckedIOException {
}
@Override
public void delete() throws UncheckedIOException {
try {
Files.delete(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public void truncate() throws UncheckedIOException {
}
@Override
public void close() throws UncheckedIOException {
}
return super.lastModified();
}
@Override

View File

@@ -0,0 +1,5 @@
package org.cryptomator.filesystem.nio;
enum OpenMode {
READ, WRITE
}

View File

@@ -0,0 +1,95 @@
package org.cryptomator.filesystem.nio;
import static java.lang.String.format;
import static org.cryptomator.filesystem.nio.OpenMode.READ;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import org.cryptomator.filesystem.ReadableFile;
import org.cryptomator.filesystem.WritableFile;
class ReadableNioFile implements ReadableFile {
private final NioFile nioFile;
private boolean open = true;
private long position = 0;
public ReadableNioFile(NioFile nioFile) {
this.nioFile = nioFile;
nioFile.channel().open(READ);
}
@Override
public int read(ByteBuffer target) throws UncheckedIOException {
assertOpen();
int read = nioFile.channel().readFully(position, target);
if (read != SharedFileChannel.EOF) {
position += read;
}
return read;
}
@Override
public boolean isOpen() {
return open;
}
@Override
public void position(long position) throws UncheckedIOException {
assertOpen();
this.position = position;
}
@Override
public void copyTo(WritableFile other) throws UncheckedIOException {
assertOpen();
if (belongsToSameFilesystem(other)) {
internalCopyTo((WritableNioFile) other);
} else {
throw new IllegalArgumentException("Can only copy to a WritableFile from the same FileSystem");
}
}
private boolean belongsToSameFilesystem(WritableFile other) {
return other instanceof WritableNioFile && ((WritableNioFile) other).nioFile().belongsToSameFilesystem(nioFile);
}
private void internalCopyTo(WritableNioFile target) {
target.ensureChannelIsOpened();
SharedFileChannel targetChannel = target.channel();
targetChannel.truncate(0);
long size = nioFile.channel().size();
long transferred = 0;
while (transferred < size) {
transferred += nioFile.channel().transferTo(transferred, size - transferred, targetChannel);
}
}
@Override
public void close() {
if (!open) {
return;
}
open = false;
try {
nioFile.channel().close();
} finally {
nioFile.lock().readLock().unlock();
}
}
private void assertOpen() {
if (!open) {
throw new UncheckedIOException(format("%s already closed.", this), new ClosedChannelException());
}
}
@Override
public String toString() {
return format("Readable%s", nioFile);
}
}

View File

@@ -0,0 +1,167 @@
package org.cryptomator.filesystem.nio;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class SharedFileChannel {
public static final int EOF = -1;
private final Path path;
private Map<Thread, Thread> openedBy = new ConcurrentHashMap<>();
private Lock lock = new ReentrantLock();
private FileChannel delegate;
public SharedFileChannel(Path path) {
this.path = path;
}
public void open(OpenMode mode) {
doLocked(() -> {
Thread thread = Thread.currentThread();
if (openedBy.put(thread, thread) != null) {
throw new IllegalStateException("A thread can only open a SharedFileChannel once");
}
if (delegate == null) {
createChannel(mode);
}
});
}
public void close() {
assertOpenedByCurrentThread();
doLocked(() -> {
openedBy.remove(Thread.currentThread());
if (openedBy.isEmpty()) {
closeChannel();
}
});
}
private void assertOpenedByCurrentThread() {
if (!openedBy.containsKey(Thread.currentThread())) {
throw new IllegalStateException("SharedFileChannel closed for current thread");
}
}
private void createChannel(OpenMode mode) {
try {
FileChannel readChannel = null;
if (mode == OpenMode.READ) {
readChannel = FileChannel.open(path, StandardOpenOption.READ);
}
delegate = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
if (readChannel != null) {
readChannel.close();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void closeChannel() {
try {
delegate.close();
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
delegate = null;
}
}
public int readFully(long position, ByteBuffer target) {
assertOpenedByCurrentThread();
try {
return tryReadFully(position, target);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private int tryReadFully(long position, ByteBuffer target) throws IOException {
int initialRemaining = target.remaining();
long maxPosition = position + initialRemaining;
do {
if (delegate.read(target, maxPosition - target.remaining()) == EOF) {
if (initialRemaining == target.remaining()) {
return EOF;
} else {
return initialRemaining - target.remaining();
}
}
} while (target.hasRemaining());
return initialRemaining - target.remaining();
}
public void truncate(int i) {
assertOpenedByCurrentThread();
try {
delegate.truncate(i);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public long size() {
assertOpenedByCurrentThread();
try {
return delegate.size();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public long transferTo(long position, long count, SharedFileChannel targetChannel) {
assertOpenedByCurrentThread();
targetChannel.assertOpenedByCurrentThread();
try {
long maxPosition = delegate.size();
long maxCount = Math.min(count, maxPosition - position);
long remaining = maxCount;
while (remaining > 0) {
remaining -= delegate.transferTo(maxPosition - remaining, remaining, targetChannel.delegate);
}
return maxCount;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void doLocked(Runnable task) {
lock.lock();
try {
task.run();
} finally {
lock.unlock();
}
}
public int writeFully(long position, ByteBuffer source) {
assertOpenedByCurrentThread();
try {
return tryWriteFully(position, source);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private int tryWriteFully(long position, ByteBuffer source) throws IOException {
int initialRemaining = source.remaining();
long maxPosition = position + initialRemaining;
do {
delegate.write(source, maxPosition - source.remaining());
} while (source.hasRemaining());
return initialRemaining - source.remaining();
}
}

View File

@@ -0,0 +1,173 @@
package org.cryptomator.filesystem.nio;
import static java.lang.String.format;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.cryptomator.filesystem.nio.OpenMode.WRITE;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import org.cryptomator.filesystem.WritableFile;
class WritableNioFile implements WritableFile {
private final NioFile nioFile;
private boolean channelOpened = false;
private boolean open = true;
private long position = 0;
public WritableNioFile(NioFile nioFile) {
this.nioFile = nioFile;
}
@Override
public int write(ByteBuffer source) throws UncheckedIOException {
assertOpen();
ensureChannelIsOpened();
int written = nioFile.channel().writeFully(position, source);
position += written;
return written;
}
@Override
public boolean isOpen() {
return open;
}
@Override
public void position(long position) throws UncheckedIOException {
assertOpen();
this.position = position;
}
private boolean belongsToSameFilesystem(WritableFile other) {
return other instanceof WritableNioFile && ((WritableNioFile) other).nioFile().belongsToSameFilesystem(nioFile);
}
@Override
public void moveTo(WritableFile other) throws UncheckedIOException {
assertOpen();
if (other == this) {
return;
} else if (belongsToSameFilesystem(other)) {
internalMoveTo((WritableNioFile) other);
} else {
throw new IllegalArgumentException("Can only move to a WritableFile from the same FileSystem");
}
}
private void internalMoveTo(WritableNioFile other) {
other.assertOpen();
try {
assertMovePreconditionsAreMet(other);
closeChannelIfOpened();
other.closeChannelIfOpened();
Files.move(path(), other.path(), REPLACE_EXISTING);
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
open = false;
other.open = false;
other.nioFile.lock().writeLock().unlock();
nioFile.lock().writeLock().unlock();
}
}
private void assertMovePreconditionsAreMet(WritableNioFile other) {
if (Files.isDirectory(path())) {
throw new UncheckedIOException(new IOException(format("Can not move %s to %s. Source is a directory", path(), other.path())));
}
if (Files.isDirectory(other.path())) {
throw new UncheckedIOException(new IOException(format("Can not move %s to %s. Target is a directory", path(), other.path())));
}
}
@Override
public void setLastModified(Instant instant) throws UncheckedIOException {
assertOpen();
ensureChannelIsOpened();
try {
Files.setLastModifiedTime(path(), FileTime.from(instant));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public void delete() throws UncheckedIOException {
assertOpen();
try {
closeChannelIfOpened();
Files.delete(nioFile.path);
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
open = false;
nioFile.lock().writeLock().unlock();
}
}
@Override
public void truncate() throws UncheckedIOException {
assertOpen();
ensureChannelIsOpened();
nioFile.channel().truncate(0);
}
@Override
public void close() throws UncheckedIOException {
if (!open) {
return;
}
open = false;
try {
closeChannelIfOpened();
} finally {
nioFile.lock().writeLock().unlock();
}
}
void ensureChannelIsOpened() {
if (!channelOpened) {
nioFile.channel().open(WRITE);
channelOpened = true;
}
}
private void closeChannelIfOpened() {
if (channelOpened) {
channel().close();
}
}
SharedFileChannel channel() {
return nioFile.channel();
}
Path path() {
return nioFile.path;
}
public NioFile nioFile() {
return nioFile;
}
private void assertOpen() {
if (!open) {
throw new UncheckedIOException(format("%s already closed.", this), new ClosedChannelException());
}
}
@Override
public String toString() {
return format("Writable%s", this.nioFile);
}
}

View File

@@ -51,6 +51,7 @@ class FilesystemSetupUtils {
public static class FileEntry implements Entry {
private Path relativePath;
private byte[] data = new byte[0];
private Instant lastModified;
public FileEntry(Path relativePath) {
this.relativePath = relativePath;
@@ -65,6 +66,11 @@ class FilesystemSetupUtils {
return withData(data.getBytes());
}
public FileEntry withLastModified(Instant lastModified) {
this.lastModified = lastModified;
return this;
}
@Override
public void create(Path root) throws IOException {
Path filePath = root.resolve(relativePath);
@@ -72,6 +78,9 @@ class FilesystemSetupUtils {
try (OutputStream out = Files.newOutputStream(filePath)) {
IOUtils.write(data, out);
}
if (lastModified != null) {
Files.setLastModifiedTime(filePath, FileTime.from(lastModified));
}
}
}

View File

@@ -0,0 +1,352 @@
package org.cryptomator.filesystem.nio;
import static java.lang.String.format;
import static org.cryptomator.common.test.matcher.OptionalMatcher.presentOptionalWithValueThat;
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.PathMatcher.doesNotExist;
import static org.cryptomator.filesystem.nio.PathMatcher.isFile;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertThat;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.time.Instant;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.Folder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import de.bechte.junit.runners.context.HierarchicalContextRunner;
@RunWith(HierarchicalContextRunner.class)
public class NioFileTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testExistsForExistingFileReturnsTrue() {
File existingFile = NioFileSystem.rootedAt(testFilesystem(file("testFile"))) //
.file("testFile");
assertThat(existingFile.exists(), is(true));
}
@Test
public void testExistsForNonExistingFileReturnsFalse() {
File nonExistingFile = NioFileSystem.rootedAt(emptyFilesystem()) //
.file("testFile");
assertThat(nonExistingFile.exists(), is(false));
}
@Test
public void testExistsForFileWhichIsAFolderReturnsFalse() {
File fileWhichIsAFolder = NioFileSystem.rootedAt(testFilesystem(folder("nameOfAnExistingFolder"))) //
.file("nameOfAnExistingFolder");
assertThat(fileWhichIsAFolder.exists(), is(false));
}
@Test
public void testLastModifiedForExistingFileReturnsLastModifiedValue() {
Instant expectedLastModified = Instant.parse("2015-12-31T15:03:34Z");
File existingFile = NioFileSystem
.rootedAt(testFilesystem( //
file("testFile").withLastModified(expectedLastModified))) //
.file("testFile");
assertThat(existingFile.lastModified(), is(expectedLastModified));
}
@Test
public void testLastModifiedForNonExistingFileThrowsUncheckedIOExceptionWithPathInMessage() {
Path filesystemPath = emptyFilesystem();
Path pathOfNonExistingFile = filesystemPath.resolve("nonExistingFile");
File nonExistingFile = NioFileSystem.rootedAt(filesystemPath) //
.file("nonExistingFile");
thrown.expect(UncheckedIOException.class);
thrown.expectMessage(pathOfNonExistingFile.toString());
nonExistingFile.lastModified();
}
@Test
public void testLastModifiedForNonFileWhichIsAFolderThrowsUncheckedIOExceptionWithPathInMessage() {
Path filesystemPath = testFilesystem(folder("nameOfAnExistingFolder"));
Path pathOfNonExistingFile = filesystemPath.resolve("nameOfAnExistingFolder");
File fileWhichIsAFolder = NioFileSystem.rootedAt(filesystemPath) //
.file("nameOfAnExistingFolder");
thrown.expect(UncheckedIOException.class);
thrown.expectMessage(pathOfNonExistingFile.toString());
fileWhichIsAFolder.lastModified();
}
@Test
public void testCompareToReturnsZeroForSameInstance() {
File file = NioFileSystem.rootedAt(emptyFilesystem()).file("fileName");
assertThat(file.compareTo(file), is(0));
}
@Test
public void testCompareToReturnsZeroForSameFile() {
FileSystem filesystem = NioFileSystem.rootedAt(emptyFilesystem());
File fileA = filesystem.file("fileName");
File fileB = filesystem.file("fileName");
assertThat(fileA.compareTo(fileB), is(0));
assertThat(fileB.compareTo(fileA), is(0));
}
@Test
public void testCompareToReturnsNonZeroForOtherFile() {
FileSystem filesystem = NioFileSystem.rootedAt(emptyFilesystem());
File fileA = filesystem.file("aFileName");
File fileB = filesystem.file("anotherFileName");
int compareAWithB = fileA.compareTo(fileB);
int compareBWithA = fileB.compareTo(fileA);
assertThat(compareAWithB, not(is(0)));
assertThat(compareBWithA, not(is(0)));
assertThat(signum(compareAWithB) + signum(compareBWithA), is(0));
}
@Test
public void testCompareToThrowsExceptionForFileFromDifferentFileSystem() {
File fileA = NioFileSystem.rootedAt(emptyFilesystem()).file("aFileName");
File fileB = NioFileSystem.rootedAt(emptyFilesystem()).file("aFileName");
thrown.expect(IllegalArgumentException.class);
fileA.compareTo(fileB);
}
@Test
public void testToString() {
Path filesystemPath = emptyFilesystem();
Path absoluteFilePath = filesystemPath.resolve("fileName").toAbsolutePath();
File file = NioFileSystem.rootedAt(filesystemPath).file("fileName");
assertThat(file.toString(), is(format("NioFile(%s)", absoluteFilePath)));
}
@Test
public void testNameReturnsNameOfFile() {
String fileName = "fileName";
File file = NioFileSystem.rootedAt(emptyFilesystem()).file(fileName);
assertThat(file.name(), is(fileName));
}
@Test
public void testParentForDirectChildOfFileSystemReturnsFileSystem() {
FileSystem fileSystem = NioFileSystem.rootedAt(emptyFilesystem());
File file = fileSystem.file("fileName");
assertThat(file.parent(), presentOptionalWithValueThat(is(sameInstance(fileSystem))));
}
@Test
public void testParentForChildOfFolderReturnsFolder() {
Folder folder = NioFileSystem.rootedAt(emptyFilesystem()).folder("folderName");
File file = folder.file("fileName");
assertThat(file.parent(), presentOptionalWithValueThat(is(sameInstance(folder))));
}
@Test
public void testCopyToNonExistingTargetCreatesTargetWithContent() {
Path filesystemPath = testFilesystem(file("sourceFile").withData("fileContents"));
FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath);
Path sourceFilePath = filesystemPath.resolve("sourceFile");
Path targetFilePath = filesystemPath.resolve("targetFile");
File source = fileSystem.file("sourceFile");
File target = fileSystem.file("targetFile");
source.copyTo(target);
assertThat(sourceFilePath, isFile().withContent("fileContents"));
assertThat(targetFilePath, isFile().withContent("fileContents"));
}
@Test
public void testCopyToExistingTargetOverwritesTargetWithContent() {
Path filesystemPath = testFilesystem( //
file("sourceFile").withData("fileContents"), //
file("targetFile").withData("wrongFileContents"));
FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath);
Path sourceFilePath = filesystemPath.resolve("sourceFile");
Path targetFilePath = filesystemPath.resolve("targetFile");
File source = fileSystem.file("sourceFile");
File target = fileSystem.file("targetFile");
source.copyTo(target);
assertThat(sourceFilePath, isFile().withContent("fileContents"));
assertThat(targetFilePath, isFile().withContent("fileContents"));
}
@Test
public void testCopyToSameFileThrowsIllegalArgumentException() {
File file = NioFileSystem.rootedAt(testFilesystem(file("sourceFile"))).file("fileName");
thrown.expect(IllegalArgumentException.class);
file.copyTo(file);
}
@Test
public void testCopyToDirectoryTargetThrowsUncheckedIOExceptionWithPathInMessage() {
Path filesystemPath = testFilesystem( //
file("sourceFile").withData("fileContents"), //
folder("aFolderName"));
FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath);
Path targetFilePath = filesystemPath.resolve("aFolderName").toAbsolutePath();
File source = fileSystem.file("sourceFile");
File target = fileSystem.file("aFolderName");
thrown.expect(UncheckedIOException.class);
thrown.expectMessage(targetFilePath.toAbsolutePath().toString());
source.copyTo(target);
}
@Test
public void testCopyToOfNonExistingFileThrowsUncheckedIOExceptionWithPathInMessage() {
Path filesystemPath = emptyFilesystem();
Path filePath = filesystemPath.resolve("nonExistingFile").toAbsolutePath();
FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath);
File nonExistingFile = fileSystem.file("nonExistingFile");
File target = fileSystem.file("target");
thrown.expect(UncheckedIOException.class);
thrown.expectMessage(filePath.toString());
nonExistingFile.copyTo(target);
}
@Test
public void testCopyToOfFileWhichIsAFolderThrowsUncheckedIOExceptionWithPathInMessage() {
Path filesystemPath = testFilesystem(folder("folderName"));
Path filePath = filesystemPath.resolve("folderName").toAbsolutePath();
FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath);
File fileWhichIsAFolder = fileSystem.file("folderName");
File target = fileSystem.file("target");
thrown.expect(UncheckedIOException.class);
thrown.expectMessage(filePath.toString());
fileWhichIsAFolder.copyTo(target);
}
@Test
public void testMoveToNonExistingTargetCreatesTargetWithContentAndDeletesSource() {
Path filesystemPath = testFilesystem(file("sourceFile").withData("fileContents"));
FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath);
Path sourceFilePath = filesystemPath.resolve("sourceFile");
Path targetFilePath = filesystemPath.resolve("targetFile");
File source = fileSystem.file("sourceFile");
File target = fileSystem.file("targetFile");
source.moveTo(target);
assertThat(sourceFilePath, doesNotExist());
assertThat(targetFilePath, isFile().withContent("fileContents"));
}
@Test
public void testMoveToExistingTargetOverwritesTargetWithContentAndDeletesSource() {
Path filesystemPath = testFilesystem( //
file("sourceFile").withData("fileContents"), //
file("targetFile").withData("wrongFileContents"));
FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath);
Path sourceFilePath = filesystemPath.resolve("sourceFile");
Path targetFilePath = filesystemPath.resolve("targetFile");
File source = fileSystem.file("sourceFile");
File target = fileSystem.file("targetFile");
source.moveTo(target);
assertThat(sourceFilePath, doesNotExist());
assertThat(targetFilePath, isFile().withContent("fileContents"));
}
@Test
public void testMoveToSameFileDoesNothing() {
Path filesystemPath = testFilesystem(file("fileName").withData("fileContents"));
Path filePath = filesystemPath.resolve("fileName");
File file = NioFileSystem.rootedAt(filesystemPath).file("fileName");
file.moveTo(file);
assertThat(filePath, isFile().withContent("fileContents"));
}
@Test
public void testMoveToDirectoryTargetThrowsUncheckedIOExceptionWithPathInMessage() {
Path filesystemPath = testFilesystem( //
file("sourceFile").withData("fileContents"), //
folder("aFolderName"));
FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath);
Path targetFilePath = filesystemPath.resolve("aFolderName").toAbsolutePath();
File source = fileSystem.file("sourceFile");
File target = fileSystem.file("aFolderName");
thrown.expect(UncheckedIOException.class);
thrown.expectMessage(targetFilePath.toAbsolutePath().toString());
source.moveTo(target);
}
@Test
public void testMoveToOfNonExistingFileThrowsUncheckedIOExceptionWithPathInMessage() {
Path filesystemPath = emptyFilesystem();
Path filePath = filesystemPath.resolve("nonExistingFile").toAbsolutePath();
FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath);
File nonExistingFile = fileSystem.file("nonExistingFile");
File target = fileSystem.file("target");
thrown.expect(UncheckedIOException.class);
thrown.expectMessage(filePath.toString());
nonExistingFile.moveTo(target);
}
@Test
public void testMoveToOfFileWhichIsAFolderThrowsUncheckedIOExceptionWithPathInMessage() {
Path filesystemPath = testFilesystem(folder("folderName"));
Path filePath = filesystemPath.resolve("folderName").toAbsolutePath();
FileSystem fileSystem = NioFileSystem.rootedAt(filesystemPath);
File fileWhichIsAFolder = fileSystem.file("folderName");
File target = fileSystem.file("target");
thrown.expect(UncheckedIOException.class);
thrown.expectMessage(filePath.toString());
fileWhichIsAFolder.moveTo(target);
}
private int signum(int value) {
if (value > 0) {
return 1;
} else if (value < 0) {
return -1;
} else {
return 0;
}
}
}

View File

@@ -32,6 +32,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
public class NioFolderTest {
@Rule
public ExpectedException thrown = ExpectedException.none();