/** * 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()); } }