com.gemstone.gemfire.internal.HeapDataOutputStream Maven / Gradle / Ivy
Show all versions of gemfire-core Show documentation
/*
* Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
*
* 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. See accompanying
* LICENSE file.
*/
package com.gemstone.gemfire.internal;
import java.io.DataOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UTFDataFormatException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.BitSet;
import java.util.Iterator;
import java.util.LinkedList;
import com.gemstone.gemfire.DataSerializer;
import com.gemstone.gemfire.internal.cache.BytesAndBitsForCompactor;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.shared.Version;
/** HeapDataOutputStream is an OutputStream that also implements DataOutput
* and stores all data written to it in heap memory.
* It is always better to use this class instead ByteArrayOutputStream.
* This class is not thread safe
*
* @author Darrel
* @since 5.0.2
*
*
*
* @author Eric Zoerner
* Added boolean flag that when turned on will throw an exception instead of allocating a new
* buffer. The exception is a BufferOverflowException thrown from expand, and will restore
* the position to the point at which the flag was set with the disallowExpansion method.
* Usage Model:
* boolean succeeded = true;
* stream.disallowExpansion();
* try {
* DataSerializer.writeObject(obj, stream);
* } catch (BufferOverflowException e) {
* succeeded = false;
* }
*/
public class HeapDataOutputStream extends OutputStream implements
ObjToByteArraySerializer, VersionedDataStream {
protected ByteBuffer buffer;
protected LinkedList chunks = null;
/** list of chunks that can be reused */
protected LinkedList reuseChunks = null;
/**
* Bit set indicating which chunks can be reused in {@link #chunks}. This will
* be set if {@link #writeWithByteArrayWrappedConditionally} invocation causes
* a wrapping of provided byte[].
*/
protected BitSet nonReusableChunks = null;
protected boolean canReuseChunks = true;
protected int size = 0;
/**
* True if this stream is currently setup for writing.
* Once it switches to reading then it must be reset before
* it can be written again.
*/
private boolean writeMode = true;
private boolean ignoreWrites = false; // added for bug 39569
private final int MIN_CHUNK_SIZE;
private boolean disallowExpansion = false;
private ExpansionExceptionGenerator expansionException = null;
private int memoPosition;
private int offset;
private Version version;
public static interface ExpansionExceptionGenerator {
public Error newExpansionException(String method);
}
private static final int INITIAL_CAPACITY = 1024;
public HeapDataOutputStream() {
this(INITIAL_CAPACITY, null);
}
public HeapDataOutputStream(Version version) {
this(INITIAL_CAPACITY, version);
}
/**
* Create a HeapDataOutputStream optimized to contain just the specified string.
* The string will be written to this stream encoded as utf.
*/
public HeapDataOutputStream(String s) {
int maxStrBytes;
if (ASCII_STRINGS) {
maxStrBytes = s.length();
} else {
maxStrBytes = s.length()*3;
}
this.MIN_CHUNK_SIZE = INITIAL_CAPACITY;
this.buffer = ByteBuffer.allocate(maxStrBytes);
writeUTFNoLength(s);
}
public HeapDataOutputStream(int allocSize) {
this(allocSize, null);
}
public HeapDataOutputStream(int allocSize, Version version) {
if (allocSize < 32) {
this.MIN_CHUNK_SIZE = 32;
} else {
this.MIN_CHUNK_SIZE = allocSize;
}
this.buffer = ByteBuffer.allocate(allocSize);
this.version = version;
}
/**
* Construct a HeapDataOutputStream which uses the byte array provided as its
* underlying ByteBuffer
*
* @param bytes
*/
public HeapDataOutputStream(byte[] bytes) {
int len = bytes.length;
if (len <= 0) {
throw new IllegalArgumentException("The byte array must not be empty");
}
if (len > 32) {
this.MIN_CHUNK_SIZE = len;
} else {
this.MIN_CHUNK_SIZE = 32;
}
this.buffer = ByteBuffer.wrap(bytes);
// cannot reuse buffers with this constructor
this.canReuseChunks = false;
}
/**
* Construct a HeapDataOutputStream which uses the byte array provided as its
* underlying ByteBuffer from given offset and with provided length.
*/
public HeapDataOutputStream(byte[] bytes, int offset, int length) {
if (length <= 0) {
throw new IllegalArgumentException("The byte array must not be empty");
}
if (length > 32) {
this.MIN_CHUNK_SIZE = length;
}
else {
this.MIN_CHUNK_SIZE = 32;
}
this.buffer = ByteBuffer.wrap(bytes, offset, length);
this.offset = offset;
// cannot reuse buffers with this constructor
this.canReuseChunks = false;
}
/**
* {@inheritDoc}
*/
@Override
public final Version getVersion() {
return this.version;
}
public final void markForReuse() {
if (this.canReuseChunks) {
this.nonReusableChunks = new BitSet();
}
else {
throw new IllegalArgumentException("cannot reuse wrapped buffers");
}
}
/*throw an exception instead of allocating a new
* buffer. The exception is a BufferOverflowException thrown from expand, and will restore
* the position to the point at which the flag was set with the disallowExpansion method.
* @param ee the exception to throw if expansion is needed
*/
public void disallowExpansion(ExpansionExceptionGenerator ee) {
this.disallowExpansion = true;
this.expansionException = ee;
this.memoPosition = this.buffer.position();
}
/** write the low-order 8 bits of the given int */
@Override
public final void write(int b) {
if (this.ignoreWrites) return;
checkIfWritable();
ensureCapacity(1);
buffer.put((byte)b);
}
protected final void ensureCapacity(int amount) {
int remainingSpace = this.buffer.capacity() - this.buffer.position();
if (amount > remainingSpace) {
expand(amount);
}
}
protected void expand(int amount) {
if (this.disallowExpansion) {
this.buffer.position(this.memoPosition);
this.ignoreWrites = true;
throw this.expansionException.newExpansionException("expand");
}
final ByteBuffer oldBuffer = this.buffer;
if (this.chunks == null) {
this.chunks = new LinkedList();
}
oldBuffer.flip(); // now ready for reading
this.size += oldBuffer.remaining();
this.chunks.add(oldBuffer);
if (amount < MIN_CHUNK_SIZE) {
amount = MIN_CHUNK_SIZE;
}
if (this.reuseChunks != null && this.reuseChunks.size() > 0) {
ByteBuffer bb = (ByteBuffer)this.reuseChunks.getLast();
if (bb.capacity() >= amount) {
this.reuseChunks.removeLast();
this.buffer = bb;
}
else {
this.buffer = null;
Iterator> chunkIter = this.reuseChunks.iterator();
int numToBeReclaimed = 0;
if (this.reuseChunks.size() > 10) {
// reclaim smaller chunks to avoid growing indefinitely
numToBeReclaimed = this.reuseChunks.size() - 10;
}
while (chunkIter.hasNext()) {
bb = (ByteBuffer)chunkIter.next();
if (bb.capacity() >= amount) {
this.buffer = bb;
chunkIter.remove();
break;
}
else if (numToBeReclaimed-- > 0) {
chunkIter.remove();
}
}
if (this.buffer == null) {
this.buffer = ByteBuffer.allocate(amount);
}
}
}
else {
this.buffer = ByteBuffer.allocate(amount);
}
}
private final void checkIfWritable() {
if (!this.writeMode) {
throw new IllegalStateException(LocalizedStrings.HeapDataOutputStream_NOT_IN_WRITE_MODE.toLocalizedString());
}
}
/** override OutputStream's write() */
@Override
public void write(byte[] source, int offset, int len) {
if (this.ignoreWrites) return;
checkIfWritable();
int remainingSpace = this.buffer.capacity() - this.buffer.position();
if (remainingSpace < len) {
this.buffer.put(source, offset, remainingSpace);
offset += remainingSpace;
len -= remainingSpace;
ensureCapacity(len);
}
this.buffer.put(source, offset, len);
}
public final int size() {
if (this.writeMode) {
return this.size + this.buffer.position() - this.offset;
} else {
return this.size;
}
}
/**
* Free up any unused memory
*/
public final void trim() {
finishWriting();
if (this.buffer.limit() < this.buffer.capacity()) {
// buffer is less than half full so allocate a new one and copy it in
ByteBuffer bb = ByteBuffer.allocate(this.buffer.limit());
bb.put(this.buffer);
bb.flip(); // now ready for reading
this.buffer = bb;
}
this.reuseChunks = null;
}
public final void writeWithByteArrayWrappedConditionally(byte[] source,
int offset, int len) {
if (this.ignoreWrites) return;
checkIfWritable();
this.expand(MIN_CHUNK_SIZE);
// Asif:
// let us expand first so that current byte buffer goes into the list
// and a new current byte buffer is created. We than place the wrapped
// ByteBuffer into the list
ByteBuffer temp = ByteBuffer.wrap(source, offset, len);
// Slicing is needed so that other functions like consolidateChunk etc work
// correctly
temp = temp.slice();
temp.limit(len);
// Hide this buffer in the linked list so that it is not used for any
// further writes as we want it to be immutable & it is possible that
// capacity is > limit
// i.e len does not cover the end of the source.
this.chunks.add(temp);
// mark this chunk as non-reusable
if (this.nonReusableChunks != null) {
this.nonReusableChunks.set(this.chunks.size() - 1);
}
this.size += len;
}
private final void consolidateChunks() {
if (this.chunks != null) {
final int size = size();
ByteBuffer newBuffer = ByteBuffer.allocate(size);
int newBufPos = 0;
for (ByteBuffer bb: this.chunks) {
newBuffer.put(bb);
newBufPos += bb.position();
newBuffer.position(newBufPos); // works around JRockit 1.4.2.04 bug
}
this.chunks = null;
if (this.nonReusableChunks != null) {
this.nonReusableChunks.clear();
}
newBuffer.put(this.buffer);
newBufPos += this.buffer.position();
newBuffer.position(newBufPos); // works around JRockit 1.4.2.04 bug
this.buffer = newBuffer;
this.buffer.flip(); // now ready for reading
}
}
/**
* Prepare the contents for sending again
*/
public final void rewind() {
finishWriting();
this.size = 0;
if (this.chunks != null) {
for (ByteBuffer bb: this.chunks) {
bb.rewind();
size += bb.remaining();
}
}
this.buffer.rewind();
size += this.buffer.remaining();
}
/**
* Clear the contents for reuse of this HeapDataOutputStream.
*/
public final void clearForReuse() {
resetInternal();
if (this.chunks != null) {
if (this.nonReusableChunks != null) {
if (this.nonReusableChunks.isEmpty()) {
for (Object chunk : this.chunks) {
((ByteBuffer)chunk).clear();
}
this.reuseChunks = this.chunks;
}
else {
int index = 0;
this.reuseChunks = new LinkedList();
for (Object chunk : this.chunks) {
if (!this.nonReusableChunks.get(index++)) {
((ByteBuffer)chunk).clear();
this.reuseChunks.add(chunk);
}
}
this.nonReusableChunks.clear();
}
}
this.chunks = null;
}
}
public final void reset() {
resetInternal();
this.chunks = null;
if (this.nonReusableChunks != null) {
this.nonReusableChunks.clear();
}
}
private void resetInternal() {
this.size = 0;
this.buffer.clear();
this.writeMode = true;
this.ignoreWrites = false;
this.disallowExpansion = false;
this.expansionException = null;
}
@Override
public void flush() {
// noop
}
public void finishWriting() {
if (this.writeMode) {
this.ignoreWrites = false;
this.writeMode = false;
this.buffer.flip();
this.size += this.buffer.remaining();
}
}
@Override
public void close() {
reset();
}
// @todo darrel: add a method that returns a list of ByteBuffer
/** gets the contents of this stream as s ByteBuffer, ready for reading.
* The stream should not be written to past this point until it has been reset.
*/
public final ByteBuffer toByteBuffer() {
finishWriting();
consolidateChunks();
return this.buffer;
}
/** gets the contents of this stream as a byte[].
* The stream should not be written to past this point until it has been reset.
*/
public final byte[] toByteArray() {
ByteBuffer bb = toByteBuffer();
if (bb.hasArray() && bb.arrayOffset() == 0
&& bb.limit() == bb.capacity()) {
return bb.array();
} else {
// create a new buffer of just the right size and copy the old buffer into it
ByteBuffer tmp = ByteBuffer.allocate(bb.remaining());
tmp.put(bb);
tmp.flip();
this.buffer = tmp;
return this.buffer.array();
}
}
/**
* Writes this stream to the wrapper object of BytesAndBitsForCompactor type. The
* byte array retrieved from the HeapDataOutputStream is set in the wrapper
* object. The byte array may be partially filled. The valid length of data in
* the byte array is set in the wrapper. It is assumed that the
* HeapDataOutputStream is appropriately seeded with a byte array from the
* wrapper. However the filled byte array may or may not be the same as that
* used for seeding , depending upon whether the data got accommodated in the
* original byte buffer or not.
*
* @param wrapper
*/
//Asif
public void sendTo(BytesAndBitsForCompactor wrapper, byte userBits) {
ByteBuffer bb = toByteBuffer();
if (bb.hasArray() && bb.arrayOffset() == 0) {
wrapper.setData(bb.array(), userBits, bb.limit(), true /* is Reusable */);
}
else {
// create a new buffer of just the right size and copy the old buffer into
// it
ByteBuffer tmp = ByteBuffer.allocate(bb.remaining());
tmp.put(bb);
tmp.flip();
this.buffer = tmp;
byte[] bytes = this.buffer.array();
wrapper.setData(bytes, userBits, bytes.length, true /* is Reusable */);
}
}
/**
* Write this stream to the specified channel. Call multiple times until size returns zero to make sure all bytes in the stream have been written.
* @return the number of bytes written, possibly zero.
* @throws IOException if channel is closed, not yet connected, or some other I/O error occurs.
*/
public final int sendTo(SocketChannel chan) throws IOException {
finishWriting();
if (size() == 0) {
return 0;
}
int result;
if (this.chunks != null) {
ByteBuffer[] bufs = new ByteBuffer[this.chunks.size()+1];
bufs = this.chunks.toArray(bufs);
bufs[this.chunks.size()] = this.buffer;
result = (int)chan.write(bufs);
} else {
result = chan.write(this.buffer);
}
this.size -= result;
return result;
}
public final void sendTo(SocketChannel chan, ByteBuffer out) throws IOException {
finishWriting();
if (size() == 0) {
return;
}
if (this.chunks != null) {
for (ByteBuffer bb: this.chunks) {
sendChunkTo(bb, chan, out);
}
}
sendChunkTo(this.buffer, chan, out);
}
/**
* sends the data from "in" by writing it to "sc" through "out" (out is used
* to chunk to data and is probably a direct memory buffer).
*/
private final void sendChunkTo(ByteBuffer in, SocketChannel sc, ByteBuffer out) throws IOException {
int bytesSent = in.remaining();
final int OUT_MAX = out.capacity();
out.clear();
final byte[] bytes = in.array();
int off = in.arrayOffset() + in.position();
int len = bytesSent;
while (len > 0) {
int bytesThisTime = len;
if (bytesThisTime > OUT_MAX) {
bytesThisTime = OUT_MAX;
}
out.put(bytes, off, bytesThisTime);
off += bytesThisTime;
len -= bytesThisTime;
out.flip();
while (out.remaining() > 0) {
sc.write(out);
}
out.clear();
}
in.position(in.limit());
this.size -= bytesSent;
}
/**
* Write the contents of this stream to the byte buffer.
* @throws BufferOverflowException if out is not large enough to contain all of
* our data.
*/
public final void sendTo(ByteBuffer out) {
finishWriting();
if (out.remaining() < size()) {
throw new BufferOverflowException();
}
if (this.chunks != null) {
for (ByteBuffer bb: this.chunks) {
int bytesToWrite = bb.remaining();
if (bytesToWrite > 0) {
out.put(bb);
this.size -= bytesToWrite;
}
}
}
{
ByteBuffer bb = this.buffer;
int bytesToWrite = bb.remaining();
if (bytesToWrite > 0) {
out.put(bb);
this.size -= bytesToWrite;
}
}
}
/**
* Write the contents of this stream to the given byte array.
*
* @throws BufferOverflowException
* if buffer is not large enough to contain all of our data.
*/
public final int sendTo(byte[] buffer, int offset) {
finishWriting();
int bytesWritten = 0;
if (buffer.length < (offset + size())) {
throw new BufferOverflowException();
}
if (this.chunks != null) {
Iterator> it = this.chunks.iterator();
while (it.hasNext()) {
ByteBuffer bb = (ByteBuffer)it.next();
int bytesToWrite = bb.remaining();
if (bytesToWrite > 0) {
bb.get(buffer, offset, bytesToWrite);
this.size -= bytesToWrite;
offset += bytesToWrite;
bytesWritten += bytesToWrite;
}
}
}
ByteBuffer bb = this.buffer;
int bytesToWrite = bb.remaining();
if (bytesToWrite > 0) {
bb.get(buffer, offset, bytesToWrite);
this.size -= bytesToWrite;
bytesWritten += bytesToWrite;
}
return bytesWritten;
}
/**
* Write the contents of this stream to the specified stream.
*/
public final void sendTo(OutputStream out) throws IOException {
finishWriting();
if (this.chunks != null) {
for (ByteBuffer bb: this.chunks) {
int bytesToWrite = bb.remaining();
if (bytesToWrite > 0) {
if (bb.hasArray()) {
out.write(bb.array(), bb.arrayOffset()+bb.position(), bytesToWrite);
bb.position(bb.limit());
} else { // fix for bug 43007
byte[] bytes = new byte[bytesToWrite];
bb.get(bytes);
out.write(bytes);
}
this.size -= bytesToWrite;
}
}
}
{
ByteBuffer bb = this.buffer;
int bytesToWrite = bb.remaining();
if (bytesToWrite > 0) {
if (bb.hasArray()) {
out.write(bb.array(), bb.arrayOffset()+bb.position(), bytesToWrite);
bb.position(bb.limit());
} else {
byte[] bytes = new byte[bytesToWrite];
bb.get(bytes);
out.write(bytes);
}
this.size -= bytesToWrite;
}
}
}
/**
* Returns an input stream that can be used to read the contents that
* where written to this output stream.
*/
public final InputStream getInputStream() {
return new HDInputStream();
}
private final class HDInputStream extends InputStream {
private Iterator chunkIt;
private ByteBuffer bb;
public HDInputStream() {
finishWriting();
if (HeapDataOutputStream.this.chunks != null) {
this.chunkIt = HeapDataOutputStream.this.chunks.iterator();
nextChunk();
} else {
this.chunkIt = null;
this.bb = HeapDataOutputStream.this.buffer;
}
}
private void nextChunk() {
if (this.chunkIt != null) {
if (this.chunkIt.hasNext()) {
this.bb = this.chunkIt.next();
} else {
this.chunkIt = null;
this.bb = HeapDataOutputStream.this.buffer;
}
} else {
this.bb = null; // EOF
}
}
@Override
public int available() {
return size();
}
@Override
public int read() {
if (available() <= 0) {
return -1;
} else {
int remaining = this.bb.limit() - this.bb.position();
while (remaining == 0) {
nextChunk();
remaining = this.bb.limit() - this.bb.position();
}
consume(1);
return this.bb.get() & 0xFF; // fix for bug 37068
}
}
@Override
public int read(byte[] dst, int off, int len) {
if (available() <= 0) {
return -1;
} else {
int readCount = 0;
while (len > 0 && this.bb != null) {
if (this.bb.limit() == this.bb.position()) {
nextChunk();
} else {
int remaining = this.bb.limit() - this.bb.position();
int bytesToRead = len;
if (len > remaining) {
bytesToRead = remaining;
}
this.bb.get(dst, off, bytesToRead);
off += bytesToRead;
len -= bytesToRead;
readCount += bytesToRead;
}
}
consume(readCount);
return readCount;
}
}
@Override
public long skip(long n) {
int remaining = size();
if (remaining <= n) {
// just skip over bytes remaining
this.chunkIt = null;
this.bb = null;
consume(remaining);
return remaining;
} else {
long skipped = 0;
do {
long skipsRemaining = n - skipped;
skipped += chunkSkip(skipsRemaining);
} while (skipped != n);
return n;
}
}
private long chunkSkip(long n) {
int remaining = this.bb.limit() - this.bb.position();
if (remaining <= n) {
// skip this whole chunk
this.bb.position(this.bb.limit());
nextChunk();
consume(remaining);
return remaining;
} else {
// skip over just a part of this chunk
this.bb.position(this.bb.position()+(int)n);
consume((int)n);
return n;
}
}
private void consume(int c) {
HeapDataOutputStream.this.size -= c;
}
}
/**
* Write the contents of this stream to the specified stream.
* Note this implementation is exactly the same as writeTo(OutputStream)
* but they do not both implement a common interface.
*/
public final void sendTo(DataOutput out) throws IOException {
finishWriting();
if (this.chunks != null) {
for (ByteBuffer bb: this.chunks) {
int bytesToWrite = bb.remaining();
if (bytesToWrite > 0) {
if (bb.hasArray()) {
out.write(bb.array(), bb.arrayOffset()+bb.position(), bytesToWrite);
bb.position(bb.limit());
} else {
byte[] bytes = new byte[bytesToWrite];
bb.get(bytes);
out.write(bytes);
}
this.size -= bytesToWrite;
}
}
}
{
ByteBuffer bb = this.buffer;
int bytesToWrite = bb.remaining();
if (bytesToWrite > 0) {
if (bb.hasArray()) {
out.write(bb.array(), bb.arrayOffset()+bb.position(), bytesToWrite);
bb.position(bb.limit());
} else {
byte[] bytes = new byte[bytesToWrite];
bb.get(bytes);
out.write(bytes);
}
this.size -= bytesToWrite;
}
}
}
// DataOutput methods
/**
* Writes a boolean
value to this output stream.
* If the argument v
* is true
, the value (byte)1
* is written; if v
is false
,
* the value (byte)0
is written.
* The byte written by this method may
* be read by the readBoolean
* method of interface DataInput
,
* which will then return a boolean
* equal to v
.
*
* @param v the boolean to be written.
*/
public final void writeBoolean(boolean v) {
write(v ? 1 : 0);
}
/**
* Writes to the output stream the eight low-
* order bits of the argument v
.
* The 24 high-order bits of v
* are ignored. (This means that writeByte
* does exactly the same thing as write
* for an integer argument.) The byte written
* by this method may be read by the readByte
* method of interface DataInput
,
* which will then return a byte
* equal to (byte)v
.
*
* @param v the byte value to be written.
*/
public final void writeByte(int v) {
write(v);
}
/**
* Writes two bytes to the output
* stream to represent the value of the argument.
* The byte values to be written, in the order
* shown, are:
*
* (byte)(0xff & (v >> 8))
* (byte)(0xff & v)
*
* The bytes written by this method may be
* read by the readShort
method
* of interface DataInput
, which
* will then return a short
equal
* to (short)v
.
*
* @param v the short
value to be written.
*/
public final void writeShort(int v) {
if (this.ignoreWrites) return;
checkIfWritable();
ensureCapacity(2);
buffer.putShort((short)v);
}
/**
* Writes a char
value, wich
* is comprised of two bytes, to the
* output stream.
* The byte values to be written, in the order
* shown, are:
*
* (byte)(0xff & (v >> 8))
* (byte)(0xff & v)
*
* The bytes written by this method may be
* read by the readChar
method
* of interface DataInput
, which
* will then return a char
equal
* to (char)v
.
*
* @param v the char
value to be written.
*/
public final void writeChar(int v) {
if (this.ignoreWrites) return;
checkIfWritable();
ensureCapacity(2);
buffer.putChar((char)v);
}
/**
* Writes an int
value, which is
* comprised of four bytes, to the output stream.
* The byte values to be written, in the order
* shown, are:
*
* (byte)(0xff & (v >> 24))
* (byte)(0xff & (v >> 16))
* (byte)(0xff & (v >> 8))
* (byte)(0xff & v)
*
* The bytes written by this method may be read
* by the readInt
method of interface
* DataInput
, which will then
* return an int
equal to v
.
*
* @param v the int
value to be written.
*/
public final void writeInt(int v) {
if (this.ignoreWrites) return;
checkIfWritable();
ensureCapacity(4);
buffer.putInt(v);
}
/**
* Writes a long
value, which is
* comprised of eight bytes, to the output stream.
* The byte values to be written, in the order
* shown, are:
*
* (byte)(0xff & (v >> 56))
* (byte)(0xff & (v >> 48))
* (byte)(0xff & (v >> 40))
* (byte)(0xff & (v >> 32))
* (byte)(0xff & (v >> 24))
* (byte)(0xff & (v >> 16))
* (byte)(0xff & (v >> 8))
* (byte)(0xff & v)
*
* The bytes written by this method may be
* read by the readLong
method
* of interface DataInput
, which
* will then return a long
equal
* to v
.
*
* @param v the long
value to be written.
*/
public final void writeLong(long v) {
if (this.ignoreWrites) return;
checkIfWritable();
ensureCapacity(8);
buffer.putLong(v);
}
/**
* Reserves space in the output for a long
* and returns a LongUpdater than can be used
* to update this particular long.
* @return the LongUpdater that allows the long to be updated
*/
public final LongUpdater reserveLong() {
if (this.ignoreWrites) return null;
checkIfWritable();
ensureCapacity(8);
LongUpdater result = new LongUpdater(this.buffer);
buffer.putLong(0L);
return result;
}
public static class LongUpdater {
private final ByteBuffer bb;
private final int pos;
public LongUpdater(ByteBuffer bb) {
this.bb = bb;
this.pos = bb.position();
}
public void update(long v) {
this.bb.putLong(this.pos, v);
}
}
/**
* Writes a float
value,
* which is comprised of four bytes, to the output stream.
* It does this as if it first converts this
* float
value to an int
* in exactly the manner of the Float.floatToIntBits
* method and then writes the int
* value in exactly the manner of the writeInt
* method. The bytes written by this method
* may be read by the readFloat
* method of interface DataInput
,
* which will then return a float
* equal to v
.
*
* @param v the float
value to be written.
*/
public final void writeFloat(float v) {
if (this.ignoreWrites) return;
checkIfWritable();
ensureCapacity(4);
buffer.putFloat(v);
}
/**
* Writes a double
value,
* which is comprised of eight bytes, to the output stream.
* It does this as if it first converts this
* double
value to a long
* in exactly the manner of the Double.doubleToLongBits
* method and then writes the long
* value in exactly the manner of the writeLong
* method. The bytes written by this method
* may be read by the readDouble
* method of interface DataInput
,
* which will then return a double
* equal to v
.
*
* @param v the double
value to be written.
*/
public final void writeDouble(double v) {
if (this.ignoreWrites) return;
checkIfWritable();
ensureCapacity(8);
buffer.putDouble(v);
}
/**
* Writes a string to the output stream.
* For every character in the string
* s
, taken in order, one byte
* is written to the output stream. If
* s
is null
, a NullPointerException
* is thrown.
If s.length
* is zero, then no bytes are written. Otherwise,
* the character s[0]
is written
* first, then s[1]
, and so on;
* the last character written is s[s.length-1]
.
* For each character, one byte is written,
* the low-order byte, in exactly the manner
* of the writeByte
method . The
* high-order eight bits of each character
* in the string are ignored.
*
* @param str the string of bytes to be written.
*/
public final void writeBytes(String str) {
if (this.ignoreWrites) return;
checkIfWritable();
int strlen = str.length();
if (strlen > 0) {
ensureCapacity(strlen);
// I know this is a deprecated method but it is PERFECT for this impl.
if (this.buffer.hasArray()) {
// I know this is a deprecated method but it is PERFECT for this impl.
int pos = this.buffer.position();
str.getBytes(0, strlen, this.buffer.array(), this.buffer.arrayOffset() + pos);
this.buffer.position(pos+strlen);
} else {
byte[] bytes = new byte[strlen];
str.getBytes(0, strlen, bytes, 0);
this.buffer.put(bytes);
}
// for (int i = 0 ; i < len ; i++) {
// this.buffer.put((byte)s.charAt(i));
// }
}
}
/**
* Writes every character in the string s
,
* to the output stream, in order,
* two bytes per character. If s
* is null
, a NullPointerException
* is thrown. If s.length
* is zero, then no characters are written.
* Otherwise, the character s[0]
* is written first, then s[1]
,
* and so on; the last character written is
* s[s.length-1]
. For each character,
* two bytes are actually written, high-order
* byte first, in exactly the manner of the
* writeChar
method.
*
* @param s the string value to be written.
*/
public final void writeChars(String s) {
if (this.ignoreWrites) return;
checkIfWritable();
int len = s.length();
if (len > 0) {
ensureCapacity(len*2);
for (int i=0; i < len; i++) {
this.buffer.putChar(s.charAt(i));
}
}
}
/**
* Use -Dgemfire.ASCII_STRINGS=true if all String instances contain
* ASCII characters. Setting this to true gives a performance improvement.
*/
private static final boolean ASCII_STRINGS = Boolean.getBoolean("gemfire.ASCII_STRINGS");
/**
* Writes two bytes of length information
* to the output stream, followed
* by the Java modified UTF representation
* of every character in the string s
.
* If s
is null
,
* a NullPointerException
is thrown.
* Each character in the string s
* is converted to a group of one, two, or
* three bytes, depending on the value of the
* character.
* If a character c
* is in the range \u0001
through
* \u007f
, it is represented
* by one byte:
*
(byte)c
* If a character c
is \u0000
* or is in the range \u0080
* through \u07ff
, then it is
* represented by two bytes, to be written
* in the order shown:
* (byte)(0xc0 | (0x1f & (c >> 6)))
* (byte)(0x80 | (0x3f & c))
*
If a character
* c
is in the range \u0800
* through uffff
, then it is
* represented by three bytes, to be written
* in the order shown:
* (byte)(0xe0 | (0x0f & (c >> 12)))
* (byte)(0x80 | (0x3f & (c >> 6)))
* (byte)(0x80 | (0x3f & c))
*
First,
* the total number of bytes needed to represent
* all the characters of s
is
* calculated. If this number is larger than
* 65535
, then a UTFDataFormatException
* is thrown. Otherwise, this length is written
* to the output stream in exactly the manner
* of the writeShort
method;
* after this, the one-, two-, or three-byte
* representation of each character in the
* string s
is written.
The
* bytes written by this method may be read
* by the readUTF
method of interface
* DataInput
, which will then
* return a String
equal to s
.
*
* @param str the string value to be written.
*/
public final void writeUTF(String str) throws UTFDataFormatException {
if (this.ignoreWrites) return;
checkIfWritable();
if (ASCII_STRINGS) {
writeAsciiUTF(str, true);
} else {
writeUTF(str, true, true);
}
}
private final void writeAsciiUTF(String str, boolean encodeLength) throws UTFDataFormatException {
int strlen = str.length();
if (encodeLength && strlen > 65535) {
throw new UTFDataFormatException();
}
int maxLen = strlen;
if (encodeLength) {
maxLen += 2;
}
ensureCapacity(maxLen);
if (encodeLength) {
this.buffer.putShort((short)strlen);
}
if (this.buffer.hasArray()) {
// I know this is a deprecated method but it is PERFECT for this impl.
int pos = this.buffer.position();
str.getBytes(0, strlen, this.buffer.array(), this.buffer.arrayOffset() + pos);
this.buffer.position(pos+strlen);
} else {
for (int i = 0 ; i < strlen ; i++) {
this.buffer.put((byte)str.charAt(i));
}
// byte[] bytes = new byte[strlen];
// str.getBytes(0, strlen, bytes, 0);
// this.buffer.put(bytes);
}
}
/**
* The logic used here is based on java's DataOutputStream.writeUTF() from
* the version 1.6.0_10.
* The reader code should use the logic similar to DataOutputStream.readUTF()
* from the version 1.6.0_10 to decode this properly.
*/
public final void writeUTF(String str, boolean encodeLength,
boolean useShortLen) throws UTFDataFormatException {
int strlen = str.length();
if (encodeLength && useShortLen && strlen > 65535) {
throw new UTFDataFormatException();
}
// make room for worst case space 3 bytes for each char and 2 for len
{
int maxLen = (strlen*3);
if (encodeLength) {
maxLen += 2;
}
ensureCapacity(maxLen);
}
int utfSizeIdx = this.buffer.position();
if (encodeLength) {
// skip bytes reserved for length
if (useShortLen) {
this.buffer.position(utfSizeIdx + 2);
} else {
this.buffer.position(utfSizeIdx + 4);
}
}
for (int i = 0; i < strlen; i++) {
int c = str.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
this.buffer.put((byte)c);
} else if (c > 0x07FF) {
this.buffer.put((byte) (0xE0 | ((c >> 12) & 0x0F)));
this.buffer.put((byte) (0x80 | ((c >> 6) & 0x3F)));
this.buffer.put((byte) (0x80 | ((c >> 0) & 0x3F)));
} else {
this.buffer.put((byte) (0xC0 | ((c >> 6) & 0x1F)));
this.buffer.put((byte) (0x80 | ((c >> 0) & 0x3F)));
}
}
int utfLen = this.buffer.position() - utfSizeIdx;
if (encodeLength) {
if (useShortLen) {
utfLen -= 2;
if (utfLen > 65535) {
// act as if we wrote nothing to this buffer
this.buffer.position(utfSizeIdx);
throw new UTFDataFormatException();
}
this.buffer.putShort(utfSizeIdx, (short)utfLen);
} else {
utfLen -= 4;
this.buffer.putInt(utfSizeIdx, utfLen);
}
}
}
/**
* Same as {@link #writeUTF} but it does not encode the length in the
* first two bytes and allows strings longer than 65k to be encoded.
*/
public void writeUTFNoLength(String str) {
if (this.ignoreWrites) return;
checkIfWritable();
try {
if (ASCII_STRINGS) {
writeAsciiUTF(str, false);
} else {
writeUTF(str, false, false);
}
} catch (UTFDataFormatException ex) {
// this shouldn't happen since we did not encode the length
throw new IllegalStateException(LocalizedStrings.HeapDataOutputStream_UNEXPECTED_0.toLocalizedString(ex));
}
}
/**
* Writes the given object to this stream as a byte array.
* The byte array is produced by serializing v. The serialization
* is done by calling DataSerializer.writeObject.
*/
public void writeAsSerializedByteArray(Object v) throws IOException {
if (this.ignoreWrites) return;
checkIfWritable();
ensureCapacity(5);
if (v instanceof HeapDataOutputStream) {
HeapDataOutputStream other = (HeapDataOutputStream)v;
InternalDataSerializer.writeArrayLength(other.size(), this);
other.sendTo((OutputStream)this);
other.rewind();
} else {
ByteBuffer sizeBuf = this.buffer;
int sizePos = sizeBuf.position();
sizeBuf.position(sizePos+5);
final int preArraySize = size();
DataSerializer.writeObject(v, this);
int arraySize = size() - preArraySize;
sizeBuf.put(sizePos, InternalDataSerializer.INT_ARRAY_LEN);
sizeBuf.putInt(sizePos+1, arraySize);
}
}
/**
* Write a byte buffer to this HeapDataOutputStream,
*
* the contents of the buffer between the position and the limit
* are copied to the output stream.
*/
public void write(ByteBuffer source) {
if (this.ignoreWrites) return;
checkIfWritable();
int remainingSpace = this.buffer.capacity() - this.buffer.position();
if (remainingSpace < source.remaining()) {
int oldLimit = source.limit();
source.limit(source.position() + remainingSpace);
this.buffer.put(source);
source.limit(oldLimit);
ensureCapacity(source.remaining());
}
this.buffer.put(source);
}
@Override
public String toString() {
return this.version == null ? super.toString() : (super.toString() + " ("
+ this.version + ')');
}
}