com.google.common.jimfs.RegularFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jimfs Show documentation
Show all versions of jimfs Show documentation
Jimfs is an in-memory implementation of Java 7's java.nio.file abstract file system API.
/*
* 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.common.jimfs;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.jimfs.Util.clear;
import static com.google.common.jimfs.Util.nextPowerOf2;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.primitives.UnsignedBytes;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* A mutable, resizable store for bytes. Bytes are stored in fixed-sized byte arrays (blocks)
* allocated by a {@link HeapDisk}.
*
* @author Colin Decker
*/
final class RegularFile extends File {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final HeapDisk disk;
/** Block list for the file. */
private byte[][] blocks;
/** Block count for the the file, which also acts as the head of the block list. */
private int blockCount;
private long size;
/**
* Creates a new regular file with the given ID and using the given disk.
*/
public static RegularFile create(int id, HeapDisk disk) {
return new RegularFile(id, disk, new byte[32][], 0, 0);
}
RegularFile(int id, HeapDisk disk, byte[][] blocks, int blockCount, long size) {
super(id);
this.disk = checkNotNull(disk);
this.blocks = checkNotNull(blocks);
this.blockCount = blockCount;
checkArgument(size >= 0);
this.size = size;
}
private int openCount = 0;
private boolean deleted = false;
/**
* Returns the read lock for this file.
*/
public Lock readLock() {
return lock.readLock();
}
/**
* Returns the write lock for this file.
*/
public Lock writeLock() {
return lock.writeLock();
}
// lower-level methods dealing with the blocks array
private void expandIfNecessary(int minBlockCount) {
if (minBlockCount > blocks.length) {
this.blocks = Arrays.copyOf(blocks, nextPowerOf2(minBlockCount));
}
}
/**
* Returns the number of blocks this file contains.
*/
int blockCount() {
return blockCount;
}
/**
* Copies the last {@code count} blocks from this file to the end of the given target file.
*/
void copyBlocksTo(RegularFile target, int count) {
int start = blockCount - count;
int targetEnd = target.blockCount + count;
target.expandIfNecessary(targetEnd);
System.arraycopy(this.blocks, start, target.blocks, target.blockCount, count);
target.blockCount = targetEnd;
}
/**
* Transfers the last {@code count} blocks from this file to the end of the given target file.
*/
void transferBlocksTo(RegularFile target, int count) {
copyBlocksTo(target, count);
truncateBlocks(blockCount - count);
}
/**
* Truncates the blocks of this file to the given block count.
*/
void truncateBlocks(int count) {
clear(blocks, count, blockCount - count);
blockCount = count;
}
/**
* Adds the given block to the end of this file.
*/
void addBlock(byte[] block) {
expandIfNecessary(blockCount + 1);
blocks[blockCount++] = block;
}
/**
* Gets the block at the given index in this file.
*/
@VisibleForTesting
byte[] getBlock(int index) {
return blocks[index];
}
// end of lower-level methods dealing with the blocks array
/**
* Gets the current size of this file in bytes. Does not do locking, so should only be called
* when holding a lock.
*/
public long sizeWithoutLocking() {
return size;
}
// need to lock in these methods since they're defined by an interface
@Override
public long size() {
readLock().lock();
try {
return size;
} finally {
readLock().unlock();
}
}
@Override
RegularFile copyWithoutContent(int id) {
byte[][] copyBlocks = new byte[Math.max(blockCount * 2, 32)][];
return new RegularFile(id, disk, copyBlocks, 0, size);
}
@Override
void copyContentTo(File file) throws IOException {
RegularFile copy = (RegularFile) file;
disk.allocate(copy, blockCount);
for (int i = 0; i < blockCount; i++) {
byte[] block = blocks[i];
byte[] copyBlock = copy.blocks[i];
System.arraycopy(block, 0, copyBlock, 0, block.length);
}
}
@Override
ReadWriteLock contentLock() {
return lock;
}
// opened/closed/delete don't use the read/write lock... they only need to ensure that they are
// synchronized among themselves
@Override
public synchronized void opened() {
openCount++;
}
@Override
public synchronized void closed() {
if (--openCount == 0 && deleted) {
deleteContents();
}
}
/**
* Marks this file as deleted. If there are no streams or channels open to the file, its
* contents are deleted if necessary.
*/
@Override
public synchronized void deleted() {
if (links() == 0) {
deleted = true;
if (openCount == 0) {
deleteContents();
}
}
}
/**
* Deletes the contents of this file. Called when this file has been deleted and all open streams
* and channels to it have been closed.
*/
private void deleteContents() {
disk.free(this);
size = 0;
}
/**
* Truncates this file to the given {@code size}. If the given size is less than the current size
* of this file, the size of the file is reduced to the given size and any bytes beyond that
* size are lost. If the given size is greater than the current size of the file, this method
* does nothing. Returns {@code true} if this file was modified by the call (its size changed)
* and {@code false} otherwise.
*/
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 = blockCount - newBlockCount;
if (blocksToRemove > 0) {
disk.free(this, 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 = blockCount - 1;
int endBlockIndex = blockIndex(end - 1);
if (endBlockIndex > lastBlockIndex) {
int additionalBlocksNeeded = endBlockIndex - lastBlockIndex;
disk.allocate(this, additionalBlocksNeeded);
}
// zero bytes between current size and pos
if (pos > size) {
long remaining = pos - size;
int blockIndex = blockIndex(size);
byte[] block = blocks[blockIndex];
int off = offsetInBlock(size);
remaining -= zero(block, off, length(off, remaining));
while (remaining > 0) {
block = blocks[++blockIndex];
remaining -= zero(block, 0, length(remaining));
}
size = pos;
}
}
/**
* Writes the given byte to this file at position {@code pos}. {@code pos} may be greater than
* the current size of this file, in which case this file 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 file needs more blocks but the disk is full
*/
public int write(long pos, byte b) throws IOException {
prepareForWrite(pos, 1);
byte[] block = blocks[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 file
* starting at position {@code pos}. {@code pos} may be greater than the current size of this
* file, in which case this file 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 file needs more blocks but the disk is full
*/
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 = blocks[blockIndex];
int offInBlock = offsetInBlock(pos);
int written = put(block, offInBlock, b, off, length(offInBlock, remaining));
remaining -= written;
off += written;
while (remaining > 0) {
block = blocks[++blockIndex];
written = put(block, 0, b, off, length(remaining));
remaining -= written;
off += written;
}
long endPos = pos + len;
if (endPos > size) {
size = endPos;
}
return len;
}
/**
* Writes all available bytes from buffer {@code buf} to this file starting at position {@code
* pos}. {@code pos} may be greater than the current size of this file, in which case this file
* 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 file needs more blocks but the disk is full
*/
public int write(long pos, ByteBuffer buf) throws IOException {
int len = buf.remaining();
prepareForWrite(pos, len);
if (len == 0) {
return 0;
}
int blockIndex = blockIndex(pos);
byte[] block = blocks[blockIndex];
int off = offsetInBlock(pos);
put(block, off, buf);
while (buf.hasRemaining()) {
block = blocks[++blockIndex];
put(block, 0, buf);
}
long endPos = pos + len;
if (endPos > size) {
size = endPos;
}
return len;
}
/**
* Writes all available bytes from each buffer in {@code bufs}, in order, to this file starting
* at position {@code pos}. {@code pos} may be greater than the current size of this file, in
* which case this file 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 file needs more blocks but the disk is full
*/
public long write(long pos, Iterable bufs) throws IOException {
long start = pos;
for (ByteBuffer buf : bufs) {
pos += write(pos, buf);
}
return pos - start;
}
/**
* Transfers up to {@code count} bytes from the given channel to this file starting at position
* {@code pos}. Returns the number of bytes transferred. If {@code pos} is greater than the
* current size of this file, the file is truncated up to size {@code pos} before writing.
*
* @throws IOException if the file needs more blocks but the disk is full or if reading from src
* throws an exception
*/
public long transferFrom(ReadableByteChannel src, long pos, long count) throws IOException {
prepareForWrite(pos, 0); // don't assume the full count bytes will be written
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));
long currentPos = pos;
int read = 0;
while (buf.hasRemaining()) {
read = src.read(buf);
if (read == -1) {
break;
}
currentPos += read;
remaining -= read;
}
// update size before trying to get next block in case the disk is out of space
if (currentPos > size) {
size = currentPos;
}
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;
}
currentPos += read;
remaining -= read;
}
if (currentPos > size) {
size = currentPos;
}
}
}
if (currentPos > size) {
size = currentPos;
}
return currentPos - pos;
}
/**
* Reads the byte at position {@code pos} in this file as an unsigned integer in the range 0-255.
* If {@code pos} is greater than or equal to the size of this file, returns -1 instead.
*/
public int read(long pos) {
if (pos >= size) {
return -1;
}
byte[] block = blocks[blockIndex(pos)];
int off = offsetInBlock(pos);
return UnsignedBytes.toInt(block[off]);
}
/**
* Reads up to {@code len} bytes starting at position {@code pos} in this file 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 file.
*/
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[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[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 file 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 file.
*/
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[blockIndex];
int off = offsetInBlock(pos);
remaining -= get(block, off, buf, length(off, remaining));
while (remaining > 0) {
int index = ++blockIndex;
block = blocks[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
* position {@code pos} in this file to the given buffers, in order. Returns the number of bytes
* read or -1 if {@code pos} is greater than or equal to the size of this file.
*/
public long read(long pos, Iterable bufs) {
if (pos >= size()) {
return -1;
}
long start = pos;
for (ByteBuffer buf : bufs) {
int read = read(pos, buf);
if (read == -1) {
break;
} else {
pos += read;
}
}
return pos - start;
}
/**
* Transfers up to {@code count} bytes to the given channel starting at position {@code pos} in
* this file. Returns the number of bytes transferred, possibly 0. Note that unlike all other
* read methods in this class, this method does not return -1 if {@code pos} is greater than or
* equal to the current size. This for consistency with {@link FileChannel#transferTo}, which
* this method is primarily intended as an implementation of.
*/
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[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[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 {
if (index >= blockCount) {
int additionalBlocksNeeded = index - blockCount + 1;
disk.allocate(this, additionalBlocksNeeded);
}
return blocks[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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy