com.arangodb.velocypack.VPackBuilder Maven / Gradle / Ivy
/*
* DISCLAIMER
*
* Copyright 2016 ArangoDB GmbH, Cologne, Germany
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright holder is ArangoDB GmbH, Cologne, Germany
*/
package com.arangodb.velocypack;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import com.arangodb.velocypack.exception.VPackBuilderException;
import com.arangodb.velocypack.exception.VPackBuilderKeyAlreadyWrittenException;
import com.arangodb.velocypack.exception.VPackBuilderNeedOpenCompoundException;
import com.arangodb.velocypack.exception.VPackBuilderNeedOpenObjectException;
import com.arangodb.velocypack.exception.VPackBuilderNumberOutOfRangeException;
import com.arangodb.velocypack.exception.VPackBuilderUnexpectedValueException;
import com.arangodb.velocypack.exception.VPackKeyTypeException;
import com.arangodb.velocypack.exception.VPackNeedAttributeTranslatorException;
import com.arangodb.velocypack.exception.VPackValueTypeException;
import com.arangodb.velocypack.internal.DefaultVPackBuilderOptions;
import com.arangodb.velocypack.internal.Value;
import com.arangodb.velocypack.internal.util.NumberUtil;
/**
* @author Mark Vollmary
*
*/
public class VPackBuilder {
private static final int INTEGER_BYTES = Integer.SIZE / Byte.SIZE;
private static final int LONG_BYTES = Long.SIZE / Byte.SIZE;
private static final int DOUBLE_BYTES = Double.SIZE / Byte.SIZE;
public interface BuilderOptions {
boolean isBuildUnindexedArrays();
void setBuildUnindexedArrays(boolean buildUnindexedArrays);
boolean isBuildUnindexedObjects();
void setBuildUnindexedObjects(boolean buildUnindexedObjects);
}
public interface Appender {
void append(VPackBuilder builder, T value) throws VPackBuilderException;
}
private static final Appender VALUE = new Appender() {
@Override
public void append(final VPackBuilder builder, final Value value) throws VPackBuilderException {
builder.set(value);
}
};
private static final Appender VALUE_TYPE = new Appender() {
@Override
public void append(final VPackBuilder builder, final ValueType value) throws VPackBuilderException {
switch (value) {
case NULL:
builder.appendNull();
break;
case ARRAY:
builder.addArray(false);
break;
case OBJECT:
builder.addObject(false);
break;
default:
throw new VPackValueTypeException(ValueType.ARRAY, ValueType.OBJECT, ValueType.NULL);
}
}
};
private static final Appender BOOLEAN = new Appender() {
@Override
public void append(final VPackBuilder builder, final Boolean value) throws VPackBuilderException {
builder.appendBoolean(value);
}
};
private static final Appender DOUBLE = new Appender() {
@Override
public void append(final VPackBuilder builder, final Double value) throws VPackBuilderException {
builder.appendDouble(value);
}
};
private static final Appender FLOAT = new Appender() {
@Override
public void append(final VPackBuilder builder, final Float value) throws VPackBuilderException {
builder.appendDouble(value);
}
};
private static final Appender BIG_DECIMAL = new Appender() {
@Override
public void append(final VPackBuilder builder, final BigDecimal value) throws VPackBuilderException {
builder.appendString(value.toString());
}
};
private static final Appender LONG = new Appender() {
@Override
public void append(final VPackBuilder builder, final Long value) throws VPackBuilderException {
if (value <= 9 && value >= -6) {
builder.appendSmallInt(value);
} else {
builder.add((byte) 0x27);
builder.append(value, LONG_BYTES);
}
}
};
private static final Appender INTEGER = new Appender() {
@Override
public void append(final VPackBuilder builder, final Integer value) throws VPackBuilderException {
if (value <= 9 && value >= -6) {
builder.appendSmallInt(value);
} else {
builder.add((byte) 0x23);
builder.append(value, INTEGER_BYTES);
}
}
};
private static final Appender SHORT = new Appender() {
@Override
public void append(final VPackBuilder builder, final Short value) throws VPackBuilderException {
if (value <= 9 && value >= -6) {
builder.appendSmallInt(value);
} else {
builder.add((byte) 0x23);
builder.append(value, INTEGER_BYTES);
}
}
};
private static final Appender BYTE = new Appender() {
@Override
public void append(final VPackBuilder builder, final Byte value) {
if (value <= 9 && value >= -6) {
builder.appendSmallInt(value);
} else {
builder.add((byte) 0x23);
builder.append(value, INTEGER_BYTES);
}
}
};
private static final Appender BIG_INTEGER = new Appender() {
@Override
public void append(final VPackBuilder builder, final BigInteger value) throws VPackBuilderException {
builder.appendString(value.toString());
}
};
private static final Appender DATE = new Appender() {
@Override
public void append(final VPackBuilder builder, final Date value) throws VPackBuilderException {
builder.appendDate(value);
}
};
private static final Appender SQL_DATE = new Appender() {
@Override
public void append(final VPackBuilder builder, final java.sql.Date value) throws VPackBuilderException {
builder.appendSQLDate(value);
}
};
private static final Appender SQL_TIMESTAMP = new Appender() {
@Override
public void append(final VPackBuilder builder, final Timestamp value) throws VPackBuilderException {
builder.appendSQLTimestamp(value);
}
};
private static final Appender STRING = new Appender() {
@Override
public void append(final VPackBuilder builder, final String value) throws VPackBuilderException {
builder.appendString(value);
}
};
private static final Appender CHARACTER = new Appender() {
@Override
public void append(final VPackBuilder builder, final Character value) throws VPackBuilderException {
builder.appendString(String.valueOf(value));
}
};
private static final Appender BYTE_ARRAY = new Appender() {
@Override
public void append(final VPackBuilder builder, final byte[] value) throws VPackBuilderException {
builder.appendBinary(value);
}
};
private static final Appender VPACK = new Appender() {
@Override
public void append(final VPackBuilder builder, final VPackSlice value) throws VPackBuilderException {
builder.appendVPack(value);
}
};
private byte[] buffer; // Here we collect the result
private int size;
private final List stack; // Start positions of open
// objects/arrays
private final List> index; // Indices for starts
// of
// subindex
private boolean keyWritten; // indicates that in the current object the key
// has been written but the value not yet
private final BuilderOptions options;
public VPackBuilder() {
this(new DefaultVPackBuilderOptions());
}
public VPackBuilder(final BuilderOptions options) {
super();
this.options = options;
size = 0;
buffer = new byte[10];
stack = new ArrayList<>();
index = new ArrayList<>(4);
}
public BuilderOptions getOptions() {
return options;
}
private void add(final byte b) {
ensureCapacity(size + 1);
buffer[size++] = b;
}
private void addUnchecked(final byte b) {
buffer[size++] = b;
}
private void remove(final int index) {
final int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(buffer, index + 1, buffer, index, numMoved);
}
buffer[--size] = 0;
}
private void ensureCapacity(final int minCapacity) {
final int oldCapacity = buffer.length;
if (minCapacity > oldCapacity) {
final byte[] oldData = buffer;
int newCapacity = (oldCapacity * 3) / 2 + 1;
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
buffer = Arrays.copyOf(oldData, newCapacity);
}
}
private void appendTag(long tag) {
if(tag <= 255) {
ensureCapacity(size + 1 + 1);
addUnchecked((byte) 0xee);
append(tag, 1);
} else {
ensureCapacity(size + 1 + 8);
addUnchecked((byte) 0xef);
append(tag, LONG_BYTES);
}
}
public VPackBuilder add(final ValueType value) throws VPackBuilderException {
return addInternal(VALUE_TYPE, value);
}
public VPackBuilder add(final ValueType value, final boolean unindexed) throws VPackBuilderException {
return addInternal(VALUE, new Value(value, unindexed));
}
public VPackBuilder add(final Boolean value) throws VPackBuilderException {
return addInternal(BOOLEAN, value);
}
public VPackBuilder add(final Double value) throws VPackBuilderException {
return addInternal(DOUBLE, value);
}
public VPackBuilder add(final Float value) throws VPackBuilderException {
return addInternal(FLOAT, value);
}
public VPackBuilder add(final BigDecimal value) throws VPackBuilderException {
return addInternal(BIG_DECIMAL, value);
}
public VPackBuilder add(final Long value) throws VPackBuilderException {
return addInternal(LONG, value);
}
public VPackBuilder add(final Long value, final ValueType type) throws VPackBuilderException {
return addInternal(VALUE, new Value(value, type));
}
public VPackBuilder add(final Integer value) throws VPackBuilderException {
return addInternal(INTEGER, value);
}
public VPackBuilder add(final Short value) throws VPackBuilderException {
return addInternal(SHORT, value);
}
public VPackBuilder add(final Byte value) throws VPackBuilderException {
return addInternal(BYTE, value);
}
public VPackBuilder add(final BigInteger value) throws VPackBuilderException {
return addInternal(BIG_INTEGER, value);
}
public VPackBuilder add(final BigInteger value, final ValueType type) throws VPackBuilderException {
return addInternal(VALUE, new Value(value, type));
}
public VPackBuilder add(final Date value) throws VPackBuilderException {
return addInternal(DATE, value);
}
public VPackBuilder add(final java.sql.Date value) throws VPackBuilderException {
return addInternal(SQL_DATE, value);
}
public VPackBuilder add(final java.sql.Timestamp value) throws VPackBuilderException {
return addInternal(SQL_TIMESTAMP, value);
}
public VPackBuilder add(final String value) throws VPackBuilderException {
return addInternal(STRING, value);
}
public VPackBuilder add(final Character value) throws VPackBuilderException {
return addInternal(CHARACTER, value);
}
public VPackBuilder add(final byte[] value) throws VPackBuilderException {
return addInternal(BYTE_ARRAY, value);
}
public VPackBuilder add(final VPackSlice value) throws VPackBuilderException {
return addInternal(VPACK, value);
}
public VPackBuilder add(final String attribute, final ValueType value) throws VPackBuilderException {
return addInternal(attribute, VALUE_TYPE, value);
}
public VPackBuilder add(final String attribute, final ValueType value, final boolean unindexed)
throws VPackBuilderException {
return addInternal(attribute, VALUE, new Value(value, unindexed));
}
public VPackBuilder add(final String attribute, final Boolean value) throws VPackBuilderException {
return addInternal(attribute, BOOLEAN, value);
}
public VPackBuilder add(final String attribute, final Double value) throws VPackBuilderException {
return addInternal(attribute, DOUBLE, value);
}
public VPackBuilder add(final String attribute, final Float value) throws VPackBuilderException {
return addInternal(attribute, FLOAT, value);
}
public VPackBuilder add(final String attribute, final BigDecimal value) throws VPackBuilderException {
return addInternal(attribute, BIG_DECIMAL, value);
}
public VPackBuilder add(final String attribute, final Long value) throws VPackBuilderException {
return addInternal(attribute, LONG, value);
}
public VPackBuilder add(final String attribute, final Long value, final ValueType type)
throws VPackBuilderException {
return addInternal(attribute, VALUE, new Value(value, type));
}
public VPackBuilder add(final String attribute, final Integer value) throws VPackBuilderException {
return addInternal(attribute, INTEGER, value);
}
public VPackBuilder add(final String attribute, final Short value) throws VPackBuilderException {
return addInternal(attribute, SHORT, value);
}
public VPackBuilder add(final String attribute, final Byte value) throws VPackBuilderException {
return addInternal(attribute, BYTE, value);
}
public VPackBuilder add(final String attribute, final BigInteger value) throws VPackBuilderException {
return addInternal(attribute, BIG_INTEGER, value);
}
public VPackBuilder add(final String attribute, final BigInteger value, final ValueType type)
throws VPackBuilderException {
return addInternal(attribute, VALUE, new Value(value, type));
}
public VPackBuilder add(final String attribute, final String value) throws VPackBuilderException {
return addInternal(attribute, STRING, value);
}
public VPackBuilder add(final String attribute, final Character value) throws VPackBuilderException {
return addInternal(attribute, CHARACTER, value);
}
public VPackBuilder add(final String attribute, final Date value) throws VPackBuilderException {
return addInternal(attribute, DATE, value);
}
public VPackBuilder add(final String attribute, final java.sql.Date value) throws VPackBuilderException {
return addInternal(attribute, SQL_DATE, value);
}
public VPackBuilder add(final String attribute, final java.sql.Timestamp value) throws VPackBuilderException {
return addInternal(attribute, SQL_TIMESTAMP, value);
}
public VPackBuilder add(final String attribute, final byte[] value) throws VPackBuilderException {
return addInternal(attribute, BYTE_ARRAY, value);
}
public VPackBuilder add(final String attribute, final VPackSlice value) throws VPackBuilderException {
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 Byte value) throws VPackBuilderException {
return addInternal(tag, BYTE, 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();
} else {
appender.append(this, value);
}
} catch (final VPackBuilderException e) {
// clean up in case of an exception
if (haveReported) {
cleanupAdd();
}
throw e;
}
return this;
}
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()) {
final byte head = head();
if (head != 0x0b && head != 0x14) {
throw new VPackBuilderNeedOpenObjectException();
}
if (keyWritten) {
throw new VPackBuilderKeyAlreadyWrittenException();
}
reportAdd();
haveReported = true;
}
try {
if (VPackSlice.attributeTranslator != null) {
final VPackSlice translate = VPackSlice.attributeTranslator.translate(attribute);
if (translate != null) {
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) {
appendNull();
} else {
appender.append(this, value);
}
return this;
}
// otherwise fall through to regular behavior
}
STRING.append(this, attribute);
keyWritten = true;
if (tag != 0) {
appendTag(tag);
}
if (value == null) {
appendNull();
} else {
appender.append(this, value);
}
} catch (final VPackBuilderException e) {
// clean up in case of an exception
if (haveReported) {
cleanupAdd();
}
throw e;
} finally {
keyWritten = false;
}
} else {
addInternal(tag, appender, value);
}
return this;
}
private void set(final Value item) throws VPackBuilderException {
final Class> clazz = item.getClazz();
switch (item.getType()) {
case NULL:
appendNull();
break;
case ARRAY:
addArray(item.isUnindexed());
break;
case OBJECT:
addObject(item.isUnindexed());
break;
case SMALLINT:
final long vSmallInt = item.getNumber().longValue();
if (vSmallInt < -6 || vSmallInt > 9) {
throw new VPackBuilderNumberOutOfRangeException(ValueType.SMALLINT);
}
appendSmallInt(vSmallInt);
break;
case INT:
final int length;
if (clazz == Long.class || clazz == BigInteger.class) {
add((byte) 0x27);
length = LONG_BYTES;
} else {
throw new VPackBuilderUnexpectedValueException(ValueType.INT, Long.class, Integer.class,
BigInteger.class, Short.class);
}
append(item.getNumber().longValue(), length);
break;
case UINT:
final BigInteger vUInt;
if (clazz == Long.class) {
vUInt = BigInteger.valueOf(item.getLong());
} else if (clazz == BigInteger.class) {
vUInt = item.getBigInteger();
} else {
throw new VPackBuilderUnexpectedValueException(ValueType.UINT, Long.class, Integer.class,
BigInteger.class);
}
if (vUInt.compareTo(BigInteger.ZERO) < 0) {
throw new VPackBuilderUnexpectedValueException(ValueType.UINT, "non-negative", Long.class,
Integer.class, BigInteger.class);
}
appendUInt(vUInt);
break;
default:
break;
}
}
private void appendNull() {
add((byte) 0x18);
}
private void appendBoolean(final boolean value) {
if (value) {
add((byte) 0x1a);
} else {
add((byte) 0x19);
}
}
private void appendDouble(final double value) {
add((byte) 0x1b);
append(value);
}
private void append(final double value) {
append(Double.doubleToRawLongBits(value), DOUBLE_BYTES);
}
private void appendSmallInt(final long value) {
if (value >= 0) {
add((byte) (value + 0x30));
} else {
add((byte) (value + 0x40));
}
}
private void appendUInt(final BigInteger value) {
add((byte) 0x2f);
append(value);
}
private void append(final long value, final int length) {
ensureCapacity(size + length);
for (int i = length - 1; i >= 0; i--) {
addUnchecked((byte) (value >> (length - i - 1 << 3)));
}
}
private void append(final BigInteger value) {
ensureCapacity(size + VPackBuilder.LONG_BYTES);
for (int i = VPackBuilder.LONG_BYTES - 1; i >= 0; i--) {
addUnchecked(value.shiftRight(VPackBuilder.LONG_BYTES - i - 1 << 3).byteValue());
}
}
private void appendDate(final Date value) {
add((byte) 0x1c);
append(value.getTime(), LONG_BYTES);
}
private void appendSQLDate(final java.sql.Date value) {
add((byte) 0x1c);
append(value.getTime(), LONG_BYTES);
}
private void appendSQLTimestamp(final Timestamp value) {
add((byte) 0x1c);
append(value.getTime(), LONG_BYTES);
}
private void appendString(final String value) {
final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
final int length = bytes.length;
if (length <= 126) {
// short string
add((byte) (0x40 + length));
} else {
// long string
add((byte) 0xbf);
appendLength(length);
}
appendString(bytes);
}
private void appendString(final byte[] bytes) {
ensureCapacity(size + bytes.length);
System.arraycopy(bytes, 0, buffer, size, bytes.length);
size += bytes.length;
}
private void appendBinary(final byte[] value) {
add((byte) 0xc3);
append(value.length, INTEGER_BYTES);
ensureCapacity(size + value.length);
System.arraycopy(value, 0, buffer, size, value.length);
size += value.length;
}
private void appendVPack(final VPackSlice value) {
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) {
addCompoundValue((byte) (unindexed ? 0x13 : 0x06));
}
private void addObject(final boolean unindexed) {
addCompoundValue((byte) (unindexed ? 0x14 : 0x0b));
}
private void addCompoundValue(final byte head) {
// an Array or Object is started:
stack.add(size);
index.add(stack.size() - 1, new ArrayList());
add(head);
// Will be filled later with bytelength and nr subs
size += 8;
ensureCapacity(size);
}
private void appendLength(final long length) {
append(length, LONG_BYTES);
}
private void reportAdd() {
final Collection depth = index.get(stack.size() - 1);
depth.add(size - stack.get(stack.size() - 1));
}
private void cleanupAdd() {
final List depth = index.get(stack.size() - 1);
depth.remove(depth.size() - 1);
}
public VPackBuilder close() throws VPackBuilderException {
try {
return close(true);
} catch (final VPackKeyTypeException | VPackNeedAttributeTranslatorException e) {
throw new VPackBuilderException(e);
}
}
protected VPackBuilder close(final boolean sort)
throws VPackBuilderNeedOpenCompoundException, VPackKeyTypeException, VPackNeedAttributeTranslatorException {
if (isClosed()) {
throw new VPackBuilderNeedOpenCompoundException();
}
final byte head = head();
final boolean isArray = head == 0x06 || head == 0x13;
final List in = index.get(stack.size() - 1);
final int tos = stack.get(stack.size() - 1);
if (in.isEmpty()) {
return closeEmptyArrayOrObject(tos, isArray);
}
if (head == 0x13 || head == 0x14 || (head == 0x06 && options.isBuildUnindexedArrays())
|| head == 0x0b && (options.isBuildUnindexedObjects() || in.size() == 1)) {
if (closeCompactArrayOrObject(tos, isArray, in)) {
return this;
}
// This might fall through, if closeCompactArrayOrObject gave up!
}
if (isArray) {
return closeArray(tos, in);
}
// fix head byte in case a compact Array / Object was originally
// requested
buffer[tos] = (byte) 0x0b;
// First determine byte length and its format:
final int offsetSize;
// can be 1, 2, 4 or 8 for the byte width of the offsets,
// the byte length and the number of subvalues:
if (size - tos + in.size() - 6 <= 0xff) {
// We have so far used _pos - tos bytes, including the reserved 8
// bytes for byte length and number of subvalues. In the 1-byte
// number
// case we would win back 6 bytes but would need one byte per
// subvalue
// for the index table
offsetSize = 1;
} else if ((size - tos) + 2 * in.size() <= 0xffff) {
offsetSize = 2;
} else {
offsetSize = 4;
}
// Maybe we need to move down data
if (offsetSize == 1) {
final int targetPos = 3;
if ((size - 1) > (tos + 9)) {
for (int i = tos + targetPos; i < tos + 9; i++) {
remove(tos + targetPos);
}
}
final int diff = 9 - targetPos;
final int n = in.size();
for (int i = 0; i < n; i++) {
in.set(i, in.get(i) - diff);
}
}
// One could move down things in the offsetSize == 2 case as well,
// since we only need 4 bytes in the beginning. However, saving these
// 4 bytes has been sacrificed on the Altar of Performance.
// Now build the table:
if (sort && in.size() >= 2) {
// Object
sortObjectIndex(tos, in);
}
// final int tableBase = size;
for (long x : in) {
ensureCapacity(size + offsetSize);
for (int j = 0; j < offsetSize; j++) {
addUnchecked(/* tableBase + offsetSize * i + j, */ (byte) (x & 0xff));
x >>= 8;
}
}
// Finally fix the byte width in the type byte:
if (offsetSize > 1) {
if (offsetSize == 2) {
buffer[tos] = (byte) (buffer[tos] + 1);
} else {
buffer[tos] = (byte) (buffer[tos] + 2);
}
}
// Fix the byte length in the beginning
long x = size - tos;
for (int i = 1; i <= offsetSize; i++) {
buffer[tos + i] = (byte) (x & 0xff);
x >>= 8;
}
// set the number of items in the beginning
x = in.size();
for (int i = offsetSize + 1; i <= 2 * offsetSize; i++) {
buffer[tos + i] = (byte) (x & 0xff);
x >>= 8;
}
stack.remove(stack.size() - 1);
return this;
}
private VPackBuilder closeEmptyArrayOrObject(final int tos, final boolean isArray) {
// empty Array or Object
buffer[tos] = (byte) (isArray ? 0x01 : 0x0a);
// no bytelength and number subvalues needed
for (int i = 1; i <= 8; i++) {
remove(tos + 1);
}
stack.remove(stack.size() - 1);
return this;
}
private boolean closeCompactArrayOrObject(final int tos, final boolean isArray, final List in) {
// use the compact Array / Object format
final long nLen = NumberUtil.getVariableValueLength(in.size());
long byteSize = size - (tos + 8) + nLen;
long bLen = NumberUtil.getVariableValueLength(byteSize);
byteSize += bLen;
if (NumberUtil.getVariableValueLength(byteSize) != bLen) {
byteSize += 1;
bLen += 1;
}
if (bLen < 9) {
// can only use compact notation if total byte length is at most
// 8 bytes long
buffer[tos] = (byte) (isArray ? 0x13 : 0x14);
final int targetPos = (int) (1 + bLen);
if (size - 1 >= (tos + 9)) {
for (int i = tos + targetPos; i < tos + 9; i++) {
remove(tos + targetPos);
}
}
// store byte length
storeVariableValueLength(tos, byteSize, false);
// need additional memory for storing the number of values
if (nLen > 8 - bLen) {
ensureCapacity((int) (size + nLen));
}
// store number of values
storeVariableValueLength((int) (tos + byteSize), in.size(), true);
size += nLen;
stack.remove(stack.size() - 1);
return true;
}
return false;
}
private void storeVariableValueLength(final int offset, final long value, final boolean reverse) {
int i = offset;
long val = value;
if (reverse) {
while (val >= 0x80) {
buffer[--i] = (byte) ((byte) (val & 0x7f) | (byte) 0x80);
val >>= 7;
}
buffer[--i] = (byte) (val & 0x7f);
} else {
while (val >= 0x80) {
buffer[++i] = (byte) ((byte) (val & 0x7f) | (byte) 0x80);
val >>= 7;
}
buffer[++i] = (byte) (val & 0x7f);
}
}
private VPackBuilder closeArray(final int tos, final List in) {
// fix head byte in case a compact Array was originally
// requested
buffer[tos] = (byte) 0x06;
boolean needIndexTable = true;
boolean needNrSubs = true;
final int n = in.size();
if (n == 1) {
needIndexTable = false;
needNrSubs = false;
} else if ((size - tos) - in.get(0) == n * (in.get(1) - in.get(0))) {
// In this case it could be that all entries have the same length
// and we do not need an offset table at all:
boolean noTable = true;
final int subLen = in.get(1) - in.get(0);
if ((size - tos) - in.get(n - 1) != subLen) {
noTable = false;
} else {
for (int i = 1; i < n - 1; i++) {
if (in.get(i + 1) - in.get(i) != subLen) {
noTable = false;
break;
}
}
}
if (noTable) {
needIndexTable = false;
needNrSubs = false;
}
}
// First determine byte length and its format:
final int offsetSize;
// can be 1, 2, 4 or 8 for the byte width of the offsets,
// the byte length and the number of subvalues:
if ((size - tos) + (needIndexTable ? n : 0) - (needNrSubs ? 6 : 7) <= 0xff) {
// We have so far used _pos - tos bytes, including the reserved 8
// bytes for byte length and number of subvalues. In the 1-byte
// number
// case we would win back 6 bytes but would need one byte per
// subvalue
// for the index table
offsetSize = 1;
} else if ((size - tos) + (needIndexTable ? 2 * n : 0) <= 0xffff) {
offsetSize = 2;
} else {
offsetSize = 4;
}
// Maybe we need to move down data
if (offsetSize == 1) {
int targetPos = 3;
if (!needIndexTable) {
targetPos = 2;
}
if ((size - 1) > (tos + 9)) {
for (int i = tos + targetPos; i < tos + 9; i++) {
remove(tos + targetPos);
}
}
final int diff = 9 - targetPos;
if (needIndexTable) {
for (int i = 0; i < n; i++) {
in.set(i, in.get(i) - diff);
}
} // Note: if !needIndexTable the index is now wrong!
}
// One could move down things in the offsetSize == 2 case as well,
// since we only need 4 bytes in the beginning. However, saving these
// 4 bytes has been sacrificed on the Altar of Performance.
// Now build the table:
if (needIndexTable) {
// final int tableBase = size;
for (long x : in) {
ensureCapacity(size + offsetSize);
for (int j = 0; j < offsetSize; j++) {
addUnchecked(/* tableBase + offsetSize * i + j, */ (byte) (x & 0xff));
x >>= 8;
}
}
} else { // no index table
buffer[tos] = (byte) 0x02;
}
// Finally fix the byte width in the type byte:
if (offsetSize > 1) {
if (offsetSize == 2) {
buffer[tos] = (byte) (buffer[tos] + 1);
} else {
buffer[tos] = (byte) (buffer[tos] + 2);
}
}
// Fix the byte length in the beginning
long x = size - tos;
for (int i = 1; i <= offsetSize; i++) {
buffer[tos + i] = (byte) (x & 0xff);
x >>= 8;
}
// set the number of items in the beginning
if (needNrSubs) {
x = n;
for (int i = offsetSize + 1; i <= 2 * offsetSize; i++) {
buffer[tos + i] = (byte) (x & 0xff);
x >>= 8;
}
}
stack.remove(stack.size() - 1);
return this;
}
private static class SortEntry {
private final VPackStringSlice slice;
private final int offset;
public SortEntry(final VPackStringSlice slice, final int offset) {
super();
this.slice = slice;
this.offset = offset;
}
}
private void sortObjectIndex(final int start, final List offsets)
throws VPackKeyTypeException, VPackNeedAttributeTranslatorException {
VPackBuilder.SortEntry[] attributes = new VPackBuilder.SortEntry[offsets.size()];
for (int i = 0; i < offsets.size(); i++) {
Integer offset = offsets.get(i);
attributes[i] = new SortEntry(new VPackSlice(buffer, start + offset).makeKey().getAsStringSlice(), offset);
}
final Comparator comparator = new Comparator() {
@Override
public int compare(final SortEntry o1, final SortEntry o2) {
return o1.slice.compareTo(o2.slice);
}
};
Arrays.sort(attributes, comparator);
offsets.clear();
for (final SortEntry sortEntry : attributes) {
offsets.add(sortEntry.offset);
}
}
public static int compareTo(
final byte[] b1,
final int b1Index,
final int b1Length,
final byte[] b2,
final int b2Index,
final int b2Length) {
final int commonLength = Math.min(b1Length, b2Length);
for (int i = 0; i < commonLength; i++) {
final byte byte1 = b1[b1Index + i];
final byte byte2 = b2[b2Index + i];
if (byte1 != byte2) {
return (byte1 < byte2) ? -1 : 1;
}
}
if (b1Length != b2Length) {
return (b1Length < b2Length) ? -2 : 2;
}
return 0;
}
private boolean isClosed() {
return stack.isEmpty();
}
private byte head() {
final Integer in = stack.get(stack.size() - 1);
return buffer[in];
}
public VPackSlice slice() {
return new VPackSlice(buffer);
}
public int getVpackSize() {
return size;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy