/**
* == @Spearal ==>
*
* Copyright (C) 2014 Franck WOLFF & William DRAI (http://www.spearal.io)
*
* 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.
*/
package org.spearal.impl;
import static org.spearal.impl.SharedConstants.BIG_NUMBER_ALPHA;
import static org.spearal.impl.SharedConstants.UTF8;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.spearal.SpearalContext;
import org.spearal.SpearalPrinter;
import org.spearal.SpearalPrinter.StringData;
import org.spearal.configuration.PartialObjectFactory.PartialObjectProxy;
import org.spearal.configuration.PropertyFactory.Property;
import org.spearal.impl.cache.AnyMap.ValueProvider;
import org.spearal.impl.cache.EqualityMap;
import org.spearal.impl.util.ClassDescriptionUtil;
import org.spearal.impl.util.TypeUtil;
/**
* @author Franck WOLFF
*/
public class SpearalDecoderImpl implements ExtendedSpearalDecoder {
private final List<String> sharedStrings;
private final List<Object> sharedObjects;
private final PathImpl path;
private final PartialObjectMap partialObjectsMap;
private final EqualityMap<String, Type, ClassDescriptor> descriptors;
private final EqualityMap<String, Object, BigInteger> bigIntegers;
private final EqualityMap<String, Object, BigDecimal> bigDecimals;
private final SpearalContext context;
private final InputStream in;
private final byte[] buffer;
private int position;
private int size;
public SpearalDecoderImpl(SpearalContext context, InputStream in) {
this(context, in, 1024);
}
public SpearalDecoderImpl(final SpearalContext context, InputStream in, int capacity) {
this.sharedStrings = new ArrayList<String>(64);
this.sharedObjects = new ArrayList<Object>(64);
this.path = new PathImpl();
this.partialObjectsMap = new PartialObjectMap();
this.descriptors = new EqualityMap<String, Type, ClassDescriptor>(new ValueProvider<String, Type, ClassDescriptor>() {
@Override
public ClassDescriptor createValue(SpearalContext context, String key, Type targetType) {
return ClassDescriptor.forDescription(context, key, targetType);
}
});
this.bigIntegers = new EqualityMap<String, Object, BigInteger>(new ValueProvider<String, Object, BigInteger>() {
@Override
public BigInteger createValue(SpearalContext context, String key, Object unsused) {
final int exponentIndex = key.indexOf('E');
BigInteger bigInteger;
if (exponentIndex == -1)
bigInteger = new BigInteger(key);
else {
bigInteger = new BigInteger(key.substring(0, exponentIndex));
int exponent = Integer.parseInt(key.substring(exponentIndex + 1));
bigInteger = bigInteger.multiply(BigInteger.TEN.pow(exponent));
}
return bigInteger;
}
});
this.bigDecimals = new EqualityMap<String, Object, BigDecimal>(new ValueProvider<String, Object, BigDecimal>() {
@Override
public BigDecimal createValue(SpearalContext context, String key, Object unsused) {
return new BigDecimal(key);
}
});
this.context = context;
this.in = in;
this.buffer = new byte[capacity];
this.position = 0;
this.size = 0;
}
@Override
public SpearalContext getContext() {
return context;
}
public Path getPath() {
return path;
}
@Override
public boolean containsPartialObjects() {
return !partialObjectsMap.isEmpty();
}
@Override
public Map<Object, List<PathSegment>> getPartialObjectsMap() {
return partialObjectsMap;
}
@Override
public Object readAny() throws IOException {
return readAny(readNextByte(), null);
}
@SuppressWarnings("unchecked")
@Override
public <T> T readAny(Type targetType) throws IOException {
return (T)readAny(readNextByte(), targetType);
}
@Override
public void skipAny() throws IOException {
readAny();
}
@Override
public void printAny(SpearalPrinter printer) throws IOException {
printAny(printer, readNextByte());
}
@Override
public Object readAny(int parameterizedType) throws IOException {
return readAny(parameterizedType, null);
}
@Override
public Object readAny(int parameterizedType, Type targetType) throws IOException {
targetType = TypeUtil.unwrapTypeVariable(targetType);
Object value;
boolean convert;
switch (SpearalType.valueOf(parameterizedType)) {
case NULL:
value = null;
convert = ((targetType instanceof Class<?>) && ((Class<?>)targetType).isPrimitive());
break;
case TRUE:
value = Boolean.TRUE;
convert = (targetType != null && targetType != boolean.class && targetType != Boolean.class);
break;
case FALSE:
value = Boolean.FALSE;
convert = (targetType != null && targetType != boolean.class && targetType != Boolean.class);
break;
case INTEGRAL:
value = Long.valueOf(readIntegral(parameterizedType));
convert = (targetType != null && targetType != long.class && targetType != Long.class);
break;
case BIG_INTEGRAL:
value = readBigIntegral(parameterizedType);
convert = (targetType != null && targetType != BigInteger.class);
break;
case FLOATING:
value = Double.valueOf(readFloating(parameterizedType));
convert = (targetType != null && targetType != double.class && targetType != Double.class);
break;
case BIG_FLOATING:
value = readBigFloating(parameterizedType);
convert = (targetType != null && targetType != BigDecimal.class);
break;
case STRING:
value = readString(parameterizedType);
convert = (targetType != null && targetType != String.class);
break;
case BYTE_ARRAY:
value = readByteArray(parameterizedType);
convert = (targetType != null && targetType != byte[].class);
break;
case DATE_TIME:
value = readDateTime(parameterizedType);
if (targetType == null) {
targetType = Object.class;
convert = true;
}
else
convert = (targetType != SpearalDateTime.class);
break;
case COLLECTION:
value = readCollection(parameterizedType, targetType);
if (targetType == null)
convert = false;
else {
Class<?> targetClass = TypeUtil.classOfType(targetType);
convert = !(targetClass.isArray() || Collection.class.isAssignableFrom(targetClass));
}
break;
case MAP:
value = readMap(parameterizedType, targetType);
convert = (targetType != null && !Map.class.isAssignableFrom(TypeUtil.classOfType(targetType)));
break;
case ENUM:
value = readEnum(parameterizedType, targetType);
convert = (targetType != null && targetType != value.getClass());
break;
case CLASS:
value = readClass(parameterizedType, targetType);
convert = (targetType != null && targetType != Class.class);
break;
case BEAN:
value = readBean(parameterizedType, targetType);
convert = (
targetType != null &&
targetType != value.getClass() &&
!((targetType instanceof Class) && ((Class<?>)targetType).isAssignableFrom(value.getClass()))
);
break;
default:
throw new RuntimeException("Unexpected parameterized type: " + parameterizedType);
}
return (convert ? context.convert(value, targetType) : value);
}
@Override
public void skipAny(int parameterizedType) throws IOException {
readAny(parameterizedType);
}
public void printAny(SpearalPrinter printer, int parameterizedType) throws IOException {
switch (SpearalType.valueOf(parameterizedType)) {
case NULL:
printer.printNull();
return;
case TRUE:
printer.printBoolean(true);
return;
case FALSE:
printer.printBoolean(false);
return;
case INTEGRAL:
printer.printIntegral(readIntegral(parameterizedType));
return;
case BIG_INTEGRAL:
printBigIntegral(printer, parameterizedType);
return;
case FLOATING:
printer.printFloating(readFloating(parameterizedType));
return;
case BIG_FLOATING:
printBigFloating(printer, parameterizedType);
return;
case STRING:
printString(printer, parameterizedType);
return;
case BYTE_ARRAY:
printByteArray(printer, parameterizedType);
return;
case DATE_TIME:
printDateTime(printer, parameterizedType);
return;
case COLLECTION:
printCollection(printer, parameterizedType);
return;
case MAP:
printMap(printer, parameterizedType);
return;
case ENUM:
printEnum(printer, parameterizedType);
return;
case CLASS:
printClass(printer, parameterizedType);
return;
case BEAN:
printBean(printer, parameterizedType);
return;
}
throw new RuntimeException("Unexpected type: " + parameterizedType);
}
@Override
public long readIntegral(int parameterizedType) throws IOException {
final int length0 = (parameterizedType & 0x07);
ensureAvailable(length0 + 1);
final byte[] buffer = this.buffer;
int position = this.position;
long v = 0L;
switch (length0) {
case 7:
v |= (buffer[position++] & 0xffL) << 56;
case 6:
v |= (buffer[position++] & 0xffL) << 48;
case 5:
v |= (buffer[position++] & 0xffL) << 40;
case 4:
v |= (buffer[position++] & 0xffL) << 32;
case 3:
v |= (buffer[position++] & 0xffL) << 24;
case 2:
v |= (buffer[position++] & 0xffL) << 16;
case 1:
v |= (buffer[position++] & 0xffL) << 8;
case 0:
v |= (buffer[position++] & 0xffL);
}
this.position = position;
if ((parameterizedType & 0x08) != 0)
v = -v;
return v;
}
@Override
public BigInteger readBigIntegral(int parameterizedType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
String representation;
if (isStringReference(parameterizedType))
representation = sharedStrings.get(indexOrLength);
else
representation = readBigNumberData(indexOrLength);
return bigIntegers.putIfAbsent(context, representation);
}
void printBigIntegral(SpearalPrinter printer, int parameterizedType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isStringReference(parameterizedType))
printer.printBigIntegral(new StringData(sharedStrings.get(indexOrLength), indexOrLength, true));
else
printer.printBigIntegral(new StringData(readBigNumberData(indexOrLength), sharedStrings.size() - 1, false));
}
@Override
public double readFloating(int parameterizedType) throws IOException {
if ((parameterizedType & 0x08) != 0) {
int length0 = (parameterizedType & 0x03);
ensureAvailable(length0 + 1);
final byte[] buffer = this.buffer;
int position = this.position;
long doubleAsLong = 0L;
switch (length0) {
case 3:
doubleAsLong |= (buffer[position++] & 0xffL) << 24;
case 2:
doubleAsLong |= (buffer[position++] & 0xffL) << 16;
case 1:
doubleAsLong |= (buffer[position++] & 0xffL) << 8;
case 0:
doubleAsLong |= (buffer[position++] & 0xffL);
}
this.position = position;
if ((parameterizedType & 0x04) != 0)
doubleAsLong = -doubleAsLong;
return (doubleAsLong / 1000.0);
}
ensureAvailable(8);
return Double.longBitsToDouble(readLongData());
}
@Override
public BigDecimal readBigFloating(int parameterizedType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
String representation;
if (isStringReference(parameterizedType))
representation = sharedStrings.get(indexOrLength);
else
representation = readBigNumberData(indexOrLength);
return bigDecimals.putIfAbsent(context, representation);
}
void printBigFloating(SpearalPrinter printer, int parameterizedType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isStringReference(parameterizedType))
printer.printBigFloating(new StringData(sharedStrings.get(indexOrLength), indexOrLength, true));
else
printer.printBigFloating(new StringData(readBigNumberData(indexOrLength), sharedStrings.size() - 1, false));
}
@Override
public String readString(int parameterizedType) throws IOException {
return readStringData(parameterizedType);
}
private void printString(SpearalPrinter printer, int parameterizedType) throws IOException {
printer.printString(getStringData(parameterizedType));
}
@Override
public byte[] readByteArray(int parameterizedType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isObjectReference(parameterizedType))
return (byte[])sharedObjects.get(indexOrLength);
byte[] bytes = new byte[indexOrLength];
sharedObjects.add(bytes);
readFully(bytes, 0, indexOrLength);
return bytes;
}
private void printByteArray(SpearalPrinter printer, int parameterizedType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isObjectReference(parameterizedType))
printer.printByteArray((byte[])sharedObjects.get(indexOrLength), indexOrLength, true);
else {
byte[] bytes = new byte[indexOrLength];
sharedObjects.add(bytes);
readFully(bytes, 0, indexOrLength);
printer.printByteArray(bytes, sharedObjects.size() - 1, false);
}
}
@Override
public SpearalDateTime readDateTime(int parameterizedType) throws IOException {
boolean hasDate = ((parameterizedType & 0x08) != 0);
boolean hasTime = ((parameterizedType & 0x04) != 0);
int year = 0;
int month = 0;
int date = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
int nanoseconds = 0;
if (hasDate) {
ensureAvailable(2);
month = (buffer[position++] & 0xff);
date = (buffer[position++] & 0xff);
int length0 = ((month >>> 4) & 0x03);
boolean inverse = ((month & 0x80) != 0);
month &= 0x0f;
ensureAvailable(length0 + 1);
year = readUnsignedIntegerValue(length0);
if (inverse)
year = -year;
year += 2000;
}
if (hasTime) {
ensureAvailable(3);
hours = (buffer[position++] & 0xff);
minutes = (buffer[position++] & 0xff);
seconds = (buffer[position++] & 0xff);
int subsecondsType = (parameterizedType & 0x03);
if (subsecondsType != 0) {
int length0 = (hours >>> 5);
ensureAvailable(length0 + 1);
nanoseconds = readUnsignedIntegerValue(length0);
if (subsecondsType == 2)
nanoseconds *= 1000;
else if (subsecondsType == 3)
nanoseconds *= 1000000;
}
hours &= 0x1f;
}
return new SpearalDateTime(year, month, date, hours, minutes, seconds, nanoseconds, hasDate, hasTime);
}
private void printDateTime(SpearalPrinter printer, int parameterizedType) throws IOException {
printer.printDateTime(readDateTime(parameterizedType));
}
@SuppressWarnings("unchecked")
@Override
public Object readCollection(int parameterizedType, Type targetType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isObjectReference(parameterizedType))
return sharedObjects.get(indexOrLength);
Object value = null;
Type elementType = null;
if (targetType != null) {
try {
value = context.instantiate(targetType, Integer.valueOf(indexOrLength));
}
catch (Exception e) {
throw new RuntimeException("Couldn't instantiate type: " + targetType, e);
}
elementType = TypeUtil.getElementType(targetType);
}
else
value = new ArrayList<Object>(indexOrLength);
sharedObjects.add(value);
if (value.getClass().isArray()) {
ArrayPathSegmentImpl segment = new ArrayPathSegmentImpl(value);
path.push(segment);
for (segment.index = 0; segment.index < indexOrLength; segment.index++)
Array.set(value, segment.index, readAny(elementType));
path.pop();
}
else {
Collection<Object> collection = (Collection<Object>)value;
CollectionPathSegmentImpl segment = new CollectionPathSegmentImpl(collection);
path.push(segment);
for (segment.index = 0; segment.index < indexOrLength; segment.index++)
collection.add(readAny(elementType));
path.pop();
}
return value;
}
private void printCollection(SpearalPrinter printer, int parameterizedType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isObjectReference(parameterizedType))
printer.printCollectionReference(indexOrLength);
else {
int index = sharedObjects.size();
printer.printCollectionStart(index, indexOrLength);
sharedObjects.add(null);
for (int i = 0; i < indexOrLength; i++) {
printer.printCollectionItemStart(index, i);
printAny(printer);
printer.printCollectionItemEnd(index, i);
}
printer.printCollectionEnd(index);
}
}
@SuppressWarnings("unchecked")
@Override
public void readCollection(int parameterizedType, Object holder, Property property)
throws IOException, InstantiationException, IllegalAccessException, InvocationTargetException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isObjectReference(parameterizedType)) {
property.set(holder, sharedObjects.get(indexOrLength));
return;
}
Collection<Object> value = (Collection<Object>)property.get(holder);
if (value != null)
value.clear();
else
value = (Collection<Object>)property.init(this, holder);
sharedObjects.add(value);
Type elementType = TypeUtil.getElementType(property.getGenericType());
CollectionPathSegmentImpl segment = new CollectionPathSegmentImpl(value);
path.push(segment);
for (segment.index = 0; segment.index < indexOrLength; segment.index++)
value.add(readAny(elementType));
path.pop();
}
@SuppressWarnings("unchecked")
@Override
public Map<?, ?> readMap(int parameterizedType, Type targetType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isObjectReference(parameterizedType))
return (Map<?, ?>)sharedObjects.get(indexOrLength);
Map<Object, Object> value = null;
Type keyType = null;
Type valType = null;
if (targetType != null && Map.class.isAssignableFrom(TypeUtil.classOfType(targetType))) {
try {
value = (Map<Object, Object>)context.instantiate(targetType, Integer.valueOf(indexOrLength));
}
catch (Exception e) {
throw new RuntimeException("Couldn't instantiate type: " + targetType, e);
}
Type[] keyValueTypes = TypeUtil.getKeyValueType(targetType);
keyType = keyValueTypes[0];
valType = keyValueTypes[1];
}
else
value = new LinkedHashMap<Object, Object>(indexOrLength);
sharedObjects.add(value);
MapPathSegmentImpl segment = new MapPathSegmentImpl(value);
path.push(segment);
for (int i = 0; i < indexOrLength; i++) {
segment.key = null;
Object key = readAny(keyType);
segment.key = key;
Object val = readAny(valType);
value.put(key, val);
}
path.pop();
return value;
}
private void printMap(SpearalPrinter printer, int parameterizedType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isObjectReference(parameterizedType))
printer.printMapReference(indexOrLength);
else {
int index = sharedObjects.size();
printer.printMapStart(index, indexOrLength);
sharedObjects.add(null);
for (int i = 0; i < indexOrLength; i++) {
printer.printMapKeyStart(index, i);
printAny(printer);
printer.printMapKeyEnd(index, i);
printer.printMapValueStart(index, i);
printAny(printer);
printer.printMapValueEnd(index, i);
}
printer.printMapEnd(index);
}
}
@SuppressWarnings("unchecked")
@Override
public void readMap(int parameterizedType, Object holder, Property property)
throws IOException, InstantiationException, IllegalAccessException, InvocationTargetException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isObjectReference(parameterizedType)) {
property.set(holder, sharedObjects.get(indexOrLength));
return;
}
Map<Object, Object> value = (Map<Object, Object>)property.get(holder);
if (value != null)
value.clear();
else
value = (Map<Object, Object>)property.init(this, holder);
sharedObjects.add(value);
Type[] keyValueTypes = TypeUtil.getKeyValueType(property.getGenericType());
Type keyType = keyValueTypes[0];
Type valType = keyValueTypes[1];
MapPathSegmentImpl segment = new MapPathSegmentImpl(value);
path.push(segment);
for (int i = 0; i < indexOrLength; i++) {
segment.key = null;
Object key = readAny(keyType);
segment.key = key;
Object val = readAny(valType);
value.put(key, val);
}
path.pop();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Enum<?> readEnum(int parameterizedType, Type targetType) throws IOException {
String className = readStringData(parameterizedType);
Class<? extends Enum> cls = (Class<? extends Enum>)context.loadClass(className, targetType);
String value = readString(readNextByte());
return Enum.valueOf(cls, value);
}
private void printEnum(SpearalPrinter printer, int parameterizedType) throws IOException {
StringData className = getStringData(parameterizedType);
parameterizedType = readNextByte();
StringData value = getStringData(parameterizedType);
printer.printEnum(className, value);
}
@Override
public Class<?> readClass(int parameterizedType, Type targetType) throws IOException {
String className = readStringData(parameterizedType);
return context.loadClass(className, targetType);
}
private void printClass(SpearalPrinter printer, int parameterizedType) throws IOException {
printer.printClass(getStringData(parameterizedType));
}
@Override
public Object readBean(int parameterizedType, Type targetType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isObjectReference(parameterizedType)) {
Object value = sharedObjects.get(indexOrLength);
partialObjectsMap.appendIfPresent(value, path.peek());
return value;
}
String classDescription = readStringData(parameterizedType, indexOrLength);
ClassDescriptor descriptor = descriptors.putIfAbsent(context, classDescription, targetType);
try {
Class<?> cls = descriptor.cls;
Object value;
if (cls == ClassNotFound.class)
value = new ClassNotFound(classDescription);
else if (!descriptor.partial)
value = context.instantiate(cls, null);
else {
value = context.instantiatePartial(cls, descriptor.properties);
partialObjectsMap.put(value, path.peek());
}
sharedObjects.add(value);
BeanPathSegmentImpl segment = new BeanPathSegmentImpl(value);
path.push(segment);
for (Property property : descriptor.properties) {
segment.property = property;
int propertyType = readNextByte();
if (property != null)
property.read(this, value, propertyType);
else
skipAny(propertyType);
}
path.pop();
return value;
}
catch (Exception e) {
throw new IOException(e);
}
}
private void printBean(SpearalPrinter printer, int parameterizedType) throws IOException {
final int indexOrLength = readIndexOrLength(parameterizedType);
if (isObjectReference(parameterizedType))
printer.printBeanReference(indexOrLength);
else {
StringData classDescription = getStringData(parameterizedType, indexOrLength);
String[] classNames = ClassDescriptionUtil.splitClassNames(classDescription.value);
final int index = sharedObjects.size();
sharedObjects.add(null);
printer.printBeanStart(index, classDescription, classNames);
String[] propertyNames = ClassDescriptionUtil.splitPropertyNames(classDescription.value);
int propertyIndex = 0;
for (String propertyName : propertyNames) {
printer.printBeanPropertyStart(index, propertyName, propertyIndex);
printAny(printer);
printer.printBeanPropertyEnd(index, propertyIndex++);
}
printer.printBeanEnd(index);
}
}
private int readNextByte() throws IOException {
ensureAvailable(1);
return (buffer[position++] & 0xff);
}
private int readIndexOrLength(int parameterizedType) throws IOException {
int length0 = (parameterizedType & 0x03);
ensureAvailable(length0 + 1);
return readUnsignedIntegerValue(length0);
}
private int readUnsignedIntegerValue(int length0) {
int v = 0;
switch (length0) {
case 3:
v |= (buffer[position++] & 0xff) << 24;
case 2:
v |= (buffer[position++] & 0xff) << 16;
case 1:
v |= (buffer[position++] & 0xff) << 8;
case 0:
v |= (buffer[position++] & 0xff);
}
return v;
}
private String readStringData(int parameterizedType) throws IOException {
int indexOrLength = readIndexOrLength(parameterizedType);
return readStringData(parameterizedType, indexOrLength);
}
private String readStringData(int parameterizedType, int indexOrLength) throws IOException {
if (isStringReference(parameterizedType))
return sharedStrings.get(indexOrLength);
if (indexOrLength == 0)
return "";
String value;
if (indexOrLength <= buffer.length) {
ensureAvailable(indexOrLength);
value = new String(buffer, position, indexOrLength, UTF8);
position += indexOrLength;
}
else {
byte[] bytes = new byte[indexOrLength];
readFully(bytes, 0, indexOrLength);
value = new String(bytes, UTF8);
}
sharedStrings.add(value);
return value;
}
private StringData getStringData(int parameterizedType) throws IOException {
int indexOrLength = readIndexOrLength(parameterizedType);
return getStringData(parameterizedType, indexOrLength);
}
private StringData getStringData(int parameterizedType, int indexOrLength) throws IOException {
if (isStringReference(parameterizedType))
return new StringData(sharedStrings.get(indexOrLength), indexOrLength, true);
if (indexOrLength == 0)
return new StringData("", -1, false);
String value = readStringData(parameterizedType, indexOrLength);
return new StringData(value, sharedStrings.size() - 1, false);
}
private String readBigNumberData(int length) throws IOException {
char[] chars = new char[length];
final byte[] buffer = this.buffer;
final int bufferLength = buffer.length;
int position = this.position;
int size = this.size;
final int count = (length / 2) + (length % 2);
int iChar = 0;
for (int i = 0; i < count; i++) {
if (position == size) {
this.position = position;
fillBuffer(Math.min(count - i, bufferLength));
position = 0;
size = this.size;
}
int b = (buffer[position++] & 0xff);
chars[iChar++] = BIG_NUMBER_ALPHA[(b & 0xf0) >>> 4];
if (iChar == length)
break;
chars[iChar++] = BIG_NUMBER_ALPHA[b & 0x0f];
}
this.position = position;
String representation = new String(chars);
sharedStrings.add(representation);
return representation;
}
private long readLongData() {
final byte[] buffer = this.buffer;
int position = this.position;
long v =
(buffer[position++] & 0xffL) << 56 |
(buffer[position++] & 0xffL) << 48 |
(buffer[position++] & 0xffL) << 40 |
(buffer[position++] & 0xffL) << 32 |
(buffer[position++] & 0xffL) << 24 |
(buffer[position++] & 0xffL) << 16 |
(buffer[position++] & 0xffL) << 8 |
(buffer[position++] & 0xffL);
this.position = position;
return v;
}
private void readFully(byte[] b, int off, int len) throws IOException {
if (b == null)
throw new NullPointerException();
if (off < 0 || len < 0 || len > b.length - off)
throw new IndexOutOfBoundsException();
if (len == 0)
return;
final int left = size - position;
if (len <= left) {
System.arraycopy(buffer, position, b, off, len);
position += len;
}
else {
if (left > 0) {
System.arraycopy(buffer, position, b, off, left);
off += left;
len -= left;
position = size;
}
while (len > 0) {
int count = in.read(b, off, len);
if (count == len)
return;
if (count <= 0)
throw new EOFException();
off += count;
len -= count;
}
}
}
private void ensureAvailable(int count) throws IOException {
if (size - position < count)
fillBuffer(count);
}
private void fillBuffer(int count) throws IOException {
if (position > 0) {
size -= position;
System.arraycopy(buffer, position, buffer, 0, size);
position = 0;
}
do {
int read = in.read(buffer, size, buffer.length - size);
if (read == -1)
throw new EOFException();
size += read;
}
while (size < count);
}
private static boolean isObjectReference(int parameterizedType) {
return ((parameterizedType & 0x08) != 0);
}
private static boolean isStringReference(int parameterizedType) {
return ((parameterizedType & 0x04) != 0);
}
private static class ClassDescriptor {
public final Class<?> cls;
public final Property[] properties;
public final boolean partial;
public static ClassDescriptor forDescription(SpearalContext context, String description, Type targetType) {
String classNames = ClassDescriptionUtil.classNames(description);
String[] propertyNames = ClassDescriptionUtil.splitPropertyNames(description);
Class<?> cls = context.loadClass(classNames, targetType);
if (cls == null) {
Property[] classNotFoundProperties = new Property[propertyNames.length];
for (int i = 0; i < propertyNames.length; i++) {
String propertyName = propertyNames[i];
classNotFoundProperties[i] = new ClassNotFoundProperty(propertyName);
}
return new ClassDescriptor(ClassNotFound.class, classNotFoundProperties, false);
}
Property[] properties = context.getProperties(cls);
Property[] serializedProperties = new Property[propertyNames.length];
boolean partial = false;
propertiesLoop:
for (Property property : properties) {
String propertyName = property.getName();
for (int i = 0; i < propertyNames.length; i++) {
if (propertyName.equals(propertyNames[i])) {
serializedProperties[i] = property;
continue propertiesLoop;
}
}
partial = true;
}
if (Proxy.isProxyClass(cls)) {
partial = true;
cls = context.loadClass(classNames + "," + PartialObjectProxy.class.getName(), targetType);
}
return new ClassDescriptor(cls, serializedProperties, partial);
}
public ClassDescriptor(Class<?> cls, Property[] properties, boolean partial) {
this.cls = cls;
this.properties = properties;
this.partial = partial;
}
}
public static class ClassNotFound extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
private final String classNotFoundDescription;
public ClassNotFound(String classNotFoundDescription) {
this.classNotFoundDescription = classNotFoundDescription;
}
public String getClassNotFoundDescription() {
return classNotFoundDescription;
}
}
private static class ClassNotFoundProperty implements Property {
private final String name;
public ClassNotFoundProperty(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public Class<?> getType() {
return Object.class;
}
@Override
public Type getGenericType() {
return Object.class;
}
@Override
public boolean hasField() {
return false;
}
@Override
public Field getField() {
return null;
}
@Override
public boolean hasGetter() {
return false;
}
@Override
public Method getGetter() {
return null;
}
@Override
public boolean hasSetter() {
return false;
}
@Override
public Method getSetter() {
return null;
}
@Override
public Class<?> getDeclaringClass() {
return ClassNotFound.class;
}
@Override
public Object init(ExtendedSpearalDecoder decoder, Object holder)
throws InstantiationException, IllegalAccessException, InvocationTargetException {
return null;
}
@Override
public Object get(Object holder)
throws IllegalAccessException, InvocationTargetException {
return ((ClassNotFound)holder).get(name);
}
@Override
public void set(Object holder, Object value)
throws IllegalAccessException, InvocationTargetException {
((ClassNotFound)holder).put(name, value);
}
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return false;
}
@Override
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
return null;
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public void write(ExtendedSpearalEncoder encoder, Object holder)
throws IOException, IllegalAccessException, InvocationTargetException {
throw new UnsupportedOperationException();
}
@Override
public void read(ExtendedSpearalDecoder decoder, Object holder, int parameterizedType)
throws IOException, InstantiationException, IllegalAccessException, InvocationTargetException {
set(holder, decoder.readAny(parameterizedType));
}
}
public static class CollectionPathSegmentImpl implements CollectionPathSegment {
private final Collection<?> collection;
private int index;
public CollectionPathSegmentImpl(Collection<?> collection) {
this.collection = collection;
}
public Collection<?> getCollection() {
return collection;
}
public int getIndex() {
return index;
}
@Override
public CollectionPathSegment copy() {
CollectionPathSegmentImpl copy = new CollectionPathSegmentImpl(collection);
copy.index = index;
return copy;
}
@Override
public String toString() {
return "[" + index + "]";
}
}
public static class ArrayPathSegmentImpl implements ArrayPathSegment {
private final Object array;
private int index;
public ArrayPathSegmentImpl(Object array) {
this.array = array;
}
public Object getArray() {
return array;
}
public int getIndex() {
return index;
}
@Override
public ArrayPathSegment copy() {
ArrayPathSegmentImpl copy = new ArrayPathSegmentImpl(array);
copy.index = index;
return copy;
}
@Override
public String toString() {
return "[" + index + "]";
}
}
public static class MapPathSegmentImpl implements MapPathSegment {
private final Map<?, ?> map;
private Object key;
public MapPathSegmentImpl(Map<?, ?> map) {
this.map = map;
}
public Map<?, ?> getMap() {
return map;
}
public Object getKey() {
return key;
}
@Override
public MapPathSegment copy() {
MapPathSegmentImpl copy = new MapPathSegmentImpl(map);
copy.key = key;
return copy;
}
@Override
public String toString() {
if (key == null)
return "";
try {
return key.toString();
}
catch (Exception e) {
return "[ERROR: " + e.getMessage() + "]";
}
}
}
public static class BeanPathSegmentImpl implements BeanPathSegment {
private final Object bean;
private Property property;
public BeanPathSegmentImpl(Object bean) {
this.bean = bean;
}
public Object getBean() {
return bean;
}
public Property getProperty() {
return property;
}
@Override
public BeanPathSegment copy() {
BeanPathSegmentImpl copy = new BeanPathSegmentImpl(bean);
copy.property = property;
return copy;
}
@Override
public String toString() {
return property.getName();
}
}
private static class PathImpl implements Path {
private static final int DEFAULT_SIZE_INCREMENT = 8;
private PathSegment[] segments;
private int size;
public PathImpl() {
this.segments = new PathSegment[0];
this.size = 0;
}
@Override
public Collection<PathSegment> segments() {
List<PathSegment> segmentsList = new ArrayList<PathSegment>(size);
for (int i = 0; i < size; i++)
segmentsList.add(segments[i]);
return segmentsList;
}
public void push(PathSegment segment) {
if (size == segments.length) {
PathSegment[] newSegments = new PathSegment[segments.length + DEFAULT_SIZE_INCREMENT];
if (segments.length > 0)
System.arraycopy(segments, 0, newSegments, 0, segments.length);
segments = newSegments;
}
segments[size++] = segment;
}
public void pop() {
if (size == 0)
throw new IndexOutOfBoundsException("Empty path");
size--;
}
public PathSegment peek() {
return (size == 0 ? null : segments[size - 1]);
}
@Override
public String toString() {
if (size == 0)
return "";
StringBuilder sb = new StringBuilder().append(segments[0]);
for (int i = 1; i < size; i++)
sb.append('.').append(segments[i]);
return sb.toString();
}
}
private static class PartialObjectMap extends IdentityHashMap<Object, List<PathSegment>> {
private static final long serialVersionUID = 1L;
public void put(Object partialObject, PathSegment segment) {
List<PathSegment> segments = new ArrayList<PathSegment>();
if (segment != null)
segments.add(segment.copy());
put(partialObject, segments);
}
public void appendIfPresent(Object partialObject, PathSegment segment) {
if (segment != null) {
List<PathSegment> segments = get(partialObject);
if (segments != null)
segments.add(segment.copy());
}
}
}
}