package com.github.marschall.memoryfilesystem;
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.SYNC;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributeView;
import java.util.Arrays;
import java.util.Set;
class MemoryFile extends MemoryEntry implements MemoryContents {
/**
* The number of open streams or channels.
*
* <p>A negative number indicates the file is marked for deletion.
*/
private int openCount;
/**
* In other to implement hard links we allow sharing hard links.
* We do not need a hard link counter since we have a garbage collector.
*/
private final MemoryInode inode;
MemoryFile(String originalName, EntryCreationContext context) {
this(originalName, context, 0);
}
MemoryFile(String originalName, EntryCreationContext context, int initialBlocks) {
super(originalName, context);
this.openCount = 0;
this.inode = new MemoryInode(initialBlocks);
}
/**
* Copy constructor.
*/
MemoryFile(String originalName, EntryCreationContext context, MemoryFile other) {
super(originalName, context);
this.openCount = 0;
this.inode = new MemoryInode(other.inode);
}
/**
* Hard link constructor.
*/
private MemoryFile(String originalName, EntryCreationContext context, MemoryInode inode, MemoryFile other) {
super(originalName, context, other);
this.openCount = 0;
this.inode = inode;
}
MemoryFile createLink(String originalName, EntryCreationContext context) {
return new MemoryFile(originalName, context, this.inode, this);
}
@Override
MemoryEntryAttributes newMemoryEntryAttributes(EntryCreationContext context) {
return new MemoryFileAttributes(context);
}
@Override
public void accessed() {
// here to increase the scope to public
super.accessed();
}
@Override
public void modified() {
// here to increase the scope to public
super.modified();
}
@Override
public long size() {
return this.inode.size();
}
InputStream newInputStream(Set<? extends OpenOption> options, Path path) throws IOException {
boolean deleteOnClose = options.contains(DELETE_ON_CLOSE);
boolean sync = options.contains(SYNC);
return this.newInputStream(deleteOnClose, path);
}
OutputStream newOutputStream(Set<? extends OpenOption> options, Path path) throws IOException {
boolean deleteOnClose = options.contains(DELETE_ON_CLOSE);
boolean append = options.contains(APPEND);
boolean truncate = options.contains(TRUNCATE_EXISTING);
if (append && truncate) {
throw new IllegalArgumentException("invalid combination of options: " + Arrays.asList(APPEND, TRUNCATE_EXISTING));
}
boolean sync = options.contains(SYNC);
if (append) {
return this.newAppendingOutputStream(deleteOnClose, path);
} else {
if (truncate) {
this.truncate(0L);
}
return this.newOutputStream(deleteOnClose, path);
}
}
BlockChannel newChannel(Set<? extends OpenOption> options, Path path) throws IOException {
boolean append = options.contains(APPEND);
boolean writable = options.contains(WRITE);
// if neither read nor wirte are present we defautl to read
// java.nio.file.Files.newByteChannel(Path, Set<? extends OpenOption>, FileAttribute<?>...)
boolean readable = options.contains(READ) || (!writable && !append);
boolean deleteOnClose = options.contains(DELETE_ON_CLOSE);
boolean sync = options.contains(SYNC);
boolean truncate = options.contains(TRUNCATE_EXISTING);
if (writable && append && truncate) {
throw new IllegalArgumentException("invalid combination of options: " + Arrays.asList(WRITE, APPEND, TRUNCATE_EXISTING));
}
if (append) {
return this.newAppendingChannel(readable, deleteOnClose, path);
} else {
if (writable) {
if (truncate) {
this.truncate(0L);
}
}
return this.newChannel(readable, writable, deleteOnClose, path);
}
}
InputStream newInputStream(boolean deleteOnClose, Path path) throws IOException {
try (AutoRelease lock = this.writeLock()) {
this.incrementOpenCount(path);
return new BlockInputStream(this, deleteOnClose, path);
}
}
OutputStream newOutputStream(boolean deleteOnClose, Path path) throws IOException {
try (AutoRelease lock = this.writeLock()) {
this.incrementOpenCount(path);
return new NonAppendingBlockOutputStream(this, deleteOnClose, path);
}
}
OutputStream newAppendingOutputStream(boolean deleteOnClose, Path path) throws IOException {
try (AutoRelease lock = this.writeLock()) {
this.incrementOpenCount(path);
return new AppendingBlockOutputStream(this, deleteOnClose, path);
}
}
BlockChannel newChannel(boolean readable, boolean writable, boolean deleteOnClose, Path path) throws IOException {
try (AutoRelease lock = this.writeLock()) {
this.incrementOpenCount(path);
return new NonAppendingBlockChannel(this, readable, writable, deleteOnClose, path);
}
}
BlockChannel newAppendingChannel(boolean readable, boolean deleteOnClose, Path path) throws IOException {
try (AutoRelease lock = this.writeLock()) {
this.incrementOpenCount(path);
return new AppendingBlockChannel(this, readable, this.size(), deleteOnClose, path);
}
}
private void incrementOpenCount(Path path) throws NoSuchFileException {
if (this.openCount < 0) {
throw new NoSuchFileException(path.toString());
}
this.openCount += 1;
}
int openCount() {
return this.openCount;
}
void markForDeletion() {
this.openCount = -1;
}
@Override
public void closedStream(Path path, boolean delete) {
try (AutoRelease lock = this.writeLock()) {
this.openCount -= 1;
}
if (delete) {
// intentionally not covered by Lock
try {
Files.delete(path);
} catch (IOException e) {
// ignore, only a best effort is made
}
}
}
@Override
public void closedChannel(Path path, boolean delete) {
try (AutoRelease lock = this.writeLock()) {
this.openCount -= 1;
}
if (delete) {
// intentionally not covered by Lock
try {
Files.delete(path);
} catch (IOException e) {
// ignore, only a best effort is made
}
}
}
@Override
public long read(ByteBuffer dst, long position, long maximum) throws IOException {
return this.inode.read(dst, position, maximum);
}
@Override
public int readShort(ByteBuffer dst, long position) throws IOException {
return this.inode.readShort(dst, position);
}
@Override
public int read(byte[] dst, long position, int off, int len) throws IOException {
return this.inode.read(dst, position, off, len);
}
@Override
public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
return this.inode.transferFrom(src, position, count);
}
@Override
public long transferTo(WritableByteChannel target, long position, long count) throws IOException {
return this.inode.transferTo(target, position, count);
}
@Override
public long write(ByteBuffer src, long position, long maximum) {
return this.inode.write(src, position, maximum);
}
@Override
public int writeShort(ByteBuffer src, long position) {
return this.inode.writeShort(src, position);
}
@Override
public int write(byte[] src, long position, int off, int len) {
return this.inode.write(src, position, off, len);
}
@Override
public long writeAtEnd(ByteBuffer src, long maximum) {
return this.inode.writeAtEnd(src, maximum);
}
@Override
public int writeAtEnd(ByteBuffer src) {
return this.inode.writeAtEnd(src);
}
@Override
public int writeAtEnd(byte[] src, int off, int len) {
return this.inode.writeAtEnd(src, off, len);
}
@Override
public void truncate(long newSize) {
this.inode.truncate(newSize);
}
@Override
public MemoryFileLock tryLock(MemoryFileLock lock) {
return this.inode.tryLock(lock);
}
@Override
public MemoryFileLock lock(MemoryFileLock lock) throws IOException {
return this.inode.lock(lock);
}
@Override
public void unlock(MemoryFileLock lock) {
this.inode.unlock(lock);
}
@Override
public String toString() {
return "file(" + this.getOriginalName() + ')';
}
final class MemoryFileAttributes extends MemoryEntryAttributes {
MemoryFileAttributes(EntryCreationContext context) {
super(context);
}
@Override
BasicFileAttributeView newBasicFileAttributeView() {
return new MemoryFileAttributesView();
}
@Override
long size() {
return MemoryFile.this.size();
}
}
boolean hasSameInodeAs(MemoryFile other) {
return this.inode == other.inode;
}
}