All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.arangodb.velocypack.VPackSlice Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
/*
 * 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.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.Map.Entry;

import com.arangodb.velocypack.exception.VPackException;
import com.arangodb.velocypack.exception.VPackKeyTypeException;
import com.arangodb.velocypack.exception.VPackNeedAttributeTranslatorException;
import com.arangodb.velocypack.exception.VPackValueTypeException;
import com.arangodb.velocypack.internal.VPackAttributeTranslatorImpl;
import com.arangodb.velocypack.internal.util.BinaryUtil;
import com.arangodb.velocypack.internal.util.DateUtil;
import com.arangodb.velocypack.internal.util.NumberUtil;
import com.arangodb.velocypack.internal.util.ObjectArrayUtil;
import com.arangodb.velocypack.internal.util.ValueLengthUtil;
import com.arangodb.velocypack.internal.util.ValueTypeUtil;

/**
 * @author Mark Vollmary
 *
 */
public class VPackSlice implements Serializable {

	private static final long serialVersionUID = -3452953589283603980L;

	public static final VPackAttributeTranslator attributeTranslator = new VPackAttributeTranslatorImpl();

	private final byte[] vpack;
	private final int start;

	protected VPackSlice() {
		this(new byte[] { 0x00 }, 0);
	}

	public VPackSlice(final byte[] vpack) {
		this(vpack, 0);
	}

	public VPackSlice(final byte[] vpack, final int start) {
		super();
		this.vpack = vpack;
		this.start = start;
	}

	public byte head() {
		return vpack[start];
	}

	public byte[] getBuffer() {
		return vpack;
	}

	public int getStart() {
		return start;
	}

	public ValueType getType() {
		return ValueTypeUtil.get(head());
	}

	private int length() {
		return ValueLengthUtil.get(head()) - 1;
	}

	public boolean isType(final ValueType type) {
		return getType() == type;
	}

	public boolean isNone() {
		return isType(ValueType.NONE);
	}

	public boolean isNull() {
		return isType(ValueType.NULL);
	}

	public boolean isIllegal() {
		return isType(ValueType.ILLEGAL);
	}

	public boolean isBoolean() {
		return isType(ValueType.BOOL);
	}

	public boolean isTrue() {
		return head() == 0x1a;
	}

	public boolean isFalse() {
		return head() == 0x19;
	}

	public boolean isArray() {
		return isType(ValueType.ARRAY);
	}

	public boolean isObject() {
		return isType(ValueType.OBJECT);
	}

	public boolean isDouble() {
		return isType(ValueType.DOUBLE);
	}

	public boolean isDate() {
		return isType(ValueType.UTC_DATE);
	}

	public boolean isExternal() {
		return isType(ValueType.EXTERNAL);
	}

	public boolean isMinKey() {
		return isType(ValueType.MIN_KEY);
	}

	public boolean isMaxKey() {
		return isType(ValueType.MAX_KEY);
	}

	public boolean isInt() {
		return isType(ValueType.INT);
	}

	public boolean isUInt() {
		return isType(ValueType.UINT);
	}

	public boolean isSmallInt() {
		return isType(ValueType.SMALLINT);
	}

	public boolean isInteger() {
		return isInt() || isUInt() || isSmallInt();
	}

	public boolean isNumber() {
		return isInteger() || isDouble();
	}

	public boolean isString() {
		return isType(ValueType.STRING);
	}

	public boolean isBinary() {
		return isType(ValueType.BINARY);
	}

	public boolean isBCD() {
		return isType(ValueType.BCD);
	}

	public boolean isCustom() {
		return isType(ValueType.CUSTOM);
	}

	public boolean getAsBoolean() {
		if (!isBoolean()) {
			throw new VPackValueTypeException(ValueType.BOOL);
		}
		return isTrue();
	}

	public double getAsDouble() {
		return getAsNumber().doubleValue();
	}

	private double getAsDoubleUnchecked() {
		return NumberUtil.toDouble(vpack, start + 1, length());
	}

	public BigDecimal getAsBigDecimal() {
		return BigDecimal.valueOf(getAsDouble());
	}

	private long getSmallInt() {
		final byte head = head();
		final long smallInt;
		if (head >= 0x30 && head <= 0x39) {
			smallInt = head - 0x30;
		} else /* if (head >= 0x3a && head <= 0x3f) */ {
			smallInt = head - 0x3a - 6;
		}
		return smallInt;
	}

	private long getInt() {
		return NumberUtil.toLong(vpack, start + 1, length(), true);
	}

