package divconq.db.util; import java.util.ArrayList; import java.util.List; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.joda.time.LocalTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import divconq.db.Constants; import divconq.lang.BigDateTime; import divconq.lang.Memory; import divconq.lang.chars.Utf8Decoder; import divconq.lang.chars.Utf8Encoder; import divconq.struct.CompositeStruct; import divconq.struct.Struct; import divconq.struct.builder.BuilderStateException; import divconq.struct.builder.ObjectBuilder; import divconq.struct.serial.BufferToCompositeParser; import divconq.util.ArrayUtil; import divconq.util.TimeUtil; import divconq.xml.XElement; public class ByteUtil { static final public DateTimeFormatter localDate = DateTimeFormat.forPattern("yyyyMMdd"); static final public DateTimeFormatter localTime = DateTimeFormat.forPattern("HHmmssSSS"); static public int compareKeys(byte[] a, byte[] b) { if ((a == null) && (b == null)) return 0; if (a == null) return -1; if (b == null) return 1; for (int i = 0; i < Math.min(a.length, b.length); i++) { if (a[i] > b[i]) return 1; if (a[i] < b[i]) return -1; } if (a.length > b.length) return 1; if (a.length < b.length) return -1; return 0; } static public boolean keyStartsWith(byte[] key, byte[] part) { if (part.length > key.length) return false; for (int i = 0; i < part.length; i++) { if (key[i] != part[i]) return false; } if (part.length == key.length) return true; return (key[part.length] == Constants.DB_TYPE_MARKER_ALPHA); } // key contains a part at offset static public boolean keyContains(byte[] key, int offset, byte[] part, int len) { if (len + offset > key.length) return false; for (int i = 0; i < len; i++) { if (key[i + offset] != part[i]) return false; } return true; } static public byte[] buildKey(Object in) { if (in == null) return null; Memory key = new Memory(); ByteUtil.encodeValue(key, in, true); return key.toArray(); } static public byte[] buildKey(Object... list) { return ByteUtil.buildKey(list, 0, list.length); } static public byte[] buildKey(Object[] list, int offset, int len) { if ((list == null) || (list.length <= offset)) return null; Memory key = new Memory(); for (int i = offset; i < (offset + len); i++) { ByteUtil.encodeValue(key, list[i], true); // add marker between keys but not after last key if (i < (offset + len - 1)) key.writeByte(Constants.DB_TYPE_MARKER_ALPHA); } return key.toArray(); } static public byte[] combineKeys(byte[] basekey, byte[] subid) { if (basekey == null) return subid; byte[] res = new byte[basekey.length + subid.length + 1]; ArrayUtil.blockCopy(basekey, 0, res, 0, basekey.length); res[basekey.length] = Constants.DB_TYPE_MARKER_ALPHA; ArrayUtil.blockCopy(subid, 0, res, basekey.length + 1, subid.length); return res; } static public byte[] buildValue(Object in) { if (in == null) return Constants.DB_EMPTY_ARRAY; Memory val = new Memory(); ByteUtil.encodeValue(val, in, false); return val.toArray(); } static public Object extractValue(byte[] value) { if ((value == null) || (value.length < 1)) return null; Memory val = new Memory(value); val.setPosition(0); return ByteUtil.extractNext(val); } static public List<Object> extractKeyParts(byte[] key) { if ((key == null) || (key.length < 1)) return null; //int cnt = ByteUtil.countKeyParts(key); //Object[] ret = new Object[cnt]; List<Object> ret = new ArrayList<>(); Memory val = new Memory(key); val.setPosition(0); while (val.readableBytes() > 0) { ret.add(ByteUtil.extractNext(val)); if (val.readableBytes() > 0) { int m = val.readByte(); // TODO throw error? if (m != Constants.DB_TYPE_MARKER_ALPHA) return null; } } return ret; } /* static public int countKeyParts(byte[] key) { int cnt = 1; for (int i = 0; i < key.length; i++) if (key[i] == Constants.DB_TYPE_MARKER_ALPHA) cnt++; return cnt; } */ static public byte[] extractNextDirect(Memory val) { if ((val == null) || (val.readableBytes() < 1)) return null; int pos = val.getPosition(); int type = val.readByte(); if (type == Constants.DB_TYPE_MARKER_OMEGA) return null; int mlen = 1; // the only type allowed to contain zeros is Number, it is fixed len if (type == Constants.DB_TYPE_NUMBER) { mlen = 18; } else { int b = val.readByte(); while ((b != -1) && (b != Constants.DB_TYPE_MARKER_ALPHA)) { mlen++; b = val.readByte(); } // something not right TODO //if (b == -1) // return null; } byte[] ret = new byte[mlen]; val.setPosition(pos); val.read(ret, 0, mlen); return ret; } static public Object extractNext(Memory val) { if ((val == null) || (val.readableBytes() < 1)) return null; int type = val.readByte(); if (type == -1) return null; if (type == Constants.DB_TYPE_NUMBER) return ByteUtil.dbNumberToNumber(val); if (type == Constants.DB_TYPE_STRING) return ByteUtil.dbStringToString(val); if (type == Constants.DB_TYPE_DATE_TIME) return ByteUtil.dbStringToDateTime(val); if (type == Constants.DB_TYPE_BIG_DATE_TIME) return ByteUtil.dbStringToBigDateTime(val); if (type == Constants.DB_TYPE_DATE) return ByteUtil.dbStringToDate(val); if (type == Constants.DB_TYPE_TIME) return ByteUtil.dbStringToTime(val); if (type == Constants.DB_TYPE_NULL) return null; if (type == Constants.DB_TYPE_BOOLEAN) return ByteUtil.dbBooleanToBoolean(val); // return as string as this is most commonly how the XML will be consumed if (type == Constants.DB_TYPE_XML) return ByteUtil.dbStringToString(val); if (type == Constants.DB_TYPE_COMPOSITE) return ByteUtil.dbCompositeToComposite(val); // TODO support more return null; } static public void encodeValue(Memory mem, Object v, boolean forKey) { v = Struct.objectToCore(v); if (v == null) { mem.writeByte(Constants.DB_TYPE_NULL); return; } if (v instanceof Boolean) { mem.writeByte(Constants.DB_TYPE_BOOLEAN); if ((Boolean)v) mem.writeByte((byte)0x01); else mem.writeByte((byte)0x00); return; } if (v instanceof DateTime) { String val = TimeUtil.stampFmt.print((DateTime)v); mem.writeByte(Constants.DB_TYPE_DATE_TIME); mem.write(val); return; } if (v instanceof BigDateTime) { String val = ((BigDateTime)v).toString(); mem.writeByte(Constants.DB_TYPE_BIG_DATE_TIME); mem.write(val); return; } if (v instanceof LocalDate) { String val = ByteUtil.localDate.print((LocalDate)v); mem.writeByte(Constants.DB_TYPE_DATE); mem.write(val); return; } if (v instanceof LocalTime) { String val = ByteUtil.localTime.print((LocalTime)v); mem.writeByte(Constants.DB_TYPE_TIME); mem.write(val); return; } if (v instanceof String) { String val = (String)v; if (forKey) { mem.writeByte(Constants.DB_TYPE_STRING); mem.write(val.substring(0, Math.min(val.length(), 1000))); // up to 1000 characters } else { mem.writeByte(Constants.DB_TYPE_STRING); mem.write(val); } return; } if (v instanceof Memory) { Memory val = (Memory)v; if (forKey) { // index is on size only ByteUtil.encodeValue(mem, val.getLength(), forKey); //mem.writeByte(Constants.DB_TYPE_BINARY); //mem.write(val.toArray(), 0, Math.min(val.getLength(), 1000)); // up to 1000 bytes } else { mem.writeByte(Constants.DB_TYPE_BINARY); mem.write(val.toArray()); } return; } if (v instanceof BigDecimal) { BigDecimal val = (BigDecimal)v; // TODO add support for DB_TYPE_NUMBER_SPECIAL mem.writeByte(Constants.DB_TYPE_NUMBER); mem.write(ByteUtil.decimalToDBNumber(val)); return; } if (v instanceof BigInteger) { BigInteger val = (BigInteger)v; // TODO add support for DB_TYPE_NUMBER_SPECIAL mem.writeByte(Constants.DB_TYPE_NUMBER); mem.write(ByteUtil.bigToDBNumber(val)); return; } if (v instanceof Long) { Long val = (Long)v; mem.writeByte(Constants.DB_TYPE_NUMBER); mem.write(ByteUtil.longToDBNumber(val)); return; } if (v instanceof CompositeStruct) { try { if (forKey) { // index on size only // TODO create a size calculator method // instead of doing the full encoding Memory m = ((CompositeStruct)v).toSerialMemory(); ByteUtil.encodeValue(mem, m.getLength(), forKey); } else { mem.writeByte(Constants.DB_TYPE_COMPOSITE); ((CompositeStruct)v).toSerialMemory(mem); } } catch (BuilderStateException x) { System.out.println("Error encoding db value: " + v); x.printStackTrace(); } return; } if (v instanceof XElement) { if (forKey) { // index on size only // TODO create a size calculator method // instead of doing the full encoding int m = Utf8Encoder.size(((XElement)v).toString()); ByteUtil.encodeValue(mem, m, forKey); } else { mem.writeByte(Constants.DB_TYPE_XML); mem.write(((XElement)v).toString()); // TODO stream xml to buffer? } return; } } /* IOUtil public static long byteArrayToLong(byte[] b) { return (b[0] & 0xff) << 56 | (b[1] & 0xff) << 48 | (b[2] & 0xff) << 40 | (b[3] & 0xff) << 32 | (b[4] & 0xff) << 24 | (b[5] & 0xff) << 16 | (b[6] & 0xff) << 8 | (b[7] & 0xff); } */ public static byte[] longToDBNumber(long a) { byte[] ret = new byte[17]; boolean isNeg = (a < 0); a = Math.abs(a); ret[5] = (byte) ((a >> 56) & 0xff); ret[6] = (byte) ((a >> 48) & 0xff); ret[7] = (byte) ((a >> 40) & 0xff); ret[8] = (byte) ((a >> 32) & 0xff); ret[9] = (byte) ((a >> 24) & 0xff); ret[10] = (byte) ((a >> 16) & 0xff); ret[11] = (byte) ((a >> 8) & 0xff); ret[12] = (byte) (a & 0xff); if (isNeg) { for (int i = 1; i < ret.length; i++) ret[i] ^= (byte)0xff; } else { // set positive flag ret[0] = (byte)0x01; } return ret; } public static byte[] bigToDBNumber(BigInteger left) { if (left.compareTo(Constants.DB_NUMBER_MAX_I) > 0) left = Constants.DB_NUMBER_MAX_I; if (left.compareTo(Constants.DB_NUMBER_MIN_I) < 0) left = Constants.DB_NUMBER_MIN_I; boolean isNeg = (left.signum() < 0); left = left.abs(); byte[] ret = new byte[17]; byte[] larray = left.toByteArray(); for (int i = 1; i < 13; i++) { int loff = larray.length - i; if (loff >= 0) ret[13 - i] = larray[loff]; } if (isNeg) { for (int i = 1; i < ret.length; i++) ret[i] ^= (byte)0xff; } else { // set positive flag ret[0] = (byte)0x01; } return ret; } public static byte[] decimalToDBNumber(BigDecimal a) { if (a.compareTo(Constants.DB_NUMBER_MAX) > 0) a = Constants.DB_NUMBER_MAX; if (a.compareTo(Constants.DB_NUMBER_MIN) < 0) a = Constants.DB_NUMBER_MIN; boolean isNeg = (a.signum() < 0); a = a.abs(); BigInteger left = a.toBigInteger(); // a.setScale(0, RoundingMode.HALF_UP); BigDecimal right = a.remainder(BigDecimal.ONE).setScale(9, RoundingMode.HALF_UP); // a.subtract(left); //.setScale(9, RoundingMode.HALF_UP); byte[] ret = new byte[17]; byte[] larray = left.toByteArray(); // .unscaledValue().toByteArray(); byte[] rarray = right.unscaledValue().toByteArray(); for (int i = 1; i < 13; i++) { int loff = larray.length - i; if (loff >= 0) ret[13 - i] = larray[loff]; } for (int i = 1; i < 5; i++) { int roff = rarray.length - i; if (roff >= 0) ret[17 - i] = rarray[roff]; } if (isNeg) { for (int i = 1; i < ret.length; i++) ret[i] ^= (byte)0xff; } else { // set positive flag ret[0] = (byte)0x01; } return ret; } public static String dbStringToString(Memory mem) { int pos = mem.getPosition(); int len = 0; while (mem.readableBytes() > 0) { if (mem.readByte() == Constants.DB_TYPE_MARKER_ALPHA) break; len++; } if (len == 0) return null; mem.setPosition(pos); byte[] str = new byte[len]; mem.read(str, 0, len); return Utf8Decoder.decode(str).toString(); } public static BigDateTime dbStringToBigDateTime(Memory mem) { int pos = mem.getPosition(); int len = 0; while (mem.readableBytes() > 0) { if (mem.readByte() == Constants.DB_TYPE_MARKER_ALPHA) break; len++; } if (len == 0) return null; mem.setPosition(pos); byte[] str = new byte[len]; mem.read(str, 0, len); return BigDateTime.parse(Utf8Decoder.decode(str).toString()).getResult(); } public static DateTime dbStringToDateTime(Memory mem) { int pos = mem.getPosition(); int len = 0; while (mem.readableBytes() > 0) { if (mem.readByte() == Constants.DB_TYPE_MARKER_ALPHA) break; len++; } if (len == 0) return null; mem.setPosition(pos); byte[] str = new byte[len]; mem.read(str, 0, len); return TimeUtil.stampFmt.parseDateTime(Utf8Decoder.decode(str).toString()); } public static LocalDate dbStringToDate(Memory mem) { int pos = mem.getPosition(); int len = 0; while (mem.readableBytes() > 0) { if (mem.readByte() == Constants.DB_TYPE_MARKER_ALPHA) break; len++; } if (len == 0) return null; mem.setPosition(pos); byte[] str = new byte[len]; mem.read(str, 0, len); return ByteUtil.localDate.parseLocalDate(Utf8Decoder.decode(str).toString()); } public static LocalTime dbStringToTime(Memory mem) { int pos = mem.getPosition(); int len = 0; while (mem.readableBytes() > 0) { if (mem.readByte() == Constants.DB_TYPE_MARKER_ALPHA) break; len++; } if (len == 0) return null; mem.setPosition(pos); byte[] str = new byte[len]; mem.read(str, 0, len); return ByteUtil.localTime.parseLocalTime(Utf8Decoder.decode(str).toString()); } public static Number dbNumberToNumber(Memory mem) { // TODO return Long if possible - no probably need to return Big Decimal always so user can count on a casting // if there is not enough bytes left then error, skip to end if (mem.readableBytes() < 17) { // TODO write error mem.setPosition(mem.getLength()); return null; } boolean isNeg = (mem.readByte() == 0x00); byte[] left = new byte[12]; byte[] right = new byte[4]; mem.read(left, 0, 12); mem.read(right, 0, 4); BigInteger ival = new BigInteger(left); if (isNeg) ival = ival.add(BigInteger.ONE); BigInteger fval = new BigInteger(right); if (isNeg) fval = fval.add(BigInteger.ONE); if (fval.compareTo(BigInteger.ZERO) == 0) return ival; BigDecimal n = new BigDecimal(fval, 9); n = n.add(new BigDecimal(ival)); return n.stripTrailingZeros(); } public static Boolean dbBooleanToBoolean(Memory mem) { // if there is not enough bytes left then error, skip to end if (mem.readableBytes() < 1) { // TODO write error return null; } return (mem.readByte() == 0x01); } public static CompositeStruct dbCompositeToComposite(Memory mem) { ObjectBuilder builder = new ObjectBuilder(); BufferToCompositeParser headerparser = new BufferToCompositeParser(builder); try { headerparser.parseStruct(mem); } catch (Exception x) { System.out.println("Error parsing dbComposite: " + x); return null; // TODO error!! } // value from db must be complete if (!headerparser.isDone()) return null; // TODO error!! return builder.getRoot(); } }