/*- * Copyright (c) 2008-2013 WiredTiger, Inc. * All rights reserved. * * See the file LICENSE for redistribution information. */ package com.wiredtiger.db; import java.io.ByteArrayOutputStream; import java.lang.StringBuffer; import com.wiredtiger.db.WiredTigerPackingException; /** * An internal helper class for encoding WiredTiger packed values. * * Applications should not need to use this class. */ public class PackOutputStream { final static int MAX_INT_BYTES = 21; protected PackFormatInputStream format; protected ByteArrayOutputStream packed; protected byte[] intBuf; /** * Constructor. * * \param format A String that contains the WiredTiger format that * defines the layout of this packed value. */ public PackOutputStream(String format) { this.format = new PackFormatInputStream(format); intBuf = new byte[MAX_INT_BYTES]; packed = new ByteArrayOutputStream(100); } /** * Returns the raw packing format string. */ public String getFormat() { return format.toString(); } /** * Returns the current packed value. */ public byte[] getValue() { return packed.toByteArray(); } /** * Reset the stream position. */ public void reset() { format.reset(); packed.reset(); } /** * Add a byte field to the stream. * * \param value The byte value to be added. */ public void addByte(byte value) throws WiredTigerPackingException { format.checkType('b', true); /* Translate to maintain ordering with the sign bit. */ byte input = (byte)(value + 0x80); packed.write(input); } /** * Add a byte array field to the stream. * * \param value The byte array value to be added. */ public void addByteArray(byte[] value) throws WiredTigerPackingException { this.addByteArray(value, 0, value.length); } /** * Add a byte array field to the stream. * * \param value The byte array value to be added. * \param off The offset from the start of value to begin using the array. * \param len The length of the value to encode. */ public void addByteArray(byte[] value, int off, int len) throws WiredTigerPackingException { format.checkType('U', true); // If this is not the last item, store the size. if (format.available() > 0) { packLong(len, false); } packed.write(value, off, len); /* TODO: padding. */ } /** * Add an integer field to the stream. * * \param value The integer value to be added. */ public void addInt(int value) throws WiredTigerPackingException { format.checkType('i', true); packLong(value, true); } /** * Add a long field to the stream. * * \param value The long value to be added. */ public void addLong(long value) throws WiredTigerPackingException { format.checkType('q', true); packLong(value, true); } /** * Add a record field to the stream. * * \param value The record value to be added. */ public void addRecord(long value) throws WiredTigerPackingException { format.checkType('r', true); packLong(value, true); } /** * Add a short field to the stream. * * \param value The short value to be added. */ public void addShort(short value) throws WiredTigerPackingException { format.checkType('h', true); packLong(value, true); } /** * Add a string field to the stream. * * \param value The string value to be added. */ public void addString(String value) throws WiredTigerPackingException { format.checkType('s', false); char fieldFormat = format.getType(); int stringLen = 0; int padBytes = 0; // Strings have two possible encodings. A lower case 's' is not null // terminated, and has a length define in the format (default 1). An // upper case 'S' is variable length and has a null terminator. if (fieldFormat == 's') { stringLen = format.getLengthFromFormat(true); if (stringLen > value.length()) { padBytes = stringLen - value.length(); } } else { stringLen = value.length(); padBytes = 1; // Null terminator } // We're done pulling information from the field now. format.consume(); // Use the default Charset. packed.write(value.getBytes(), 0, stringLen); while(padBytes-- > 0) { packed.write(0); } } /** * Add a long field to the stream. * The packing format is defined in the WiredTiger C integer packing * implementation, which is at src/include/intpack.i * * \param x The long value to be added. * \param signed Whether the value is signed or unsigned. */ private void packLong(long x, boolean signed) throws WiredTigerPackingException { int offset = 0; if (!signed && x < 0) { throw new WiredTigerPackingException("Overflow packing long."); } if (x < PackUtil.NEG_2BYTE_MIN) { intBuf[offset] = PackUtil.NEG_MULTI_MARKER; int lz = Long.numberOfLeadingZeros(~x) / 8; int len = PackUtil.SIZEOF_LONG - lz; // // There are four size bits we can use in the first // byte. For negative numbers, we store the number of // leading 0xff byes to maintain ordering (if this is // not obvious, it may help to remember that -1 is the // largest negative number). intBuf[offset++] |= (lz & 0xf); for (int shift = (len - 1) << 3; len != 0; shift -= 8, --len) { intBuf[offset++] = (byte)(x >> shift); } } else if (x < PackUtil.NEG_1BYTE_MIN) { x -= PackUtil.NEG_2BYTE_MIN; intBuf[offset++] = (byte)(PackUtil.NEG_2BYTE_MARKER | PackUtil.GET_BITS(x, 13, 8)); intBuf[offset++] = PackUtil.GET_BITS(x, 8, 0); } else if (x < 0) { x -= PackUtil.NEG_1BYTE_MIN; intBuf[offset++] = (byte)(PackUtil.NEG_1BYTE_MARKER | PackUtil.GET_BITS(x, 6, 0)); } else if (x <= PackUtil.POS_1BYTE_MAX) { intBuf[offset++] = (byte)(PackUtil.POS_1BYTE_MARKER | PackUtil.GET_BITS(x, 6, 0)); } else if (x <= PackUtil.POS_2BYTE_MAX) { x -= PackUtil.POS_1BYTE_MAX + 1; intBuf[offset++] = (byte)(PackUtil.POS_2BYTE_MARKER | PackUtil.GET_BITS(x, 13, 8)); intBuf[offset++] = PackUtil.GET_BITS(x, 8, 0); } else { x -= PackUtil.POS_2BYTE_MAX + 1; intBuf[offset] = PackUtil.POS_MULTI_MARKER; int lz = Long.numberOfLeadingZeros(x) / 8; int len = PackUtil.SIZEOF_LONG - lz; // There are four bits we can use in the first byte. intBuf[offset++] |= (len & 0xf); for (int shift = (len - 1) << 3; len != 0; --len, shift -= 8) { intBuf[offset++] = (byte)(x >> shift); } } packed.write(intBuf, 0, offset); } }