	private long getUInt() {
		return NumberUtil.toLong(vpack, start + 1, length());
	}

	public Number getAsNumber() {
		final Number result;
		if (isSmallInt()) {
			result = getSmallInt();
		} else if (isInt()) {
			result = getInt();
		} else if (isUInt()) {
			result = getUInt();
		} else if (isDouble()) {
			result = getAsDoubleUnchecked();
		} else {
			throw new VPackValueTypeException(ValueType.INT, ValueType.UINT, ValueType.SMALLINT);
		}
		return result;
	}

	public long getAsLong() {
		return getAsNumber().longValue();
	}

	public int getAsInt() {
		return getAsNumber().intValue();
	}

	public float getAsFloat() {
		return getAsNumber().floatValue();
	}

	public short getAsShort() {
		return getAsNumber().shortValue();
	}

	public byte getAsByte() {
		return getAsNumber().byteValue();
	}

	public BigInteger getAsBigInteger() {
		if (isSmallInt() || isInt()) {
			return BigInteger.valueOf(getAsLong());
		} else if (isUInt()) {
			return NumberUtil.toBigInteger(vpack, start + 1, length());
		} else {
			throw new VPackValueTypeException(ValueType.INT, ValueType.UINT, ValueType.SMALLINT);
		}
	}

	public Date getAsDate() {
		if (!isDate()) {
			throw new VPackValueTypeException(ValueType.UTC_DATE);
		}
		return DateUtil.toDate(vpack, start + 1, length());
	}

	public java.sql.Date getAsSQLDate() {
		if (!isDate()) {
			throw new VPackValueTypeException(ValueType.UTC_DATE);
		}
		return DateUtil.toSQLDate(vpack, start + 1, length());
	}

	public java.sql.Timestamp getAsSQLTimestamp() {
		if (!isDate()) {
			throw new VPackValueTypeException(ValueType.UTC_DATE);
		}
		return DateUtil.toSQLTimestamp(vpack, start + 1, length());
	}

	public String getAsString() {
		if (!isString()) {
			throw new VPackValueTypeException(ValueType.STRING);
		}
		return isLongString() ? getLongString() : getShortString();
	}

	public char getAsChar() {
		return getAsString().charAt(0);
	}

	private boolean isLongString() {
		return head() == (byte) 0xbf;
	}

	private String getShortString() {
		return new String(vpack, start + 1, length(), Charset.forName("UTF-8"));
	}

	private String getLongString() {
		return new String(vpack, start + 9, getLongStringLength(), Charset.forName("UTF-8"));
	}

	private int getLongStringLength() {
		return (int) NumberUtil.toLong(vpack, start + 1, 8);
	}

	private int getStringLength() {
		return isLongString() ? getLongStringLength() : head() - 0x40;
	}

	public byte[] getAsBinary() {
		if (!isBinary()) {
			throw new VPackValueTypeException(ValueType.BINARY);
		}
		final byte[] binary = BinaryUtil.toBinary(vpack, start + 1 + head() - ((byte) 0xbf), getBinaryLength());
		return binary;
	}

	public int getBinaryLength() {
		if (!isBinary()) {
			throw new VPackValueTypeException(ValueType.BINARY);
		}
		return getBinaryLengthUnchecked();
	}

	private int getBinaryLengthUnchecked() {
		return (int) NumberUtil.toLong(vpack, start + 1, head() - ((byte) 0xbf));
	}

	/**
	 * @return the number of members for an Array, Object or String
	 */
	public int getLength() {
		final long length;
		if (isString()) {
			length = getStringLength();
		} else if (!isArray() && !isObject()) {
			throw new VPackValueTypeException(ValueType.ARRAY, ValueType.OBJECT, ValueType.STRING);
		} else {
			final byte head = head();
			if (head == 0x01 || head == 0x0a) {
				// empty
				length = 0;
			} else if (head == 0x13 || head == 0x14) {
				// compact array or object
				final long end = NumberUtil.readVariableValueLength(vpack, start + 1, false);
				length = NumberUtil.readVariableValueLength(vpack, (int) (start + end - 1), true);
			} else {
				final int offsetsize = ObjectArrayUtil.getOffsetSize(head);
				final long end = NumberUtil.toLong(vpack, start + 1, offsetsize);
				if (head <= 0x05) {
					// array with no offset table or length
					final int dataOffset = findDataOffset();
					final VPackSlice first = new VPackSlice(vpack, start + dataOffset);
					length = (end - dataOffset) / first.getByteSize();
				} else if (offsetsize < 8) {
					length = NumberUtil.toLong(vpack, start + 1 + offsetsize, offsetsize);
				} else {
					length = NumberUtil.toLong(vpack, (int) (start + end - offsetsize), offsetsize);
				}
			}
		}
		return (int) length;
	}

