mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-23 13:11:28 +00:00
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:
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.cryptomator.filesystem.nio;
|
||||
|
||||
enum OpenMode {
|
||||
READ, WRITE
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
public class NioFolderTest {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user