diff options
28 files changed, 853 insertions, 1267 deletions
diff --git a/jimfs/src/main/java/com/google/jimfs/internal/BlockList.java b/jimfs/src/main/java/com/google/jimfs/internal/BlockList.java new file mode 100644 index 0000000..338d0ea --- /dev/null +++ b/jimfs/src/main/java/com/google/jimfs/internal/BlockList.java @@ -0,0 +1,110 @@ +/* + * Copyright 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.jimfs.internal; + +import static com.google.jimfs.internal.Util.clear; +import static com.google.jimfs.internal.Util.nextPowerOf2; + +import java.util.Arrays; + +/** + * Simple list of byte array blocks. Blocks can only be added and removed at the end of the list. + * + * @author Colin Decker + */ +final class BlockList { + + private byte[][] blocks; + private int head; + + /** + * Creates a new list with a default initial capacity. + */ + public BlockList() { + this(32); + } + + /** + * Creates a new list with the given initial capacity. + */ + public BlockList(int initialCapacity) { + this.blocks = new byte[initialCapacity][]; + } + + private void expandIfNecessary(int minSize) { + if (minSize > blocks.length) { + this.blocks = Arrays.copyOf(blocks, nextPowerOf2(minSize)); + } + } + + /** + * Returns true if size is 0. + */ + public boolean isEmpty() { + return head == 0; + } + + /** + * Returns the number of blocks this list contains. + */ + public int size() { + return head; + } + + /** + * Copies the last {@code count} blocks from this list to the end of the given target list. + */ + public void copyTo(BlockList target, int count) { + int start = head - count; + int targetEnd = target.head + count; + target.expandIfNecessary(targetEnd); + + System.arraycopy(this.blocks, start, target.blocks, target.head, count); + target.head = targetEnd; + } + + /** + * Transfers the last {@code count} blocks from this list to the end of the given target list. + */ + public void transferTo(BlockList target, int count) { + copyTo(target, count); + truncate(head - count); + } + + /** + * Truncates this list to the given size. + */ + public void truncate(int size) { + clear(blocks, size, head - size); + head = size; + } + + /** + * Adds the given block to the end of this list. + */ + public void add(byte[] block) { + expandIfNecessary(head + 1); + blocks[head++] = block; + } + + /** + * Gets the block at the given index in this list. + */ + public byte[] get(int index) { + return blocks[index]; + } +} diff --git a/jimfs/src/main/java/com/google/jimfs/internal/ByteStore.java b/jimfs/src/main/java/com/google/jimfs/internal/ByteStore.java index 07dcb20..ab114ba 100644 --- a/jimfs/src/main/java/com/google/jimfs/internal/ByteStore.java +++ b/jimfs/src/main/java/com/google/jimfs/internal/ByteStore.java @@ -16,8 +16,11 @@ package com.google.jimfs.internal; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.primitives.UnsignedBytes; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; @@ -32,24 +35,40 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * * @author Colin Decker */ -abstract class ByteStore implements FileContent { +final class ByteStore implements FileContent { private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final HeapDisk disk; + private final BlockList blocks; + private long size; + + public ByteStore(HeapDisk disk) { + this(disk, new BlockList(32), 0); + } + + private ByteStore(HeapDisk disk, BlockList blocks, long size) { + this.disk = checkNotNull(disk); + this.blocks = checkNotNull(blocks); + + checkArgument(size >= 0); + this.size = size; + } + private int openCount = 0; private boolean deleted = false; /** * Returns the read lock for this store. */ - protected final Lock readLock() { + public final Lock readLock() { return lock.readLock(); } /** * Returns the write lock for this store. */ - protected final Lock writeLock() { + public final Lock writeLock() { return lock.writeLock(); } @@ -57,27 +76,41 @@ abstract class ByteStore implements FileContent { * Gets the current size of this store in bytes. Does not do locking, so should only be called * when holding a lock. */ - public abstract long currentSize(); + public long currentSize() { + return size; + } /** * Creates a copy of this byte store. + * + * @throws IOException if the disk cannot allocate enough new blocks to create a copy */ - protected abstract ByteStore createCopy(); + protected ByteStore createCopy() throws IOException { + BlockList copyBlocks = new BlockList(Math.max(blocks.size() * 2, 32)); + disk.allocate(copyBlocks, blocks.size()); + + for (int i = 0; i < blocks.size(); i++) { + byte[] block = blocks.get(i); + byte[] copy = copyBlocks.get(i); + System.arraycopy(block, 0, copy, 0, block.length); + } + return new ByteStore(disk, copyBlocks, size); + } // need to lock in these methods since they're defined by an interface @Override - public final long size() { + public long size() { readLock().lock(); try { - return currentSize(); + return size; } finally { readLock().unlock(); } } @Override - public final ByteStore copy() { + public ByteStore copy() throws IOException { readLock().lock(); try { return createCopy(); @@ -91,7 +124,7 @@ abstract class ByteStore implements FileContent { /** * Called when a stream or channel to this store is opened. */ - public final synchronized void opened() { + public synchronized void opened() { openCount++; } @@ -99,7 +132,7 @@ abstract class ByteStore implements FileContent { * Called when a stream or channel to this store is closed. If there are no more streams or * channels open to the store and it has been deleted, its contents may be deleted. */ - public final synchronized void closed() { + public synchronized void closed() { if (--openCount == 0 && deleted) { deleteContents(); } @@ -119,7 +152,7 @@ abstract class ByteStore implements FileContent { * contents are deleted if necessary. */ @Override - public final synchronized void deleted() { + public synchronized void deleted() { deleted = true; if (openCount == 0) { deleteContents(); @@ -130,7 +163,10 @@ abstract class ByteStore implements FileContent { * Deletes the contents of this store. Called when the file that contains this store has been * deleted and all open streams and channels to the file have been closed. */ - protected abstract void deleteContents(); + protected void deleteContents() { + disk.free(blocks); + size = 0; + } /** * Truncates this store to the given {@code size}. If the given size is less than the current size @@ -139,38 +175,167 @@ abstract class ByteStore implements FileContent { * does nothing. Returns {@code true} if this store was modified by the call (its size changed) * and {@code false} otherwise. */ - public abstract boolean truncate(long size); + public boolean truncate(long size) { + if (size >= this.size) { + return false; + } + + long lastPosition = size - 1; + this.size = size; + + int newBlockCount = blockIndex(lastPosition) + 1; + int blocksToRemove = blocks.size() - newBlockCount; + if (blocksToRemove > 0) { + disk.free(blocks, blocksToRemove); + } + + return true; + } + + /** + * Prepares for a write of len bytes starting at position pos. + */ + private void prepareForWrite(long pos, long len) throws IOException { + long end = pos + len; + + // allocate any additional blocks needed + int lastBlockIndex = blocks.size() - 1; + int endBlockIndex = blockIndex(end - 1); + + if (endBlockIndex > lastBlockIndex) { + int additionalBlocksNeeded = endBlockIndex - lastBlockIndex; + disk.allocate(blocks, additionalBlocksNeeded); + } + + // zero bytes between current size and pos + if (pos > size) { + long remaining = pos - size; + + int blockIndex = blockIndex(size); + byte[] block = blocks.get(blockIndex); + int off = offsetInBlock(size); + + remaining -= zero(block, off, length(off, remaining)); + + while (remaining > 0) { + block = blocks.get(++blockIndex); + + remaining -= zero(block, 0, length(remaining)); + } + + size = pos; + } + } /** * Writes the given byte to this store at position {@code pos}. {@code pos} may be greater than * the current size of this store, in which case this store is resized and all bytes between the * current size and {@code pos} are set to 0. Returns the number of bytes written. + * + * @throws IOException if the store needs more blocks but the disk is full */ - public abstract int write(long pos, byte b); + public int write(long pos, byte b) throws IOException { + prepareForWrite(pos, 1); + + byte[] block = blocks.get(blockIndex(pos)); + int off = offsetInBlock(pos); + block[off] = b; + + if (pos >= size) { + size = pos + 1; + } + + return 1; + } /** * Writes {@code len} bytes starting at offset {@code off} in the given byte array to this store * starting at position {@code pos}. {@code pos} may be greater than the current size of this * store, in which case this store is resized and all bytes between the current size and {@code * pos} are set to 0. Returns the number of bytes written. + * + * @throws IOException if the store needs more blocks but the disk is full */ - public abstract int write(long pos, byte[] b, int off, int len); + public int write(long pos, byte[] b, int off, int len) throws IOException { + prepareForWrite(pos, len); + + if (len == 0) { + return 0; + } + + int remaining = len; + + int blockIndex = blockIndex(pos); + byte[] block = blockForWrite(blockIndex); + int offInBlock = offsetInBlock(pos); + + int written = put(block, offInBlock, b, off, length(offInBlock, remaining)); + remaining -= written; + off += written; + + while (remaining > 0) { + block = blocks.get(++blockIndex); + + written = put(block, 0, b, off, length(remaining)); + remaining -= written; + off += written; + } + + long newPos = pos + len; + if (newPos > size) { + size = newPos; + } + + return len; + } /** * Writes all available bytes from buffer {@code buf} to this store starting at position {@code * pos}. {@code pos} may be greater than the current size of this store, in which case this store * is resized and all bytes between the current size and {@code pos} are set to 0. Returns the * number of bytes written. + * + * @throws IOException if the store needs more blocks but the disk is full */ - public abstract int write(long pos, ByteBuffer buf); + public int write(long pos, ByteBuffer buf) throws IOException { + int len = buf.remaining(); + + prepareForWrite(pos, len); + + if (len == 0) { + return 0; + } + + int bytesToWrite = buf.remaining(); + + int blockIndex = blockIndex(pos); + byte[] block = blockForWrite(blockIndex); + int off = offsetInBlock(pos); + + put(block, off, buf); + + while (buf.hasRemaining()) { + block = blocks.get(++blockIndex); + + put(block, 0, buf); + } + + if (pos + bytesToWrite > size) { + size = pos + bytesToWrite; + } + + return bytesToWrite; + } /** * Writes all available bytes from each buffer in {@code bufs}, in order, to this store starting * at position {@code pos}. {@code pos} may be greater than the current size of this store, in * which case this store is resized and all bytes between the current size and {@code pos} are set * to 0. Returns the number of bytes written. + * + * @throws IOException if the store needs more blocks but the disk is full */ - public long write(long pos, Iterable<ByteBuffer> bufs) { + public long write(long pos, Iterable<ByteBuffer> bufs) throws IOException { long start = pos; for (ByteBuffer buf : bufs) { pos += write(pos, buf); @@ -182,29 +347,135 @@ abstract class ByteStore implements FileContent { * Transfers up to {@code count} bytes from the given channel to this store starting at position * {@code pos}. Returns the number of bytes transferred. If {@code pos} is greater than the * current size of this store, the store is truncated up to size {@code pos} before writing. + * + * @throws IOException if the store needs more blocks but the disk is full or if reading from src + * throws an exception */ - public abstract long transferFrom( - ReadableByteChannel src, long pos, long count) throws IOException; + public long transferFrom( + ReadableByteChannel src, long pos, long count) throws IOException { + prepareForWrite(pos, 0); + + if (count == 0) { + return 0; + } + + long remaining = count; + + int blockIndex = blockIndex(pos); + byte[] block = blockForWrite(blockIndex); + int off = offsetInBlock(pos); + + ByteBuffer buf = ByteBuffer.wrap(block, off, length(off, remaining)); + + int read = 0; + while (buf.hasRemaining()) { + read = src.read(buf); + if (read == -1) { + break; + } + + remaining -= read; + } + + if (read != -1) { + outer: while (remaining > 0) { + block = blockForWrite(++blockIndex); + + buf = ByteBuffer.wrap(block, 0, length(remaining)); + while (buf.hasRemaining()) { + read = src.read(buf); + if (read == -1) { + break outer; + } + + remaining -= read; + } + } + } + + long written = count - remaining; + long newPos = pos + written; + if (newPos > size) { + size = newPos; + } + + return written; + } /** * Reads the byte at position {@code pos} in this store as an unsigned integer in the range 0-255. * If {@code pos} is greater than or equal to the size of this store, returns -1 instead. */ - public abstract int read(long pos); + public int read(long pos) { + if (pos >= size) { + return -1; + } + + byte[] block = blocks.get(blockIndex(pos)); + int off = offsetInBlock(pos); + return UnsignedBytes.toInt(block[off]); + } /** * Reads up to {@code len} bytes starting at position {@code pos} in this store to the given byte * array starting at offset {@code off}. Returns the number of bytes actually read or -1 if {@code * pos} is greater than or equal to the size of this store. */ - public abstract int read(long pos, byte[] b, int off, int len); + public int read(long pos, byte[] b, int off, int len) { + // since max is len (an int), result is guaranteed to be an int + int bytesToRead = (int) bytesToRead(pos, len); + + if (bytesToRead > 0) { + int remaining = bytesToRead; + + int blockIndex = blockIndex(pos); + byte[] block = blocks.get(blockIndex); + int offsetInBlock = offsetInBlock(pos); + + int read = get(block, offsetInBlock, b, off, length(offsetInBlock, remaining)); + remaining -= read; + off += read; + + while (remaining > 0) { + int index = ++blockIndex; + block = blocks.get(index); + + read = get(block, 0, b, off, length(remaining)); + remaining -= read; + off += read; + } + } + + return bytesToRead; + } /** * Reads up to {@code buf.remaining()} bytes starting at position {@code pos} in this store to the * given buffer. Returns the number of bytes read or -1 if {@code pos} is greater than or equal to * the size of this store. */ - public abstract int read(long pos, ByteBuffer buf); + public int read(long pos, ByteBuffer buf) { + // since max is buf.remaining() (an int), result is guaranteed to be an int + int bytesToRead = (int) bytesToRead(pos, buf.remaining()); + + if (bytesToRead > 0) { + int remaining = bytesToRead; + + int blockIndex = blockIndex(pos); + byte[] block = blocks.get(blockIndex); + int off = offsetInBlock(pos); + + remaining -= get(block, off, buf, length(off, remaining)); + + while (remaining > 0) { + int index = ++blockIndex; + block = blocks.get(index); + remaining -= get(block, 0, buf, length(remaining)); + } + } + + return bytesToRead; + } /** * Reads up to the total {@code remaining()} number of bytes in each of {@code bufs} starting at @@ -236,6 +507,118 @@ abstract class ByteStore implements FileContent { * equal to the current size. This for consistency with {@link FileChannel#transferTo}, which * this method is primarily intended as an implementation of. */ - public abstract long transferTo( - long pos, long count, WritableByteChannel dest) throws IOException; + public long transferTo( + long pos, long count, WritableByteChannel dest) throws IOException { + long bytesToRead = bytesToRead(pos, count); + + if (bytesToRead > 0) { + long remaining = bytesToRead; + + int blockIndex = blockIndex(pos); + byte[] block = blocks.get(blockIndex); + int off = offsetInBlock(pos); + + ByteBuffer buf = ByteBuffer.wrap(block, off, length(off, remaining)); + while (buf.hasRemaining()) { + remaining -= dest.write(buf); + } + buf.clear(); + + while (remaining > 0) { + int index = ++blockIndex; + block = blocks.get(index); + + buf = ByteBuffer.wrap(block, 0, length(remaining)); + while (buf.hasRemaining()) { + remaining -= dest.write(buf); + } + buf.clear(); + } + } + + return Math.max(bytesToRead, 0); // don't return -1 for this method + } + + /** + * Gets the block at the given index, expanding to create the block if necessary. + */ + private byte[] blockForWrite(int index) throws IOException { + int blockCount = blocks.size(); + if (index >= blockCount) { + int additionalBlocksNeeded = index - blockCount + 1; + disk.allocate(blocks, additionalBlocksNeeded); + } + + return blocks.get(index); + } + + private int blockIndex(long position) { + return (int) (position / disk.blockSize()); + } + + private int offsetInBlock(long position) { + return (int) (position % disk.blockSize()); + } + + private int length(long max) { + return (int) Math.min(disk.blockSize(), max); + } + + private int length(int off, long max) { + return (int) Math.min(disk.blockSize() - off, max); + } + + /** + * Returns the number of bytes that can be read starting at position {@code pos} (up to a maximum + * of {@code max}) or -1 if {@code pos} is greater than or equal to the current size. + */ + private long bytesToRead(long pos, long max) { + long available = size - pos; + if (available <= 0) { + return -1; + } + return Math.min(available, max); + } + + /** + * Zeroes len bytes in the given block starting at the given offset. Returns len. + */ + private static int zero(byte[] block, int offset, int len) { + Util.zero(block, offset, len); + return len; + } + + /** + * Puts the given slice of the given array at the given offset in the given block. + */ + private static int put(byte[] block, int offset, byte[] b, int off, int len) { + System.arraycopy(b, off, block, offset, len); + return len; + } + + /** + * Puts the contents of the given byte buffer at the given offset in the given block. + */ + private static int put(byte[] block, int offset, ByteBuffer buf) { + int len = Math.min(block.length - offset, buf.remaining()); + buf.get(block, offset, len); + return len; + } + + /** + * Reads len bytes starting at the given offset in the given block into the given slice of the + * given byte array. + */ + private static int get(byte[] block, int offset, byte[] b, int off, int len) { + System.arraycopy(block, offset, b, off, len); + return len; + } + + /** + * Reads len bytes starting at the given offset in the given block into the given byte buffer. + */ + private static int get(byte[] block, int offset, ByteBuffer buf, int len) { + buf.put(block, offset, len); + return len; + } } diff --git a/jimfs/src/main/java/com/google/jimfs/internal/DirectMemoryDisk.java b/jimfs/src/main/java/com/google/jimfs/internal/DirectMemoryDisk.java deleted file mode 100644 index 84c4491..0000000 --- a/jimfs/src/main/java/com/google/jimfs/internal/DirectMemoryDisk.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2013 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.jimfs.internal; - -import static com.google.jimfs.internal.Util.nextPowerOf2; - -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * {@link MemoryDisk} using {@linkplain ByteBuffer#allocateDirect(int) direct byte buffers} for - * blocks. - * - * @author Colin Decker - */ -final class DirectMemoryDisk extends MemoryDisk { - - private ByteBuffer[] blocks = new ByteBuffer[4096]; - - DirectMemoryDisk() { - this(DEFAULT_BLOCK_SIZE); - } - - /** - * Creates a disk with the given block size and max cache size. - */ - public DirectMemoryDisk(int blockSize) { - super(blockSize); - } - - @Override - protected int allocateMoreBlocks(int count) { - int newBlockCount = blockCount() + count; - if (newBlockCount > blocks.length) { - // using nextPowerOf2 instead of multiplying blocks.length * 2 until it's >= newBlockCount - blocks = Arrays.copyOf(blocks, nextPowerOf2(newBlockCount)); - } - - for (int i = blockCount(); i < newBlockCount; i++) { - blocks[i] = ByteBuffer.allocateDirect(blockSize); - free.add(i); - } - - return count; - } - - @Override - public int zero(int block, int offset, int len) { - ByteBuffer buffer = blockForRead(block); - int limit = offset + len; - for (int i = offset; i < limit; i++) { - buffer.put(i, (byte) 0); - } - return len; - } - - @Override - public void copy(int block, int copy) { - ByteBuffer blockBuf = blockForRead(block); - ByteBuffer copyBuf = blocks[copy]; - copyBuf.put(blockBuf); - copyBuf.clear(); - } - - @Override - public void put(int block, int offset, byte b) { - blocks[block].put(offset, b); - } - - @Override - public int put(int block, int offset, byte[] b, int off, int len) { - ByteBuffer blockBuf = blocks[block]; - blockBuf.position(offset); - blockBuf.put(b, off, len); - blockBuf.clear(); - return len; - } - - @Override - public int put(int block, int offset, ByteBuffer buf) { - ByteBuffer blockBuf = blocks[block]; - blockBuf.position(offset); - int len = Math.min(blockSize - offset, buf.remaining()); - blockBuf.put(buf); - blockBuf.clear(); - return len; - } - - @Override - public byte get(int block, int offset) { - return blocks[block].get(offset); - } - - @Override - public int get(int block, int offset, byte[] b, int off, int len) { - ByteBuffer blockBuf = blockForRead(block); - blockBuf.position(offset); - blockBuf.get(b, off, len); - return len; - } - - @Override - public int get(int block, int offset, ByteBuffer buf, int len) { - ByteBuffer blockBuf = blockForRead(block); - blockBuf.position(offset); - blockBuf.limit(offset + len); - buf.put(blockBuf); - return len; - } - - @Override - public ByteBuffer asByteBuffer(int block, int offset, int len) { - ByteBuffer result = blockForRead(block); - result.position(offset); - result.limit(offset + len); - return result; - } - - /** - * Must duplicate the block because we can't have multiple reading threads manipulating the state - * of the same buffer at the same time. Why oh why couldn't some kind of public buffer type - * without positional state have been created? ...and a better buffer API than ByteBuffer created - * on top of it, while I'm wishing. - */ - private ByteBuffer blockForRead(int block) { - return blocks[block].duplicate(); - } - -} diff --git a/jimfs/src/main/java/com/google/jimfs/internal/FileContent.java b/jimfs/src/main/java/com/google/jimfs/internal/FileContent.java index 70347f5..6fb013e 100644 --- a/jimfs/src/main/java/com/google/jimfs/internal/FileContent.java +++ b/jimfs/src/main/java/com/google/jimfs/internal/FileContent.java @@ -16,6 +16,7 @@ package com.google.jimfs.internal; +import java.io.IOException; import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.Path; @@ -33,7 +34,7 @@ interface FileContent { * the original; for example, a copy of a directory is an empty directory regardless of what the * original directory contains. */ - FileContent copy(); + FileContent copy() throws IOException; /** * Returns the size, in bytes, of this content. This may be 0 when there is no logical size we diff --git a/jimfs/src/main/java/com/google/jimfs/internal/FileFactory.java b/jimfs/src/main/java/com/google/jimfs/internal/FileFactory.java index 2a41503..a62a14e 100644 --- a/jimfs/src/main/java/com/google/jimfs/internal/FileFactory.java +++ b/jimfs/src/main/java/com/google/jimfs/internal/FileFactory.java @@ -21,6 +21,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Supplier; +import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; /** @@ -32,12 +33,12 @@ final class FileFactory { private final AtomicInteger idGenerator = new AtomicInteger(); - private final MemoryDisk disk; + private final HeapDisk disk; /** * Creates a new file factory using the given disk for regular files. */ - public FileFactory(MemoryDisk disk) { + public FileFactory(HeapDisk disk) { this.disk = checkNotNull(disk); } @@ -71,7 +72,7 @@ final class FileFactory { /** * Creates and returns a copy of the given file. */ - public File copy(File file) { + public File copy(File file) throws IOException { return new File(nextFileId(), file.content().copy()); } diff --git a/jimfs/src/main/java/com/google/jimfs/internal/HeapDisk.java b/jimfs/src/main/java/com/google/jimfs/internal/HeapDisk.java new file mode 100644 index 0000000..1a97ed5 --- /dev/null +++ b/jimfs/src/main/java/com/google/jimfs/internal/HeapDisk.java @@ -0,0 +1,154 @@ +/* + * Copyright 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.jimfs.internal; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.annotations.VisibleForTesting; + +import java.io.IOException; + +/** + * A resizable pseudo-disk acting as a shared space for storing file data. A disk allocates fixed + * size blocks of bytes to files as needed and may cache blocks that have been freed for reuse. A + * memory disk has a fixed maximum number of blocks it will allocate at a time (which sets the + * total "size" of the disk) and a maximum number of unused blocks it will cache for reuse at a + * time (which sets the minimum amount of space the disk will use once + * + * @author Colin Decker + */ +final class HeapDisk { + + /** 8 KB blocks. */ + public static final int DEFAULT_BLOCK_SIZE = 8192; + + /** 16 GB of space with 8 KB blocks. */ + public static final int DEFAULT_MAX_BLOCK_COUNT = + (int) ((16L * 1024 * 1024 * 1024) / DEFAULT_BLOCK_SIZE); + + /** Fixed size of each block for this disk. */ + private final int blockSize; + + /** Maximum total number of blocks that the disk may contain at any time. */ + private final int maxBlockCount; + + /** Maximum total number of unused blocks that may be cached for reuse at any time. */ + private final int maxCachedBlockCount; + + /** Cache of free blocks to be allocated to files. */ + @VisibleForTesting final BlockList blockCache; + + /** The current total number of blocks that are currently allocated to files. */ + private int allocatedBlockCount; + + /** + * Creates a new heap disk with 8 KB blocks that can store up to 16 GB of data and caches all + * blocks that are freed. + */ + public HeapDisk() { + this(DEFAULT_BLOCK_SIZE, DEFAULT_MAX_BLOCK_COUNT, DEFAULT_MAX_BLOCK_COUNT); + } + + /** + * Creates a new disk with the given {@code blockSize}, {@code maxBlockCount} and + * {@code maxCachedBlockCount}. + */ + public HeapDisk(int blockSize, int maxBlockCount, int maxCachedBlockCount) { + checkArgument(blockSize > 0, "blockSize (%s) must be positive", blockSize); + checkArgument(blockSize % 2 == 0, "blockSize (%s) must be a multiple of 2", blockSize); + checkArgument(maxBlockCount > 0, "maxBlockCount (%s) must be positive", maxBlockCount); + checkArgument(maxCachedBlockCount >= 0, + "maxCachedBlockCount must be non-negative", maxCachedBlockCount); + this.blockSize = blockSize; + this.maxBlockCount = maxBlockCount; + this.maxCachedBlockCount = maxCachedBlockCount; + this.blockCache = new BlockList(Math.min(maxCachedBlockCount, 8192)); + } + + /** + * Creates a new, empty byte store. + */ + public ByteStore createByteStore() { + return new ByteStore(this); + } + + /** + * Returns the size of blocks created by this disk. + */ + public int blockSize() { + return blockSize; + } + + /** + * Returns the total size of this disk. This is the maximum size of the disk and does not reflect + * the amount of data currently allocated or cached. + */ + public synchronized long getTotalSpace() { + return maxBlockCount * (long) blockSize; + } + + /** + * Returns the current number of unallocated bytes on this disk. This is the maximum number of + * additional bytes that could be allocated and does not reflect the number of bytes currently + * actually cached in the disk. + */ + public synchronized long getUnallocatedSpace() { + return (maxBlockCount - allocatedBlockCount) * (long) blockSize; + } + + /** + * Allocates the given number of blocks and adds their identifiers to the given list. + */ + public synchronized void allocate(BlockList blocks, int count) throws IOException { + int newAllocatedBlockCount = allocatedBlockCount + count; + if (newAllocatedBlockCount > maxBlockCount) { + throw new IOException("out of disk space"); + } + + int newBlocksNeeded = Math.max(count - blockCache.size(), 0); + + for (int i = 0; i < newBlocksNeeded; i++) { + blocks.add(new byte[blockSize]); + } + + if (newBlocksNeeded != count) { + blockCache.transferTo(blocks, count - newBlocksNeeded); + } + + allocatedBlockCount = newAllocatedBlockCount; + } + + /** + * Frees all blocks in the given list. + */ + public void free(BlockList blocks) { + free(blocks, blocks.size()); + } + + /** + * Frees the last count blocks from the given list. + */ + public synchronized void free(BlockList blocks, int count) { + int remainingCacheSpace = maxCachedBlockCount - blocks.size(); + if (remainingCacheSpace != 0) { + blocks.copyTo(blockCache, Math.min(count, remainingCacheSpace)); + } + blocks.truncate(blocks.size() - count); + + allocatedBlockCount -= count; + } +} diff --git a/jimfs/src/main/java/com/google/jimfs/internal/HeapMemoryDisk.java b/jimfs/src/main/java/com/google/jimfs/internal/HeapMemoryDisk.java deleted file mode 100644 index 802a4ea..0000000 --- a/jimfs/src/main/java/com/google/jimfs/internal/HeapMemoryDisk.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2013 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.jimfs.internal; - -import static com.google.jimfs.internal.Util.nextPowerOf2; - -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * {@link MemoryDisk} using byte arrays for blocks. - * - * @author Colin Decker - */ -final class HeapMemoryDisk extends MemoryDisk { - - private byte[][] blocks = new byte[4096][]; - - HeapMemoryDisk() { - this(DEFAULT_BLOCK_SIZE); - } - - /** - * Creates a disk with the given block size. - */ - public HeapMemoryDisk(int blockSize) { - super(blockSize); - } - - @Override - protected int allocateMoreBlocks(int count) { - int newBlockCount = blockCount() + count; - if (newBlockCount > blocks.length) { - // using nextPowerOf2 instead of multiplying blocks.length * 2 until it's >= newBlockCount - blocks = Arrays.copyOf(blocks, nextPowerOf2(newBlockCount)); - } - - for (int i = blockCount(); i < newBlockCount; i++) { - blocks[i] = new byte[blockSize]; - free.add(i); - } - - return count; - } - - @Override - public int zero(int block, int offset, int len) { - Util.zero(blocks[block], offset, len); - return len; - } - - @Override - public void copy(int block, int copy) { - System.arraycopy(blocks[block], 0, blocks[copy], 0, blockSize); - } - - @Override - public void put(int block, int offset, byte b) { - blocks[block][offset] = b; - } - - @Override - public int put(int block, int offset, byte[] b, int off, int len) { - System.arraycopy(b, off, blocks[block], offset, len); - return len; - } - - @Override - public int put(int block, int offset, ByteBuffer buf) { - int len = Math.min(blockSize - offset, buf.remaining()); - buf.get(blocks[block], offset, len); - return len; - } - - @Override - public byte get(int block, int offset) { - return blocks[block][offset]; - } - - @Override - public int get(int block, int offset, byte[] b, int off, int len) { - System.arraycopy(blocks[block], offset, b, off, len); - return len; - } - - @Override - public int get(int block, int offset, ByteBuffer buf, int len) { - buf.put(blocks[block], offset, len); - return len; - } - - @Override - public ByteBuffer asByteBuffer(int block, int offset, int len) { - return ByteBuffer.wrap(blocks[block], offset, len); - } -} diff --git a/jimfs/src/main/java/com/google/jimfs/internal/JimfsFileStore.java b/jimfs/src/main/java/com/google/jimfs/internal/JimfsFileStore.java index e36c67f..8ba8800 100644 --- a/jimfs/src/main/java/com/google/jimfs/internal/JimfsFileStore.java +++ b/jimfs/src/main/java/com/google/jimfs/internal/JimfsFileStore.java @@ -53,14 +53,14 @@ import javax.annotation.Nullable; final class JimfsFileStore extends FileStore { private final FileTree tree; - private final MemoryDisk disk; + private final HeapDisk disk; private final AttributeService attributes; private final FileFactory factory; private final Lock readLock; private final Lock writeLock; - public JimfsFileStore(FileTree tree, FileFactory factory, MemoryDisk disk, + public JimfsFileStore(FileTree tree, FileFactory factory, HeapDisk disk, AttributeService attributes) { this.tree = checkNotNull(tree); this.factory = checkNotNull(factory); @@ -143,7 +143,7 @@ final class JimfsFileStore extends FileStore { * Creates a copy of the given file, copying its attributes as well if copy attributes is true. * Returns the copy. */ - File copy(File file, boolean copyAttributes) { + File copy(File file, boolean copyAttributes) throws IOException { File copy = factory.copy(file); setInitialAttributes(copy); if (copyAttributes) { diff --git a/jimfs/src/main/java/com/google/jimfs/internal/JimfsFileSystems.java b/jimfs/src/main/java/com/google/jimfs/internal/JimfsFileSystems.java index ba887f4..21e751f 100644 --- a/jimfs/src/main/java/com/google/jimfs/internal/JimfsFileSystems.java +++ b/jimfs/src/main/java/com/google/jimfs/internal/JimfsFileSystems.java @@ -56,7 +56,9 @@ final class JimfsFileSystems { private static JimfsFileStore createFileStore( Configuration config, PathService pathService) { AttributeService attributeService = new AttributeService(config); - MemoryDisk disk = new HeapMemoryDisk(); + + // TODO(cgdecker): Make disk values configurable + HeapDisk disk = new HeapDisk(); FileFactory fileFactory = new FileFactory(disk); Map<Name, File> roots = new HashMap<>(); diff --git a/jimfs/src/main/java/com/google/jimfs/internal/MemoryDisk.java b/jimfs/src/main/java/com/google/jimfs/internal/MemoryDisk.java deleted file mode 100644 index a882e83..0000000 --- a/jimfs/src/main/java/com/google/jimfs/internal/MemoryDisk.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2013 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.jimfs.internal; - -import static com.google.common.base.Preconditions.checkArgument; - -import java.nio.ByteBuffer; - -/** - * A resizable pseudo-disk acting as a shared space for storing file data. A disk allocates fixed - * size blocks of bytes to files as needed and caches blocks that have been freed for reuse. Each - * block is represented by an integer which is used to locate the block for read and write - * operations implemented by the disk. - * - * <p>Currently, once a memory disk has allocated a block it will cache that block indefinitely - * once freed. This means that the total size of the disk is always the maximum number of bytes - * that have been allocated to files at one time so far. - * - * @author Colin Decker - */ -abstract class MemoryDisk { - - /** - * 8K blocks. - */ - protected static final int DEFAULT_BLOCK_SIZE = 8192; - - /** - * Fixed size of each block for this disk. - */ - protected final int blockSize; - - /** - * The current total number of blocks this disk contains, including both free blocks and blocks - * that are allocated to files. - */ - private int blockCount; - - /** - * Queue of free blocks to be allocated to files. - */ - protected final IntList free = new IntList(1024); - - protected MemoryDisk(int blockSize) { - checkArgument(blockSize > 0, "blockSize (%s) must be positive", blockSize); - checkArgument(blockSize % 2 == 0, "blockSize (%s) must be a multiple of 2", blockSize); - this.blockSize = blockSize; - } - - /** - * Creates a new, empty byte store. - */ - public final ByteStore createByteStore() { - return new MemoryDiskByteStore(this); - } - - /** - * Allocates at least {@code minBlocks} more blocks if possible. The {@code free} list - * should have blocks in it when this returns if an exception is not thrown. Returns the number - * of new blocks that were allocated. - */ - protected abstract int allocateMoreBlocks(int count); - - /** - * Returns the size of blocks created by this disk. - */ - public final int blockSize() { - return blockSize; - } - - /** - * Returns the current total number of blocks this disk contains. - */ - protected final int blockCount() { - return blockCount; - } - - /** - * Returns the current total size of this disk. - */ - public final synchronized long getTotalSpace() { - return blockCount * blockSize; - } - - /** - * Returns the current number of unallocated bytes on this disk. - */ - public final synchronized long getUnallocatedSpace() { - return free.size() * blockSize; - } - - /** - * Allocates the given number of blocks and adds their identifiers to the given list. - */ - public final synchronized void allocate(IntList blocks, int count) { - int additionalBlocksNeeded = count - free.size(); - if (additionalBlocksNeeded > 0) { - blockCount += allocateMoreBlocks(additionalBlocksNeeded); - } - - free.transferTo(blocks, count); - } - - /** - * Frees all blocks in the given list. - */ - public final void free(IntList blocks) { - free(blocks, blocks.size()); - } - - /** - * Frees the last count blocks from the given list. - */ - public final synchronized void free(IntList blocks, int count) { - blocks.transferTo(free, count); - } - - /** - * Zeroes len bytes in the given block starting at the given offset. Returns len. - */ - public abstract int zero(int block, int offset, int len); - - /** - * Copies the given block and returns the copy. - */ - public abstract void copy(int block, int copy); - - /** - * Puts the given byte at the given offset in the given block. - */ - public abstract void put(int block, int offset, byte b); - - /** - * Puts the given slice of the given array at the given offset in the given block. - */ - public abstract int put(int block, int offset, byte[] b, int off, int len); - - /** - * Puts the contents of the given byte buffer at the given offset in the given block. - */ - public abstract int put(int block, int offset, ByteBuffer buf); - - /** - * Returns the byte at the given offset in the given block. - */ - public abstract byte get(int block, int offset); - - /** - * Reads len bytes starting at the given offset in the given block into the given slice of the - * given byte array. - */ - public abstract int get(int block, int offset, byte[] b, int off, int len); - - /** - * Reads len bytes starting at the given offset in the given block into the given byte buffer. - */ - public abstract int get(int block, int offset, ByteBuffer buf, int len); - - /** - * Returns a ByteBuffer view of the slice of the given block starting at the given offset and - * having the given length. - */ - public abstract ByteBuffer asByteBuffer(int block, int offset, int len); -} diff --git a/jimfs/src/main/java/com/google/jimfs/internal/MemoryDiskByteStore.java b/jimfs/src/main/java/com/google/jimfs/internal/MemoryDiskByteStore.java deleted file mode 100644 index f79fb10..0000000 --- a/jimfs/src/main/java/com/google/jimfs/internal/MemoryDiskByteStore.java +++ /dev/null @@ -1,396 +0,0 @@ -/* - * Copyright 2013 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.jimfs.internal; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.primitives.UnsignedBytes; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; - -/** - * Byte store backed by a {@link MemoryDisk}. - * - * @author Colin Decker - */ -final class MemoryDiskByteStore extends ByteStore { - - private final MemoryDisk disk; - private final IntList blocks; - private long size; - - public MemoryDiskByteStore(MemoryDisk disk) { - this(disk, new IntList(32), 0); - } - - private MemoryDiskByteStore(MemoryDisk disk, IntList blocks, long size) { - this.disk = checkNotNull(disk); - this.blocks = checkNotNull(blocks); - - checkArgument(size >= 0); - this.size = size; - } - - @Override - public long currentSize() { - return size; - } - - @Override - protected ByteStore createCopy() { - IntList copyBlocks = new IntList(Math.max(blocks.size() * 2, 32)); - disk.allocate(copyBlocks, blocks.size()); - - for (int i = 0; i < blocks.size(); i++) { - int block = blocks.get(i); - int copy = copyBlocks.get(i); - disk.copy(block, copy); - } - return new MemoryDiskByteStore(disk, copyBlocks, size); - } - - @Override - protected final void deleteContents() { - disk.free(blocks); - size = 0; - } - - @Override - public boolean truncate(long size) { - if (size >= this.size) { - return false; - } - - long lastPosition = size - 1; - this.size = size; - - int newBlockCount = blockIndex(lastPosition) + 1; - int blocksToRemove = blocks.size() - newBlockCount; - if (blocksToRemove > 0) { - disk.free(blocks, blocksToRemove); - } - - return true; - } - - /** - * Prepares for a write of len bytes starting at position pos. - */ - private void prepareForWrite(long pos, long len) { - long end = pos + len; - - // allocate any additional blocks needed - int lastBlockIndex = blocks.size() - 1; - int endBlockIndex = blockIndex(end - 1); - - if (endBlockIndex > lastBlockIndex) { - int additionalBlocksNeeded = endBlockIndex - lastBlockIndex; - disk.allocate(blocks, additionalBlocksNeeded); - } - - // zero bytes between current size and pos - if (pos > size) { - long remaining = pos - size; - - int blockIndex = blockIndex(size); - int block = blocks.get(blockIndex); - int off = offsetInBlock(size); - - remaining -= disk.zero(block, off, length(off, remaining)); - - while (remaining > 0) { - block = blocks.get(++blockIndex); - - remaining -= disk.zero(block, 0, length(remaining)); - } - - size = pos; - } - } - - @Override - public int write(long pos, byte b) { - prepareForWrite(pos, 1); - - int block = blocks.get(blockIndex(pos)); - int off = offsetInBlock(pos); - disk.put(block, off, b); - - if (pos >= size) { - size = pos + 1; - } - - return 1; - } - - @Override - public int write(long pos, byte[] b, int off, int len) { - prepareForWrite(pos, len); - - if (len == 0) { - return 0; - } - - int remaining = len; - - int blockIndex = blockIndex(pos); - int block = blockForWrite(blockIndex); - int offInBlock = offsetInBlock(pos); - - int written = disk.put(block, offInBlock, b, off, length(offInBlock, remaining)); - remaining -= written; - off += written; - - while (remaining > 0) { - block = blocks.get(++blockIndex); - - written = disk.put(block, 0, b, off, length(remaining)); - remaining -= written; - off += written; - } - - long newPos = pos + len; - if (newPos > size) { - size = newPos; - } - - return len; - } - - @Override - public int write(long pos, ByteBuffer buf) { - int len = buf.remaining(); - - prepareForWrite(pos, len); - - if (len == 0) { - return 0; - } - - int bytesToWrite = buf.remaining(); - - int blockIndex = blockIndex(pos); - int block = blockForWrite(blockIndex); - int off = offsetInBlock(pos); - - disk.put(block, off, buf); - - while (buf.hasRemaining()) { - block = blocks.get(++blockIndex); - - disk.put(block, 0, buf); - } - - if (pos + bytesToWrite > size) { - size = pos + bytesToWrite; - } - - return bytesToWrite; - } - - @Override - public long transferFrom(ReadableByteChannel src, long pos, long count) throws IOException { - prepareForWrite(pos, 0); - - if (count == 0) { - return 0; - } - - long remaining = count; - - int blockIndex = blockIndex(pos); - int block = blockForWrite(blockIndex); - int off = offsetInBlock(pos); - - ByteBuffer buf = disk.asByteBuffer(block, off, length(off, remaining)); - - int read = 0; - while (buf.hasRemaining()) { - read = src.read(buf); - if (read == -1) { - break; - } - - remaining -= read; - } - - if (read != -1) { - outer: while (remaining > 0) { - block = blockForWrite(++blockIndex); - - buf = disk.asByteBuffer(block, 0, length(remaining)); - while (buf.hasRemaining()) { - read = src.read(buf); - if (read == -1) { - break outer; - } - - remaining -= read; - } - } - } - - long written = count - remaining; - long newPos = pos + written; - if (newPos > size) { - size = newPos; - } - - return written; - } - - @Override - public int read(long pos) { - if (pos >= size) { - return -1; - } - - int block = blocks.get(blockIndex(pos)); - int off = offsetInBlock(pos); - return UnsignedBytes.toInt(disk.get(block, off)); - } - - @Override - public int read(long pos, byte[] b, int off, int len) { - // since max is len (an int), result is guaranteed to be an int - int bytesToRead = (int) bytesToRead(pos, len); - - if (bytesToRead > 0) { - int remaining = bytesToRead; - - int blockIndex = blockIndex(pos); - int block = blocks.get(blockIndex); - int offsetInBlock = offsetInBlock(pos); - - int read = disk.get(block, offsetInBlock, b, off, length(offsetInBlock, remaining)); - remaining -= read; - off += read; - - while (remaining > 0) { - int index = ++blockIndex; - block = blocks.get(index); - - read = disk.get(block, 0, b, off, length(remaining)); - remaining -= read; - off += read; - } - } - - return bytesToRead; - } - - @Override - public int read(long pos, ByteBuffer buf) { - // since max is buf.remaining() (an int), result is guaranteed to be an int - int bytesToRead = (int) bytesToRead(pos, buf.remaining()); - - if (bytesToRead > 0) { - int remaining = bytesToRead; - - int blockIndex = blockIndex(pos); - int block = blocks.get(blockIndex); - int off = offsetInBlock(pos); - - remaining -= disk.get(block, off, buf, length(off, remaining)); - - while (remaining > 0) { - int index = ++blockIndex; - block = blocks.get(index); - remaining -= disk.get(block, 0, buf, length(remaining)); - } - } - - return bytesToRead; - } - - @Override - public long transferTo(long pos, long count, WritableByteChannel dest) throws IOException { - long bytesToRead = bytesToRead(pos, count); - - if (bytesToRead > 0) { - long remaining = bytesToRead; - - int blockIndex = blockIndex(pos); - int block = blocks.get(blockIndex); - int off = offsetInBlock(pos); - - ByteBuffer buf = disk.asByteBuffer(block, off, length(off, remaining)); - while (buf.hasRemaining()) { - remaining -= dest.write(buf); - } - buf.clear(); - - while (remaining > 0) { - int index = ++blockIndex; - block = blocks.get(index); - - buf = disk.asByteBuffer(block, 0, length(remaining)); - while (buf.hasRemaining()) { - remaining -= dest.write(buf); - } - buf.clear(); - } - } - - return Math.max(bytesToRead, 0); // don't return -1 for this method - } - - /** - * Gets the block at the given index, expanding to create the block if necessary. - */ - private int blockForWrite(int index) { - int blockCount = blocks.size(); - if (index >= blockCount) { - int additionalBlocksNeeded = index - blockCount + 1; - disk.allocate(blocks, additionalBlocksNeeded); - } - - return blocks.get(index); - } - - private int blockIndex(long position) { - return (int) (position / disk.blockSize()); - } - - private int offsetInBlock(long position) { - return (int) (position % disk.blockSize()); - } - - private int length(long max) { - return (int) Math.min(disk.blockSize(), max); - } - - private int length(int off, long max) { - return (int) Math.min(disk.blockSize() - off, max); - } - - /** - * Returns the number of bytes that can be read starting at position {@code pos} (up to a maximum - * of {@code max}) or -1 if {@code pos} is greater than or equal to the current size. - */ - private long bytesToRead(long pos, long max) { - long available = size - pos; - if (available <= 0) { - return -1; - } - return Math.min(available, max); - } -} diff --git a/jimfs/src/main/java/com/google/jimfs/internal/Util.java b/jimfs/src/main/java/com/google/jimfs/internal/Util.java index d73dacd..7b5943e 100644 --- a/jimfs/src/main/java/com/google/jimfs/internal/Util.java +++ b/jimfs/src/main/java/com/google/jimfs/internal/Util.java @@ -76,30 +76,42 @@ final class Util { return C2 * Integer.rotateLeft(hashCode * C1, 15); } - private static final int ZERO_ARRAY_LEN = 8192; - private static final byte[] ZERO_ARRAY = new byte[ZERO_ARRAY_LEN]; + private static final int ARRAY_LEN = 8192; + private static final byte[] ZERO_ARRAY = new byte[ARRAY_LEN]; + private static final byte[][] NULL_ARRAY = new byte[ARRAY_LEN][]; /** - * Zeroes all bytes of the given array. + * Zeroes all bytes between off (inclusive) and off + len (exclusive) in the given array. */ - static void zero(byte[] bytes) { - zero(bytes, 0, bytes.length); + static void zero(byte[] bytes, int off, int len) { + // this significantly faster than looping or Arrays.fill (which loops), particularly when the + // length of the slice to be zeroed is <= to ARRAY_LEN (in that case, it's faster by a + // factor of 2) + int remaining = len; + while (remaining >= ARRAY_LEN) { + System.arraycopy(ZERO_ARRAY, 0, bytes, off, ARRAY_LEN); + off += ARRAY_LEN; + remaining -= ARRAY_LEN; + } + + System.arraycopy(ZERO_ARRAY, 0, bytes, off, remaining); } /** - * Zeroes all bytes between off (inclusive) and off + len (exclusive) in the given array. + * Clears (sets to null) all blocks between off (inclusive) and off + len (exclusive) in the + * given array. */ - static void zero(byte[] bytes, int off, int len) { + static void clear(byte[][] blocks, int off, int len) { // this significantly faster than looping or Arrays.fill (which loops), particularly when the - // length of the slice to be zeroed is <= to ZERO_ARRAY_LEN (in that case, it's faster by a + // length of the slice to be cleared is <= to ARRAY_LEN (in that case, it's faster by a // factor of 2) int remaining = len; - while (remaining >= ZERO_ARRAY_LEN) { - System.arraycopy(ZERO_ARRAY, 0, bytes, off, ZERO_ARRAY_LEN); - off += ZERO_ARRAY_LEN; - remaining -= ZERO_ARRAY_LEN; + while (remaining >= ARRAY_LEN) { + System.arraycopy(NULL_ARRAY, 0, blocks, off, ARRAY_LEN); + off += ARRAY_LEN; + remaining -= ARRAY_LEN; } - System.arraycopy(ZERO_ARRAY, 0, bytes, off, remaining); + System.arraycopy(NULL_ARRAY, 0, blocks, off, remaining); } } diff --git a/jimfs/src/main/java/com/google/jimfs/internal/package-info.java b/jimfs/src/main/java/com/google/jimfs/internal/package-info.java index 10a8174..7cef362 100644 --- a/jimfs/src/main/java/com/google/jimfs/internal/package-info.java +++ b/jimfs/src/main/java/com/google/jimfs/internal/package-info.java @@ -48,7 +48,7 @@ * <ul> * <li>{@link com.google.jimfs.internal.FileFactory FileFactory} handles creation of new file * objects.</li> - * <li>{@link com.google.jimfs.internal.MemoryDisk MemoryDisk} handles creation and storage of + * <li>{@link com.google.jimfs.internal.HeapDisk HeapDisk} handles creation and storage of * {@link com.google.jimfs.internal.ByteStore ByteStore} instances, which act as the content of * regular files.</li> * <li>{@link com.google.jimfs.internal.FileTree FileTree} stores the root of the file hierarchy @@ -121,12 +121,10 @@ * <h3>Regular files</h3> * * Currently, the only implementation for regular file content is - * {@link com.google.jimfs.internal.MemoryDiskByteStore MemoryDiskByteStore}, which makes use of a - * singleton {@link com.google.jimfs.internal.MemoryDisk MemoryDisk}. A disk (which may either use - * {@linkplain com.google.jimfs.internal.HeapMemoryDisk heap} or - * {@linkplain com.google.jimfs.internal.DirectMemoryDisk direct} memory) is a resizable cache for + * {@link com.google.jimfs.internal.ByteStore ByteStore}, which makes use of a singleton + * {@link com.google.jimfs.internal.HeapDisk HeapDisk}. A disk is a resizable factory and cache for * fixed size blocks of memory. These blocks are allocated to files as needed and returned to the - * disk when a file is deleted or truncated. When existing free blocks are available, those blocks + * disk when a file is deleted or truncated. When cached free blocks are available, those blocks * are allocated to files first. If more blocks are needed, they are created. * * <h3>Linking</h3> diff --git a/jimfs/src/test/java/com/google/jimfs/JimfsUnixLikeFileSystemTest.java b/jimfs/src/test/java/com/google/jimfs/JimfsUnixLikeFileSystemTest.java index 545ad88..eab9af2 100644 --- a/jimfs/src/test/java/com/google/jimfs/JimfsUnixLikeFileSystemTest.java +++ b/jimfs/src/test/java/com/google/jimfs/JimfsUnixLikeFileSystemTest.java @@ -135,10 +135,10 @@ public class JimfsUnixLikeFileSystemTest extends AbstractJimfsIntegrationTest { ASSERT.that(fileStore.type()).is("jimfs"); ASSERT.that(fileStore.isReadOnly()).isFalse(); - // no regular files have been created and written to, so no blocks have been allocated yet - ASSERT.that(fileStore.getTotalSpace()).is(0); - ASSERT.that(fileStore.getUnallocatedSpace()).is(0); - ASSERT.that(fileStore.getUsableSpace()).is(0); + long expectedSize = 16L * 1024 * 1024 * 1024; // 16 GB + ASSERT.that(fileStore.getTotalSpace()).is(expectedSize); + ASSERT.that(fileStore.getUnallocatedSpace()).is(expectedSize); + ASSERT.that(fileStore.getUsableSpace()).is(expectedSize); } @Test diff --git a/jimfs/src/test/java/com/google/jimfs/internal/AbstractByteStoreTest.java b/jimfs/src/test/java/com/google/jimfs/internal/AbstractByteStoreTest.java index c4195ba..64efc4f 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/AbstractByteStoreTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/AbstractByteStoreTest.java @@ -48,7 +48,7 @@ public abstract class AbstractByteStoreTest { store = createByteStore(); } - private void fillContent(String fill) { + private void fillContent(String fill) throws IOException { store.write(0, buffer(fill)); } @@ -90,65 +90,65 @@ public abstract class AbstractByteStoreTest { } @Test - public void testEmpty_write_singleByte_atStart() { + public void testEmpty_write_singleByte_atStart() throws IOException { store.write(0, (byte) 1); assertContentEquals("1", store); } @Test - public void testEmpty_write_byteArray_atStart() { + public void testEmpty_write_byteArray_atStart() throws IOException { byte[] bytes = bytes("111111"); store.write(0, bytes, 0, bytes.length); assertContentEquals(bytes, store); } @Test - public void testEmpty_write_partialByteArray_atStart() { + public void testEmpty_write_partialByteArray_atStart() throws IOException { byte[] bytes = bytes("2211111122"); store.write(0, bytes, 2, 6); assertContentEquals("111111", store); } @Test - public void testEmpty_write_singleBuffer_atStart() { + public void testEmpty_write_singleBuffer_atStart() throws IOException { store.write(0, buffer("111111")); assertContentEquals("111111", store); } @Test - public void testEmpty_write_multipleBuffers_atStart() { + public void testEmpty_write_multipleBuffers_atStart() throws IOException { store.write(0, buffers("111", "111")); assertContentEquals("111111", store); } @Test - public void testEmpty_write_singleByte_atNonZeroPosition() { + public void testEmpty_write_singleByte_atNonZeroPosition() throws IOException { store.write(5, (byte) 1); assertContentEquals("000001", store); } @Test - public void testEmpty_write_byteArray_atNonZeroPosition() { + public void testEmpty_write_byteArray_atNonZeroPosition() throws IOException { byte[] bytes = bytes("111111"); store.write(5, bytes, 0, bytes.length); assertContentEquals("00000111111", store); } @Test - public void testEmpty_write_partialByteArray_atNonZeroPosition() { + public void testEmpty_write_partialByteArray_atNonZeroPosition() throws IOException { byte[] bytes = bytes("2211111122"); store.write(5, bytes, 2, 6); assertContentEquals("00000111111", store); } @Test - public void testEmpty_write_singleBuffer_atNonZeroPosition() { + public void testEmpty_write_singleBuffer_atNonZeroPosition() throws IOException { store.write(5, buffer("111")); assertContentEquals("00000111", store); } @Test - public void testEmpty_write_multipleBuffers_atNonZeroPosition() { + public void testEmpty_write_multipleBuffers_atNonZeroPosition() throws IOException { store.write(5, buffers("111", "222")); assertContentEquals("00000111222", store); } @@ -202,31 +202,31 @@ public abstract class AbstractByteStoreTest { } @Test - public void testEmpty_copy() { + public void testEmpty_copy() throws IOException { ByteStore copy = store.copy(); assertContentEquals("", copy); } @Test - public void testEmpty_truncate_toZero() { + public void testEmpty_truncate_toZero() throws IOException { store.truncate(0); assertContentEquals("", store); } @Test - public void testEmpty_truncate_sizeUp() { + public void testEmpty_truncate_sizeUp() throws IOException { store.truncate(10); assertContentEquals("", store); } @Test - public void testNonEmpty() { + public void testNonEmpty() throws IOException { fillContent("222222"); assertContentEquals("222222", store); } @Test - public void testNonEmpty_read_singleByte() { + public void testNonEmpty_read_singleByte() throws IOException { fillContent("123456"); assertEquals(1, store.read(0)); assertEquals(2, store.read(1)); @@ -236,7 +236,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_all_byteArray() { + public void testNonEmpty_read_all_byteArray() throws IOException { fillContent("222222"); byte[] array = new byte[6]; assertEquals(6, store.read(0, array, 0, array.length)); @@ -244,7 +244,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_all_singleBuffer() { + public void testNonEmpty_read_all_singleBuffer() throws IOException { fillContent("222222"); ByteBuffer buffer = ByteBuffer.allocate(6); assertEquals(6, store.read(0, buffer)); @@ -252,7 +252,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_all_multipleBuffers() { + public void testNonEmpty_read_all_multipleBuffers() throws IOException { fillContent("223334"); ByteBuffer buf1 = ByteBuffer.allocate(3); ByteBuffer buf2 = ByteBuffer.allocate(3); @@ -262,7 +262,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_all_byteArray_largerThanContent() { + public void testNonEmpty_read_all_byteArray_largerThanContent() throws IOException { fillContent("222222"); byte[] array = new byte[10]; assertEquals(6, store.read(0, array, 0, array.length)); @@ -273,7 +273,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_all_singleBuffer_largerThanContent() { + public void testNonEmpty_read_all_singleBuffer_largerThanContent() throws IOException { fillContent("222222"); ByteBuffer buffer = ByteBuffer.allocate(16); assertBufferEquals("0000000000000000", 16, buffer); @@ -282,7 +282,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_all_multipleBuffers_largerThanContent() { + public void testNonEmpty_read_all_multipleBuffers_largerThanContent() throws IOException { fillContent("222222"); ByteBuffer buf1 = ByteBuffer.allocate(4); ByteBuffer buf2 = ByteBuffer.allocate(8); @@ -292,7 +292,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_all_multipleBuffers_extraBuffers() { + public void testNonEmpty_read_all_multipleBuffers_extraBuffers() throws IOException { fillContent("222222"); ByteBuffer buf1 = ByteBuffer.allocate(4); ByteBuffer buf2 = ByteBuffer.allocate(8); @@ -304,7 +304,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_partial_fromStart_byteArray() { + public void testNonEmpty_read_partial_fromStart_byteArray() throws IOException { fillContent("222222"); byte[] array = new byte[3]; assertEquals(3, store.read(0, array, 0, array.length)); @@ -315,7 +315,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_partial_fromMiddle_byteArray() { + public void testNonEmpty_read_partial_fromMiddle_byteArray() throws IOException { fillContent("22223333"); byte[] array = new byte[3]; assertEquals(3, store.read(3, array, 0, array.length)); @@ -326,7 +326,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_partial_fromEnd_byteArray() { + public void testNonEmpty_read_partial_fromEnd_byteArray() throws IOException { fillContent("2222222222"); byte[] array = new byte[3]; assertEquals(2, store.read(8, array, 0, array.length)); @@ -337,7 +337,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_partial_fromStart_singleBuffer() { + public void testNonEmpty_read_partial_fromStart_singleBuffer() throws IOException { fillContent("222222"); ByteBuffer buffer = ByteBuffer.allocate(3); assertEquals(3, store.read(0, buffer)); @@ -345,7 +345,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_partial_fromMiddle_singleBuffer() { + public void testNonEmpty_read_partial_fromMiddle_singleBuffer() throws IOException { fillContent("22223333"); ByteBuffer buffer = ByteBuffer.allocate(3); assertEquals(3, store.read(3, buffer)); @@ -353,7 +353,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_partial_fromEnd_singleBuffer() { + public void testNonEmpty_read_partial_fromEnd_singleBuffer() throws IOException { fillContent("2222222222"); ByteBuffer buffer = ByteBuffer.allocate(3); assertEquals(2, store.read(8, buffer)); @@ -361,7 +361,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_partial_fromStart_multipleBuffers() { + public void testNonEmpty_read_partial_fromStart_multipleBuffers() throws IOException { fillContent("12345678"); ByteBuffer buf1 = ByteBuffer.allocate(2); ByteBuffer buf2 = ByteBuffer.allocate(2); @@ -371,7 +371,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_partial_fromMiddle_multipleBuffers() { + public void testNonEmpty_read_partial_fromMiddle_multipleBuffers() throws IOException { fillContent("12345678"); ByteBuffer buf1 = ByteBuffer.allocate(2); ByteBuffer buf2 = ByteBuffer.allocate(2); @@ -381,7 +381,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_partial_fromEnd_multipleBuffers() { + public void testNonEmpty_read_partial_fromEnd_multipleBuffers() throws IOException { fillContent("123456789"); ByteBuffer buf1 = ByteBuffer.allocate(2); ByteBuffer buf2 = ByteBuffer.allocate(2); @@ -391,7 +391,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_fromPastEnd_byteArray() { + public void testNonEmpty_read_fromPastEnd_byteArray() throws IOException { fillContent("123"); byte[] array = new byte[3]; assertEquals(-1, store.read(3, array, 0, array.length)); @@ -401,7 +401,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_fromPastEnd_singleBuffer() { + public void testNonEmpty_read_fromPastEnd_singleBuffer() throws IOException { fillContent("123"); ByteBuffer buffer = ByteBuffer.allocate(3); store.read(3, buffer); @@ -409,7 +409,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_read_fromPastEnd_multipleBuffers() { + public void testNonEmpty_read_fromPastEnd_multipleBuffers() throws IOException { fillContent("123"); ByteBuffer buf1 = ByteBuffer.allocate(2); ByteBuffer buf2 = ByteBuffer.allocate(2); @@ -419,28 +419,28 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_write_partial_fromStart_singleByte() { + public void testNonEmpty_write_partial_fromStart_singleByte() throws IOException { fillContent("222222"); assertEquals(1, store.write(0, (byte) 1)); assertContentEquals("122222", store); } @Test - public void testNonEmpty_write_partial_fromMiddle_singleByte() { + public void testNonEmpty_write_partial_fromMiddle_singleByte() throws IOException { fillContent("222222"); assertEquals(1, store.write(3, (byte) 1)); assertContentEquals("222122", store); } @Test - public void testNonEmpty_write_partial_fromEnd_singleByte() { + public void testNonEmpty_write_partial_fromEnd_singleByte() throws IOException { fillContent("222222"); assertEquals(1, store.write(6, (byte) 1)); assertContentEquals("2222221", store); } @Test - public void testNonEmpty_write_partial_fromStart_byteArray() { + public void testNonEmpty_write_partial_fromStart_byteArray() throws IOException { fillContent("222222"); assertEquals(3, store.write(0, bytes("111"), 0, 3)); assertContentEquals("111222", store); @@ -449,7 +449,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_write_partial_fromMiddle_byteArray() { + public void testNonEmpty_write_partial_fromMiddle_byteArray() throws IOException { fillContent("22222222"); assertEquals(3, store.write(3, buffer("111"))); assertContentEquals("22211122", store); @@ -458,7 +458,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_write_partial_fromBeforeEnd_byteArray() { + public void testNonEmpty_write_partial_fromBeforeEnd_byteArray() throws IOException { fillContent("22222222"); assertEquals(3, store.write(6, bytes("111"), 0, 3)); assertContentEquals("222222111", store); @@ -467,7 +467,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_write_partial_fromEnd_byteArray() { + public void testNonEmpty_write_partial_fromEnd_byteArray() throws IOException { fillContent("222222"); assertEquals(3, store.write(6, bytes("111"), 0, 3)); assertContentEquals("222222111", store); @@ -476,7 +476,7 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_write_partial_fromPastEnd_byteArray() { + public void testNonEmpty_write_partial_fromPastEnd_byteArray() throws IOException { fillContent("222222"); assertEquals(3, store.write(8, bytes("111"), 0, 3)); assertContentEquals("22222200111", store); @@ -485,84 +485,84 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_write_partial_fromStart_singleBuffer() { + public void testNonEmpty_write_partial_fromStart_singleBuffer() throws IOException { fillContent("222222"); assertEquals(3, store.write(0, buffer("111"))); assertContentEquals("111222", store); } @Test - public void testNonEmpty_write_partial_fromMiddle_singleBuffer() { + public void testNonEmpty_write_partial_fromMiddle_singleBuffer() throws IOException { fillContent("22222222"); assertEquals(3, store.write(3, buffer("111"))); assertContentEquals("22211122", store); } @Test - public void testNonEmpty_write_partial_fromBeforeEnd_singleBuffer() { + public void testNonEmpty_write_partial_fromBeforeEnd_singleBuffer() throws IOException { fillContent("22222222"); assertEquals(3, store.write(6, buffer("111"))); assertContentEquals("222222111", store); } @Test - public void testNonEmpty_write_partial_fromEnd_singleBuffer() { + public void testNonEmpty_write_partial_fromEnd_singleBuffer() throws IOException { fillContent("222222"); assertEquals(3, store.write(6, buffer("111"))); assertContentEquals("222222111", store); } @Test - public void testNonEmpty_write_partial_fromPastEnd_singleBuffer() { + public void testNonEmpty_write_partial_fromPastEnd_singleBuffer() throws IOException { fillContent("222222"); assertEquals(3, store.write(8, buffer("111"))); assertContentEquals("22222200111", store); } @Test - public void testNonEmpty_write_partial_fromStart_multipleBuffers() { + public void testNonEmpty_write_partial_fromStart_multipleBuffers() throws IOException { fillContent("222222"); assertEquals(4, store.write(0, buffers("11", "33"))); assertContentEquals("113322", store); } @Test - public void testNonEmpty_write_partial_fromMiddle_multipleBuffers() { + public void testNonEmpty_write_partial_fromMiddle_multipleBuffers() throws IOException { fillContent("22222222"); assertEquals(4, store.write(2, buffers("11", "33"))); assertContentEquals("22113322", store); } @Test - public void testNonEmpty_write_partial_fromBeforeEnd_multipleBuffers() { + public void testNonEmpty_write_partial_fromBeforeEnd_multipleBuffers() throws IOException { fillContent("22222222"); assertEquals(6, store.write(6, buffers("111", "333"))); assertContentEquals("222222111333", store); } @Test - public void testNonEmpty_write_partial_fromEnd_multipleBuffers() { + public void testNonEmpty_write_partial_fromEnd_multipleBuffers() throws IOException { fillContent("222222"); assertEquals(6, store.write(6, buffers("111", "333"))); assertContentEquals("222222111333", store); } @Test - public void testNonEmpty_write_partial_fromPastEnd_multipleBuffers() { + public void testNonEmpty_write_partial_fromPastEnd_multipleBuffers() throws IOException { fillContent("222222"); assertEquals(4, store.write(10, buffers("11", "33"))); assertContentEquals("22222200001133", store); } @Test - public void testNonEmpty_write_overwrite_sameLength() { + public void testNonEmpty_write_overwrite_sameLength() throws IOException { fillContent("2222"); assertEquals(4, store.write(0, buffer("1234"))); assertContentEquals("1234", store); } @Test - public void testNonEmpty_write_overwrite_greaterLength() { + public void testNonEmpty_write_overwrite_greaterLength() throws IOException { fillContent("2222"); assertEquals(8, store.write(0, buffer("12345678"))); assertContentEquals("12345678", store); @@ -682,14 +682,14 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_copy() { + public void testNonEmpty_copy() throws IOException { fillContent("123456"); ByteStore copy = store.copy(); assertContentEquals("123456", copy); } @Test - public void testNonEmpty_copy_multipleTimes() { + public void testNonEmpty_copy_multipleTimes() throws IOException { /* This test exposes a bug where the position of the new buffer in ByteBufferByteStore wasn't being reset to 0 after the old buffer's content was copied into it. If the buffer was then @@ -703,28 +703,28 @@ public abstract class AbstractByteStoreTest { } @Test - public void testNonEmpty_truncate_toZero() { + public void testNonEmpty_truncate_toZero() throws IOException { fillContent("123456"); store.truncate(0); assertContentEquals("", store); } @Test - public void testNonEmpty_truncate_partial() { + public void testNonEmpty_truncate_partial() throws IOException { fillContent("12345678"); store.truncate(5); assertContentEquals("12345", store); } @Test - public void testNonEmpty_truncate_sizeUp() { + public void testNonEmpty_truncate_sizeUp() throws IOException { fillContent("123456"); store.truncate(12); assertContentEquals("123456", store); } @Test - public void testDeletedStoreRemainsUsableWhileOpen() { + public void testDeletedStoreRemainsUsableWhileOpen() throws IOException { byte[] bytes = bytes("1234567890"); store.write(0, bytes, 0, bytes.length); diff --git a/jimfs/src/test/java/com/google/jimfs/internal/DirectoryTableTest.java b/jimfs/src/test/java/com/google/jimfs/internal/DirectoryTableTest.java index 4e70a2c..544a0c3 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/DirectoryTableTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/DirectoryTableTest.java @@ -16,6 +16,7 @@ package com.google.jimfs.internal; +import static com.google.jimfs.internal.InternalTestUtils.byteStore; import static com.google.jimfs.internal.Name.PARENT; import static com.google.jimfs.internal.Name.SELF; import static org.junit.Assert.fail; @@ -216,8 +217,8 @@ public class DirectoryTableTest { @Test public void testSnapshot() { - root.link(Name.simple("bar"), new File(2, new StubByteStore(10))); - root.link(Name.simple("abc"), new File(3, new StubByteStore(10))); + root.link(Name.simple("bar"), new File(2, byteStore(10))); + root.link(Name.simple("abc"), new File(3, byteStore(10))); // does not include . or .. and is sorted by the name ASSERT.that(root.snapshot()) @@ -226,8 +227,8 @@ public class DirectoryTableTest { @Test public void testSnapshot_sortsUsingStringAndNotCanonicalValueOfNames() { - table.link(caseInsensitive("FOO"), new File(2, new StubByteStore(10))); - table.link(caseInsensitive("bar"), new File(3, new StubByteStore(10))); + table.link(caseInsensitive("FOO"), new File(2, byteStore(10))); + table.link(caseInsensitive("bar"), new File(3, byteStore(10))); ImmutableSortedSet<Name> snapshot = table.snapshot(); Iterable<String> strings = Iterables.transform(snapshot, Functions.toStringFunction()); diff --git a/jimfs/src/test/java/com/google/jimfs/internal/FileFactoryTest.java b/jimfs/src/test/java/com/google/jimfs/internal/FileFactoryTest.java index 3f90dc5..bc6a116 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/FileFactoryTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/FileFactoryTest.java @@ -24,8 +24,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.nio.ByteBuffer; - /** * Tests for {@link FileFactory}. * @@ -38,55 +36,7 @@ public class FileFactoryTest { @Before public void setUp() { - factory = new FileFactory(new MemoryDisk(2) { - @Override - protected int allocateMoreBlocks(int count) { - return 0; - } - - @Override - public int zero(int block, int offset, int len) { - return 0; - } - - @Override - public void copy(int block, int copy) { - } - - @Override - public void put(int block, int offset, byte b) { - } - - @Override - public int put(int block, int offset, byte[] b, int off, int len) { - return 0; - } - - @Override - public int put(int block, int offset, ByteBuffer buf) { - return 0; - } - - @Override - public byte get(int block, int offset) { - return 0; - } - - @Override - public int get(int block, int offset, byte[] b, int off, int len) { - return 0; - } - - @Override - public int get(int block, int offset, ByteBuffer buf, int len) { - return 0; - } - - @Override - public ByteBuffer asByteBuffer(int block, int offset, int len) { - return null; - } - }); + factory = new FileFactory(new HeapDisk(2, 2, 0)); } @Test diff --git a/jimfs/src/test/java/com/google/jimfs/internal/FileTest.java b/jimfs/src/test/java/com/google/jimfs/internal/FileTest.java index 3090d3e..80d966d 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/FileTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/FileTest.java @@ -17,6 +17,7 @@ package com.google.jimfs.internal; import static com.google.jimfs.internal.FileFactoryTest.fakePath; +import static com.google.jimfs.internal.InternalTestUtils.byteStore; import static org.truth0.Truth.ASSERT; import org.junit.Test; @@ -52,11 +53,11 @@ public class FileTest { @Test public void testRegularFile() { - File file = new File(0, new StubByteStore(10)); + File file = new File(0, byteStore(10)); ASSERT.that(file.isDirectory()).isFalse(); ASSERT.that(file.isRegularFile()).isTrue(); ASSERT.that(file.isSymbolicLink()).isFalse(); - ASSERT.that(file.content()).isA(StubByteStore.class); + ASSERT.that(file.content()).isA(ByteStore.class); } @Test diff --git a/jimfs/src/test/java/com/google/jimfs/internal/FileTreeTest.java b/jimfs/src/test/java/com/google/jimfs/internal/FileTreeTest.java index 8368d76..c12b061 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/FileTreeTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/FileTreeTest.java @@ -17,6 +17,7 @@ package com.google.jimfs.internal; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.jimfs.internal.InternalTestUtils.byteStore; import static com.google.jimfs.internal.PathServiceTest.fakePathService; import static java.nio.file.LinkOption.NOFOLLOW_LINKS; import static org.junit.Assert.fail; @@ -459,7 +460,7 @@ public class FileTreeTest { private File createFile(String parent, String name) { File dir = files.get(parent); - File newFile = new File(new Random().nextInt(), new StubByteStore(0)); + File newFile = new File(new Random().nextInt(), byteStore(0)); dir.asDirectory().link(Name.simple(name), newFile); diff --git a/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskReuseTest.java b/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskReuseTest.java index 25d0c31..b3c2879 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskReuseTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskReuseTest.java @@ -23,8 +23,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.IOException; + /** - * Tests for {@link HeapMemoryDisk} that reuse a disk for each store created. Stores are not deleted + * Tests for {@link HeapDisk} that reuse a disk for each store created. Stores are not deleted * after most tests, meaning new blocks must be allocated for each store created. * * @author Colin Decker @@ -32,7 +34,7 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class HeapDiskReuseTest extends HeapDiskTest { - private final MemoryDisk disk = new HeapMemoryDisk(8); + private final HeapDisk disk = new HeapDisk(8, 1000, 1000); @Override protected ByteStore createByteStore() { @@ -40,25 +42,25 @@ public class HeapDiskReuseTest extends HeapDiskTest { } @Test - public void testDeletedStore_contentsReturnedToDisk() { + public void testDeletedStore_contentsReturnedToDisk() throws IOException { byte[] bytes = new byte[1000]; store.opened(); store.write(0, bytes, 0, bytes.length); - int freeBlocksAfterWrite = disk.free.size(); + int cachedBlocksAfterWrite = disk.blockCache.size(); assertContentEquals(bytes, store); store.deleted(); - assertEquals(freeBlocksAfterWrite, disk.free.size()); + assertEquals(cachedBlocksAfterWrite, disk.blockCache.size()); assertContentEquals(bytes, store); store.closed(); assertContentEquals(new byte[0], store); - int freeBlocksAfterDelete = disk.free.size(); - assertTrue(freeBlocksAfterDelete > freeBlocksAfterWrite); + int cachedBlocksAfterDelete = disk.blockCache.size(); + assertTrue(cachedBlocksAfterDelete > cachedBlocksAfterWrite); } } diff --git a/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskReuseWithDeleteTest.java b/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskReuseWithDeleteTest.java index edb24fd..03a2318 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskReuseWithDeleteTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskReuseWithDeleteTest.java @@ -21,8 +21,8 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** - * Tests for {@link HeapMemoryDisk} that reuse a disk for each store created. Stores are deleted - * after each test, meaning blocks will be reused. + * Tests for {@link HeapDisk} that reuse a disk for each store created. Stores are deleted after + * each test, meaning blocks will be reused. * * @author Colin Decker */ diff --git a/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskTest.java b/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskTest.java index c9fed1f..2dbf11e 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/HeapDiskTest.java @@ -20,8 +20,8 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** - * Tests for {@link HeapMemoryDisk} that create a new disk for each byte store created (testing - * fresh state). + * Tests for {@link HeapDisk} that create a new disk for each byte store created (testing fresh + * state). * * @author Colin Decker */ @@ -30,6 +30,6 @@ public class HeapDiskTest extends AbstractByteStoreTest { @Override protected ByteStore createByteStore() { - return new HeapMemoryDisk(4).createByteStore(); + return new HeapDisk(4, 100, 10).createByteStore(); } } diff --git a/jimfs/src/test/java/com/google/jimfs/internal/DirectDiskTest.java b/jimfs/src/test/java/com/google/jimfs/internal/InternalTestUtils.java index 3a307a3..71d99d4 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/DirectDiskTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/InternalTestUtils.java @@ -16,19 +16,23 @@ package com.google.jimfs.internal; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import java.io.IOException; /** * @author Colin Decker */ -@RunWith(JUnit4.class) -public class DirectDiskTest extends AbstractByteStoreTest { +class InternalTestUtils { - private final MemoryDisk disk = new DirectMemoryDisk(); + private InternalTestUtils() {} - @Override - protected ByteStore createByteStore() { - return disk.createByteStore(); + + static ByteStore byteStore(int size) { + ByteStore store = new ByteStore(new HeapDisk()); + try { + store.write(0, new byte[size], 0, size); + return store; + } catch (IOException e) { + throw new AssertionError(e); + } } } diff --git a/jimfs/src/test/java/com/google/jimfs/internal/JimfsAsynchronousFileChannelTest.java b/jimfs/src/test/java/com/google/jimfs/internal/JimfsAsynchronousFileChannelTest.java index 0e9c841..53f6d73 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/JimfsAsynchronousFileChannelTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/JimfsAsynchronousFileChannelTest.java @@ -16,6 +16,7 @@ package com.google.jimfs.internal; +import static com.google.jimfs.internal.InternalTestUtils.byteStore; import static com.google.jimfs.testing.TestUtils.buffer; import static java.nio.file.StandardOpenOption.READ; import static java.nio.file.StandardOpenOption.WRITE; @@ -66,17 +67,13 @@ public class JimfsAsynchronousFileChannelTest { Options.getOptionsForChannel(ImmutableSet.copyOf(options))), executor); } - private static StubByteStore store(int size) throws IOException { - return new StubByteStore(size); - } - /** * Just tests the main read/write methods... the methods all delegate to the non-async channel * anyway. */ @Test public void testAsyncChannel() throws Throwable { - StubByteStore store = store(15); + ByteStore store = byteStore(15); ExecutorService executor = Executors.newSingleThreadExecutor(); JimfsAsynchronousFileChannel channel = channel(store, executor, READ, WRITE); @@ -86,9 +83,8 @@ public class JimfsAsynchronousFileChannelTest { assertSame(channel, channel.truncate(5)); assertEquals(5, channel.size()); - store.setSize(10); + store.write(5, new byte[5], 0, 5); checkAsyncRead(channel); - checkAsyncReadFailure(executor); checkAsyncWrite(channel); checkAsyncLock(channel); @@ -101,7 +97,7 @@ public class JimfsAsynchronousFileChannelTest { @Test public void testClosedChannel() throws IOException, InterruptedException { - StubByteStore store = store(15); + ByteStore store = byteStore(15); ExecutorService executor = Executors.newSingleThreadExecutor(); try { @@ -119,7 +115,7 @@ public class JimfsAsynchronousFileChannelTest { @Test public void testAsyncClose_write() throws IOException, InterruptedException { - StubByteStore store = store(15); + ByteStore store = byteStore(15); ExecutorService executor = Executors.newFixedThreadPool(4); try { @@ -166,7 +162,7 @@ public class JimfsAsynchronousFileChannelTest { @Test public void testAsyncClose_read() throws IOException, InterruptedException { - StubByteStore store = store(15); + ByteStore store = byteStore(15); ExecutorService executor = Executors.newFixedThreadPool(2); try { @@ -242,46 +238,6 @@ public class JimfsAsynchronousFileChannelTest { } } - private static void checkAsyncReadFailure(ExecutorService executor) throws Throwable { - StubByteStore store = store(10); - store.setThrowException(true); - AsynchronousFileChannel channel = channel(store, executor, READ); - - ByteBuffer buf = buffer("1234567890"); - try { - channel.read(buf, 0).get(); - fail(); - } catch (ExecutionException expected) { - assertTrue(expected.getCause() instanceof RuntimeException); - assertEquals("error", expected.getCause().getMessage()); - } - - buf.flip(); - final AtomicReference<Throwable> exceptionHolder = new AtomicReference<>(); - final CountDownLatch completionLatch = new CountDownLatch(1); - channel.read(buf, 0, null, new CompletionHandler<Integer, Object>() { - @Override - public void completed(Integer result, Object attachment) { - completionLatch.countDown(); - } - - @Override - public void failed(Throwable exc, Object attachment) { - exceptionHolder.set(exc); - completionLatch.countDown(); - } - }); - - completionLatch.await(); - Throwable exception = exceptionHolder.get(); - if (exception == null) { - fail(); - } else { - assertTrue(exception instanceof RuntimeException); - assertEquals("error", exception.getMessage()); - } - } - private static void checkAsyncWrite(AsynchronousFileChannel asyncChannel) throws Throwable { ByteBuffer buf = buffer("1234567890"); assertEquals(10, (int) asyncChannel.write(buf, 0).get()); diff --git a/jimfs/src/test/java/com/google/jimfs/internal/JimfsFileChannelTest.java b/jimfs/src/test/java/com/google/jimfs/internal/JimfsFileChannelTest.java index 0529ba8..3cbfee4 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/JimfsFileChannelTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/JimfsFileChannelTest.java @@ -16,6 +16,7 @@ package com.google.jimfs.internal; +import static com.google.jimfs.internal.InternalTestUtils.byteStore; import static com.google.jimfs.testing.TestUtils.assertNotEquals; import static com.google.jimfs.testing.TestUtils.buffer; import static com.google.jimfs.testing.TestUtils.bytes; @@ -76,13 +77,9 @@ public class JimfsFileChannelTest { Options.getOptionsForChannel(ImmutableSet.copyOf(options))); } - private static StubByteStore store(int size) throws IOException { - return new StubByteStore(size); - } - @Test public void testPosition() throws IOException { - FileChannel channel = channel(store(10), READ); + FileChannel channel = channel(byteStore(10), READ); assertEquals(0, channel.position()); assertSame(channel, channel.position(100)); assertEquals(100, channel.position()); @@ -90,18 +87,18 @@ public class JimfsFileChannelTest { @Test public void testSize() throws IOException { - StubByteStore store = store(10); + ByteStore store = byteStore(10); FileChannel channel = channel(store, READ); assertEquals(10, channel.size()); - store.setSize(100); + store.write(10, new byte[90], 0, 90); assertEquals(100, channel.size()); } @Test public void testRead() throws IOException { - StubByteStore store = store(20); + ByteStore store = byteStore(20); FileChannel channel = channel(store, READ); assertEquals(0, channel.position()); @@ -116,7 +113,7 @@ public class JimfsFileChannelTest { buf.flip(); buf2.flip(); - store.setSize(30); + store.write(20, new byte[10], 0, 10); assertEquals(10, channel.read(new ByteBuffer[]{buf, buf2}, 0, 2)); assertEquals(30, channel.position()); @@ -131,7 +128,7 @@ public class JimfsFileChannelTest { @Test public void testWrite() throws IOException { - ByteStore store = store(0); + ByteStore store = byteStore(0); FileChannel channel = channel(store, WRITE); assertEquals(0, channel.position()); @@ -156,7 +153,7 @@ public class JimfsFileChannelTest { @Test public void testAppend() throws IOException { - ByteStore store = store(0); + ByteStore store = byteStore(0); FileChannel channel = channel(store, WRITE, APPEND); assertEquals(0, channel.position()); @@ -190,7 +187,7 @@ public class JimfsFileChannelTest { @Test public void testTransferTo() throws IOException { - ByteStore store = store(10); + ByteStore store = byteStore(10); FileChannel channel = channel(store, READ); ByteBufferChannel writeChannel = new ByteBufferChannel(buffer("1234567890")); @@ -200,7 +197,7 @@ public class JimfsFileChannelTest { @Test public void testTransferFrom() throws IOException { - ByteStore store = store(0); + ByteStore store = byteStore(0); FileChannel channel = channel(store, WRITE); ByteBufferChannel readChannel = new ByteBufferChannel(buffer("1234567890")); @@ -210,7 +207,7 @@ public class JimfsFileChannelTest { @Test public void testTruncate() throws IOException { - ByteStore store = store(10); + ByteStore store = byteStore(10); FileChannel channel = channel(store, WRITE); channel.truncate(10); // no resize, >= size @@ -229,7 +226,7 @@ public class JimfsFileChannelTest { @Test public void testFileTimeUpdates() throws IOException { - File file = new File(-1, new StubByteStore(10)); + File file = new File(-1, byteStore(10)); FileChannel channel = new JimfsFileChannel(file, ImmutableSet.<OpenOption>of(READ, WRITE)); // accessed @@ -303,7 +300,7 @@ public class JimfsFileChannelTest { @Test public void testClose() throws IOException { - FileChannel channel = channel(store(0), READ, WRITE); + FileChannel channel = channel(byteStore(0), READ, WRITE); ExecutorService executor = Executors.newSingleThreadExecutor(); assertTrue(channel.isOpen()); channel.close(); @@ -422,7 +419,7 @@ public class JimfsFileChannelTest { @Test public void testWritesInReadOnlyMode() throws IOException { - FileChannel channel = channel(store(0), READ); + FileChannel channel = channel(byteStore(0), READ); try { channel.write(buffer("111")); @@ -468,7 +465,7 @@ public class JimfsFileChannelTest { @Test public void testReadsInWriteOnlyMode() throws IOException { - FileChannel channel = channel(store(0), WRITE); + FileChannel channel = channel(byteStore(0), WRITE); try { channel.read(buffer("111")); @@ -508,7 +505,7 @@ public class JimfsFileChannelTest { @Test public void testPositionNegative() throws IOException { - FileChannel channel = channel(store(0), READ, WRITE); + FileChannel channel = channel(byteStore(0), READ, WRITE); try { channel.position(-1); @@ -519,7 +516,7 @@ public class JimfsFileChannelTest { @Test public void testTruncateNegative() throws IOException { - FileChannel channel = channel(store(0), READ, WRITE); + FileChannel channel = channel(byteStore(0), READ, WRITE); try { channel.truncate(-1); @@ -530,7 +527,7 @@ public class JimfsFileChannelTest { @Test public void testWriteNegative() throws IOException { - FileChannel channel = channel(store(0), READ, WRITE); + FileChannel channel = channel(byteStore(0), READ, WRITE); try { channel.write(buffer("111"), -1); @@ -554,7 +551,7 @@ public class JimfsFileChannelTest { @Test public void testReadNegative() throws IOException { - FileChannel channel = channel(store(0), READ, WRITE); + FileChannel channel = channel(byteStore(0), READ, WRITE); try { channel.read(buffer("111"), -1); @@ -578,7 +575,7 @@ public class JimfsFileChannelTest { @Test public void testTransferToNegative() throws IOException { - FileChannel channel = channel(store(0), READ, WRITE); + FileChannel channel = channel(byteStore(0), READ, WRITE); try { channel.transferTo(-1, 0, new ByteBufferChannel(10)); @@ -595,7 +592,7 @@ public class JimfsFileChannelTest { @Test public void testTransferFromNegative() throws IOException { - FileChannel channel = channel(store(0), READ, WRITE); + FileChannel channel = channel(byteStore(0), READ, WRITE); try { channel.transferFrom(new ByteBufferChannel(10), -1, 0); @@ -612,7 +609,7 @@ public class JimfsFileChannelTest { @Test public void testLockNegative() throws IOException { - FileChannel channel = channel(store(0), READ, WRITE); + FileChannel channel = channel(byteStore(0), READ, WRITE); try { channel.lock(-1, 10, true); @@ -641,7 +638,7 @@ public class JimfsFileChannelTest { @Test public void testNullPointerExceptions() throws IOException { - FileChannel channel = channel(store(100), READ, WRITE); + FileChannel channel = channel(byteStore(100), READ, WRITE); NullPointerTester tester = new NullPointerTester(); tester.testAllPublicInstanceMethods(channel); @@ -649,7 +646,7 @@ public class JimfsFileChannelTest { @Test public void testLock() throws IOException { - FileChannel channel = channel(store(10), READ, WRITE); + FileChannel channel = channel(byteStore(10), READ, WRITE); assertNotNull(channel.lock()); assertNotNull(channel.lock(0, 10, false)); @@ -667,7 +664,7 @@ public class JimfsFileChannelTest { @Test public void testAsynchronousClose() throws IOException, InterruptedException { - ByteStore store = store(10); + ByteStore store = byteStore(10); final FileChannel channel = channel(store, READ, WRITE); store.writeLock().lock(); // ensure all operations on the channel will block @@ -693,7 +690,7 @@ public class JimfsFileChannelTest { @Test public void testCloseByInterrupt() throws IOException, InterruptedException { - ByteStore store = store(10); + ByteStore store = byteStore(10); final FileChannel channel = channel(store, READ, WRITE); store.writeLock().lock(); // ensure all operations on the channel will block diff --git a/jimfs/src/test/java/com/google/jimfs/internal/JimfsInputStreamTest.java b/jimfs/src/test/java/com/google/jimfs/internal/JimfsInputStreamTest.java index 97ac6cf..dec458b 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/JimfsInputStreamTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/JimfsInputStreamTest.java @@ -16,6 +16,7 @@ package com.google.jimfs.internal; +import static com.google.jimfs.internal.InternalTestUtils.byteStore; import static com.google.jimfs.testing.TestUtils.bytes; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.fail; @@ -165,7 +166,7 @@ public class JimfsInputStreamTest { } @Test - public void testMark_unsupported() { + public void testMark_unsupported() throws IOException { JimfsInputStream in = newInputStream(1, 2, 3); ASSERT.that(in.markSupported()).isFalse(); @@ -218,13 +219,13 @@ public class JimfsInputStreamTest { in.close(); // does nothing } - private static JimfsInputStream newInputStream(int... bytes) { + private static JimfsInputStream newInputStream(int... bytes) throws IOException { byte[] b = new byte[bytes.length]; for (int i = 0; i < bytes.length; i++) { b[i] = (byte) bytes[i]; } - ByteStore store = new StubByteStore(0); + ByteStore store = byteStore(0); store.write(0, b, 0, b.length); return new JimfsInputStream(new File(1, store)); } diff --git a/jimfs/src/test/java/com/google/jimfs/internal/JimfsOutputStreamTest.java b/jimfs/src/test/java/com/google/jimfs/internal/JimfsOutputStreamTest.java index 3d495c3..9d013ed 100644 --- a/jimfs/src/test/java/com/google/jimfs/internal/JimfsOutputStreamTest.java +++ b/jimfs/src/test/java/com/google/jimfs/internal/JimfsOutputStreamTest.java @@ -16,6 +16,7 @@ package com.google.jimfs.internal; +import static com.google.jimfs.internal.InternalTestUtils.byteStore; import static com.google.jimfs.testing.TestUtils.bytes; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.fail; @@ -165,11 +166,11 @@ public class JimfsOutputStreamTest { } private static JimfsOutputStream newOutputStream(boolean append) { - File file = new File(1, new StubByteStore(0)); + File file = new File(1, byteStore(0)); return new JimfsOutputStream(file, append); } - private static void addBytesToStore(JimfsOutputStream out, int... bytes) { + private static void addBytesToStore(JimfsOutputStream out, int... bytes) throws IOException { ByteStore store = out.file.asBytes(); long pos = store.currentSize(); for (int b : bytes) { diff --git a/jimfs/src/test/java/com/google/jimfs/internal/StubByteStore.java b/jimfs/src/test/java/com/google/jimfs/internal/StubByteStore.java deleted file mode 100644 index e7f7613..0000000 --- a/jimfs/src/test/java/com/google/jimfs/internal/StubByteStore.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2013 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.jimfs.internal; - -import com.google.common.primitives.UnsignedBytes; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; - -/** - * Fake byte store implementation. - * - * @author Colin Decker - */ -public class StubByteStore extends ByteStore { - - private final Bytes bytes = new Bytes(); - private long size; - private boolean throwException; - - StubByteStore(long initialSize) { - setSize(initialSize); - } - - @Override - public long currentSize() { - return size; - } - - @Override - public ByteStore createCopy() { - return new StubByteStore(size); - } - - @Override - public void deleteContents() { - } - - public void setSize(long size) { - this.size = size; - } - - public void setThrowException(boolean throwException) { - this.throwException = throwException; - } - - @Override - public boolean truncate(long size) { - checkThrowException(); - if (size < this.size) { - setSize(size); - return true; - } - return false; - } - - @Override - public int write(long pos, byte b) { - return write(pos, new byte[] {b}, 0, 1); - } - - @Override - public int write(long pos, byte[] b, int off, int len) { - return write(pos, ByteBuffer.wrap(b, off, len)); - } - - @Override - public int write(long pos, ByteBuffer buf) { - checkThrowException(); - int written = buf.remaining(); - bytes.position((int) pos); - bytes.write(buf.array(), buf.position(), buf.remaining()); - setSize(Math.max(size, pos + written)); - buf.position(buf.position() + written); - return written; - } - - @Override - public long transferFrom(ReadableByteChannel src, long position, long count) - throws IOException { - checkThrowException(); - ByteBuffer buffer = ByteBuffer.allocate((int) count); - src.read(buffer); - buffer.flip(); - return write(position, buffer); - } - - @Override - public int read(long pos) { - byte[] b = new byte[1]; - if (read(pos, b, 0, 1) != -1) { - return UnsignedBytes.toInt(b[0]); - } - return -1; - } - - @Override - public int read(long pos, byte[] b, int off, int len) { - return read(pos, ByteBuffer.wrap(b, off, len)); - } - - @Override - public int read(long pos, ByteBuffer buf) { - checkThrowException(); - int len = (int) Math.min(buf.remaining(), size - pos); - if (len <= 0) { - return -1; - } - byte[] b = bytes.getBytes(); - buf.put(b, (int) pos, len); - return len; - } - - @Override - public long transferTo(long position, long count, WritableByteChannel target) - throws IOException { - int len = (int) Math.min(count, size - position); - if (len > 0) { - ByteBuffer buf = ByteBuffer.allocate(len); - read(position, buf); - buf.flip(); - while (buf.hasRemaining()) { - target.write(buf); - } - } - return Math.max(len, 0); - } - - private void checkThrowException() { - if (throwException) { - throw new RuntimeException("error"); - } - } - - private static class Bytes extends ByteArrayOutputStream { - - void position(int pos) { - count = pos; - } - - byte[] getBytes() { - return buf; - } - } -}
\ No newline at end of file |