	public int size() {
		return getLength();
	}

	/**
	 * Must be called for a nonempty array or object at start():
	 */
	protected int findDataOffset() {
		final int fsm = ObjectArrayUtil.getFirstSubMap(head());
		final int offset;
		if (fsm <= 2 && vpack[start + 2] != 0) {
			offset = 2;
		} else if (fsm <= 3 && vpack[start + 3] != 0) {
			offset = 3;
		} else if (fsm <= 5 && vpack[start + 6] != 0) {
			offset = 5;
		} else {
			offset = 9;
		}
		return offset;
	}

	public int getByteSize() {
		final long size;
		final byte head = head();
		final int valueLength = ValueLengthUtil.get(head);
		if (valueLength != 0) {
			size = valueLength;
		} else {
			switch (getType()) {
			case ARRAY:
			case OBJECT:
				if (head == 0x13 || head == 0x14) {
					// compact Array or Object
					size = NumberUtil.readVariableValueLength(vpack, start + 1, false);
				} else /* if (head <= 0x14) */ {
					size = NumberUtil.toLong(vpack, start + 1, ObjectArrayUtil.getOffsetSize(head));
				}
				break;
			case STRING:
				// long UTF-8 String
				size = getLongStringLength() + 1 + 8;
				break;
			case BINARY:
				size = 1 + head - ((byte) 0xbf) + getBinaryLengthUnchecked();
				break;
			case BCD:
				if (head <= 0xcf) {
					size = 1 + head + ((byte) 0xc7) + NumberUtil.toLong(vpack, start + 1, head - ((byte) 0xc7));
				} else {
					size = 1 + head - ((byte) 0xcf) + NumberUtil.toLong(vpack, start + 1, head - ((byte) 0xcf));
				}
				break;
			case CUSTOM:
				if (head == 0xf4 || head == 0xf5 || head == 0xf6) {
					size = 2 + NumberUtil.toLong(vpack, start + 1, 1);
				} else if (head == 0xf7 || head == 0xf8 || head == 0xf9) {
					size = 3 + NumberUtil.toLong(vpack, start + 1, 2);
				} else if (head == 0xfa || head == 0xfb || head == 0xfc) {
					size = 5 + NumberUtil.toLong(vpack, start + 1, 4);
				} else /* if (head == 0xfd || head == 0xfe || head == 0xff) */ {
					size = 9 + NumberUtil.toLong(vpack, start + 1, 8);
				}
				break;
			default:
				// TODO
				throw new InternalError();
			}
		}
		return (int) size;
	}

	/**
	 * @return array value at the specified index
	 * @throws VPackValueTypeException
	 */
	public VPackSlice get(final int index) {
		if (!isArray()) {
			throw new VPackValueTypeException(ValueType.ARRAY);
		}
		return getNth(index);
	}

	public VPackSlice get(final String attribute) throws VPackException {
		if (!isObject()) {
			throw new VPackValueTypeException(ValueType.OBJECT);
		}
		final byte head = head();
		VPackSlice result = new VPackSlice();
		if (head == 0x0a) {
			// special case, empty object
			result = new VPackSlice();
		} else if (head == 0x14) {
			// compact Object
			result = getFromCompactObject(attribute);
		} else {
			final int offsetsize = ObjectArrayUtil.getOffsetSize(head);
			final long end = NumberUtil.toLong(vpack, start + 1, offsetsize);
			final long n;
			if (offsetsize < 8) {
				n = NumberUtil.toLong(vpack, start + 1 + offsetsize, offsetsize);
			} else {
				n = NumberUtil.toLong(vpack, (int) (start + end - offsetsize), offsetsize);
			}
			if (n == 1) {
				// Just one attribute, there is no index table!
				final VPackSlice key = new VPackSlice(vpack, start + findDataOffset());

				if (key.isString()) {
					if (key.isEqualString(attribute)) {
						result = new VPackSlice(vpack, key.start + key.getByteSize());
					} else {
						// no match
						result = new VPackSlice();
					}
				} else if (key.isInteger()) {
					// translate key
					if (VPackSlice.attributeTranslator == null) {
						throw new VPackNeedAttributeTranslatorException();
					}
					if (key.translateUnchecked().isEqualString(attribute)) {
						result = new VPackSlice(vpack, key.start + key.getByteSize());
					} else {
						// no match
						result = new VPackSlice();
					}
				} else {
					// no match
					result = new VPackSlice();
				}
			} else {
				final long ieBase = end - n * offsetsize - (offsetsize == 8 ? 8 : 0);

				// only use binary search for attributes if we have at least
				// this many entries
				// otherwise we'll always use the linear search
				final long sortedSearchEntriesThreshold = 4;

				final boolean sorted = head >= 0x0b && head <= 0x0e;
				if (sorted && n >= sortedSearchEntriesThreshold) {
					// This means, we have to handle the special case n == 1
					// only in the linear search!
					result = searchObjectKeyBinary(attribute, ieBase, offsetsize, n);
				} else {
					result = searchObjectKeyLinear(attribute, ieBase, offsetsize, n);
				}
			}
		}
		return result;
	}

