diff --git a/.gitignore b/.gitignore index c855f72..ced438c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ /.idea /*.iml *.jfr +/.gradle +/bin +/build diff --git a/src/main/java/com/arangodb/velocypack/VPackBuilder.java b/src/main/java/com/arangodb/velocypack/VPackBuilder.java index 4cd7ae6..b3cf009 100644 --- a/src/main/java/com/arangodb/velocypack/VPackBuilder.java +++ b/src/main/java/com/arangodb/velocypack/VPackBuilder.java @@ -266,6 +266,18 @@ private void ensureCapacity(final int minCapacity) { } } + private void appendTag(long tag) { + if(tag <= 255) { + ensureCapacity(1+1); + addUnchecked((byte) 0xee); + append(tag, 1); + } else { + ensureCapacity(1+8); + addUnchecked((byte) 0xef); + append(tag, LONG_BYTES); + } + } + public VPackBuilder add(final ValueType value) throws VPackBuilderException { return addInternal(VALUE_TYPE, value); } @@ -425,12 +437,180 @@ public VPackBuilder add(final String attribute, final VPackSlice value) throws V return addInternal(attribute, VPACK, value); } + public VPackBuilder addTagged(final long tag, final ValueType value) throws VPackBuilderException { + return addInternal(tag, VALUE_TYPE, value); + } + + public VPackBuilder addTagged(final long tag, final ValueType value, final boolean unindexed) throws VPackBuilderException { + return addInternal(tag, VALUE, new Value(value, unindexed)); + } + + public VPackBuilder addTagged(final long tag, final Boolean value) throws VPackBuilderException { + return addInternal(tag, BOOLEAN, value); + } + + public VPackBuilder addTagged(final long tag, final Double value) throws VPackBuilderException { + return addInternal(tag, DOUBLE, value); + } + + public VPackBuilder addTagged(final long tag, final Float value) throws VPackBuilderException { + return addInternal(tag, FLOAT, value); + } + + public VPackBuilder addTagged(final long tag, final BigDecimal value) throws VPackBuilderException { + return addInternal(tag, BIG_DECIMAL, value); + } + + public VPackBuilder addTagged(final long tag, final Long value) throws VPackBuilderException { + return addInternal(tag, LONG, value); + } + + public VPackBuilder addTagged(final long tag, final Long value, final ValueType type) throws VPackBuilderException { + return addInternal(tag, VALUE, new Value(value, type)); + } + + public VPackBuilder addTagged(final long tag, final Integer value) throws VPackBuilderException { + return addInternal(tag, INTEGER, value); + } + + public VPackBuilder addTagged(final long tag, final Short value) throws VPackBuilderException { + return addInternal(tag, SHORT, value); + } + + public VPackBuilder addTagged(final long tag, final BigInteger value) throws VPackBuilderException { + return addInternal(tag, BIG_INTEGER, value); + } + + public VPackBuilder addTagged(final long tag, final BigInteger value, final ValueType type) throws VPackBuilderException { + return addInternal(tag, VALUE, new Value(value, type)); + } + + public VPackBuilder addTagged(final long tag, final Date value) throws VPackBuilderException { + return addInternal(tag, DATE, value); + } + + public VPackBuilder addTagged(final long tag, final java.sql.Date value) throws VPackBuilderException { + return addInternal(tag, SQL_DATE, value); + } + + public VPackBuilder addTagged(final long tag, final java.sql.Timestamp value) throws VPackBuilderException { + return addInternal(tag, SQL_TIMESTAMP, value); + } + + public VPackBuilder addTagged(final long tag, final String value) throws VPackBuilderException { + return addInternal(tag, STRING, value); + } + + public VPackBuilder addTagged(final long tag, final Character value) throws VPackBuilderException { + return addInternal(tag, CHARACTER, value); + } + + public VPackBuilder addTagged(final long tag, final byte[] value) throws VPackBuilderException { + return addInternal(tag, BYTE_ARRAY, value); + } + + public VPackBuilder addTagged(final long tag, final VPackSlice value) throws VPackBuilderException { + return addInternal(tag, VPACK, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final ValueType value) throws VPackBuilderException { + return addInternal(attribute, tag, VALUE_TYPE, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final ValueType value, final boolean unindexed) + throws VPackBuilderException { + return addInternal(attribute, tag, VALUE, new Value(value, unindexed)); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final Boolean value) throws VPackBuilderException { + return addInternal(attribute, tag, BOOLEAN, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final Double value) throws VPackBuilderException { + return addInternal(attribute, tag, DOUBLE, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final Float value) throws VPackBuilderException { + return addInternal(attribute, tag, FLOAT, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final BigDecimal value) throws VPackBuilderException { + return addInternal(attribute, tag, BIG_DECIMAL, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final Long value) throws VPackBuilderException { + return addInternal(attribute, tag, LONG, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final Long value, final ValueType type) + throws VPackBuilderException { + return addInternal(attribute, tag, VALUE, new Value(value, type)); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final Integer value) throws VPackBuilderException { + return addInternal(attribute, tag, INTEGER, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final Short value) throws VPackBuilderException { + return addInternal(attribute, tag, SHORT, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final Byte value) throws VPackBuilderException { + return addInternal(attribute, tag, BYTE, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final BigInteger value) throws VPackBuilderException { + return addInternal(attribute, tag, BIG_INTEGER, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final BigInteger value, final ValueType type) + throws VPackBuilderException { + return addInternal(attribute, tag, VALUE, new Value(value, type)); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final String value) throws VPackBuilderException { + return addInternal(attribute, tag, STRING, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final Character value) throws VPackBuilderException { + return addInternal(attribute, tag, CHARACTER, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final Date value) throws VPackBuilderException { + return addInternal(attribute, tag, DATE, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final java.sql.Date value) throws VPackBuilderException { + return addInternal(attribute, tag, SQL_DATE, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final java.sql.Timestamp value) throws VPackBuilderException { + return addInternal(attribute, tag, SQL_TIMESTAMP, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final byte[] value) throws VPackBuilderException { + return addInternal(attribute, tag, BYTE_ARRAY, value); + } + + public VPackBuilder addTagged(final String attribute, final long tag, final VPackSlice value) throws VPackBuilderException { + return addInternal(attribute, tag, VPACK, value); + } + private VPackBuilder addInternal(final Appender appender, final T value) throws VPackBuilderException { + return addInternal(0, appender, value); + } + + private VPackBuilder addInternal(final long tag, final Appender appender, final T value) throws VPackBuilderException { boolean haveReported = false; if (!stack.isEmpty() && !keyWritten) { reportAdd(); haveReported = true; } + + if (tag != 0) { + appendTag(tag); + } + try { if (value == null) { appendNull(); @@ -449,6 +629,11 @@ private VPackBuilder addInternal(final Appender appender, final T value) private VPackBuilder addInternal(final String attribute, final Appender appender, final T value) throws VPackBuilderException { + return addInternal(attribute, 0, appender, value); + } + + private VPackBuilder addInternal(final String attribute, final long tag, final Appender appender, final T value) + throws VPackBuilderException { if (attribute != null) { boolean haveReported = false; if (!stack.isEmpty()) { @@ -466,10 +651,12 @@ private VPackBuilder addInternal(final String attribute, final Appender a if (VPackSlice.attributeTranslator != null) { final VPackSlice translate = VPackSlice.attributeTranslator.translate(attribute); if (translate != null) { - final byte[] trValue = translate.getRawVPack(); - ensureCapacity(size + trValue.length); - for (byte b : trValue) { - addUnchecked(b); + final byte[] trValue = translate.getBuffer(); + int trValueLength = translate.getByteSize(); + int trValueStart = translate.getStart(); + ensureCapacity(size + trValueLength); + for (int i = 0; i < trValueLength; i++) { + addUnchecked(trValue[i+trValueStart]); } keyWritten = true; if (value == null) { @@ -483,6 +670,11 @@ private VPackBuilder addInternal(final String attribute, final Appender a } STRING.append(this, attribute); keyWritten = true; + + if (tag != 0) { + appendTag(tag); + } + if (value == null) { appendNull(); } else { @@ -646,10 +838,11 @@ private void appendBinary(final byte[] value) { } private void appendVPack(final VPackSlice value) { - final byte[] vpack = value.getRawVPack(); - ensureCapacity(size + vpack.length); - System.arraycopy(vpack, 0, buffer, size, vpack.length); - size += vpack.length; + final byte[] vpack = value.getBuffer(); + int length = value.getByteSize(); + ensureCapacity(size + length); + System.arraycopy(vpack, value.getStart(), buffer, size, length); + size += length; } private void addArray(final boolean unindexed) { diff --git a/src/main/java/com/arangodb/velocypack/VPackSlice.java b/src/main/java/com/arangodb/velocypack/VPackSlice.java index 3a4da6c..cbe96b2 100644 --- a/src/main/java/com/arangodb/velocypack/VPackSlice.java +++ b/src/main/java/com/arangodb/velocypack/VPackSlice.java @@ -24,9 +24,11 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.Iterator; +import java.util.List; import java.util.Map.Entry; import com.arangodb.velocypack.exception.VPackException; @@ -49,13 +51,16 @@ public class VPackSlice implements Serializable { private static final long serialVersionUID = -3452953589283603980L; + private static final byte[] NONE_SLICE_DATA = new byte[] { 0x00 }; + public static final VPackSlice NONE_SLICE = new VPackSlice(); + public static final VPackAttributeTranslator attributeTranslator = new VPackAttributeTranslatorImpl(); private final byte[] vpack; private final int start; protected VPackSlice() { - this(new byte[] { 0x00 }, 0); + this(NONE_SLICE_DATA, 0); } public VPackSlice(final byte[] vpack) { @@ -80,6 +85,14 @@ public int getStart() { return start; } + public int getValueStart() { + return start + tagsOffset(start); + } + + public VPackSlice value() { + return isTagged() ? new VPackSlice(vpack, getValueStart()) : this; + } + public ValueType getType() { return ValueTypeUtil.get(head()); } @@ -180,6 +193,80 @@ public boolean isCustom() { return isType(ValueType.CUSTOM); } + public boolean isTagged() { + return isType(ValueType.TAGGED); + } + + public long getFirstTag() { + if(isTagged()) { + if(vpack[start] == (byte)0xee) { + return NumberUtil.toLong(vpack, start+1, 1, false); + } else if(vpack[start] == (byte)0xef) { + return NumberUtil.toLong(vpack, start+1, 8, false); + } else { + throw new IllegalStateException("Invalid tag type ID"); + } + } + + return 0; + } + + public List getTags() { + if(!isTagged()) { + return Collections.emptyList(); + } + + List ret = new ArrayList<>(); + int start = this.start; + + while(ValueTypeUtil.get(vpack[start]) == ValueType.TAGGED) { + int offset; + long tag; + + if(vpack[start] == (byte)0xee) { + tag = NumberUtil.toLong(vpack, start+1, 1, false); + offset = 2; + } else if(vpack[start] == (byte)0xef) { + tag = NumberUtil.toLong(vpack, start+1, 8, false); + offset = 9; + } else { + throw new IllegalStateException("Invalid tag type ID"); + } + + ret.add(tag); + start += offset; + } + + return ret; + } + + public boolean hasTag(long tagId) { + int start = this.start; + + while(ValueTypeUtil.get(vpack[start]) == ValueType.TAGGED) { + int offset; + long tag; + + if(vpack[start] == (byte)0xee) { + tag = NumberUtil.toLong(vpack, start+1, 1, false); + offset = 2; + } else if(vpack[start] == (byte)0xef) { + tag = NumberUtil.toLong(vpack, start+1, 8, false); + offset = 9; + } else { + throw new IllegalStateException("Invalid tag type ID"); + } + + if(tag == tagId) { + return true; + } + + start += offset; + } + + return false; + } + public boolean getAsBoolean() { if (!isBoolean()) { throw new VPackValueTypeException(ValueType.BOOL); @@ -405,13 +492,17 @@ protected int findDataOffset() { } public int getByteSize() { - final long size; - final byte head = head(); + return getByteSize(start); + } + + private int getByteSize(int start) { + long size; + final byte head = vpack[start]; final int valueLength = ValueLengthUtil.get(head); if (valueLength != 0) { size = valueLength; } else { - switch (getType()) { + switch (ValueTypeUtil.get(head)) { case ARRAY: case OBJECT: if (head == 0x13 || head == 0x14) { @@ -423,10 +514,10 @@ public int getByteSize() { break; case STRING: // long UTF-8 String - size = getLongStringLength() + 1 + 8; + size = NumberUtil.toLong(vpack, start + 1, 8) + 1 + 8; break; case BINARY: - size = 1 + head - ((byte) 0xbf) + getBinaryLengthUnchecked(); + size = 1 + head - ((byte) 0xbf) + NumberUtil.toLong(vpack, start + 1, head - ((byte) 0xbf)); break; case BCD: if (head <= 0xcf) { @@ -435,6 +526,10 @@ public int getByteSize() { size = 1 + head - ((byte) 0xcf) + NumberUtil.toLong(vpack, start + 1, head - ((byte) 0xcf)); } break; + case TAGGED: + int offset = tagsOffset(start); + size = getByteSize(start + offset) + offset; + break; case CUSTOM: if (head == 0xf4 || head == 0xf5 || head == 0xf6) { size = 2 + NumberUtil.toLong(vpack, start + 1, 1); @@ -448,12 +543,40 @@ public int getByteSize() { break; default: // TODO - throw new InternalError(); + throw new IllegalStateException("Invalid type for byteSize()"); } } return (int) size; } + private int tagOffset(int start) { + byte v = vpack[start]; + + if(ValueTypeUtil.get(v) == ValueType.TAGGED) { + if(v == (byte)0xee) { + return 2; + } else if(v == (byte)0xef) { + return 9; + } else { + throw new IllegalStateException("Invalid tag type ID"); + } + } + + return 0; + } + + private int tagsOffset(int start) { + int ret = 0; + + while(ValueTypeUtil.get(vpack[start]) == ValueType.TAGGED) { + int offset = tagOffset(start); + ret += offset; + start += offset; + } + + return ret; + } + /** * @return array value at the specified index * @throws VPackValueTypeException @@ -473,7 +596,7 @@ public VPackSlice get(final String attribute) throws VPackException { VPackSlice result; if (head == 0x0a) { // special case, empty object - result = new VPackSlice(); + result = NONE_SLICE; } else if (head == 0x14) { // compact Object result = getFromCompactObject(attribute); @@ -495,7 +618,7 @@ public VPackSlice get(final String attribute) throws VPackException { result = new VPackSlice(vpack, key.start + key.getByteSize()); } else { // no match - result = new VPackSlice(); + result = NONE_SLICE; } } else if (key.isInteger()) { // translate key @@ -503,11 +626,11 @@ public VPackSlice get(final String attribute) throws VPackException { result = new VPackSlice(vpack, key.start + key.getByteSize()); } else { // no match - result = new VPackSlice(); + result = NONE_SLICE; } } else { // no match - result = new VPackSlice(); + result = NONE_SLICE; } } else { final long ieBase = end - n * offsetsize - (offsetsize == 8 ? 8 : 0); @@ -535,7 +658,7 @@ public VPackSlice get(final String attribute) throws VPackException { */ protected VPackSlice translateUnchecked() { final VPackSlice result = attributeTranslator.translate(getAsInt()); - return result != null ? result : new VPackSlice(); + return result != null ? result : NONE_SLICE; } protected VPackSlice makeKey() throws VPackKeyTypeException, VPackNeedAttributeTranslatorException { @@ -557,7 +680,7 @@ private VPackSlice getFromCompactObject(final String attribute) } } // not found - return new VPackSlice(); + return NONE_SLICE; } private VPackSlice searchObjectKeyBinary( @@ -585,7 +708,7 @@ private VPackSlice searchObjectKeyBinary( res = key.translateUnchecked().getAsStringSlice().compareToBytes(attributeBytes); } else { // invalid key - result = new VPackSlice(); + result = NONE_SLICE; break; } if (res == 0) { @@ -595,7 +718,7 @@ private VPackSlice searchObjectKeyBinary( } if (res > 0) { if (index == 0) { - result = new VPackSlice(); + result = NONE_SLICE; break; } r = index - 1; @@ -603,7 +726,7 @@ private VPackSlice searchObjectKeyBinary( l = index + 1; } if (r < l) { - result = new VPackSlice(); + result = NONE_SLICE; break; } } @@ -616,7 +739,7 @@ private VPackSlice searchObjectKeyLinear( final int offsetsize, final long n) throws VPackValueTypeException, VPackNeedAttributeTranslatorException { - VPackSlice result = new VPackSlice(); + VPackSlice result = NONE_SLICE; for (long index = 0; index < n; index++) { final long offset = ieBase + index * offsetsize; final long keyIndex = NumberUtil.toLong(vpack, (int) (start + offset), offsetsize); @@ -632,7 +755,7 @@ private VPackSlice searchObjectKeyLinear( } } else { // invalid key type - result = new VPackSlice(); + result = NONE_SLICE; break; } // key is identical. now return value @@ -667,7 +790,7 @@ private VPackSlice getNth(final int index) { } /** - * + * * @return the offset for the nth member from an Array or Object type */ private int getNthOffset(final int index) { @@ -761,10 +884,6 @@ public Iterator> objectIterator() { } } - protected byte[] getRawVPack() { - return Arrays.copyOfRange(vpack, start, start + getByteSize()); - } - @Override public String toString() { try { @@ -779,7 +898,12 @@ public int hashCode() { final int prime = 31; int result = 1; result = prime * result + start; - result = prime * result + Arrays.hashCode(getRawVPack()); + + int arrayHash = 1; + for (int i = start, max = getByteSize(); i < max; i++) + arrayHash = 31 * arrayHash + vpack[i]; + + result = prime * result + arrayHash; return result; } @@ -795,10 +919,21 @@ public boolean equals(final Object obj) { return false; } final VPackSlice other = (VPackSlice) obj; - if (start != other.start) { + + int byteSize = getByteSize(); + int otherByteSize = other.getByteSize(); + + if(byteSize != otherByteSize) { return false; } - return Arrays.equals(getRawVPack(), other.getRawVPack()); + + for(int i = 0; i < byteSize; i++) { + if(vpack[i+start] != other.vpack[i+other.start]) { + return false; + } + } + + return true; } diff --git a/src/main/java/com/arangodb/velocypack/ValueType.java b/src/main/java/com/arangodb/velocypack/ValueType.java index 9b77189..e808bbe 100644 --- a/src/main/java/com/arangodb/velocypack/ValueType.java +++ b/src/main/java/com/arangodb/velocypack/ValueType.java @@ -43,5 +43,6 @@ public enum ValueType { BINARY, BCD, CUSTOM, + TAGGED, VPACK } diff --git a/src/main/java/com/arangodb/velocypack/internal/util/ValueTypeUtil.java b/src/main/java/com/arangodb/velocypack/internal/util/ValueTypeUtil.java index 08dd782..db14c1e 100644 --- a/src/main/java/com/arangodb/velocypack/internal/util/ValueTypeUtil.java +++ b/src/main/java/com/arangodb/velocypack/internal/util/ValueTypeUtil.java @@ -267,8 +267,8 @@ public class ValueTypeUtil { ValueType.NONE, // 0xeb ValueType.NONE, // 0xec ValueType.NONE, // 0xed - ValueType.NONE, // 0xee - ValueType.NONE, // 0xef + ValueType.TAGGED, // 0xee + ValueType.TAGGED, // 0xef ValueType.CUSTOM, // 0xf0 ValueType.CUSTOM, // 0xf1 ValueType.CUSTOM, // 0xf2 diff --git a/src/test/java/com/arangodb/velocypack/VPackSliceTest.java b/src/test/java/com/arangodb/velocypack/VPackSliceTest.java index f93e5d8..2f89b5b 100644 --- a/src/test/java/com/arangodb/velocypack/VPackSliceTest.java +++ b/src/test/java/com/arangodb/velocypack/VPackSliceTest.java @@ -21,7 +21,7 @@ package com.arangodb.velocypack; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.*; import java.math.BigInteger; import java.util.Arrays; @@ -1460,4 +1460,51 @@ public void maxNegativeIntFromIntAsLong() { assertThat(slice.getAsLong(), is((long) -Integer.MAX_VALUE)); } + @Test + public void testReadTag() { + testReadTag(1); + testReadTag(1000); + testReadTag(21474836); + } + + @Test + public void testReadTags() { + testReadTags(1); + testReadTags(1000); + testReadTags(21474836); + } + + protected void testReadTag(int size) { + VPackBuilder b = new VPackBuilder(); + b.addTagged(42 * size, 5); + + VPackSlice s = b.slice(); + + assertTrue(s.isTagged()); + assertEquals(s.getFirstTag(), 42 * size); + assertEquals((long) s.getTags().get(0), 42 * size); + assertEquals(s.getTags().size(), 1); + assertTrue(s.hasTag(42 * size)); + assertFalse(s.hasTag(49 * size)); + assertEquals(s.value().getAsInt(), 5); + } + + protected void testReadTags(int size) { + VPackBuilder b = new VPackBuilder(); + b.addTagged(42 * size, 5); + + VPackBuilder bb = new VPackBuilder(); + bb.addTagged(49 * size, b.slice()); + + VPackSlice s = bb.slice(); + + assertTrue(s.isTagged()); + assertEquals(s.getFirstTag(), 49 * size); + assertEquals(s.getTags().size(), 2); + assertEquals((long) s.getTags().get(0), 49 * size); + assertEquals((long) s.getTags().get(1), 42 * size); + assertTrue(s.hasTag(42 * size)); + assertTrue(s.hasTag(49 * size)); + assertFalse(s.hasTag(50 * size)); + } } diff --git a/src/test/java/com/arangodb/velocypack/VPackUtil.java b/src/test/java/com/arangodb/velocypack/VPackUtil.java index 8e8feb5..7ff7ebc 100644 --- a/src/test/java/com/arangodb/velocypack/VPackUtil.java +++ b/src/test/java/com/arangodb/velocypack/VPackUtil.java @@ -29,10 +29,12 @@ public class VPackUtil { private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); public static String toHex(final VPackSlice vpack) { - final byte[] bytes = vpack.getRawVPack(); - final char[] hexChars = new char[bytes.length * 2]; + final byte[] bytes = vpack.getBuffer(); + final int bytesLength = vpack.getByteSize(); + final int bytesStart = vpack.getStart(); + final char[] hexChars = new char[bytesLength * 2]; for (int j = 0; j < bytes.length; j++) { - final int v = bytes[j] & 0xFF; + final int v = bytes[j+bytesStart] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; }