blob: 669dafd55c18c8e247b0a0ae622620f7a88bc91a [file] [log] [blame]
/*
* 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.file;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
/**
* {@link ByteStore} implemented with a byte array that doubles in size when it needs to expand.
*
* @author Colin Decker
*/
public final class ArrayByteStore extends ByteStore {
private static final int MIN_ARRAY_SIZE = 128;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 10;
private int size = 0;
private byte[] bytes;
public ArrayByteStore() {
this(new byte[MIN_ARRAY_SIZE], 0);
}
private ArrayByteStore(byte[] bytes, int size) {
this.bytes = bytes;
this.size = size;
}
@Override
public int sizeInBytes() {
readLock().lock();
try {
return size;
} finally {
readLock().unlock();
}
}
@Override
public ByteStore copy() {
readLock().lock();
try {
return new ArrayByteStore(copyArray(size), size);
} finally {
readLock().unlock();
}
}
/**
* Returns a byte array sized to hold at least {@code minSize} bytes.
*/
private static byte[] createArray(int minSize) {
int newSize = nextPowerOf2(minSize);
// if the new size overflows or would be too large to create an array
if (newSize < 0 || newSize > MAX_ARRAY_SIZE) {
// if the minimum size overflowed or is too large, have to throw
if (minSize < 0 || minSize > MAX_ARRAY_SIZE) {
throw new OutOfMemoryError();
}
// otherwise, just use the maximum array size
newSize = MAX_ARRAY_SIZE;
}
// don't create an array smaller than the default initial array size
return new byte[Math.max(newSize, MIN_ARRAY_SIZE)];
}
/**
* Returns the next power of 2 >= n.
*/
private static int nextPowerOf2(int n) {
int highestOneBit = Integer.highestOneBit(n);
return highestOneBit == n ? n : highestOneBit << 1;
}
/**
* Returns a byte array containing the current content of this store.
*/
private byte[] copyArray(int minSize) {
byte[] copy = createArray(minSize);
System.arraycopy(bytes, 0, copy, 0, size);
return copy;
}
@Override
public boolean truncate(int size) {
checkNotNegative(size, "size");
writeLock().lock();
try {
if (size >= this.size) {
return false;
}
this.size = size;
return true;
} finally {
writeLock().unlock();
}
}
private void resizeArray(int minSize) {
bytes = copyArray(minSize);
}
private void resizeForWrite(int minSize) {
if (minSize > size) {
if (minSize > bytes.length) {
resizeArray(minSize);
}
this.size = minSize;
}
}
@Override
public int write(int pos, ByteBuffer buf) {
checkNotNegative(pos, "pos");
writeLock().lock();
try {
int len = buf.remaining();
resizeForWrite(pos + len);
buf.get(bytes, pos, len);
return len;
} finally {
writeLock().unlock();
}
}
@Override
public int transferFrom(ReadableByteChannel src, int pos, int count) throws IOException {
checkNotNegative(pos, "pos");
checkNotNegative(count, "count");
if (count == 0) {
return 0;
}
writeLock().lock();
try {
int originalSize = size;
resizeForWrite(pos + count);
try {
// transfer directly into array
ByteBuffer buffer = ByteBuffer.wrap(bytes, pos, count);
int read = 0;
while (read >= 0 && buffer.hasRemaining()) {
read = src.read(buffer);
}
int bytesTransferred = buffer.position() - pos;
// reset size to the correct size, since fewer than count bytes may have been transferred
size = Math.max(originalSize, pos + bytesTransferred);
return bytesTransferred;
} catch (Throwable e) {
// if there was an exception copying from src into the array, set the size back to the
// original size... the array may be corrupted in the area that was being copied to
truncate(originalSize);
throw e;
}
} finally {
writeLock().unlock();
}
}
@Override
public int read(int pos, ByteBuffer buf) {
checkNotNegative(pos, "pos");
readLock().lock();
try {
int bytesToRead = bytesToRead(pos, buf.remaining());
if (bytesToRead > 0) {
buf.put(bytes, pos, bytesToRead);
}
return bytesToRead;
} finally {
readLock().unlock();
}
}
@Override
public int transferTo(int pos, int count, WritableByteChannel dest) throws IOException {
checkNotNegative(pos, "pos");
checkNotNegative(count, "count");
if (count == 0) {
return 0;
}
readLock().lock();
try {
int bytesToRead = bytesToRead(pos, count);
if (bytesToRead > 0) {
ByteBuffer buffer = ByteBuffer.wrap(bytes, pos, bytesToRead);
while (buffer.hasRemaining()) {
dest.write(buffer);
}
}
return Math.max(bytesToRead, 0); // don't return -1 for this method
} finally {
readLock().unlock();
}
}
/**
* 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 int bytesToRead(int pos, int max) {
int available = size - pos;
if (available <= 0) {
return -1;
}
return Math.min(available, max);
}
}