package com.github.marschall.memoryfilesystem; import static java.lang.Math.min; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.util.concurrent.atomic.AtomicLong; final class BlockInputStream extends InputStream { /** * The maximum number of bytes to skip. */ private static final int MAX_SKIP_SIZE = 2048; private final MemoryContents memoryContents; private final ClosedStreamChecker checker; private final AtomicLong position; private final Path path; private final boolean deleteOnClose; // // private final Lock markLock; // // private long markPositon; // private int readLimit; BlockInputStream(MemoryContents memoryContents, boolean deleteOnClose, Path path) { this.memoryContents = memoryContents; this.deleteOnClose = deleteOnClose; this.checker = new ClosedStreamChecker(); this.position = new AtomicLong(0L); this.path = path; // this.markLock = new ReentrantLock(); // this.readLimit = -1; // this.markPositon = -1L; } @Override public int read(byte[] b, int off, int len) throws IOException { this.checker.check(this.path); boolean success = false; int read = 0; while (!success) { long positionBefore = this.position.get(); read = this.memoryContents.read(b, positionBefore, off, len); if (read == -1) { return read; } success = this.position.compareAndSet(positionBefore, positionBefore + read); } return read; } @Override public long skip(long n) throws IOException { this.checker.check(this.path); long positionBefore = this.position.get(); long fileSize = this.memoryContents.size(); // do not skip more than MAX_SKIP_SIZE // this intentionally introduces a subtle bug in code that doesn't check // for the return value of #skip // REVIEW subtle issue when fileSize - positionBefore becomes negative // due to concurrent updates long skipped = min(min(n, fileSize - positionBefore), MAX_SKIP_SIZE); this.position.compareAndSet(positionBefore, positionBefore + skipped); return skipped; } @Override public int available() throws IOException { this.checker.check(this.path); long available = this.memoryContents.size() - this.position.get(); if (available > Integer.MAX_VALUE) { return Integer.MAX_VALUE; } else if (available > 1L) { // introduce a subtle but in code that assumes #available() returns // everything untile the end return (int) (available -1); } else { return (int) available; } } @Override public void close() throws IOException { if (this.checker.close()) { this.memoryContents.accessed(); this.memoryContents.closedStream(this.path, this.deleteOnClose); } } // FileInputStream doesn't support marks so neither do we // @Override // public void mark(int readlimit) { // try (AutoRelease lock = AutoReleaseLock.autoRelease(this.markLock)) { // if (readlimit <= 0) { // throw new IllegalArgumentException("read limit must be positive"); // } // this.markPositon = this.position.get(); // this.readLimit = readlimit; // } // } // // @Override // public void reset() throws IOException { // // REVIEW this doesn't actually guarantee the same bytes are read // // by concurrent access, I'm not sure this complies with the specification // // the specification doesn't say anything about concurrent access // try (AutoRelease lock = AutoReleaseLock.autoRelease(this.markLock)) { // if (this.readLimit == -1) { // throw new IOException("#mark has not been called"); // } // if (this.position.get() > this.markPositon + this.readLimit) { // throw new IOException("readlimit has passed"); // } // this.position.set(this.markPositon); // } // } @Override public int read() throws IOException { byte[] data = new byte[1]; int read = this.read(data); if (read == -1) { return read; } else { return data[0] & 0xff; } } }