package org.rakam.kume;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Output;
import io.netty.buffer.ByteBuf;
import org.rakam.kume.util.NotImplementedException;
import java.io.OutputStream;
/**
* A proxy serializer for Kryo to write the output into Netty buffers.
* I'm not sure whether this is a good idea or not but if use Kryo's Output when when writing data to Netty's buffer
* we have to clear the buffers of the Output for every object that we serialize which is an expensive operation.
* However; this proxy class directly writes the data into Netty's buffers.
*/
public class ByteBufOutput extends Output {
private final ByteBuf byteBuf;
public ByteBufOutput(ByteBuf byteBuf) {
super();
this.byteBuf = byteBuf;
}
@Override
public OutputStream getOutputStream() {
throw new NotImplementedException();
}
@Override
public void setOutputStream(OutputStream outputStream) {
throw new NotImplementedException();
}
@Override
public void setBuffer(byte[] buffer) {
throw new NotImplementedException();
}
@Override
public void setBuffer(byte[] buffer, int maxBufferSize) {
throw new NotImplementedException();
}
@Override
public byte[] getBuffer() {
return byteBuf.array();
}
@Override
public byte[] toBytes() {
throw new NotImplementedException();
}
@Override
public void setPosition(int position) {
// netty has different positions for read and write operations.
// so this method is not compatible.
throw new NotImplementedException();
}
public void setWriterPosition(int pos) {
byteBuf.writerIndex(pos);
}
public void setReaderPosition(int pos) {
byteBuf.readerIndex(pos);
}
@Override
public void clear() {
byteBuf.clear();
}
@Override
protected boolean require(int required) throws KryoException {
return byteBuf.writableBytes() >= required;
}
@Override
public void flush() throws KryoException {
throw new NotImplementedException();
}
@Override
public void close() throws KryoException {
throw new NotImplementedException();
}
@Override
public void write(int value) throws KryoException {
byteBuf.writeByte(value);
}
@Override
public void write(byte[] bytes) throws KryoException {
byteBuf.writeBytes(bytes);
}
@Override
public void write(byte[] bytes, int offset, int length) throws KryoException {
byteBuf.writeBytes(bytes, offset, length);
}
@Override
public void writeByte(byte value) throws KryoException {
byteBuf.writeByte(value);
}
@Override
public void writeByte(int value) throws KryoException {
byteBuf.writeByte(value);
}
@Override
public void writeBytes(byte[] bytes) throws KryoException {
byteBuf.writeBytes(bytes);
}
@Override
public void writeBytes(byte[] bytes, int offset, int count) throws KryoException {
byteBuf.writeBytes(bytes, offset, count);
}
@Override
public void writeInt(int value) throws KryoException {
byteBuf.writeInt(value);
}
@Override
public int writeInt(int value, boolean optimizePositive) throws KryoException {
// The ByteBuf we usually act as a proxy of unsafe.putInt, however Kryo use more sophisticated algorithm.
// I don't know whether writing data to unsafe buffers byte by byte is better compared to unsafe.putInt so
// we may test it in the future.
byteBuf.writeInt(value);
return 4;
}
@Override
public int writeVarInt(int value, boolean optimizePositive) throws KryoException {
byteBuf.writeInt(value);
return 4;
}
@Override
public void writeString(String value) throws KryoException {
writeString((CharSequence)value);
}
public int capacity() {
return byteBuf.capacity();
}
private void writeString_slow (CharSequence value, int charCount, int charIndex) {
for (; charIndex < charCount; charIndex++) {
if (position() == capacity()) require(Math.min(capacity(), charCount - charIndex));
int c = value.charAt(charIndex);
if (c <= 0x007F) {
write(c);
} else if (c > 0x07FF) {
write((0xE0 | c >> 12 & 0x0F));
require(2);
write(0x80 | c >> 6 & 0x3F);
write(0x80 | c & 0x3F);
} else {
write(0xC0 | c >> 6 & 0x1F);
require(1);
write(0x80 | c & 0x3F);
}
}
}
/** Writes the length of a string, which is a variable length encoded int except the first byte uses bit 8 to denote UTF8 and
* bit 7 to denote if another byte is present. */
private void writeUtf8Length (int value) {
if (value >>> 6 == 0) {
require(1);
write(value | 0x80); // Set bit 8.
} else if (value >>> 13 == 0) {
require(2);
write(value | 0x40 | 0x80); // Set bit 7 and 8.
write(value >>> 6);
} else if (value >>> 20 == 0) {
require(3);
write(value | 0x40 | 0x80); // Set bit 7 and 8.
write((value >>> 6) | 0x80); // Set bit 8.
write(value >>> 13);
} else if (value >>> 27 == 0) {
require(4);
write(value | 0x40 | 0x80); // Set bit 7 and 8.
write((value >>> 6) | 0x80); // Set bit 8.
write((value >>> 13) | 0x80); // Set bit 8.
write(value >>> 20);
} else {
require(5);
write(value | 0x40 | 0x80); // Set bit 7 and 8.
write((value >>> 6) | 0x80); // Set bit 8.
write((value >>> 13) | 0x80); // Set bit 8.
write((value >>> 20) | 0x80); // Set bit 8.
write(value >>> 27);
}
}
@Override
public void writeString(CharSequence value) throws KryoException {
if (value == null) {
writeByte(0x80); // 0 means null, bit 8 means UTF8.
return;
}
int charCount = value.length();
if (charCount == 0) {
writeByte(1 | 0x80); // 1 means empty string, bit 8 means UTF8.
return;
}
// TODO: For now, treat all strings as UTF.
// Detect ASCII.
// boolean ascii = false;
// if (charCount > 1 && charCount < 64) {
// ascii = true;
// for (int i = 0; i < charCount; i++) {
// int c = value.charAt(i);
// if (c > 127) {
// ascii = false;
// break;
// }
// }
// }
// if (ascii) {
// if (capacity() - position() < charCount)
// writeAscii_slow(value, charCount);
// else {
// value.getBytes(0, charCount, buffer, position);
// }
// buffer[position() - 1] |= 0x80;
// } else {
writeUtf8Length(charCount + 1);
int charIndex = 0;
if (capacity() - position() >= charCount) {
// Try to write 8 bit chars.
for (; charIndex < charCount; charIndex++) {
int c = value.charAt(charIndex);
if (c > 127) break;
write(c);
}
}
if (charIndex < charCount) writeString_slow(value, charCount, charIndex);
// }
}
@Override
public void writeAscii(String value) throws KryoException {
writeString((CharSequence)value);
}
@Override
public void writeFloat(float value) throws KryoException {
byteBuf.writeFloat(value);
}
@Override
public int writeFloat(float value, float precision, boolean optimizePositive) throws KryoException {
byteBuf.writeFloat(value);
return 4;
}
@Override
public void writeShort(int value) throws KryoException {
byteBuf.writeShort(value);
}
@Override
public void writeLong(long value) throws KryoException {
byteBuf.writeLong(value);
}
@Override
public int writeLong(long value, boolean optimizePositive) throws KryoException {
byteBuf.writeLong(value);
return 8;
}
@Override
public int writeVarLong(long value, boolean optimizePositive) throws KryoException {
byteBuf.writeLong(value);
return 8;
}
@Override
public void writeBoolean(boolean value) throws KryoException {
byteBuf.writeBoolean(value);
}
@Override
public void writeChar(char value) throws KryoException {
byteBuf.writeChar(value);
}
@Override
public void writeDouble(double value) throws KryoException {
byteBuf.writeDouble(value);
}
@Override
public int writeDouble(double value, double precision, boolean optimizePositive) throws KryoException {
byteBuf.writeDouble(value);
return 8;
}
}