/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.tuple; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.UUID; import com.foundationdb.tuple.TupleUtil.DecodeResult; import com.foundationdb.util.WrappingByteSource; /** * * Utility functions for encoding/decoding tuples. * */ class TupleFloatingUtil { private static final byte nil = 0x0; static final int FLOAT_LEN = 4; static final int DOUBLE_LEN = 8; static final int INT_LEN = 4; static final int UUID_LEN = 16; static final byte FLOAT_CODE = 0x20; static final byte DOUBLE_CODE = 0x21; static final byte BIGINT_NEG_CODE = 0x0b; static final byte BIGINT_POS_CODE = 0x1d; static final byte BIGDEC_NEG_CODE = 0x23; static final byte BIGDEC_POS_CODE = 0x24; static final byte DEPRECATED_TRUE_CODE = 0x25; static final byte FALSE_CODE = 0x26; static final byte TRUE_CODE = 0x27; static final byte UUID_CODE = 0x30; static byte[] floatingPointToByteArray (float value) { return ByteBuffer.allocate(FLOAT_LEN).putFloat(value).order(ByteOrder.BIG_ENDIAN).array(); } static byte[] floatingPointToByteArray(double value) { return ByteBuffer.allocate(DOUBLE_LEN).putDouble(value).order(ByteOrder.BIG_ENDIAN).array(); } static float byteArrayToFloat(byte[] bytes) { return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat(); } static double byteArrayToDouble(byte[] bytes) { return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getDouble(); } /** * For encoding: if the sign bit is 1, flips all bits in the {@code byte[]}; * else, just flips the sign bit. * <br><br> * For decoding: if the sign bit is 1, flips all bits in the {@code byte[]}; * else, just flips the sign bit. * * @param bytes - a Big-Endian IEEE binary representation of float, double, or BigInteger * @param encode - if true, encodes; if false, decodes * @return the encoded {@code byte[]} */ static byte[] floatingPointCoding(byte[] bytes, boolean encode) { if (encode && (bytes[0] & (byte) 0x80) != (byte) 0x00) { for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) (bytes[i] ^ 0xff); } } else if (!encode && (bytes[0] & (byte) 0x80) != (byte) 0x80) { for (int i = 0; i < bytes.length; i++) { bytes[i] = (byte) (bytes[i] ^ 0xff); } } else { bytes[0] = (byte) (0x80 ^ bytes[0]); } return bytes; } static byte[] encode(Object t) { if(t == null) return new byte[] {nil}; if (t instanceof UUID) return encode((UUID) t); if(t instanceof byte[]) return TupleUtil.encode((byte[]) t); if(t instanceof WrappingByteSource) return TupleUtil.encode(((WrappingByteSource)t).byteArray()); if(t instanceof String) return TupleUtil.encode((String) t); if (t instanceof Float) return encode((Float) t); if (t instanceof Double) return encode((Double) t); if (t instanceof BigDecimal) return encode((BigDecimal) t); if (t instanceof BigInteger) return encode((BigInteger) t); if (t instanceof Number) return TupleUtil.encode(((Number)t).longValue()); if (t instanceof Boolean) return encode((Boolean) t); throw new IllegalArgumentException("Unsupported data type: " + t.getClass().getName()); } static byte[] encode(Float value) { byte[] bytes = floatingPointToByteArray(value); bytes = floatingPointCoding(bytes, true); byte[] typecode = {FLOAT_CODE}; return ByteArrayUtil.join(typecode, bytes); } static byte[] encode(Double value) { byte[] bytes = floatingPointToByteArray(value); bytes = floatingPointCoding(bytes, true); byte[] typecode = {DOUBLE_CODE}; return ByteArrayUtil.join(typecode, bytes); } static byte[] encode(UUID value) { return ByteBuffer.allocate(1+UUID_LEN).put(UUID_CODE).order(ByteOrder.BIG_ENDIAN) .putLong(value.getMostSignificantBits()).putLong(value.getLeastSignificantBits()) .array(); } static byte[] encode(BigInteger value) { byte[] bigIntBytes = encodeBigIntNoTypeCode(value); byte[] length; byte[] typecode = new byte[1]; if (value.compareTo(BigInteger.ZERO) >= 0) { typecode[0] = BIGINT_POS_CODE; length = encodeIntNoTypeCode(bigIntBytes.length); } else { typecode[0] = BIGINT_NEG_CODE; length = encodeIntNoTypeCode(-bigIntBytes.length); } return ByteArrayUtil.join(typecode, length, bigIntBytes); } static byte[] encode(BigDecimal value) { byte[] bigIntBytes = encodeBigIntNoTypeCode(value.unscaledValue()); byte[] scaleBytes = encodeIntNoTypeCode(value.scale()); byte[] typecode = new byte[1]; byte[] length; if (value.compareTo(BigDecimal.ZERO) >= 0) { typecode[0] = BIGDEC_POS_CODE; length = encodeIntNoTypeCode(bigIntBytes.length); } else { typecode[0] = BIGDEC_NEG_CODE; length = encodeIntNoTypeCode(-bigIntBytes.length); } return ByteArrayUtil.join(typecode, scaleBytes, length, bigIntBytes); } static byte[] encode(Boolean value) { byte[] encoded = {value ? TRUE_CODE : FALSE_CODE}; return encoded; } static DecodeResult decodeFloat(byte[] bytes, int start) { int end = start + FLOAT_LEN; bytes = floatingPointCoding(Arrays.copyOfRange(bytes, start, start + FLOAT_LEN), false); return new DecodeResult(end, byteArrayToFloat(bytes)); } static DecodeResult decodeUUID(byte[] bytes, int start) { ByteBuffer bb = ByteBuffer.wrap(bytes, start, UUID_LEN).order(ByteOrder.BIG_ENDIAN); long msb = bb.getLong(); long lsb = bb.getLong(); return new DecodeResult(start + UUID_LEN, new UUID(msb, lsb)); } static DecodeResult decodeDouble(byte[] bytes, int start) { int end = start + DOUBLE_LEN; bytes = floatingPointCoding(Arrays.copyOfRange(bytes, start, end), false); return new DecodeResult(end, byteArrayToDouble(bytes)); } static DecodeResult decodeBigInt(byte[] bytes, int start) { int length = Math.abs(decodeIntNoTypeCode(Arrays.copyOfRange(bytes, start, start + INT_LEN))); BigInteger bigInt = decodeBigIntNoTypeCode(Arrays.copyOfRange(bytes, start + INT_LEN, start + INT_LEN + length)); return new DecodeResult(start + INT_LEN + length, bigInt); } static DecodeResult decodeBigDecimal(byte[] bytes, int start) { int scale = decodeIntNoTypeCode(Arrays.copyOfRange(bytes, start, start + INT_LEN)); int length = Math.abs(decodeIntNoTypeCode(Arrays.copyOfRange(bytes, start + INT_LEN, start + INT_LEN * 2))); BigInteger bigInt = decodeBigIntNoTypeCode(Arrays.copyOfRange(bytes, start + INT_LEN * 2, start + INT_LEN * 2 + length)); return new DecodeResult(start + INT_LEN * 2 + length, new BigDecimal(bigInt, scale)); } static byte[] encodeBigIntNoTypeCode(BigInteger value) { byte[] bytes = value.toByteArray(); return floatingPointCoding(bytes, true); } static BigInteger decodeBigIntNoTypeCode(byte[] bytes) { bytes = floatingPointCoding(bytes, false); return new BigInteger(bytes); } static byte[] encodeIntNoTypeCode(int i) { return ByteBuffer.allocate(INT_LEN).order(ByteOrder.BIG_ENDIAN).putInt(i).array(); } static int decodeIntNoTypeCode(byte[] bytes) { if(bytes.length != INT_LEN) { throw new IllegalArgumentException("Source array must be of length "+String.valueOf((INT_LEN))); } return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt(); } static List<Object> unpack(byte[] bytes, int start, int length) { List<Object> items = new LinkedList<Object>(); int pos = start; int end = start + length; while(pos < bytes.length) { DecodeResult decoded = decode(bytes, pos, end); items.add(decoded.o); pos = decoded.end; } return items; } static byte[] pack(List<Object> items) { if(items.size() == 0) return new byte[0]; List<byte[]> parts = new ArrayList<byte[]>(items.size()); for(Object t : items) { //System.out.println("Starting encode: " + ArrayUtils.printable((byte[])t)); byte[] encoded = encode(t); //System.out.println(" encoded -> '" + ArrayUtils.printable(encoded) + "'"); parts.add(encoded); } //System.out.println("Joining whole tuple..."); return ByteArrayUtil.join(null, parts); } static DecodeResult decode(byte[] rep, int pos, int last) { //System.out.println("Decoding '" + ArrayUtils.printable(rep) + "' at " + pos); // SOMEDAY: codes over 127 will be a problem with the signed Java byte mess int code = rep[pos]; int start = pos + 1; if(code >= 0x0 && code <= 0x2 || code >= 12 && code <= 28) { return TupleUtil.decode(rep, pos, last); } if (code == UUID_CODE) { return decodeUUID(rep, start); } if (code == FLOAT_CODE) { return decodeFloat(rep, start); } if (code == DOUBLE_CODE) { return decodeDouble(rep, start); } if (code == TRUE_CODE) { return new DecodeResult(start, true); } if (code == FALSE_CODE) { return new DecodeResult(start, false); } if (code == BIGDEC_POS_CODE || code == BIGDEC_NEG_CODE) { return decodeBigDecimal(rep, start); } if (code == BIGINT_POS_CODE || code == BIGINT_NEG_CODE) { return decodeBigInt(rep, start); } if (code == DEPRECATED_TRUE_CODE) { throw new IllegalArgumentException("Deprecated tuple data type " + code + " at index " + pos); } throw new IllegalArgumentException("Unknown tuple data type " + code + " at index " + pos); } }