	/**
	 * translates an integer key into a string, without checks
	 */
	protected VPackSlice translateUnchecked() {
		final VPackSlice result = attributeTranslator.translate(getAsInt());
		return result != null ? result : new VPackSlice();
	}

	protected VPackSlice makeKey() throws VPackKeyTypeException, VPackNeedAttributeTranslatorException {
		if (isString()) {
			return this;
		}
		if (isInteger()) {
			if (VPackSlice.attributeTranslator == null) {
				throw new VPackNeedAttributeTranslatorException();
			}
			return translateUnchecked();
		}
		throw new VPackKeyTypeException("Cannot translate key of this type");
	}

	private VPackSlice getFromCompactObject(final String attribute)
			throws VPackKeyTypeException, VPackNeedAttributeTranslatorException {
		for (final Iterator> iterator = objectIterator(); iterator.hasNext();) {
			final Entry next = iterator.next();
			if (next.getKey().equals(attribute)) {
				return next.getValue();
			}
		}
		// not found
		return new VPackSlice();
	}

	private VPackSlice searchObjectKeyBinary(
		final String attribute,
		final long ieBase,
		final int offsetsize,
		final long n) throws VPackValueTypeException, VPackNeedAttributeTranslatorException {

		final boolean useTranslator = VPackSlice.attributeTranslator != null;
		VPackSlice result;
		long l = 0;
		long r = n - 1;

		for (;;) {
			// midpoint
			final long index = l + ((r - l) / 2);
			final long offset = ieBase + index * offsetsize;
			final long keyIndex = NumberUtil.toLong(vpack, (int) (start + offset), offsetsize);
			final VPackSlice key = new VPackSlice(vpack, (int) (start + keyIndex));
			int res = 0;
			if (key.isString()) {
				res = key.compareString(attribute);
			} else if (key.isInteger()) {
				// translate key
				if (!useTranslator) {
					// no attribute translator
					throw new VPackNeedAttributeTranslatorException();
				}
				res = key.translateUnchecked().compareString(attribute);
			} else {
				// invalid key
				result = new VPackSlice();
				break;
			}
			if (res == 0) {
				// found
				result = new VPackSlice(vpack, key.start + key.getByteSize());
				break;
			}
			if (res > 0) {
				if (index == 0) {
					result = new VPackSlice();
					break;
				}
				r = index - 1;
			} else {
				l = index + 1;
			}
			if (r < l) {
				result = new VPackSlice();
				break;
			}
		}
		return result;
	}

	private VPackSlice searchObjectKeyLinear(
		final String attribute,
		final long ieBase,
		final int offsetsize,
		final long n) throws VPackValueTypeException, VPackNeedAttributeTranslatorException {

		final boolean useTranslator = VPackSlice.attributeTranslator != null;
		VPackSlice result = new VPackSlice();
		for (long index = 0; index < n; index++) {
			final long offset = ieBase + index * offsetsize;
			final long keyIndex = NumberUtil.toLong(vpack, (int) (start + offset), offsetsize);
			final VPackSlice key = new VPackSlice(vpack, (int) (start + keyIndex));
			if (key.isString()) {
				if (!key.isEqualString(attribute)) {
					continue;
				}
			} else if (key.isInteger()) {
				// translate key
				if (!useTranslator) {
					// no attribute translator
					throw new VPackNeedAttributeTranslatorException();
				}
				if (!key.translateUnchecked().isEqualString(attribute)) {
					continue;
				}
			} else {
				// invalid key type
				result = new VPackSlice();
				break;
			}
			// key is identical. now return value
			result = new VPackSlice(vpack, key.start + key.getByteSize());
			break;
		}
		return result;

	}

