com.tangosol.io.ByteArrayWriteBuffer Maven / Gradle / Ivy
Show all versions of coherence Show documentation
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.io;
import java.io.EOFException;
import java.io.IOException;
import java.io.UTFDataFormatException;
import com.tangosol.util.Binary;
import com.tangosol.util.ExternalizableHelper;
/**
* ByteArrayWriteBuffer is an implementation of WriteBuffer on a byte array.
* It is designed to support both fixed length buffers and resizable buffers.
*
* This implementation is not intended to be thread safe.
*
* @author cp 2005.03.24
*/
public class ByteArrayWriteBuffer
extends AbstractWriteBuffer
{
// ----- constructors ---------------------------------------------------
/**
* Default constructor; intended only for use by subclasses.
*
* Note that this default constructor leaves the buffer in an invalid
* state.
*/
protected ByteArrayWriteBuffer()
{
}
/**
* Construct a ByteArrayWriteBuffer on a byte array.
*
* @param ab a byte array
*
* @exception NullPointerException if ab is null
*/
public ByteArrayWriteBuffer(byte[] ab)
{
m_ab = ab;
m_cbMax = ab.length;
}
/**
* Construct an ByteArrayWriteBuffer with a certain initial capacity.
*
* @param cbCap initial capacity
*
* @exception IllegalArgumentException if cbCap is negative
*/
public ByteArrayWriteBuffer(int cbCap)
{
this(cbCap, Integer.MAX_VALUE);
}
/**
* Construct an ByteArrayWriteBuffer with a certain initial capacity and
* a certain maximum capacity.
*
* @param cbCap initial capacity
* @param cbMax maximum capacity
*
* @exception IllegalArgumentException if cbCap or cbMax
* is negative, or if cbCap is greater than
* cbMax
*/
public ByteArrayWriteBuffer(int cbCap, int cbMax)
{
if (cbCap < 0 || cbMax < 0 || cbCap > cbMax)
{
throw new IllegalArgumentException("cap=" + cbCap + "; max=" + cbMax);
}
m_ab = createBytes(cbCap);
m_cbMax = cbMax;
}
/**
* Create a new ByteArrayWriteBuffer based on a region of an already
* existing WriteBuffer.
*
* @param buffer the source buffer
* @param i the offset within the source buffer
* @param cb the number of bytes to copy
*/
public ByteArrayWriteBuffer(WriteBuffer buffer, int i, int cb)
{
m_cbMax = cb - i;
m_ab = createBytes(m_cbMax);
write(0, buffer.getUnsafeReadBuffer(), i, cb);
}
// ----- buffer write operations ----------------------------------------
/**
* {@inheritDoc}
*/
public final void write(int ofDest, byte b)
{
checkBounds(ofDest, 1);
m_ab[ofDest] = b;
updateLength(ofDest + 1);
}
/**
* {@inheritDoc}
*/
public final void write(int ofDest, byte[] abSrc, int ofSrc, int cbSrc)
{
checkBounds(ofDest, cbSrc);
// it's necessary to call this (even if cbSrc==0) in order to
// correctly validate the arguments
System.arraycopy(abSrc, ofSrc, m_ab, ofDest, cbSrc);
if (cbSrc > 0)
{
updateLength(ofDest + cbSrc);
}
}
/**
* {@inheritDoc}
*/
public final void write(int ofDest, ReadBuffer bufSrc, int ofSrc, int cbSrc)
{
checkBounds(ofDest, cbSrc);
// it's necessary to call this (even if cbSrc==0) in order to
// correctly validate the arguments
bufSrc.copyBytes(ofSrc, ofSrc + cbSrc, m_ab, ofDest);
updateLength(ofDest + cbSrc);
}
/**
* {@inheritDoc}
*/
public final void write(int ofDest, InputStreaming stream, int cbSrc)
throws IOException
{
// see if it is a known implementation that we can optimize for
if (stream instanceof ReadBuffer.BufferInput)
{
copyBufferInputPortion(ofDest, (ReadBuffer.BufferInput) stream, cbSrc);
return;
}
// read the stream straight into the underlying byte[]
checkBounds(ofDest, cbSrc);
int cbRead = 0;
try
{
while (cbRead < cbSrc)
{
int cbActual = stream.read(m_ab, ofDest + cbRead, cbSrc - cbRead);
if (cbActual < 0)
{
throw new EOFException("instructed to copy " + cbSrc
+ " bytes, but only " + cbRead + " were available");
}
else
{
cbRead += cbActual;
}
}
}
finally
{
if (cbRead > 0)
{
updateLength(ofDest + cbRead);
}
}
}
// ----- buffer maintenance ----------------------------------------------
/**
* {@inheritDoc}
*/
public final int length()
{
return m_cb;
}
/**
* Reconfigure the length of the buffer. The length must not be longer than
* the available capacity.
*
* @param cb the new length of the buffer
*/
public final void setLength(int cb)
{
assert cb <= m_cbMax;
updateLength(cb);
}
/**
* {@inheritDoc}
*/
public final void retain(int of, int cb)
{
if (of < 0 || cb < 0 || of + cb > m_cb)
{
throw new IndexOutOfBoundsException("of=" + of + ", cb="
+ cb + ", length()=" + m_cb);
}
if (of > 0 && cb > 0)
{
byte[] ab = m_ab;
System.arraycopy(ab, of, ab, 0, cb);
}
m_cb = cb;
m_bufUnsafe = null;
}
/**
* {@inheritDoc}
*/
public final int getCapacity()
{
return m_ab.length;
}
/**
* {@inheritDoc}
*/
public final int getMaximumCapacity()
{
return m_cbMax;
}
// ----- obtaining different "write views" to the buffer ----------------
/**
* {@inheritDoc}
*/
public final BufferOutput getBufferOutput(int of)
{
return new ByteArrayBufferOutput(of);
}
// ----- accessing the buffered data ------------------------------------
/**
* {@inheritDoc}
*/
public final ReadBuffer getUnsafeReadBuffer()
{
ByteArrayReadBuffer buf = m_bufUnsafe;
if (buf == null)
{
m_bufUnsafe = buf = new ByteArrayReadBuffer(m_ab, 0, m_cb,
false, isByteArrayPrivate(), false);
}
else
{
buf.updateLength(m_cb);
}
return buf;
}
/**
* {@inheritDoc}
*
* For efficiency purposes, it is possible to obtain the internal byte
* array that the ByteArrayWriteBuffer is using by calling {@link
* #getRawByteArray()}; if the internal byte array is private (i.e. if it
* cannot be exposed to the caller), then the result will be the same as
* would be returned by toByteArray().
*/
public final byte[] toByteArray()
{
int cb = m_cb;
if (cb == 0)
{
return NO_BYTES;
}
byte[] ab = new byte[cb];
System.arraycopy(m_ab, 0, ab, 0, cb);
return ab;
}
/**
* {@inheritDoc}
*/
public Binary toBinary()
{
int cb = m_cb;
if (cb == 0)
{
return NO_BINARY;
}
return new Binary(m_ab, 0, cb);
}
// ----- accessors ------------------------------------------------------
/**
* Determine if the underlying byte[] should be treated as private data.
*
* @return true iff the underlying data should not ever be exposed by
* this object
*/
public boolean isByteArrayPrivate()
{
return m_fPrivate;
}
/**
* Make sure that the underlying byte[] will be treated as private data.
*/
public final void makeByteArrayPrivate()
{
m_fPrivate = true;
m_bufUnsafe = null;
}
/**
* Obtain the byte array that this WriteBuffer uses. If the underlying
* byte array is private, then this method will always return a copy of
* the portion of the byte array that this WriteBuffer represents as if
* the called had called {@link #toByteArray()}.
*
* @return the byte array that this WriteBuffer uses
*/
public final byte[] getRawByteArray()
{
return isByteArrayPrivate() ? toByteArray() : m_ab;
}
// ----- internal -------------------------------------------------------
/**
* {@inheritDoc}
*/
protected final int copyStream(int ofDest, InputStreaming stream, int cbMax)
throws IOException
{
// see if it is a known implementation that we can optimize for
if (stream instanceof ReadBuffer.BufferInput)
{
return copyBufferInputRemainder(ofDest, (ReadBuffer.BufferInput) stream, cbMax);
}
int ofOrig = ofDest;
int cbRemain = cbMax;
while (true)
{
// while there is additional capacity in the buffer, read
byte[] ab = m_ab;
int cbCap = Math.min(ab.length, ofDest + cbRemain);
while (ofDest < cbCap)
{
int cbActual;
try
{
cbActual = stream.read(ab, ofDest, cbCap - ofDest);
}
catch (EOFException e)
{
cbActual = -1;
}
if (cbActual < 0)
{
updateLength(ofDest);
return ofDest - ofOrig;
}
else
{
ofDest += cbActual;
cbRemain -= cbActual;
}
}
if (cbRemain > 0)
{
// when out of room, grow
updateLength(ofDest);
grow(ofDest);
}
// once we reach max cap, just read one byte to prove overflow
if (ofDest >= m_ab.length || cbRemain == 0)
{
if (stream.read() < 0)
{
// filled the capacity perfectly; no more data to read
updateLength(ofDest);
return ofDest - ofOrig;
}
else
{
throw new IOException("Overflow: write buffer limited to "
+ cbMax + " bytes, but input stream is not exhausted");
}
}
}
}
/**
* Create a byte array of the specified size. The main reason to make this
* into a separate method is a fact the native OOME comes without any stack trace.
*
* @param cb the specified size
*
* @return a byte array
*/
protected static byte[] createBytes(int cb)
{
try
{
return new byte[cb];
}
catch (OutOfMemoryError e)
{
if (cb == Integer.MAX_VALUE)
{
throw new UnsupportedOperationException(
"buffer has reached its max capacity of 2GB");
}
throw new OutOfMemoryError(
"Failed to allocate a byte array of the requested size: " + cb);
}
}
/**
* Validate the ranges for the passed bounds and make sure that the
* underlying array is big enough to handle them.
*
* @param of the offset that data is about to be written to
* @param cb the length of the data that is about to be written
*/
protected void checkBounds(int of, int cb)
{
int cbTotal = of + cb;
if (of < 0 || cb < 0 || cbTotal > m_cbMax || cbTotal < 0)
{
boundsException(of, cb);
}
if (cbTotal > m_ab.length)
{
grow(cbTotal);
}
}
/**
* Raise an exception for the offset/length being out of bounds. This
* code was moved out of checkBounds in order to encourage more aggressive
* in-lining by the HotSpot JVM.
*
* @param of the current offset
* @param cb the current length
*
* @throws IndexOutOfBoundsException always
*/
private void boundsException(int of, int cb)
throws IndexOutOfBoundsException
{
if ((long) of + (long) cb > Integer.MAX_VALUE)
{
throw new UnsupportedOperationException(
"buffer has reached its max capacity of 2GB");
}
throw new IndexOutOfBoundsException("of=" + of + ", cb="
+ cb + ", max=" + m_cbMax);
}
/**
* Grow the underlying byte array to at least the specified size.
*
* @param cbCap the required or requested capacity
*/
protected final void grow(int cbCap)
{
// desired growth is 100% for "small" buffers and 50% for "huge"
// minimum growth is 1KB
byte[] abOld = m_ab;
int cbOld = abOld.length;
int cbAdd = Math.max(1024, cbOld > 0x100000 ? cbOld >>> 1 : cbOld);
int cbNew = (int) Math.min(m_cbMax,
Math.max(((long) cbCap) + 1024, ((long) cbOld) + cbAdd));
if (cbNew > cbOld)
{
// ensure that we don't allocate more than the configured maximum
ExternalizableHelper.validateBufferSize(cbNew);
byte[] abNew;
while (true)
{
try
{
abNew = createBytes(cbNew);
break;
}
catch (UnsupportedOperationException | OutOfMemoryError e)
{
if (cbCap == Integer.MAX_VALUE)
{
throw e;
}
// our pre-sizing was too aggressive; try to back down a bit
cbNew -= (cbNew - cbCap) / 2;
if (cbNew - 1 <= cbCap)
{
// create a new OOME to throw since the original one
// most likely doesn't have any stack trace info
throw e;
}
}
}
int cbData = m_cb;
if (cbData > 0)
{
System.arraycopy(abOld, 0, abNew, 0, cbData);
}
m_ab = abNew;
m_bufUnsafe = null;
}
}
/**
* Update the length if the passed length is greater than the current
* buffer length.
*
* @param cb the count of the last byte written (or the index of the
* next byte to write)
*/
protected final void updateLength(int cb)
{
if (cb > m_cb)
{
m_cb = cb;
}
}
// ----- inner class: ByteArrayBufferOutput ----------------------------------
/**
* ByteArrayBufferOutput is an implementation of BufferOutput optimized
* for writing to the buffer's underlying byte array.
*
* @author cp 2005.03.25
*/
public final class ByteArrayBufferOutput
extends AbstractBufferOutput
{
// ----- constructors -------------------------------------------
/**
* Construct an ByteArrayBufferOutput that will begin writing at the
* start of the containing WriteBuffer.
*/
public ByteArrayBufferOutput()
{
}
/**
* Construct an ByteArrayBufferOutput that will begin writing at the
* specified offset within the containing WriteBuffer.
*
* @param of the offset at which to begin writing
*/
public ByteArrayBufferOutput(int of)
{
super(of);
}
// ----- DataOutput methods -------------------------------------
/**
* {@inheritDoc}
*/
public void writeShort(int n)
throws IOException
{
int of = m_ofWrite;
checkBounds(of, 2);
byte[] ab = m_ab;
ab[of] = (byte) (n >>> 8);
ab[of + 1] = (byte) (n);
moveOffset(2);
}
/**
* {@inheritDoc}
*/
public void writeInt(int n)
throws IOException
{
int of = m_ofWrite;
checkBounds(of, 4);
byte[] ab = m_ab;
ab[of ] = (byte) (n >>> 24);
ab[of + 1] = (byte) (n >>> 16);
ab[of + 2] = (byte) (n >>> 8);
ab[of + 3] = (byte) (n);
moveOffset(4);
}
/**
* {@inheritDoc}
*/
public void writeLong(long l)
throws IOException
{
int of = m_ofWrite;
checkBounds(of, 8);
byte[] ab = m_ab;
// hi word
int n = (int) (l >>> 32);
ab[of ] = (byte) (n >>> 24);
ab[of + 1] = (byte) (n >>> 16);
ab[of + 2] = (byte) (n >>> 8);
ab[of + 3] = (byte) (n);
// lo word
n = (int) l;
ab[of + 4] = (byte) (n >>> 24);
ab[of + 5] = (byte) (n >>> 16);
ab[of + 6] = (byte) (n >>> 8);
ab[of + 7] = (byte) (n);
moveOffset(8);
}
/**
* {@inheritDoc}
*/
public void writeBytes(String s)
throws IOException
{
int of = m_ofWrite;
int cb = s.length();
checkBounds(of, cb);
s.getBytes(0, cb, m_ab, of); // deprecated, but avoids encoding
moveOffset(cb);
}
/**
* {@inheritDoc}
*/
public void writeChars(String s)
throws IOException
{
char[] ach = s.toCharArray();
int cch = ach.length;
int of = m_ofWrite;
int cb = cch << 1;
checkBounds(of, cb);
byte[] ab = m_ab;
for (int ofch = 0; ofch < cch; ++ofch)
{
int ch = ach[ofch];
ab[of++] = (byte) (ch >>> 8);
ab[of++] = (byte) (ch);
}
moveOffset(cb);
}
/**
* {@inheritDoc}
*/
public void writeUTF(String s)
throws IOException
{
if (s.length() == 0)
{
// 0-length UTF (Java UTF has a 2-byte length indicator)
writeShort(0);
}
else
{
// calculate the length (in bytes) of the resulting UTF
int cb = calcUTF(s);
// Java UTF binary format has only 2 bytes for length
if (cb > 0xFFFF)
{
throw new UTFDataFormatException("UTF binary length="
+ cb + ", max=65535");
}
// now that we know the UTF length (including the 2-byte
// length-ecoding that precedes it), make sure the bufer
// is big enough
int ofb = m_ofWrite;
checkBounds(ofb, 2 + cb);
// 2-byte length encoding
byte[] ab = m_ab;
ab[ofb++] = (byte) (cb >>> 8);
ab[ofb++] = (byte) (cb );
// write the UTF directly into the buffer
formatUTF(ab, ofb, cb, s);
moveOffset(2 + cb);
}
}
// ----- BufferOutput methods -----------------------------------
/**
* {@inheritDoc}
*/
public void writeSafeUTF(String s)
throws IOException
{
if (s == null)
{
writePackedInt(-1);
}
else
{
if (s.length() == 0)
{
writePackedInt(0);
}
else
{
// calculate the length (in bytes) of the resulting UTF
int cb = calcUTF(s);
// write the UTF header (the length)
writePackedInt(cb);
// now that we know the UTF length, make sure the bufer
// is big enough
int ofb = m_ofWrite;
checkBounds(ofb, cb);
// write the UTF directly into the buffer
formatUTF(m_ab, ofb, cb, s);
moveOffset(cb);
}
}
}
/**
* {@inheritDoc}
*/
public void writePackedInt(int n)
throws IOException
{
// first byte contains sign bit (bit 7 set if neg)
int b = 0;
if (n < 0)
{
b = 0x40;
n = ~n;
}
// now that the value is positive, check its magnitude and see
// how many bytes it will take to store
int ofb = m_ofWrite;
checkBounds(ofb, n < 0x40 ? 1 : (39 - Integer.numberOfLeadingZeros(n)) / 7);
byte[] ab = m_ab;
int ofOrig = ofb;
// first byte contains only 6 data bits
b |= (byte) (n & 0x3F);
n >>>= 6;
while (n != 0)
{
b |= 0x80; // bit 8 is a continuation bit
ab[ofb++] = (byte) b;
b = (n & 0x7F);
n >>>= 7;
}
ab[ofb++] = (byte) b;
moveOffset(ofb - ofOrig);
}
/**
* {@inheritDoc}
*/
public void writePackedLong(long l)
throws IOException
{
// first byte contains sign bit (bit 7 set if neg)
int b = 0;
if (l < 0)
{
b = 0x40;
l = ~l;
}
// now that the value is positive, check its magnitude and see
// how many bytes it will take to store
int ofb = m_ofWrite;
checkBounds(ofb, l < 0x40 ? 1 : (71 - Long.numberOfLeadingZeros(l)) / 7);
byte[] ab = m_ab;
int ofOrig = ofb;
// first byte contains only 6 data bits
b |= (byte) (((int) l) & 0x3F);
l >>>= 6;
while (l != 0)
{
b |= 0x80; // bit 8 is a continuation bit
ab[ofb++] = (byte) b;
b = (((int) l) & 0x7F);
l >>>= 7;
}
ab[ofb++] = (byte) b;
moveOffset(ofb - ofOrig);
}
// ----- internal -----------------------------------------------
/**
* Move the offset within the stream forward.
*
* @param cb the number of bytes to advance the offset
*/
protected void moveOffset(int cb)
{
int of = m_ofWrite + cb;
m_ofWrite = of;
updateLength(of);
}
}
// ----- inner class: Allocator -----------------------------------------
/**
* Allocator is a WriteBufferPool implementation which allocates a new
* ByteArrayWriteBuffer on each request to the pool, and does not retain
* the returned buffer. Essentially it is dummy pool which acts as an
* allocator.
*/
public static class Allocator
implements MultiBufferWriteBuffer.WriteBufferPool
{
// ----- constructors -------------------------------------------
/**
* Construct an Allocator for ByteArrayWriteBuffers of a given size.
*
* @param cb the capacity of the ByteArrayWriteBuffer to be allocated
*/
public Allocator(int cb)
{
m_cb = cb;
}
// ----- WriteBufferPool interface ------------------------------
/**
* {@inheritDoc}
*/
public int getMaximumCapacity()
{
return m_cb;
}
/**
* Allocate a new ByteArrayWriteBuffer.
*
* @param cbPreviousTotal unused
*
* @return a new ByteArrayWriteBuffer with this Allocator's
* {@link #getMaximumCapacity() capacity}
*/
public WriteBuffer allocate(int cbPreviousTotal)
{
return new ByteArrayWriteBuffer(createBytes(m_cb));
}
/**
* Release the supplied buffer into the pool.
*
* This method is a no op.
*
* @param buffer unused
*/
public void release(WriteBuffer buffer)
{
// no op
}
// ----- data members -------------------------------------------
/**
* The capacity of the ByteArrayWriteBuffer instances to allocate.
*/
protected int m_cb;
}
// ----- data members ---------------------------------------------------
/**
* The byte array that holds the binary data.
*/
protected byte[] m_ab;
/**
* Number of bytes in the byte array that have been written by this
* WriteBuffer. This is the length.
*/
protected int m_cb;
/**
* Number of bytes that the byte array can be grown to. This is the
* maximum capacity.
*/
protected int m_cbMax;
/**
* Cached ReadBuffer to quickly provide an answer to
* {@link #getUnsafeReadBuffer()}.
*/
protected transient ByteArrayReadBuffer m_bufUnsafe;
/**
* Specifies whether or not the byte array is treated as private data.
*/
private boolean m_fPrivate;
}