package org.infinispan.marshall.core;
import java.io.IOException;
import java.io.ObjectOutput;
import org.infinispan.commons.io.ByteBuffer;
import org.infinispan.commons.io.ByteBufferImpl;
/**
* Array backed, expandable {@link ObjectOutput} implementation.
*/
final class BytesObjectOutput implements ObjectOutput {
final GlobalMarshaller marshaller;
byte bytes[];
int pos;
BytesObjectOutput(int size, GlobalMarshaller marshaller) {
this.bytes = new byte[size];
this.marshaller = marshaller;
}
@Override
public void writeObject(Object obj) throws IOException {
marshaller.writeNullableObject(obj, this);
}
@Override
public void write(int b) {
writeByte(b);
}
@Override
public void write(byte[] b) {
final int len = b.length;
final int newcount = ensureCapacity(len);
System.arraycopy(b, 0, bytes, pos, len);
pos = newcount;
}
@Override
public void write(byte[] b, int off, int len) {
final int newcount = ensureCapacity(len);
System.arraycopy(b, off, bytes, pos, len);
pos = newcount;
}
@Override
public void writeBoolean(boolean v) {
writeByte((byte) (v ? 1 : 0));
}
@Override
public void writeByte(int v) {
final int newcount = ensureCapacity(1);
bytes[pos] = (byte) v;
pos = newcount;
}
@Override
public void writeShort(int v) {
int newcount = ensureCapacity(2);
final int s = pos;
bytes[s] = (byte) (v >> 8);
bytes[s+1] = (byte) v;
pos = newcount;
}
@Override
public void writeChar(int v) {
int newcount = ensureCapacity(2);
final int s = pos;
bytes[s] = (byte) (v >> 8);
bytes[s+1] = (byte) v;
pos = newcount;
}
@Override
public void writeInt(int v) {
int newcount = ensureCapacity(4);
final int s = pos;
bytes[s] = (byte) (v >> 24);
bytes[s+1] = (byte) (v >> 16);
bytes[s+2] = (byte) (v >> 8);
bytes[s+3] = (byte) v;
pos = newcount;
}
@Override
public void writeLong(long v) {
int newcount = ensureCapacity(8);
final int s = pos;
bytes[s] = (byte) (v >> 56L);
bytes[s+1] = (byte) (v >> 48L);
bytes[s+2] = (byte) (v >> 40L);
bytes[s+3] = (byte) (v >> 32L);
bytes[s+4] = (byte) (v >> 24L);
bytes[s+5] = (byte) (v >> 16L);
bytes[s+6] = (byte) (v >> 8L);
bytes[s+7] = (byte) v;
pos = newcount;
}
@Override
public void writeFloat(float v) {
writeInt(Float.floatToIntBits(v));
}
@Override
public void writeDouble(double v) {
writeLong(Double.doubleToLongBits(v));
}
@Override
public void writeBytes(String s) {
writeString(s);
}
@Override
public void writeChars(String s) {
writeString(s);
}
void writeString(String s) {
int len;
if ((len = s.length()) == 0){
writeByte(0); // empty string
} else if (isAscii(s, len)) {
writeByte(1); // small ascii
writeByte(len);
int newcount = ensureCapacity(len);
s.getBytes(0, len, bytes, pos);
pos = newcount;
} else {
writeByte(2); // large string
writeUTF(s);
}
}
private boolean isAscii(String s, int len) {
boolean ascii = false;
if(len < 64) {
ascii = true;
for (int i = 0; i < len; i++) {
if (s.charAt(i) > 127) {
ascii = false;
break;
}
}
}
return ascii;
}
@Override
public void writeUTF(String s) {
int startPos = skipIntSize();
int localPos = pos; /* avoid getfield opcode */
byte[] localBuf = bytes; /* avoid getfield opcode */
int strlen = s.length();
int c = 0;
int i=0;
for (i=0; i<strlen; i++) {
c = s.charAt(i);
if (!((c >= 0x0001) && (c <= 0x007F))) break;
if(localPos == bytes.length) {
pos = localPos;
ensureCapacity(1);
localBuf = bytes;
}
localBuf[localPos++] = (byte) c;
}
for (;i < strlen; i++){
c = s.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
if(localPos == bytes.length) {
pos = localPos;
ensureCapacity(1);
localBuf = bytes;
}
localBuf[localPos++] = (byte) c;
} else if (c > 0x07FF) {
if(localPos+3 >= bytes.length) {
pos = localPos;
ensureCapacity(3);
localBuf = bytes;
}
localBuf[localPos++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
localBuf[localPos++] = (byte) (0x80 | ((c >> 6) & 0x3F));
localBuf[localPos++] = (byte) (0x80 | (c & 0x3F));
} else {
if(localPos + 2 >= bytes.length) {
pos = localPos;
ensureCapacity(2);
localBuf = bytes;
}
localBuf[localPos++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
localBuf[localPos++] = (byte) (0x80 | (c & 0x3F));
}
}
pos = localPos;
writeIntDirect(localPos - 4 - startPos, startPos);
}
private int skipIntSize() {
ensureCapacity(4);
int count = pos;
pos +=4;
return count;
}
private void writeIntDirect(int intValue, int index) {
byte[] buf = bytes; /* avoid getfield opcode */
buf[index] = (byte) ((intValue >>> 24) & 0xFF);
buf[index+1] = (byte) ((intValue >>> 16) & 0xFF);
buf[index+2] = (byte) ((intValue >>> 8) & 0xFF);
buf[index+3] = (byte) (intValue & 0xFF);
}
@Override
public void flush() {
// No-op
}
@Override
public void close() {
// No-op
}
private int ensureCapacity(int len) {
int newcount = pos + len;
if (newcount > bytes.length) {
byte newbuf[] = new byte[getNewBufferSize(bytes.length, newcount)];
System.arraycopy(bytes, 0, newbuf, 0, pos);
bytes = newbuf;
}
return newcount;
}
private static final int DEFAULT_DOUBLING_SIZE = 4 * 1024 * 1024; // 4MB
/**
* Gets the number of bytes to which the internal buffer should be resized.
* If not enough space, it doubles the size until the internal buffer
* reaches a configurable max size (default is 4MB), after which it begins
* growing the buffer in 25% increments. This is intended to help prevent
* an OutOfMemoryError during a resize of a large buffer.
*
* @param curSize the current number of bytes
* @param minNewSize the minimum number of bytes required
* @return the size to which the internal buffer should be resized
*/
private int getNewBufferSize(int curSize, int minNewSize) {
if (curSize <= DEFAULT_DOUBLING_SIZE)
return Math.max(curSize << 1, minNewSize);
else
return Math.max(curSize + (curSize >> 2), minNewSize);
}
byte[] toBytes() {
// Trim out unused bytes
byte[] b = new byte[pos];
System.arraycopy(bytes, 0, b, 0, pos);
pos = 0;
return b;
}
ByteBuffer toByteBuffer() {
// No triming, just take position as length
return new ByteBufferImpl(bytes, 0, pos);
}
}