	public VPackSlice keyAt(final int index) {
		if (!isObject()) {
			throw new VPackValueTypeException(ValueType.OBJECT);
		}
		return getNthKey(index);
	}

	public VPackSlice valueAt(final int index) {
		if (!isObject()) {
			throw new VPackValueTypeException(ValueType.OBJECT);
		}
		final VPackSlice key = getNthKey(index);
		return new VPackSlice(vpack, key.start + key.getByteSize());
	}

	private VPackSlice getNthKey(final int index) {
		return new VPackSlice(vpack, start + getNthOffset(index));
	}

	private VPackSlice getNth(final int index) {
		return new VPackSlice(vpack, start + getNthOffset(index));
	}

	/**
	 * 
	 * @return the offset for the nth member from an Array or Object type
	 */
	private int getNthOffset(final int index) {
		final int offset;
		final byte head = head();
		if (head == 0x13 || head == 0x14) {
			// compact Array or Object
			offset = getNthOffsetFromCompact(index);
		} else if (head == 0x01 || head == 0x0a) {
			// special case: empty Array or empty Object
			throw new IndexOutOfBoundsException();
		} else {
			final long n;
			final int offsetsize = ObjectArrayUtil.getOffsetSize(head);
			final long end = NumberUtil.toLong(vpack, start + 1, offsetsize);
			int dataOffset = findDataOffset();
			if (head <= 0x05) {
				// array with no offset table or length
				final VPackSlice first = new VPackSlice(vpack, start + dataOffset);
				n = (end - dataOffset) / first.getByteSize();
			} else if (offsetsize < 8) {
				n = NumberUtil.toLong(vpack, start + 1 + offsetsize, offsetsize);
			} else {
				n = NumberUtil.toLong(vpack, (int) (start + end - offsetsize), offsetsize);
			}
			if (index >= n) {
				throw new IndexOutOfBoundsException();
			}
			if (head <= 0x05 || n == 1) {
				// no index table, but all array items have the same length
				// or only one item is in the array
				// now fetch first item and determine its length
				if (dataOffset == 0) {
					dataOffset = findDataOffset();
				}
				offset = dataOffset + index * new VPackSlice(vpack, start + dataOffset).getByteSize();
			} else {
				final long ieBase = end - n * offsetsize + index * offsetsize - (offsetsize == 8 ? 8 : 0);
				offset = (int) NumberUtil.toLong(vpack, (int) (start + ieBase), offsetsize);
			}
		}
		return offset;
	}

	/**
	 * @return the offset for the nth member from a compact Array or Object type
	 */
	private int getNthOffsetFromCompact(final int index) {
		final long end = NumberUtil.readVariableValueLength(vpack, start + 1, false);
		final long n = NumberUtil.readVariableValueLength(vpack, (int) (start + end - 1), true);
		if (index >= n) {
			throw new IndexOutOfBoundsException();
		}
		final byte head = head();
		long offset = 1 + NumberUtil.getVariableValueLength(end);
		long current = 0;
		while (current != index) {
			final long byteSize = new VPackSlice(vpack, (int) (start + offset)).getByteSize();
			offset += byteSize;
			if (head == 0x14) {
				offset += byteSize;
			}
			++current;
		}
		return (int) offset;
	}

	private boolean isEqualString(final String s) {
		final String string = getAsString();
		return string.equals(s);
	}

	private int compareString(final String s) {
		final String string = getAsString();
		return string.compareTo(s);
	}

	public Iterator arrayIterator() {
		if (isArray()) {
			return new ArrayIterator(this);
		} else {
			throw new VPackValueTypeException(ValueType.ARRAY);
		}
	}

	public Iterator> objectIterator() {
		if (isObject()) {
			return new ObjectIterator(this);
		} else {
			throw new VPackValueTypeException(ValueType.OBJECT);
		}
	}

	protected byte[] getRawVPack() {
		return Arrays.copyOfRange(vpack, start, start + getByteSize());
	}

	@Override
	public String toString() {
		try {
			return new VPackParser.Builder().build().toJson(this, true);
		} catch (final VPackException e) {
			return super.toString();
		}
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + start;
		result = prime * result + Arrays.hashCode(getRawVPack());
		return result;
	}

	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final VPackSlice other = (VPackSlice) obj;
		if (start != other.start) {
			return false;
		}
		if (!Arrays.equals(getRawVPack(), other.getRawVPack())) {
			return false;
		}
		return true;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy