/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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.asakusafw.runtime.io.util;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.EOFException;
import java.io.IOException;
import java.text.MessageFormat;
/**
* A data buffer with {@link DataInput} and {@link DataOutput} interfaces.
* @since 0.7.0
* @version 0.7.1
*/
public class DataBuffer implements DataInput, DataOutput {
private static final byte[] EMPTY = new byte[0];
private static final double DEFAULT_BUFFER_EXPANSION_FACTOR = 1.5;
private static final double MINIMUM_BUFFER_EXPANSION_FACTOR = 1.1;
private static final int MINIMUM_EXPANSION_SIZE = 256;
private byte[] buffer;
private final double expansionFactor;
private int readCursor;
private int writeCursor;
/**
* Creates a new instance with empty buffer.
*/
public DataBuffer() {
this(0);
}
/**
* Creates a new instance with empty buffer.
* @param initialCapacity the initial buffer capacity in bytes
*/
public DataBuffer(int initialCapacity) {
this(initialCapacity, DEFAULT_BUFFER_EXPANSION_FACTOR);
}
/**
* Creates a new instance with empty buffer.
* @param initialCapacity the initial buffer capacity in bytes
* @param expansionFactor the buffer expansion factor
* @since 0.7.1
*/
public DataBuffer(int initialCapacity, double expansionFactor) {
this.buffer = initialCapacity == 0 ? EMPTY : new byte[initialCapacity];
this.expansionFactor = Math.max(MINIMUM_BUFFER_EXPANSION_FACTOR, expansionFactor);
}
/**
* Resets data in this buffer.
* @param bytes the data
* @param offset the offset in bytes
* @param length the length in bytes
*/
public void reset(byte[] bytes, int offset, int length) {
this.buffer = bytes;
reset(offset, length);
}
/**
* Resets cursors in this buffer.
* @param offset the offset in bytes
* @param length the length in bytes
*/
public void reset(int offset, int length) {
if (offset < 0 || offset > buffer.length) {
throw new IllegalArgumentException();
}
if (length < 0 || offset + length > buffer.length) {
throw new IllegalArgumentException();
}
this.readCursor = offset;
this.writeCursor = offset + length;
}
/**
* Returns the data bytes.
* @return the data bytes
* @see #getReadPosition()
* @see #getReadRemaining()
*/
public byte[] getData() {
return buffer;
}
/**
* Returns the reading position in this buffer.
* @return the reading position in bytes
*/
public int getReadPosition() {
return readCursor;
}
/**
* Returns the reading limit position in this buffer.
* @return the reading limit position in bytes
*/
public int getReadLimit() {
return writeCursor;
}
/**
* Returns the next write position in this buffer.
* @return the next write position in bytes
*/
public int getWritePosition() {
return writeCursor;
}
/**
* Returns the data length in this buffer.
* @return the data length in bytes
*/
public int getReadRemaining() {
return writeCursor - readCursor;
}
/**
* Ensures capacity of this buffer.
* @param newSize the required capacity in bytes
*/
public void ensureCapacity(int newSize) {
byte[] newBuffer = new byte[newSize];
if (writeCursor != 0) {
System.arraycopy(buffer, 0, newBuffer, 0, writeCursor);
}
buffer = newBuffer;
}
/**
* Reads a byte value.
* @return an unsigned byte value, or {@code -1} to no remained values
* @throws IOException if failed to read a value
*/
public int read() throws IOException {
if (getReadRemaining() == 0) {
return -1;
}
int offset = prepareRead(1);
byte result = buffer[offset];
return result & 0xff;
}
@Override
public boolean readBoolean() throws IOException {
byte v = readByte();
return v != 0;
}
@Override
public byte readByte() throws IOException {
int offset = prepareRead(1);
byte result = buffer[offset];
return result;
}
@Override
public int readUnsignedByte() throws IOException {
byte v = readByte();
return v & 0xff;
}
@Override
public short readShort() throws IOException {
int offset = prepareRead(2);
byte[] b = buffer;
int result = 0
| (b[offset + 0] & 0xff) << 8
| (b[offset + 1] & 0xff);
return (short) result;
}
@Override
public int readUnsignedShort() throws IOException {
short v = readShort();
return v & 0xffff;
}
@Override
public char readChar() throws IOException {
return (char) readShort();
}
@Override
public int readInt() throws IOException {
int offset = prepareRead(4);
byte[] b = buffer;
int result = 0
| (b[offset + 0] & 0xff) << 24
| (b[offset + 1] & 0xff) << 16
| (b[offset + 2] & 0xff) << 8
| (b[offset + 3] & 0xff);
return result;
}
@Override
public long readLong() throws IOException {
int offset = prepareRead(8);
byte[] b = buffer;
long result = 0L
| (b[offset + 0] & 0xffL) << 56
| (b[offset + 1] & 0xffL) << 48
| (b[offset + 2] & 0xffL) << 40
| (b[offset + 3] & 0xffL) << 32
| (b[offset + 4] & 0xffL) << 24
| (b[offset + 5] & 0xffL) << 16
| (b[offset + 6] & 0xffL) << 8
| (b[offset + 7] & 0xffL);
return result;
}
@Override
public float readFloat() throws IOException {
int v = readInt();
return Float.intBitsToFloat(v);
}
@Override
public double readDouble() throws IOException {
long v = readLong();
return Double.longBitsToDouble(v);
}
@Override
public void readFully(byte[] b) throws IOException {
readFully(b, 0, b.length);
}
@Override
public void readFully(byte[] b, int off, int len) throws IOException {
if (len == 0) {
return;
}
int offset = prepareRead(len);
System.arraycopy(buffer, offset, b, off, len);
}
@Override
public int skipBytes(int n) throws IOException {
if (n <= 0) {
return 0;
}
int skip = Math.min(n, getReadRemaining());
assert skip >= 0;
readCursor += skip;
return skip;
}
@Override
public String readLine() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public String readUTF() throws IOException {
return DataIoUtils.readUTF(this);
}
private int prepareRead(int length) throws EOFException {
if (getReadRemaining() < length) {
throw new EOFException();
}
int offset = readCursor;
readCursor = offset + length;
return offset;
}
@Override
public void write(int b) throws IOException {
int offset = prepareWrite(1);
buffer[offset] = (byte) b;
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (len == 0) {
return;
}
int offset = prepareWrite(len);
System.arraycopy(b, off, buffer, offset, len);
}
@Override
public void writeBoolean(boolean v) throws IOException {
write(v ? 1 : 0);
}
@Override
public void writeByte(int v) throws IOException {
write(v);
}
@Override
public void writeShort(int v) throws IOException {
int offset = prepareWrite(2);
byte[] b = buffer;
b[offset + 0] = (byte) (v >> 8);
b[offset + 1] = (byte) v;
}
@Override
public void writeChar(int v) throws IOException {
writeShort(v);
}
@Override
public void writeInt(int v) throws IOException {
int offset = prepareWrite(4);
byte[] b = buffer;
b[offset + 0] = (byte) (v >> 24);
b[offset + 1] = (byte) (v >> 16);
b[offset + 2] = (byte) (v >> 8);
b[offset + 3] = (byte) v;
}
@Override
public void writeLong(long v) throws IOException {
int offset = prepareWrite(8);
byte[] b = buffer;
b[offset + 0] = (byte) (v >> 56);
b[offset + 1] = (byte) (v >> 48);
b[offset + 2] = (byte) (v >> 40);
b[offset + 3] = (byte) (v >> 32);
b[offset + 4] = (byte) (v >> 24);
b[offset + 5] = (byte) (v >> 16);
b[offset + 6] = (byte) (v >> 8);
b[offset + 7] = (byte) v;
}
@Override
public void writeFloat(float v) throws IOException {
writeInt(Float.floatToRawIntBits(v));
}
@Override
public void writeDouble(double v) throws IOException {
writeLong(Double.doubleToRawLongBits(v));
}
@Override
public void writeBytes(String s) throws IOException {
for (int i = 0, n = s.length(); i < n; i++) {
writeByte(s.charAt(i));
}
}
@Override
public void writeChars(String s) throws IOException {
for (int i = 0, n = s.length(); i < n; i++) {
writeChar(s.charAt(i));
}
}
@Override
public void writeUTF(String s) throws IOException {
DataIoUtils.writeUTF(this, s);
}
/**
* Writes data from {@link DataInput}.
* @param in the source input
* @param len data size in bytes
* @throws IOException if failed to read {@link DataInput}
*/
public void write(DataInput in, int len) throws IOException {
if (len == 0) {
return;
}
int offset = prepareWrite(len);
byte[] b = buffer;
in.readFully(b, offset, len);
}
private int prepareWrite(int length) {
int offset = writeCursor;
if (buffer.length < offset + length) {
int newSize = Math.max(
Math.max(offset + length, (int) (buffer.length * expansionFactor)) + 1,
MINIMUM_EXPANSION_SIZE);
ensureCapacity(newSize);
}
writeCursor += length;
return offset;
}
@Override
public String toString() {
return MessageFormat.format(
"{0}(read={1}, write={2})", //$NON-NLS-1$
getClass().getSimpleName(),
getReadPosition(),
getWritePosition());
}
}