From 43fc31f75a2de06b23faefb0d2bbf1dd97290232 Mon Sep 17 00:00:00 2001
From: Julien HENRY
Date: Mon, 5 May 2014 16:43:19 +0200
Subject: SONAR-5189 Temporary fix of persistIt bugs
---
sonar-batch/src/main/java/com/persistit/Value.java | 5467 ++++++++++++++++++++
.../src/main/java/org/sonar/batch/index/Cache.java | 31 +-
.../main/java/org/sonar/batch/index/Caches.java | 38 +-
.../org/sonar/batch/scan/measure/MeasureCache.java | 2 +
.../sonar/batch/scan/measure/MeasureCacheTest.java | 13 +-
5 files changed, 5505 insertions(+), 46 deletions(-)
create mode 100644 sonar-batch/src/main/java/com/persistit/Value.java
(limited to 'sonar-batch/src')
diff --git a/sonar-batch/src/main/java/com/persistit/Value.java b/sonar-batch/src/main/java/com/persistit/Value.java
new file mode 100644
index 00000000000..e71ec43d4b6
--- /dev/null
+++ b/sonar-batch/src/main/java/com/persistit/Value.java
@@ -0,0 +1,5467 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package com.persistit;
+
+import com.persistit.encoding.CoderContext;
+import com.persistit.encoding.CoderManager;
+import com.persistit.encoding.HandleCache;
+import com.persistit.encoding.SerialValueCoder;
+import com.persistit.encoding.ValueCoder;
+import com.persistit.encoding.ValueDisplayer;
+import com.persistit.encoding.ValueRenderer;
+import com.persistit.exception.ConversionException;
+import com.persistit.exception.InvalidKeyException;
+import com.persistit.exception.MalformedValueException;
+import com.persistit.exception.PersistitException;
+import com.persistit.util.Debug;
+import com.persistit.util.Util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.NotActiveException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * Encapsulates the serialized form of an Object or a primitive
+ * value. To store data, the application modifies the Exchange's
+ * Value object and then invokes the {@link Exchange#store()}
+ * operation to write the encoded value into the database. To fetch data, the
+ * application modifies the Exchange's Key object,
+ * invokes the {@link Exchange#fetch()} operation and then reads the resulting
+ * state from the Value.
+ *
+ *
+ * A Value's state is represented internally by an array of bytes.
+ * Methods of this class encode primitive or object values into the byte array,
+ * and decode bytes in the array into values equivalent to their original
+ * values. For primitive-valued values, the decoded value is identical to the
+ * original value. That is, after:
+ *
+ *
+ * int a = 123;
+ * value.put(a);
+ * int b = value.get();
+ *
+ *
+ *
a == b is true. For object-valued items, the
+ * result will be an object that, subject to the accuracy of serialization code,
+ * is equal to the original object, but generally with different identity. That
+ * is, after:
+ *
+ *
+ * Object a = new Fricostat();
+ * value.put(a);
+ * int b = value.get();
+ *
+ *
+ *
usually a == b is false, but
+ * a.equals(b) is true.
+ *
+ *
+ * Value uses three strategies for these conversions:
+ *
+ *
For primitive types, their wrapper classes and certain other classes,
+ * Value uses built-in logic to perform these conversions. Objects
+ * of class java.lang.String, java.util.Date,
+ * java.math.BigInteger, java.math.BigDecimal and all
+ * arrays are encoded and decoded by built-in methods.
+ *
For an object of any other class, the encoding and decoding methods of
+ * Value attempt to find an associated
+ * {@link com.persistit.encoding.ValueCoder} to perform custom encoding and
+ * decoding of the object.
+ *
If there is no ValueCoder then if the class implements
+ * java.io.Serializable or java.io.Externalizable,
+ * encoding and decoding is performed through serialization logic using extended
+ * java.io.ObjectOutputStream and
+ * java.io.ObjectInputStream classes implemented by
+ * Value.
+ *
+ * Note that Value can only encode an object if it has a
+ * ValueCoder or implements either
+ * java.io.Serializable or java.io.Externalizable.
+ *
+ *
+ * Persistit JSA 1.1 introduces a faster, more compact internal storage format
+ * for serialization. Objects encoded in 1.1 without the assistance of a
+ * registered ValueCoder are normally stored in this new format and
+ * cannot be decoded by earlier versions of Persistit. However, the converse is
+ * not true: objects serialized by earlier versions of Persistit can be
+ * deserialized properly by 1.1. Thus you may upgrade an installed application
+ * from version 1.0 to 1.1 without running any type of database conversion
+ * process.
+ *
+ *
+ * In certain cases it may be preferable to store values using the default Java
+ * serialization format defined by the Java Object Serialization Specification. You may set the
+ * serialOverride configuration property to specify classes that
+ * are to be serialized in standard form.
+ *
+ * It may be useful to build a WeakHashMap associating the serialized content of
+ * a Value with an associated deserialized object to avoid object
+ * deserialization overhead or to implement correct identity semantics. Since
+ * Value is mutable it is a poor choice for use as a map key.
+ * Instead, an immutable {@link ValueState} should be used to hold an immutable
+ * copy of this state. Value and ValueState implement
+ * hashCode and equals in a compatible fashion so that
+ * code similar to the following works as expected:
+ *
+ *
+ * ...
+ * Value value = <some value>;
+ * if (!map.contains(value)) // uses the transient current state
+ * {
+ * ValueState vs = new ValueState(value);
+ * map.put(vs, object); // uses an immutable copy as the key
+ * }
+ *
+ *
+ *
+ *
+ *
Displayable Format
+ *
+ * The {@link #toString()} method of this class attempts to construct a
+ * human-readable representation of the serialized value. The Tree display panel
+ * of the AdminUI utility uses this capability to summarize the contents of
+ * values stored in a tree. The string representation is constructed as follows:
+ *
+ *
If the state represented by this Value is undefined, then
+ * return "undefined".
+ *
If the state is null or a boolean, return
+ * "null" "false", or "true".
+ *
If the value represents a primitive type, return the string
+ * representation of the value, prefixed by "(byte)", "(short)", "(char)",
+ * "(long)", or "(float)" for the corresponding types. Values of type
+ * int and double are presented without prefix to
+ * reduce clutter.
+ *
If the value represents a String, return a modified form of the string
+ * enclosed in double quotes. For each character of the string, if it is a
+ * double quote replace it by "\"", otherwise if it is outside of the printable
+ * ASCII character set replace the character in the modified string by "\b",
+ * "\t", "\n", "\r" or "\u0000" such that the modified string would be a valid
+ * Java string constant.
+ *
If the value represents a java.util.Date, return a formatted
+ * representation of the date using the format specified by {@link Key#SDF}.
+ * This is a readable format the displays the date with full precision,
+ * including milliseconds.
+ *
If the value represents an array, return a list of comma-separated
+ * element values surrounded by square brackets.
+ *
If the value represents one of the standard Collection
+ * implementations in the java.util package, then return a
+ * comma-separated list of values surrounded by square brackets.
+ *
If the value represents one of the standard Map
+ * implementations in the java.util package, then return a
+ * comma-separated list of key/value pairs surrounded by square brackets. Each
+ * key/value pair is represented by a string in the form
+ * key->value.
+ *
If the value represents an object of a class for which there is a
+ * registered {@link com.persistit.encoding.ValueDisplayer}, invoke the
+ * displayer's {@link com.persistit.encoding.ValueDisplayer#display display}
+ * method to format a displayable representation of the object.
+ *
If the value represents an object that has been stored using the version
+ * 1.1 storage mechanism described object, return the class name of the object
+ * followed by a comma-separated tuple, enclosed within curly brace characters,
+ * representing the value of each field of the object.
+ *
If the value represents an object encoded through standard Java
+ * serialization, return the string "(Serialized-object)" followed by a sequence
+ * of hex digits representing the serialized bytes. Note that this process does
+ * not attempt to deserialize the object, which might have unintended
+ * consequences.
+ *
If the value represents an object that has already been represented
+ * within the formatted result - for example, if a Collection
+ * contain two references to the same object - then instead of creating an
+ * additional string representing the second or subsequent instance, emit a back
+ * reference pointer in the form @NNN where NNN is the character offset within
+ * the displayable string where the first instance was found. (Note: does not
+ * apply to strings and the primitive wrapper classes.)
+ *
+ *
+ * For example, consider a Person class with fields for date of birth, first
+ * name, last name, salary and friends, an array of other Person objects. The
+ * result returned by {@link #toString} on a Value representing two
+ * Person instances, each with just the other as a friend, might appear as
+ * follows: (Note, space added for legibility.)
+ *
+ *
+ *
+ * In this example, John Smith's friends array contains a back
+ * reference to Mary Jones in the form "@0" because Mary's displayable reference
+ * starts at the beginning of the string.
+ *
+ *
Stream mode
+ *
+ * A Value normally contains just one object or primitive value. In
+ * its normal mode of operation, the put operation overwrites any
+ * previously held state, and the get operation retrieves the one
+ * object or primitive value represented by the current state of the
+ * Value. A subsequent invocation of get returns the
+ * same value.
+ *
+ *
+ * However, at certain times it is useful to store multiple items (fields)
+ * together in one Value object. To allow this, Value
+ * implements an alternative mode of operation called stream mode in
+ * which each put invocation appends a new field to the state
+ * rather than replacing the previous state. Similarly, get
+ * operations retrieve sequentially written fields rather than rereading the
+ * same field. Stream allows {@link com.persistit.encoding.ValueCoder
+ * ValueCoder} implementations to aggregate the multiple fields encapsulated
+ * within an encoded value.
+ *
+ *
Low-Level API
+ *
+ * The low-level API allows an application to bypass the encoding and decoding
+ * operations described above and instead to operate directly on the byte array
+ * stored in the database. This might be appropriate for an existing application
+ * that has already implemented its own serialization mechanisms. Applications
+ * should use these methods only if there is a compelling design requirement to
+ * do so.
+ *
+ *
+ *
+ *
+ *
+ *
+ * @version 1.1
+ */
+public final class Value {
+
+ /**
+ * A Value that is always EMPTY - i.e., for which isDefined()
+ * is always false.
+ */
+ public final static Value EMPTY_VALUE = new Value(null, 0, 0);
+ /**
+ * Default initial size of the byte array that backs this Value
+ * .
+ */
+ public final static int INITIAL_SIZE = 256;
+ /**
+ * Default maximum size to which the backing buffer can grow. The default
+ * value is 4Mb.
+ */
+ public final static int DEFAULT_MAXIMUM_SIZE = 1024 * 1024 * 4;
+
+ /**
+ * Absolute maximum size limit.
+ */
+ public final static int MAXIMUM_SIZE = 64 * 1024 * 1024;
+
+ private final static int SIZE_GRANULARITY = 256;
+
+ private final static char TRUE_CHAR = 'T';
+ private final static char FALSE_CHAR = 'F';
+ private final static String UNDEFINED = "undefined";
+
+ //
+ // Primitive values first. Codes allocated for .net types as well as
+ // Java and mutually available types.
+ //
+ private final static int TYPE_NULL = 1;
+ private final static int TYPE_BOOLEAN = 2;
+ private final static int TYPE_BYTE = 3;
+ // private final static int TYPE_UBYTE = 4;
+ private final static int TYPE_SHORT = 5;
+ // private final static int TYPE_USHORT = 6;
+ private final static int TYPE_CHAR = 7;
+ private final static int TYPE_INT = 8;
+ // private final static int TYPE_UINT = 9;
+ private final static int TYPE_LONG = 10;
+ // private final static int TYPE_ULONG = 11;
+ // private final static int TYPE_DECIMAL = 12;
+ private final static int TYPE_FLOAT = 13;
+ private final static int TYPE_DOUBLE = 14;
+ //
+ // Wrapper classes for primitive types.
+ // Note: we need to encode these differently than primitive
+ // types, even though we automatically convert ("autobox") because
+ // we need to know the component type of an array. Byte[] is
+ // different than byte[], and so we need to differentiate.
+ //
+ private final static int CLASS_BOOLEAN = 18;
+ private final static int CLASS_BYTE = 19;
+ // private final static int CLASS_UBYTE = 20;
+ private final static int CLASS_SHORT = 21;
+ // private final static int CLASS_USHORT = 22;
+ private final static int CLASS_CHAR = 23;
+ private final static int CLASS_INT = 24;
+ // private final static int CLASS_UINT = 25;
+ private final static int CLASS_LONG = 26;
+ // private final static int CLASS_ULONG = 27;
+ // private final static int CLASS_DECIMAL = 28;
+ private final static int CLASS_FLOAT = 29;
+ private final static int CLASS_DOUBLE = 30;
+ //
+ // Used when recording the component type of an array
+ //
+ private final static int CLASS_OBJECT = 31;
+ //
+ // Standard classes encoded with built-in encoding scheme.
+ //
+ private final static int CLASS_STRING = 32;
+ private final static int CLASS_DATE = 33;
+ private final static int CLASS_BIG_INTEGER = 34;
+ private final static int CLASS_BIG_DECIMAL = 35;
+
+ //
+ // Indicates a key range to be removed. Used only in representing
+ // pending remove operations in the Transaction tree.O
+ //
+ final static int CLASS_ANTIVALUE = 49;
+ //
+ // Indicates a reference to an object that was encoded earlier in this
+ // Value. Followed by the identityHashCode and a unique handle for the
+ // object.
+ //
+ private final static int CLASS_REREF = 50;
+ //
+ // Indicates a record in a directory tree.
+ //
+ final static int CLASS_ACCUMULATOR = 58;
+ final static int CLASS_TREE_STATISTICS = 59;
+ final static int CLASS_TREE = 60;
+ //
+ // Serialized type introducer. Followed by the Persistit handle for the
+ // type (even though serialization also represents that type - we need to
+ // be able to decode the class without deserializing the object).
+ //
+ private final static int CLASS_SERIALIZED = 61;
+ //
+ // Array class introducer. Followed by component type and
+ // length.
+ //
+ private final static int CLASS_ARRAY = 62;
+ //
+ // Array of arrays. Is followed by the number of dimensions and then the
+ // component type.
+ //
+ private final static int CLASS_MULTI_ARRAY = 63;
+ //
+ // The following introduce integer-valued class IDs, sizes and counts.
+ // For each of these, the bottom four bits hold the most significant 4
+ // bits of the integer value being represented.
+ //
+ // A CLASS handle is by the current ClassIndex. There is a
+ // one-to-one mapping between handle values and ClassInfo objects.
+ // Each ClassInfo identifies a Class.
+ //
+ // A SIZE is a count of bytes. A size is encoded as a prefix for a
+ // variable-length inner item. The outermost item is never prefixed
+ // the size is given by the raw byte count.
+ //
+ // A COUNT is a count of items within a list.
+ //
+ // CLASS1 / SIZE2 / COUNT2
+ // is followed by no additional bytes, and therefore
+ // can encode values between 0 and 15
+ //
+ // CLASS2 / SIZE2 / COUNT2
+ // is followed by one byte, and therefore can encode
+ // values between 0 and 2**12 - 1.
+ //
+ // CLASS3 / SIZE4 /COUNT3
+ // is followed by two bytes, and therefore can encode
+ // values between 0 and 2**20 - 1.
+ //
+ // CLASS5 / SIZE5 / COUNT5
+ // is followed by a 4-byte integer. The low bytes of
+ // the introducer byte are ignored. This representation
+ // can encode values up to Integer.MAX_VALUE. (With
+ // 5 bits left available if needed for longer values.
+ //
+ // There are 15 open values following the CLASS encoding scheme. Collisions
+ // are avoided because CLASS5 always has zeros in its low 4 bits, meaning
+ // that the highest byte in a standard class encoding will be 0xF0 (240).
+ //
+ // An MVV is introduced by 0xFE (254) as the first byte. This is mostly
+ // opaque to the Value class but exposed here for consistency,
+ // documentation,
+ // and for use by debug and toString() methods.
+ //
+ private final static int TYPE_MVV = MVV.TYPE_MVV;
+ //
+ // Note that a LONGREC is introduced by 0xFF (255) as the first byte.
+ //
+
+ private final static int BASE1 = 0x00;
+ private final static int BASE2 = 0x10;
+ private final static int BASE3 = 0x20;
+ private final static int BASE5 = 0x30;
+
+ private final static int CLASS1 = 0x40;
+ // private final static int CLASS2 = 0x50;
+ // private final static int CLASS3 = 0x60;
+ private final static int CLASS5 = 0x70;
+ //
+ private final static int COUNT1 = 0x80;
+ // private final static int COUNT2 = 0x90;
+ // private final static int COUNT3 = 0xA0;
+ private final static int COUNT5 = 0xB0;
+ //
+ private final static int SIZE1 = 0xC0;
+ // private final static int SIZE2 = 0xD0;
+ // private final static int SIZE3 = 0xE0;
+ private final static int SIZE5 = 0xF0;
+
+ private final static int[] ENCODED_SIZE_BITS = {-1, 0x00, 0x10, 0x20, -1, 0x30};
+
+ private final static Class>[] CLASSES = {
+ null, // 0
+ Void.TYPE, Boolean.TYPE, Byte.TYPE,
+ null, // reserved for .net unsigned byte
+ Short.TYPE,
+ null, // reserved for .net unsigned short
+ Character.TYPE, Integer.TYPE,
+ null, // reserved for .net unsigned int
+ Long.TYPE,
+ null, // reserved for .net unsigned long
+ null, // reserved for .net decimal
+ Float.TYPE, Double.TYPE, null,
+
+ null, // 16
+ Void.class, Boolean.class, Byte.class,
+ null, // reserved for .net unsigned byte
+ Short.class,
+ null, // reserved for .net unsigned short
+ Character.class, Integer.class,
+ null, // reserved for .net unsigned int
+ Long.class,
+ null, // reserved for .net unsigned long
+ null, // reserved for .net decimal
+ Float.class, Double.class, Object.class,
+
+ String.class, // 32
+ Date.class, BigInteger.class, BigDecimal.class, null, null, null, null, null, null, null, null, null, null,
+ null, null,
+
+ null, // 48
+ AntiValue.class, Object.class, // 50 Reference to previously encoded
+ // Object,
+ null, null, null, null, null, null, null, null, null, Serializable.class, // 60
+ Serializable.class, // 61
+
+ };
+
+ //
+ // A non-negative element of this array denotes the fixed number of bytes
+ // required to represent the corresponding array element.
+ // Element value -1 means that the corresponding item is variable-length.
+ //
+ private final static int[] FIXED_ENCODING_SIZES = {0, // 0
+ 0, // null
+ 1, // boolean
+ 1, // byte
+ 1, // unsigned byte
+ 2, // short
+ 2, // unsigned short
+ 2, // char
+ 4, // int
+ 4, // unsigned int
+ 8, // long
+ 8, // unsigned long
+ 16, // decimal
+ 4, // float
+ 8, // double
+ -1,
+
+ 0, // 16
+ 0, // null
+ 1, // boolean
+ 1, // byte
+ 1, // unsigned byte
+ 2, // short
+ 2, // unsigned short
+ 2, // char
+ 4, // int
+ 4, // unsigned int
+ 8, // long
+ 8, // unsigned long
+ 16, // decimal
+ 4, // float
+ 8, // double
+ -1,
+
+ -1, // 32 String
+ 8, // Date
+ -1, // BigInteger
+ -1 // BigDecimal
+
+ };
+
+ private final static int TOO_MANY_LEVELS_THRESHOLD = 100;
+ private final static int SAB_INCREMENT = 1024;
+ private final Map, Class>[]> _arrayTypeCache = new HashMap, Class>[]>();
+ private int _maximumSize = DEFAULT_MAXIMUM_SIZE;
+
+ private int _size = 0;
+ private int _end = 0;
+ private int _next = 0;
+ private int _depth = 0;
+ private int[] _endArray;
+ private int _level;
+
+ private byte[] _bytes;
+ private byte[] _longBytes;
+ private int _longSize;
+ private boolean _longMode;
+
+ private long _pointer = -1;
+ private int _pointerPageType = -1;
+
+ private ValueObjectInputStream _vis;
+ private ValueObjectOutputStream _vos;
+
+ private int _serializedItemCount;
+ private WeakReference _valueCacheWeakRef;
+ private ValueCache _valueCache;
+
+ private boolean _shared = true;
+ private DefaultValueCoder _currentCoder;
+ private Object _currentObject;
+
+ private final Persistit _persistit;
+
+ private SoftReference _stringAssemblyBufferSoftRef;
+
+ /**
+ * Construct a Value object with default initial and maximum
+ * encoded sizes.
+ */
+ public Value(final Persistit persistit) {
+ this(persistit, INITIAL_SIZE, DEFAULT_MAXIMUM_SIZE);
+ }
+
+ /**
+ * Construct a Value object with specified initial encoded size
+ * and default maximum size.
+ *
+ * @param initialSize
+ * Initial size of the encoded value buffer.
+ */
+ public Value(final Persistit persistit, final int initialSize) {
+ this(persistit, initialSize, DEFAULT_MAXIMUM_SIZE);
+ }
+
+ /**
+ * Construct a Value object with specific initial encoded size
+ * and specified maximum size.
+ *
+ * @param initialSize
+ * Initial size of the encoded value buffer.
+ * @param maximumSize
+ * Maximum size of the encoded value buffer.
+ */
+ public Value(final Persistit persistit, final int initialSize, final int maximumSize) {
+ _persistit = persistit;
+ _bytes = new byte[initialSize];
+ _maximumSize = maximumSize;
+ }
+
+ /**
+ * Construct a new Value that represents the same data as the
+ * source.
+ *
+ * @param source
+ * A Value whose state should be copied as the
+ * initial state of this Value.
+ */
+ public Value(final Value source) {
+ this(source._persistit, source._bytes.length, source._maximumSize);
+ source.copyTo(this);
+ }
+
+ /**
+ * Remove all content from this Value. This method also
+ * disables stream mode.
+ *
+ * @return this Value to permit call-chaining
+ *
+ */
+ public Value clear() {
+ _size = 0;
+ reset();
+ return this;
+ }
+
+ void clear(final boolean secure) {
+ if (secure) {
+ Util.clearBytes(_bytes, 0, _bytes.length);
+ if (_longBytes != null) {
+ Util.clearBytes(_longBytes, 0, _longBytes.length);
+ }
+ _longSize = 0;
+ if (_stringAssemblyBufferSoftRef != null) {
+ final StringBuilder sb = _stringAssemblyBufferSoftRef.get();
+ if (sb != null) {
+ final int length = sb.length();
+ for (int index = 0; index < length; index++) {
+ sb.setCharAt(index, (char) 0);
+ }
+ }
+ }
+ }
+ clear();
+ }
+
+ private StringBuilder getStringAssemblyBuffer(final int size) {
+ StringBuilder sb = null;
+ if (_stringAssemblyBufferSoftRef != null) {
+ sb = _stringAssemblyBufferSoftRef.get();
+ }
+ if (sb == null) {
+ sb = new StringBuilder(size + SAB_INCREMENT);
+ _stringAssemblyBufferSoftRef = new SoftReference(sb);
+ } else {
+ sb.setLength(0);
+ }
+ return sb;
+ }
+
+ /**
+ * Copy the state of this Value to another Value.
+ *
+ * @param target
+ * The Value to which state should be copied.
+ */
+ public void copyTo(final Value target) {
+ if (target == this) {
+ return;
+ }
+ Debug.$assert0.t(!isLongRecordMode());
+ Debug.$assert0.t(!target.isLongRecordMode());
+
+ target._maximumSize = _maximumSize;
+ target.ensureFit(_size);
+ System.arraycopy(_bytes, 0, target._bytes, 0, _size);
+ target._size = _size;
+ target._pointer = _pointer;
+ target._longMode = _longMode;
+ target.reset();
+ }
+
+ /**
+ * Hash code for the current state of this Value. Note that if
+ * the underlying state changes, hashCode will produce a
+ * different result. Construct a {@link ValueState} instance to hold an
+ * immutable copy of the current state of a Value.
+ */
+ @Override
+ public int hashCode() {
+ int hashCode = 0;
+ for (int index = 0; index < _size; index++) {
+ hashCode = (hashCode * 17) ^ (_bytes[index] & 0xFF);
+ }
+ return hashCode & 0x7FFFFFFF;
+ }
+
+ /**
+ * Implements the equals method such that Value
+ * and {@link ValueState} objects may be used interchangeably as map keys.
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof Value) {
+ final Value value = (Value) obj;
+ if (value._size != _size)
+ return false;
+ for (int i = 0; i < _size; i++) {
+ if (value._bytes[i] != _bytes[i])
+ return false;
+ }
+ return true;
+ } else if (obj instanceof ValueState) {
+ return ((ValueState) obj).equals(this);
+ } else return false;
+ }
+
+ /**
+ * Reduce the backing byte buffer to the minimal length needed to represent
+ * the current state.
+ *
+ * @return true if the size was actually reduced.
+ */
+ public boolean trim() {
+ return trim(0);
+ }
+
+ /**
+ * Reduce the backing byte buffer to the greater of the minimal length
+ * needed to represent the current state and the specified lower bound.
+ *
+ * @param newSize
+ * the minimum size of the backing buffer.
+ * @return true if the size was actually reduced.
+ */
+ public boolean trim(final int newSize) {
+ if (_bytes.length > _size && _bytes.length > newSize) {
+ final byte[] bytes = new byte[Math.max(_size, newSize)];
+ System.arraycopy(_bytes, 0, bytes, 0, _size);
+ _bytes = bytes;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ *
+ * Ensures that the specified number of bytes can be appended to the backing
+ * byte array. If the available space is too small, this method replaces the
+ * array with a new one having at least length available bytes.
+ * Applications using the low-level API must call {@link #getEncodedBytes}
+ * to get a reference to the new array after this replacement has occurred.
+ *
+ *
+ * @param length
+ * @return true if the backing byte array was replaced by a
+ * larger array.
+ */
+ public boolean ensureFit(final int length) {
+ if (_size + length <= _bytes.length) {
+ return false;
+ }
+ if (_size + length > _maximumSize) {
+ throw new ConversionException("Requested size=" + (_size + length) + " exceeds maximum size="
+ + _maximumSize);
+ }
+ int newArraySize = (((_size + length) * 2 + SIZE_GRANULARITY - 1) / SIZE_GRANULARITY) * SIZE_GRANULARITY;
+ if (newArraySize > _maximumSize) {
+ newArraySize = _maximumSize;
+ }
+ final byte[] bytes = new byte[newArraySize];
+ System.arraycopy(_bytes, 0, bytes, 0, _size);
+ _bytes = bytes;
+
+ return true;
+ }
+
+ /**
+ * Copy a subarray from the encoded byte array to a target. This method is
+ * part of the Low-Level API.
+ *
+ * @param dest
+ * The target byte array
+ * @param from
+ * Offset from which to start the copy
+ * @param to
+ * Offset into the target at which the subarray should be copied
+ * @param length
+ * Number of bytes to copy
+ * @throws ArrayIndexOutOfBoundsException
+ */
+ public void copyFromEncodedBytes(final byte[] dest, final int from, final int to, final int length) {
+ System.arraycopy(_bytes, from, dest, to, length);
+ }
+
+ /**
+ * Returns the number of bytes used to encode the current value. This method
+ * is part of the Low-Level API.
+ *
+ * @return The size
+ */
+ public int getEncodedSize() {
+ return _size;
+ }
+
+ /**
+ * Replace the encoded value with bytes from a supplied array. This method
+ * is part of the Low-Level API.
+ *
+ * @param from
+ * Byte array from which to copy the encoded value
+ * @param offset
+ * Offset to first byte in the supplied array from which to copy
+ * @param length
+ * Number of bytes to copy
+ * @throws ArrayIndexOutOfBoundsException
+ * if the supplied offset or size exceed the bounds of the
+ * supplied array
+ * @throws ConversionException
+ * if the resulting value size exceeds the maximum size
+ */
+ public void putEncodedBytes(final byte[] from, final int offset, final int length) {
+ ensureFit(length);
+ if (length > 0) {
+ System.arraycopy(from, offset, _bytes, 0, length);
+ }
+ setEncodedSize(length);
+ }
+
+ /**
+ * Returns the backing byte array used to hold the state of this
+ * Value. This method is part of the Low-Level API.
+ *
+ * @return The byte array
+ */
+ public byte[] getEncodedBytes() {
+ return _bytes;
+ }
+
+ /**
+ * Sets the length of the encoded data in the backing byte array. This
+ * length governs the number of bytes from the backing byte array that will
+ * be stored in the database during the next store operation.
+ * This method is part of the Low-Level API.
+ */
+ public void setEncodedSize(final int size) {
+ if (size < 0 || size > _bytes.length) {
+ throw new IllegalArgumentException("Size " + size + " exceeds capacity");
+ }
+ _size = size;
+ _depth = 0;
+ }
+
+ /**
+ * Returns the maximum size to which the backing buffer can grow.
+ *
+ * @return The maximum size
+ */
+ public int getMaximumSize() {
+ return _maximumSize;
+ }
+
+ /**
+ * Modifies the maximum size to which the backing buffer can grow and trims
+ * the current backing buffer to be no larger than the new maximum.
+ *
+ * @param size
+ * The maximum size
+ *
+ * @throws IllegalArgumentException
+ * If the backing buffer is already larger than
+ * size, this method
+ *
+ */
+ public void setMaximumSize(final int size) {
+ if (size < _size) {
+ throw new IllegalArgumentException("Value is larger than new maximum size");
+ }
+ if (size > MAXIMUM_SIZE) {
+ throw new IllegalArgumentException("Value is larger than absolute limit " + MAXIMUM_SIZE);
+ }
+ trim(size);
+ _maximumSize = size;
+ }
+
+ public int getCursor() {
+ return _next;
+ }
+
+ public void setCursor(final int cursor) {
+ if (cursor < 0 || cursor > _size) {
+ throw new IllegalArgumentException("Cursor out of bound (0," + _size + ")");
+ }
+ _next = cursor;
+ }
+
+ /**
+ * Enables or disables stream mode. See Stream
+ * Mode for further information.
+ *
+ * @param b
+ * true to enable stream mode, false to
+ * disable it.
+ */
+ public void setStreamMode(final boolean b) {
+ reset();
+ _depth = b ? 1 : 0;
+ }
+
+ /**
+ * Indicates whether stream mode is enabled. See Stream Mode for further information.
+ *
+ * @return true if stream mode is enabled.
+ */
+ public boolean isStreamMode() {
+ return _depth > 0;
+ }
+
+ /**
+ * Indicates whether there is data associated with this Value.
+ * The result of fetching a Key that has no associated record
+ * in the database leaves the corresponding Value in an
+ * undefined state. Note that a Value containing null is
+ * defined. Persistit distinguishes between null and undefined states.
+ *
+ * @return true if there is data represented by this
+ * Value .
+ */
+ public boolean isDefined() {
+ return _size != 0;
+ }
+
+ /**
+ * Tests whether the data held by this Value is null.
+ *
+ * @return true if the current state of this Value
+ * represents null.
+ */
+ public boolean isNull() {
+ return getTypeHandle() == TYPE_NULL;
+ }
+
+ /**
+ * Determine whether the data held by this Value is null. As a
+ * side effect, if skipNull is true and Stream Mode is enabled this method also advances
+ * the cursor to the next field if the current field is null.
+ *
+ * @param skipNull
+ * if true, the Value is in stream mode
+ * and the current field is null, then advance the cursor to next
+ * field
+ * @return true if the current state of this Value
+ * represents null.
+ */
+ public boolean isNull(final boolean skipNull) {
+ if (isNull()) {
+ if (skipNull && _depth > 0) {
+ _next++;
+ _serializedItemCount++;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ boolean isAntiValue() {
+ if (_size == 0) {
+ return false;
+ }
+ return (_bytes[0] & 0xFF) == CLASS_ANTIVALUE;
+ }
+
+ /**
+ * Provides a String representation of the state of this Value.
+ *
+ * @see #decodeDisplayable(boolean, StringBuilder)
+ *
+ * @return A String value. If this Value is undefined, returns the word
+ * "undefined". Note that this value is indistinguishable from the
+ * result of toString on a Value whose
+ * state represents the string "undefined". Invoke the
+ * {@link #isDefined()} method to determine reliably whether the
+ * Value is defined.
+ */
+ @Override
+ public String toString() {
+ if (_size == 0) {
+ return UNDEFINED;
+ }
+
+ if (_longMode && (_bytes[0] & 0xFF) == Buffer.LONGREC_TYPE && (_size >= Buffer.LONGREC_SIZE)) {
+ return toStringLongMode();
+ }
+
+ final int saveDepth = _depth;
+ final int saveLevel = _level;
+ final int saveNext = _next;
+ final int saveEnd = _end;
+ final StringBuilder sb = new StringBuilder();
+ setStreamMode(true);
+ try {
+ boolean first = true;
+ while (_next < _size) {
+ if (!first) {
+ sb.append(",");
+ }
+ first = false;
+ decodeDisplayable(true, sb, null);
+ }
+ } catch (final ConversionException e) {
+ final int truncatedSize = Math.min(_size - _next, 256);
+ sb.append("ConversionException " + e.getCause() + " index=" + _next + " size=" + (_size - _next) + ": "
+ + Util.hexDump(_bytes, 0, truncatedSize));
+ } catch (final Exception e) {
+ sb.append("Exception " + e + " while decoding value at index=" + _next + ": " + e);
+ } finally {
+ _end = saveEnd;
+ _next = saveNext;
+ _level = saveLevel;
+ _depth = saveDepth;
+ }
+ return sb.toString();
+ }
+
+ private String toStringLongMode() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("LongRec size=");
+ sb.append(Buffer.decodeLongRecordDescriptorSize(_bytes, 0));
+ sb.append(" page=");
+ sb.append(Buffer.decodeLongRecordDescriptorPointer(_bytes, 0));
+ return sb.toString();
+ }
+
+ /**
+ * Appends a displayable, printable String version of a value into the
+ * supplied StringBuilder. If quoted is true, then
+ * the all String values in the result will be enclosed and converted to a
+ * printable format.
+ *
+ * @see #decodeDisplayable(boolean, StringBuilder, CoderContext)
+ *
+ * @param quoted
+ * true to quote and convert all strings to
+ * printable form.
+ * @param sb
+ * A StringBuilder to which the displayable value
+ * will be appended.
+ */
+ public void decodeDisplayable(final boolean quoted, final StringBuilder sb) {
+ decodeDisplayable(quoted, sb, null);
+ }
+
+ /**
+ * Appends a displayable String version of a value into the supplied
+ * StringBuilder. If quoted is true, then the all
+ * String values in the result will be enclosed and converted to a printable
+ * format.
+ *
+ * @param quoted
+ * true to quote and convert all strings to
+ * printable form.
+ * @param sb
+ * A StringBuilder to which the displayable value
+ * will be appended.
+ * @param context
+ * A CoderContext to be passed to any underlying
+ * {@link ValueDisplayer}.
+ */
+ public void decodeDisplayable(final boolean quoted, final StringBuilder sb, final CoderContext context) {
+ checkSize(1);
+
+ final int start = _next;
+ final int level = _level;
+
+ final int classHandle = nextType();
+ final int currentItemCount = _serializedItemCount;
+ final boolean isVariableLength = (_next - start) > 1;
+ switch (classHandle) {
+ case TYPE_NULL: {
+ _next = start;
+ sb.append(getNull());
+ break;
+ }
+
+ case TYPE_BYTE: {
+ _next = start;
+ appendParenthesizedFriendlyClassName(sb, byte.class);
+ sb.append(getByte());
+ break;
+ }
+
+ case TYPE_CHAR: {
+ _next = start;
+ appendParenthesizedFriendlyClassName(sb, char.class);
+ if (quoted)
+ Util.appendQuotedChar(sb, getChar());
+ else sb.append((int) getChar());
+ break;
+ }
+
+ case TYPE_INT: {
+ _next = start;
+ sb.append(getInt());
+ break;
+ }
+
+ case TYPE_LONG: {
+ _next = start;
+ appendParenthesizedFriendlyClassName(sb, long.class);
+ sb.append(getLong());
+ break;
+ }
+
+ case TYPE_FLOAT: {
+ _next = start;
+ appendParenthesizedFriendlyClassName(sb, float.class);
+ sb.append(getFloat());
+ break;
+ }
+
+ case TYPE_DOUBLE: {
+ _next = start;
+ sb.append(getDouble());
+ break;
+ }
+
+ case TYPE_BOOLEAN: {
+ _next = start;
+ sb.append(getBoolean());
+ break;
+ }
+
+ case CLASS_STRING:
+ case CLASS_BOOLEAN:
+ case CLASS_BYTE:
+ case CLASS_SHORT:
+ case CLASS_CHAR:
+ case CLASS_INT:
+ case CLASS_LONG:
+ case CLASS_FLOAT:
+ case CLASS_DOUBLE: {
+ // For these built-in types we don't display the back
+ // reference.
+ _next = start;
+ if (_level != level)
+ _end = popEnd();
+ final Object value = get(null, context);
+ appendDisplayable(sb, value, quoted, false);
+ break;
+ }
+
+ case CLASS_REREF: {
+ _next = start;
+ final Object value = get(null, context);
+ appendDisplayable(sb, value, quoted, true);
+ break;
+ }
+
+ case CLASS_ARRAY: {
+ try {
+ _depth++;
+ _serializedItemCount++;
+ registerEncodedObject(sb.length());
+ final int componentClassHandle = nextType();
+ switch (componentClassHandle) {
+ case TYPE_BOOLEAN: {
+ sb.append("boolean[]{");
+ final int length = _end - _next;
+ for (int index = 0; index < length; index++) {
+ if (index > 0)
+ sb.append(',');
+ sb.append(toBoolean(_next) ? "true" : "false");
+ _next++;
+ }
+ break;
+ }
+
+ case TYPE_BYTE: {
+ sb.append("byte[]{");
+ final int length = _end - _next;
+ for (int index = 0; index < length; index++) {
+ if (index > 0)
+ sb.append(',');
+ sb.append(Util.getByte(_bytes, _next));
+ _next++;
+ }
+ break;
+ }
+
+ case TYPE_SHORT: {
+ sb.append("short[]{");
+ final int length = arraySize(_end, _next, 2);
+ for (int index = 0; index < length; index++) {
+ if (index > 0)
+ sb.append(',');
+ sb.append(Util.getShort(_bytes, _next));
+ _next += 2;
+ }
+ break;
+ }
+
+ case TYPE_CHAR: {
+ sb.append("char[]{");
+ final int length = arraySize(_end, _next, 2);
+ for (int index = 0; index < length; index++) {
+ if (index > 0)
+ sb.append(',');
+ final int c = Util.getChar(_bytes, _next);
+ if (quoted)
+ Util.appendQuotedChar(sb, c);
+ else sb.append(c);
+ _next += 2;
+ }
+ break;
+ }
+
+ case TYPE_INT: {
+ sb.append("int[]{");
+ final int length = arraySize(_end, _next, 4);
+ for (int index = 0; index < length; index++) {
+ if (index > 0)
+ sb.append(',');
+ sb.append(Util.getInt(_bytes, _next));
+ _next += 4;
+ }
+ break;
+ }
+
+ case TYPE_LONG: {
+ sb.append("long[]{");
+ final int length = arraySize(_end, _next, 8);
+ for (int index = 0; index < length; index++) {
+ if (index > 0)
+ sb.append(',');
+ sb.append(Util.getLong(_bytes, _next));
+ _next += 8;
+ }
+ break;
+ }
+
+ case TYPE_FLOAT: {
+ sb.append("float[]{");
+ final int length = arraySize(_end, _next, 4);
+ for (int index = 0; index < length; index++) {
+ if (index > 0)
+ sb.append(',');
+ final float f = Float.intBitsToFloat(Util.getInt(_bytes, _next));
+ sb.append(f);
+ _next += 4;
+ }
+ break;
+ }
+
+ case TYPE_DOUBLE: {
+ sb.append("double[]{");
+ final int length = arraySize(_end, _next, 8);
+ for (int index = 0; index < length; index++) {
+ if (index > 0)
+ sb.append(',');
+ final double d = Double.longBitsToDouble(Util.getLong(_bytes, _next));
+ sb.append(d);
+ _next += 8;
+ }
+ break;
+ }
+
+ default: {
+ final Class> cl = classForHandle(componentClassHandle);
+ if (cl != null)
+ appendFriendlyClassName(sb, cl);
+ sb.append("[]{");
+ final int length = decodeElementCount();
+ for (int index = 0; index < length; index++) {
+ if (index > 0)
+ sb.append(',');
+ decodeDisplayable(quoted, sb, context);
+ }
+ break;
+ }
+ }
+ sb.append('}');
+ } finally {
+ _depth--;
+ }
+ if (isVariableLength)
+ closeVariableLengthItem();
+ break;
+ }
+ case CLASS_MULTI_ARRAY: {
+ _next = start;
+ decodeDisplayableMultiArray(quoted, sb, context, null);
+ break;
+ }
+ case CLASS_SERIALIZED: {
+ _next = start;
+ final int length = sb.length();
+ _depth++;
+ try {
+ final Object object = get(null, context);
+ getValueCache().store(currentItemCount, new DisplayMarker(sb.length()));
+ appendDisplayable(sb, object, quoted, false);
+ } catch (final Exception e) {
+ sb.setLength(length);
+ sb.append("(Serialized-Object)");
+ Util.bytesToHex(sb, _bytes, start, _end - start);
+ } finally {
+ _depth--;
+ if (isVariableLength)
+ closeVariableLengthItem();
+ }
+ break;
+ }
+
+ case TYPE_MVV: {
+ final int savedSize = _size;
+ sb.append('[');
+
+ try {
+ MVV.visitAllVersions(new MVV.VersionVisitor() {
+ boolean first = true;
+
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public void sawVersion(final long version, final int offset, final int valueLength) {
+ if (!first) {
+ sb.append(',');
+ }
+ sb.append(TransactionStatus.versionString(version));
+ try {
+ final long tc = _persistit.getTransactionIndex().commitStatus(version, Long.MAX_VALUE, 0);
+ sb.append("<" + TransactionStatus.tcString(tc) + ">");
+ } catch (final Exception e) {
+ sb.append("<" + e + ">");
+ }
+
+ sb.append(':');
+ if (valueLength == 0) {
+ sb.append(UNDEFINED);
+ } else {
+ _next = offset;
+ _end = _size = _next + valueLength;
+ decodeDisplayable(quoted, sb, context);
+ }
+ first = false;
+ }
+
+ }, getEncodedBytes(), 0, getEncodedSize());
+ } catch (final Throwable t) {
+ sb.append("<<").append(t).append(">>");
+ } finally {
+ _next = _end = _size = savedSize;
+ }
+
+ sb.append(']');
+ }
+ break;
+
+ default: {
+ if (classHandle >= CLASS1) {
+ try {
+ final Class> clazz = classForHandle(classHandle);
+ ValueCoder coder = null;
+ _depth++;
+ getValueCache().store(currentItemCount, new DisplayMarker(sb.length()));
+
+ _serializedItemCount++;
+
+ if (clazz != null) {
+ coder = getValueCoder(clazz);
+ }
+ if (coder instanceof ValueDisplayer) {
+ appendParenthesizedFriendlyClassName(sb, clazz);
+ ((ValueDisplayer) coder).display(this, sb, clazz, context);
+ } else if (coder instanceof SerialValueCoder) {
+ final int length = sb.length();
+ try {
+ _next = start;
+ final Object object = get(null, context);
+ getValueCache().store(currentItemCount, new DisplayMarker(sb.length()));
+ appendDisplayable(sb, object, quoted, false);
+ } catch (final Exception e) {
+ sb.setLength(length);
+ sb.append("(Serialized-Object)");
+ Util.bytesToHex(sb, _bytes, start, _end - start);
+ }
+ } else {
+ appendParenthesizedFriendlyClassName(sb, clazz);
+ sb.append('{');
+ boolean first = true;
+ while (hasMoreItems()) {
+ if (!first)
+ sb.append(',');
+ first = false;
+ decodeDisplayable(true, sb, null);
+ }
+ sb.append('}');
+ }
+ break;
+ } catch (final Throwable t) {
+ sb.append("<<" + t + ">>");
+ } finally {
+ _depth--;
+ if (isVariableLength)
+ closeVariableLengthItem();
+ }
+ } else {
+ try {
+ _next = start;
+ final Object value = get(null, context);
+ getValueCache().store(currentItemCount, new DisplayMarker(sb.length()));
+ appendDisplayable(sb, value, quoted, false);
+ } catch (final Throwable t) {
+ sb.append("<<" + t + ">>");
+ } finally {
+ if (isVariableLength)
+ closeVariableLengthItem();
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ private void decodeDisplayableMultiArray(final boolean quoted, final StringBuilder sb, final CoderContext context,
+ Class> prototype) {
+ final int start = _next;
+ final int type = nextType(CLASS_MULTI_ARRAY, CLASS_REREF);
+ if (type == CLASS_REREF) {
+ _next = start;
+ final Object array = get(null, null);
+ if (array == null || array instanceof DisplayMarker || array.getClass().isArray()) {
+ appendDisplayable(sb, array, quoted, true);
+ } else {
+ throw new ConversionException("Referenced object is not an array");
+ }
+ } else {
+ try {
+ _depth++;
+ final int componentClassHandle = nextType();
+ checkSize(1);
+ final int dimensions = _bytes[_next++] & 0xFF;
+ if (prototype == null) {
+ prototype = Array.newInstance(classForHandle(componentClassHandle), new int[dimensions]).getClass();
+ }
+ final int length = decodeElementCount();
+
+ _serializedItemCount++;
+ registerEncodedObject(sb.length());
+ sb.append('[');
+ final Class> componentType = prototype.getComponentType();
+ if (componentType.getComponentType().isArray()) {
+ for (int index = 0; index < length; index++) {
+ if (index > 0)
+ sb.append(',');
+ decodeDisplayableMultiArray(quoted, sb, context, componentType);
+ }
+ } else {
+ for (int index = 0; index < length; index++) {
+ if (index > 0)
+ sb.append(',');
+ decodeDisplayable(quoted, sb, context);
+ }
+ }
+ sb.append(']');
+ } finally {
+ _depth--;
+ }
+ closeVariableLengthItem();
+ }
+ }
+
+ private void appendParenthesizedFriendlyClassName(final StringBuilder sb, final Class> cl) {
+ sb.append('(');
+ appendFriendlyClassName(sb, cl);
+ sb.append(')');
+ }
+
+ private void appendFriendlyClassName(final StringBuilder sb, final Class> cl) {
+ if (cl == null) {
+ sb.append(cl);
+ return;
+ }
+ if (cl.isPrimitive()) {
+ sb.append(cl.getName());
+ } else if (cl.isArray()) {
+ appendFriendlyClassName(sb, cl.getComponentType());
+ sb.append("[]");
+ } else if (cl == String.class) {
+ sb.append("String");
+ } else if (cl == Date.class) {
+ sb.append("Date");
+ } else if (Number.class.isAssignableFrom(cl) && cl.getName().startsWith("java.lang.")
+ || cl.getName().startsWith("java.math.")) {
+ sb.append(cl.getName().substring(10));
+ } else {
+ sb.append(cl.getName());
+ }
+ }
+
+ private void appendDisplayable(final StringBuilder sb, final Object value, final boolean quoted,
+ final boolean reference) {
+ if (value == null) {
+ sb.append(value);
+ } else {
+ final Class> cl = value.getClass();
+ final String className = cl.getName();
+
+ if (cl == String.class) {
+ final String s = (String) value;
+ int length = s.length();
+ if (length > 24 && reference)
+ length = 21;
+ if (quoted) {
+ sb.append("\"");
+ for (int index = 0; index < s.length(); index++) {
+ Util.appendQuotedChar(sb, s.charAt(index));
+ }
+ sb.append("\"");
+ } else {
+ sb.append(s.substring(0, length));
+ }
+ if (length < s.length())
+ sb.append("...");
+ } else if (cl == Date.class) {
+ appendParenthesizedFriendlyClassName(sb, cl);
+ sb.append(Key.SDF.format((Date) value));
+ } else if (value instanceof Number) {
+ sb.append('(');
+ sb.append(className.startsWith("java.lang.") ? className.substring(10) : className);
+ sb.append(')');
+ sb.append(value);
+ } else if (value instanceof DisplayMarker) {
+ sb.append(value);
+ } else if (value instanceof AntiValue) {
+ sb.append(cl.getSimpleName());
+ sb.append(value);
+ } else {
+ appendParenthesizedFriendlyClassName(sb, cl);
+ try {
+ final String s = value.toString();
+ appendDisplayable(sb, s, false, reference);
+ } catch (final Throwable t) {
+ sb.append("<<" + t + ">>");
+ }
+ }
+ }
+ }
+
+ int getTypeHandle() {
+ final int saveDepth = _depth;
+ final int saveLevel = _level;
+ final int saveNext = _next;
+ final int saveEnd = _end;
+ final int result = nextType();
+ _end = saveEnd;
+ _next = saveNext;
+ _level = saveLevel;
+ _depth = saveDepth;
+ return result;
+ }
+
+ /**
+ * Returns the type of the object represented by the current state of this
+ * Value.
+ *
+ * @return The type
+ */
+ public Class> getType() {
+ final int saveDepth = _depth;
+ final int saveLevel = _level;
+ final int saveNext = _next;
+ final int saveEnd = _end;
+ try {
+ final int classHandle = nextType();
+ if (classHandle == CLASS_REREF) {
+ final int base = _bytes[_next++] & 0xFF;
+ final int handle = decodeVariableLengthInt(base);
+ final Object object = getValueCache().get(handle);
+ if (object == null) {
+ throw new IllegalStateException("Reference to handle " + handle + " has no value");
+ }
+ return object.getClass();
+ } else if (classHandle > 0 && classHandle < CLASSES.length && CLASSES[classHandle] != null) {
+ return CLASSES[classHandle];
+ } else if (classHandle == CLASS_ARRAY) {
+ _depth++;
+ final int componentClassHandle = nextType();
+ return arrayClass(classForHandle(componentClassHandle), 1);
+ } else if (classHandle == CLASS_MULTI_ARRAY) {
+ _depth++;
+ final int componentClassHandle = nextType();
+ checkSize(1);
+ final int dimensions = _bytes[_next++] & 0xFF;
+ return arrayClass(classForHandle(componentClassHandle), dimensions);
+ }
+
+ else return classForHandle(classHandle);
+ } finally {
+ _end = saveEnd;
+ _next = saveNext;
+ _level = saveLevel;
+ _depth = saveDepth;
+ }
+ }
+
+ public boolean isType(final Class> clazz) {
+ final int classHandle = getTypeHandle();
+ if (classHandle == TYPE_MVV || classHandle == CLASS_ANTIVALUE) {
+ return false;
+ }
+ if (classHandle > 0 && classHandle < CLASSES.length) {
+ return CLASSES[classHandle] == clazz;
+ }
+ try {
+ return getType() == clazz;
+ } catch (final Exception e) {
+ return false;
+ }
+ }
+
+ private Class> arrayClass(final Class> componentClass, final int dimensions) {
+ Class>[] arraysByDimension = _arrayTypeCache.get(componentClass);
+ Class> result = null;
+ if (arraysByDimension != null && arraysByDimension.length > dimensions)
+ result = arraysByDimension[dimensions];
+ if (result != null)
+ return result;
+
+ if (dimensions == 1)
+ result = Array.newInstance(componentClass, 0).getClass();
+
+ else result = Array.newInstance(componentClass, new int[dimensions]).getClass();
+ if (arraysByDimension != null) {
+ if (arraysByDimension.length <= dimensions) {
+ final Class>[] temp = new Class>[dimensions + 2];
+ System.arraycopy(arraysByDimension, 0, temp, 0, arraysByDimension.length);
+ arraysByDimension = temp;
+ _arrayTypeCache.put(componentClass, arraysByDimension);
+ }
+ } else arraysByDimension = new Class>[dimensions + 2];
+
+ arraysByDimension[dimensions] = result;
+ return result;
+ }
+
+ /**
+ * Decodes the object value represented by the current state of this
+ * Value and verifies that it is null.
+ *
+ * @return null
+ * @throws ConversionException
+ * if this Value does not currently represent
+ * null.
+ */
+ public Object getNull() {
+ final int start = _next;
+ final int type = nextType();
+ if (type == TYPE_NULL) {
+ _serializedItemCount++;
+ return null;
+ }
+ _next = start;
+ final Object object = get(null, null);
+ if (object == null)
+ return null;
+ throw new ConversionException("Expected null");
+ }
+
+ /**
+ * Decodes the boolean value represented by the current state of this
+ * Value.
+ *
+ * @return The value as a boolean.
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of this type.
+ */
+ public boolean getBoolean() {
+ final int start = _next;
+ if (nextType() == TYPE_BOOLEAN) {
+ _serializedItemCount++;
+ return getBooleanInternal();
+ }
+ _next = start;
+ return ((Boolean) getExpectedType(Boolean.class)).booleanValue();
+ }
+
+ private boolean getBooleanInternal() {
+ checkSize(1);
+ final boolean result = toBoolean(_next);
+ _next++;
+ return result;
+ }
+
+ /**
+ * Decodes the byte value represented by the current state of this
+ * Value.
+ *
+ * @return The value as a byte.
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of this type.
+ */
+ public byte getByte() {
+ final int start = _next;
+ if (nextType() == TYPE_BYTE) {
+ _serializedItemCount++;
+ return getByteInternal();
+ }
+ _next = start;
+ return ((Byte) getExpectedType(Byte.class)).byteValue();
+ }
+
+ private byte getByteInternal() {
+ checkSize(1);
+ final byte result = _bytes[_next++];
+ return result;
+ }
+
+ /**
+ * Decodes the short value represented by the current state of this
+ * Value.
+ *
+ * @return The value as a short.
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of this type.
+ */
+ public short getShort() {
+ final int start = _next;
+ if (nextType() == TYPE_SHORT) {
+ _serializedItemCount++;
+ return getShortInternal();
+ }
+ _next = start;
+ return ((Short) getExpectedType(Short.class)).shortValue();
+ }
+
+ private short getShortInternal() {
+ checkSize(2);
+ final short result = (short) Util.getShort(_bytes, _next);
+ _next += 2;
+ return result;
+ }
+
+ /**
+ * Decodes the char value represented by the current state of this
+ * Value.
+ *
+ * @return The value as a char.
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of this type.
+ */
+ public char getChar() {
+ final int start = _next;
+ if (nextType() == TYPE_CHAR) {
+ _serializedItemCount++;
+ return getCharInternal();
+ }
+ _next = start;
+ return ((Character) getExpectedType(Character.class)).charValue();
+ }
+
+ private char getCharInternal() {
+ checkSize(2);
+ final char result = (char) Util.getChar(_bytes, _next);
+ _next += 2;
+ return result;
+ }
+
+ /**
+ * Decodes the int value represented by the current state of this
+ * Value.
+ *
+ * @return The value as a int.
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of this type.
+ */
+ public int getInt() {
+ final int start = _next;
+ if (nextType() == TYPE_INT) {
+ _serializedItemCount++;
+ return getIntInternal();
+ }
+ _next = start;
+ return ((Integer) getExpectedType(Integer.class)).intValue();
+ }
+
+ private int getIntInternal() {
+ checkSize(4);
+ final int result = Util.getInt(_bytes, _next);
+ _next += 4;
+ return result;
+ }
+
+ /**
+ * Decodes the long value represented by the current state of this
+ * Value.
+ *
+ * @return The value as a long.
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of this type.
+ */
+ public long getLong() {
+ final int start = _next;
+ if (nextType() == TYPE_LONG) {
+ _serializedItemCount++;
+ return getLongInternal();
+ }
+ _next = start;
+ return ((Long) getExpectedType(Long.class)).longValue();
+ }
+
+ private long getLongInternal() {
+ checkSize(8);
+ final long result = Util.getLong(_bytes, _next);
+ _next += 8;
+ return result;
+ }
+
+ /**
+ * Decodes the float value represented by the current state of this
+ * Value.
+ *
+ * @return The value as a float.
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of this type.
+ */
+ public float getFloat() {
+ final int start = _next;
+ if (nextType() == TYPE_FLOAT) {
+ _serializedItemCount++;
+ return getFloatInternal();
+ }
+ _next = start;
+ return ((Float) getExpectedType(Float.class)).floatValue();
+ }
+
+ private float getFloatInternal() {
+ checkSize(4);
+ final float result = Float.intBitsToFloat(Util.getInt(_bytes, _next));
+ _next += 4;
+ return result;
+ }
+
+ /**
+ * Decodes the double value represented by the current state of this
+ * Value.
+ *
+ * @return The value as a double.
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of this type.
+ */
+ public double getDouble() {
+ final int start = _next;
+ if (nextType() == TYPE_DOUBLE) {
+ _serializedItemCount++;
+ return getDoubleInternal();
+ }
+ _next = start;
+ return ((Double) getExpectedType(Double.class)).doubleValue();
+ }
+
+ private double getDoubleInternal() {
+ checkSize(8);
+ final double result = Double.longBitsToDouble(Util.getLong(_bytes, _next));
+ _next += 8;
+ return result;
+ }
+
+ /**
+ * Decodes the object value represented by the current state of this
+ * Value. This method is identical to {@link #get()} except
+ * that in Stream Mode the pointer to the next
+ * retrieved value is not advanced.
+ *
+ * @return The value as a Object.
+ *
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of a recognizable class.
+ *
+ * @throws MalformedValueException
+ * if this Value is structurally corrupt.
+ */
+ public Object peek() {
+ return peek(null, null);
+ }
+
+ /**
+ *
+ * Decodes the object value represented by the current state of this
+ * Value. This method is identical to {@link #get(Object)}
+ * except that in Stream Mode the pointer to the
+ * next retrieved value is not advanced.
+ *
+ *
+ * This variant of getmay modify and return the target
+ * object supplied as a parameter, rather than creating a new object. This
+ * behavior will occur only if the encoded value has a registered
+ * {@link ValueRenderer}. See the documentation for
+ * ValueRenderer for more information.
+ *
+ *
+ * @param target
+ * A mutable object into which a {@link ValueRenderer} may
+ * decode this Value.
+ *
+ * @return The value as a Object.
+ *
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of a recognizable class.
+ *
+ * @throws MalformedValueException
+ * if this Value is structurally corrupt.
+ */
+ public Object peek(final Object target) {
+ return peek(target, null);
+ }
+
+ /**
+ *
+ * Decodes the object value represented by the current state of this
+ * Value. This method is identical to
+ * {@link #get(Object, CoderContext)} except that in Stream Mode the pointer to the next retrieved
+ * value is not advanced.
+ *
+ *
+ * @param target
+ * A mutable object into which a {@link ValueRenderer} may
+ * decode this Value.
+ *
+ * @return The value as a Object.
+ *
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of a recognizable class.
+ *
+ * @throws MalformedValueException
+ * if this Value is structurally corrupt.
+ */
+ public Object peek(final Object target, final CoderContext context) {
+ final Object object;
+ final int saveDepth = _depth;
+ final int saveLevel = _level;
+ final int saveNext = _next;
+ final int saveEnd = _end;
+ try {
+ object = get(target, context);
+ } finally {
+ _end = saveEnd;
+ _next = saveNext;
+ _level = saveLevel;
+ _depth = saveDepth;
+ }
+ return object;
+ }
+
+ /**
+ * Decodes the object value represented by the current state of this
+ * Value. If the represented value is primitive, this method
+ * returns the wrapped object of the corresponding class. For example, if
+ * the value represents an int, this method returns a
+ * java.lang.Integer.
+ *
+ * @return The value as a Object.
+ *
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of a recognizable class.
+ *
+ * @throws MalformedValueException
+ * if this Value is structurally corrupt.
+ */
+ public Object get() {
+ return get(null, null);
+ }
+
+ /**
+ *
+ * Decodes the object value represented by the current state of this
+ * Value. If the represented value is primitive, this method
+ * returns the wrapped object of the corresponding class. For example, if
+ * the value represents an int, this method returns a
+ * java.lang.Integer.
+ *
+ *
+ * This variant of getmay modify and return the target
+ * object supplied as a parameter, rather than creating a new object. This
+ * behavior will occur only if the encoded value has a registered
+ * {@link ValueRenderer}. See the documentation for
+ * ValueRenderer for more information.
+ *
+ *
+ * @param target
+ * A mutable object into which a {@link ValueRenderer} may
+ * decode this Value.
+ *
+ * @return The value as a Object.
+ *
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of a recognizable class.
+ *
+ * @throws MalformedValueException
+ * if this Value is structurally corrupt.
+ */
+ public Object get(final Object target) {
+ return get(target, null);
+ }
+
+ /**
+ *
+ * Decodes the object value represented by the current state of this
+ * Value. If the represented value is primitive, this method
+ * returns the wrapped object of the corresponding class. For example, if
+ * the value represents an int, this method returns a
+ * java.lang.Integer.
+ *
+ *
+ * This variant of getmay modify and return the target
+ * object supplied as a parameter, rather than creating a new object. This
+ * behavior will occur only if the encoded value has an associated
+ * {@link ValueRenderer} registered by {@link CoderManager}. See the
+ * documentation for those classes for a detailed explanation of value
+ * rendering.
+ *
+ *
+ * @param target
+ * A mutable object into which a {@link ValueRenderer} may
+ * decode this Value.
+ *
+ * @param context
+ * An application-specified value that may assist a
+ * {@link ValueCoder}. The context is passed to the
+ * {@link ValueCoder#get} method.
+ *
+ * @return The value as a Object.
+ *
+ * @throws ConversionException
+ * if this Value does not currently represent data
+ * of a recognizable class.
+ *
+ * @throws MalformedValueException
+ * if this Value is structurally corrupt.
+ */
+ public Object get(final Object target, final CoderContext context) {
+ Object object = null;
+ final int start = _next;
+ final int classHandle = nextType();
+ final int currentItemCount = _serializedItemCount++;
+
+ switch (classHandle) {
+ case TYPE_NULL:
+ break;
+
+ case TYPE_BOOLEAN:
+ case CLASS_BOOLEAN:
+ object = getBooleanInternal() ? Boolean.TRUE : Boolean.FALSE;
+ break;
+
+ case TYPE_BYTE:
+ case CLASS_BYTE:
+ object = Byte.valueOf(getByteInternal());
+ break;
+
+ case TYPE_SHORT:
+ case CLASS_SHORT:
+ object = Short.valueOf(getShortInternal());
+ break;
+
+ case TYPE_CHAR:
+ case CLASS_CHAR:
+ object = Character.valueOf(getCharInternal());
+ break;
+
+ case TYPE_INT:
+ case CLASS_INT:
+ object = Integer.valueOf(getIntInternal());
+ break;
+
+ case TYPE_FLOAT:
+ case CLASS_FLOAT:
+ object = Float.valueOf(getFloatInternal());
+ break;
+
+ case TYPE_LONG:
+ case CLASS_LONG:
+ object = Long.valueOf(getLongInternal());
+ break;
+
+ case TYPE_DOUBLE:
+ case CLASS_DOUBLE:
+ object = Double.valueOf(getDoubleInternal());
+ break;
+
+ case CLASS_STRING: {
+ if (target != null && target instanceof Appendable) {
+ utfToAppendable((Appendable) target, _next, _end);
+ object = target;
+ } else {
+ final StringBuilder sb = getStringAssemblyBuffer(_end - _next);
+ utfToAppendable(sb, _next, _end);
+ object = sb.toString();
+ }
+ closeVariableLengthItem();
+ break;
+ }
+
+ case CLASS_DATE:
+ final long time = Util.getLong(_bytes, _next);
+ _next += 8;
+ object = new Date(time);
+ break;
+
+ case CLASS_BIG_INTEGER: {
+ final int length = _end - _next;
+ final byte[] bytes = new byte[length];
+ System.arraycopy(_bytes, _next, bytes, 0, length);
+ _next += length;
+ object = new BigInteger(bytes);
+ closeVariableLengthItem();
+ break;
+ }
+
+ case CLASS_ACCUMULATOR: {
+ AccumulatorState accumulator;
+ if (target != null && target instanceof AccumulatorState) {
+ accumulator = (AccumulatorState) target;
+ } else {
+ accumulator = new AccumulatorState();
+ }
+ _depth++;
+ try {
+ accumulator.load(this);
+ } finally {
+ _depth--;
+ }
+ object = accumulator;
+ break;
+ }
+
+ case CLASS_TREE_STATISTICS: {
+ TreeStatistics treeStatistics;
+ if (target != null && target instanceof TreeStatistics) {
+ treeStatistics = (TreeStatistics) target;
+ } else {
+ treeStatistics = new TreeStatistics();
+ }
+ _next += treeStatistics.load(_bytes, _next, _end - _next);
+ object = treeStatistics;
+ break;
+ }
+
+ case CLASS_TREE: {
+ if (target != null && target instanceof Tree) {
+ final Tree tree = (Tree) target;
+ _next += tree.load(_bytes, _next, _end - _next);
+ object = tree;
+ } else {
+ final TreeState treeState = new TreeState();
+ _next += treeState.load(_bytes, _next, _end - _next);
+ object = treeState;
+ }
+ break;
+ }
+
+ case CLASS_BIG_DECIMAL: {
+ final int length = _end - _next;
+ final int scale = Util.getInt(_bytes, _next);
+ final byte[] bytes = new byte[length - 4];
+ System.arraycopy(_bytes, _next + 4, bytes, 0, length - 4);
+ _next += length;
+ object = new BigDecimal(new BigInteger(bytes), scale);
+ closeVariableLengthItem();
+ break;
+ }
+
+ case CLASS_ANTIVALUE: {
+ final int length = _end - _next;
+ int elisionCount = 0;
+ byte[] bytes = null;
+ if (length > 0) {
+ elisionCount = Util.getShort(_bytes, _next);
+ bytes = new byte[length - 2];
+ System.arraycopy(_bytes, _next + 2, bytes, 0, length - 2);
+ }
+ _next += length;
+ object = new AntiValue(elisionCount, bytes);
+ closeVariableLengthItem();
+ break;
+ }
+
+ case CLASS_ARRAY: {
+ try {
+ _depth++;
+ final int componentClassHandle = nextType();
+ switch (componentClassHandle) {
+ case TYPE_BOOLEAN: {
+ final boolean[] result = new boolean[_end - _next];
+ for (int index = 0; index < result.length; index++) {
+ result[index] = toBoolean(_next + index);
+ }
+ object = result;
+ break;
+ }
+
+ case TYPE_BYTE: {
+ final byte[] result = new byte[_end - _next];
+ System.arraycopy(_bytes, _next, result, 0, _end - _next);
+ object = result;
+ break;
+ }
+
+ case TYPE_SHORT: {
+ final short[] result = new short[arraySize(_end, _next, 2)];
+ for (int index = 0; index < result.length; index++) {
+ result[index] = (short) Util.getShort(_bytes, _next + (index * 2));
+ }
+ object = result;
+ break;
+ }
+
+ case TYPE_CHAR: {
+ final char[] result = new char[arraySize(_end, _next, 2)];
+ for (int index = 0; index < result.length; index++) {
+ result[index] = (char) Util.getChar(_bytes, _next + (index * 2));
+ }
+ object = result;
+ break;
+ }
+
+ case TYPE_INT: {
+ final int[] result = new int[arraySize(_end, _next, 4)];
+ for (int index = 0; index < result.length; index++) {
+ result[index] = Util.getInt(_bytes, _next + (index * 4));
+ }
+ object = result;
+ break;
+ }
+
+ case TYPE_LONG: {
+ final long[] result = new long[arraySize(_end, _next, 8)];
+ for (int index = 0; index < result.length; index++) {
+ result[index] = Util.getLong(_bytes, _next + (index * 8));
+ }
+ object = result;
+ break;
+ }
+
+ case TYPE_FLOAT: {
+ final float[] result = new float[arraySize(_end, _next, 4)];
+ for (int index = 0; index < result.length; index++) {
+ result[index] = Float.intBitsToFloat(Util.getInt(_bytes, _next + (index * 4)));
+ }
+ object = result;
+ break;
+ }
+
+ case TYPE_DOUBLE: {
+ final double[] result = new double[arraySize(_end, _next, 8)];
+ for (int index = 0; index < result.length; index++) {
+ result[index] = Double.longBitsToDouble(Util.getLong(_bytes, _next + (index * 8)));
+ }
+ object = result;
+ break;
+ }
+
+ case CLASS_STRING: {
+ final int length = decodeElementCount();
+ final String[] result = new String[length];
+ for (int index = 0; index < length; index++) {
+ result[index] = getString();
+ }
+ object = result;
+ break;
+ }
+
+ default: {
+ final Class> componentClass = classForHandle(componentClassHandle);
+ final int length = decodeElementCount();
+ final Object[] result = (Object[]) Array.newInstance(componentClass, length);
+ getValueCache().store(currentItemCount, result);
+ for (int index = 0; index < length; index++) {
+ Array.set(result, index, get(null, null));
+ }
+ object = result;
+ break;
+ }
+ }
+ } finally {
+ _depth--;
+ }
+ closeVariableLengthItem();
+ break;
+
+ }
+
+ case CLASS_MULTI_ARRAY:
+ _next--;
+ _serializedItemCount--;
+ object = getMultiArray(null);
+ break;
+
+ case CLASS_SERIALIZED:
+ _depth++;
+ try {
+ final ObjectInputStream ois = new OldValueInputStream(this);
+ object = ois.readObject();
+ if (_next != _end) {
+ throw new ConversionException("Invalid serialized Object at index=" + _next);
+ }
+ closeVariableLengthItem();
+ } catch (final IOException ioe) {
+ throw new ConversionException("@" + start, ioe);
+ } catch (final ClassNotFoundException cnfe) {
+ throw new ConversionException("@" + start, cnfe);
+ } finally {
+ _depth--;
+ }
+
+ break;
+
+ case CLASS_REREF: {
+ final int base = _bytes[_next++] & 0xFF;
+ final int handle = decodeVariableLengthInt(base);
+ object = getValueCache().get(handle);
+ break;
+ }
+
+ case TYPE_MVV: {
+ final int savedSize = _size;
+ final ArrayList