/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2016 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.exist.storage.io;
import java.io.IOException;
import java.io.OutputStream;
import org.exist.util.ByteArray;
import org.exist.util.FastByteBuffer;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* A byte array output stream using VBE (Variable Byte Encoding).
*
* Note that the VBE scheme used by this class
* does not offer any advantage for negative numbers, in fact
* it requires significantly more storage for those; see the javadoc
* on the appropriate encoding method for details.
*
* If support for negative numbers is desired then, the reader
* should look to zig-zag encoding as used in the varint's of
* Google's Protocol Buffers https://developers.google.com/protocol-buffers/docs/encoding#signed-integers
* or Hadoop's VarInt encoding {@see org.apache.hadoop.io.file.tfile.Utils#writeVInt(java.io.DataOutput, int)}
*
* VBE is never an alternative to having advance knowledge of number
* ranges and using fixed size byte arrays to represent them.
*
* Rather, for example, it is useful when you have an int that could be
* in any range between 0 and {@link Integer#MAX_VALUE}, but is likely
* less than 2,097,151, in that case you would save at least 1 byte for
* each int value that is written to the output stream that is
* less than 2,097,151.
*
* @author wolf
*/
public class VariableByteOutputStream extends OutputStream {
private static final int MAX_BUFFER_SIZE = 65536;
private FastByteBuffer buf;
private final byte[] temp = new byte[5];
public VariableByteOutputStream() {
super();
buf = new FastByteBuffer(9);
}
public VariableByteOutputStream(final int size) {
super();
buf = new FastByteBuffer(size);
}
public void clear() {
if (buf.size() > MAX_BUFFER_SIZE) {
buf = new FastByteBuffer(9);
} else {
buf.setLength(0);
}
}
@Override
public void close() throws IOException {
buf = null;
}
public int size() {
return buf.length();
}
@Override
public void flush() throws IOException {
//Nothing to do
}
public int position() {
return buf.size();
}
public byte[] toByteArray() {
final byte[] b = new byte[buf.size()];
buf.copyTo(b, 0);
return b;
}
public ByteArray data() {
return buf;
}
@Override
public void write(final int b) throws IOException {
buf.append((byte) b);
}
@Override
public void write(final byte[] b) {
buf.append(b);
}
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
buf.append(b, off, len);
}
public void write(final ByteArray b) {
b.copyTo(buf);
}
public void writeByte(final byte b) {
buf.append(b);
}
/**
* Writes a VBE short to the output stream
*
* The encoding scheme requires the following storage
* for numbers between (inclusive):
*
* {@link Short#MIN_VALUE} and -1, 5 bytes
* 0 and 127, 1 byte
* 128 and 16383, 2 bytes
* 16384 and {@link Short#MAX_VALUE}, 3 bytes
*
* @param s the short to write
*/
public void writeShort(int s) {
while ((s & ~0177) != 0) {
buf.append((byte) ((s & 0177) | 0200));
s >>>= 7;
}
buf.append((byte) s);
}
/**
* Writes a VBE int to the output stream
*
* The encoding scheme requires the following storage
* for numbers between (inclusive):
*
* {@link Integer#MIN_VALUE} and -1, 5 bytes
* 0 and 127, 1 byte
* 128 and 16383, 2 bytes
* 16384 and 2097151, 3 bytes
* 2097152 and 268435455, is 4 bytes
* 268435456 and {@link Integer#MAX_VALUE}, 5 bytes
*
* @param i the integer to write
*/
public void writeInt(int i) {
int count = 0;
while ((i & ~0177) != 0) {
temp[count++] = (byte) ((i & 0177) | 0200);
i >>>= 7;
}
temp[count++] = (byte) i;
buf.append(temp, 0, count);
}
public void writeFixedInt(final int i) {
temp[0] = (byte) ( ( i >>> 0 ) & 0xff );
temp[1] = (byte) ( ( i >>> 8 ) & 0xff );
temp[2] = (byte) ( ( i >>> 16 ) & 0xff );
temp[3] = (byte) ( ( i >>> 24 ) & 0xff );
buf.append(temp, 0, 4);
}
public void writeFixedInt(final int position, final int i) {
buf.set(position, (byte) ( ( i >>> 0 ) & 0xff ));
buf.set(position + 1, (byte) ( ( i >>> 8 ) & 0xff ));
buf.set(position + 2, (byte) ( ( i >>> 16 ) & 0xff ));
buf.set(position + 3, (byte) ( ( i >>> 24 ) & 0xff ));
}
/**
* Writes a VBE int to the output stream
*
* The encoding scheme requires the following storage
* for numbers between (inclusive):
*
* {@link Integer#MIN_VALUE} and -1, 5 bytes
* 0 and 127, 1 byte
* 128 and 16383, 2 bytes
* 16384 and 2097151, 3 bytes
* 2097152 and 268435455, is 4 bytes
* 268435456 and {@link Integer#MAX_VALUE}, 5 bytes
*
* @param position the position in the output buffer to write the integer
* @param i the integer to write
*/
public void writeInt(int position, int i) {
while ((i & ~0177) != 0) {
buf.set(position++, (byte) ((i & 0177) | 0200));
i >>>= 7;
}
buf.set(position, (byte) i);
}
/**
* Writes a VBE long to the output stream
*
* The encoding scheme requires the following storage
* for numbers between (inclusive):
*
* {@link Long#MIN_VALUE} and -1, 10 bytes
* 0 and 127, 1 byte
* 128 and 16383, 2 bytes
* 16384 and 2097151, 3 bytes
* 2097152 and 268435455, is 4 bytes
* 268435456 and 34359738367, 5 bytes
* 34359738368 and 4398046511103, 6 bytes
* 4398046511104 and 562949953421311, 7 bytes
* 562949953421312 and 72057594037927935, 8 bytes
* 72057594037927936 and 9223372036854775807, 9 bytes
* 9223372036854775808 and {@link Long#MAX_VALUE}, 10 bytes
*
* @param l the long to write
*/
public void writeLong(long l) {
while ((l & ~0177) != 0) {
buf.append((byte) ((l & 0177) | 0200));
l >>>= 7;
}
buf.append((byte) l);
}
public void writeFixedLong(final long l) {
buf.append((byte) ((l >>> 56) & 0xff));
buf.append((byte) ((l >>> 48) & 0xff));
buf.append((byte) ((l >>> 40) & 0xff));
buf.append((byte) ((l >>> 32) & 0xff));
buf.append((byte) ((l >>> 24) & 0xff));
buf.append((byte) ((l >>> 16) & 0xff));
buf.append((byte) ((l >>> 8) & 0xff));
buf.append((byte) ((l >>> 0) & 0xff));
}
public void writeUTF(final String s) throws IOException {
final byte[] data = s.getBytes(UTF_8);
writeInt(data.length);
write(data, 0, data.length);
}
}