blob: 53cf81d2369b6d680d25bae39420c845a195624c [file] [log] [blame]
/*
* Copyright (c) 2007, Rickard Öberg. All Rights Reserved.
* Copyright (c) 2010, Niclas Hehdman. All Rights Reserved.
* Copyright (c) 2012-2014, Paul Merlin. All Rights Reserved.
*
* 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.qi4j.spi.value;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.qi4j.api.association.AssociationDescriptor;
import org.qi4j.api.entity.EntityReference;
import org.qi4j.api.injection.scope.Service;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.property.PropertyDescriptor;
import org.qi4j.api.service.ServiceReference;
import org.qi4j.api.structure.Application;
import org.qi4j.api.structure.Module;
import org.qi4j.api.type.CollectionType;
import org.qi4j.api.type.EnumType;
import org.qi4j.api.type.MapType;
import org.qi4j.api.type.ValueCompositeType;
import org.qi4j.api.type.ValueType;
import org.qi4j.api.type.Serialization;
import org.qi4j.api.util.Base64Encoder;
import org.qi4j.api.util.Dates;
import org.qi4j.api.value.ValueBuilder;
import org.qi4j.api.value.ValueDescriptor;
import org.qi4j.api.value.ValueDeserializer;
import org.qi4j.api.value.ValueSerializationException;
import org.qi4j.functional.Function;
import org.qi4j.functional.Function2;
import static org.qi4j.functional.Iterables.empty;
import static org.qi4j.functional.Iterables.first;
/**
* Adapter for pull-parsing and tree-parsing capable ValueDeserializers.
*
* <p>
* Among Plain values (see {@link ValueDeserializer}) some are considered primitives to underlying serialization
* mechanisms and by so handed/come without conversion to/from implementations. Primitive values can be one of:
* </p>
* <ul>
* <li>String,</li>
* <li>Character or char,</li>
* <li>Boolean or boolean,</li>
* <li>Integer or int,</li>
* <li>Long or long,</li>
* <li>Short or short,</li>
* <li>Byte or byte,</li>
* <li>Float or float,</li>
* <li>Double or double.</li>
* </ul>
* <p>
* Some other Plain values are expected in given formats:
* </p>
* <ul>
* <li>BigInteger and BigDecimal depends on {@link org.qi4j.api.value.ValueSerializer.Options};</li>
* <li>Date as String in ISO-8601, {@literal @millis@} or {@literal /Date(..)} Microsoft format;</li>
* <li>DateTime (JodaTime) as a ISO-8601 String with optional timezone offset;</li>
* <li>LocalDateTime (JodaTime) as whatever {@link LocalDateTime#LocalDateTime(java.lang.Object)} accept as {@literal instant};</li>
* <li>LocalDate (JodaTime) as whatever {@link LocalDate#LocalDate(java.lang.Object)} accept as {@literal instant};</li>
* </ul>
*
* @param <InputType> Implementor pull-parser type
* @param <InputNodeType> Implementor tree-parser node type
*/
public abstract class ValueDeserializerAdapter<InputType, InputNodeType>
implements ValueDeserializer
{
public static interface ComplexDeserializer<T, InputType, InputNodeType>
{
T deserializePull( InputType input )
throws Exception;
T deserializeTree( InputNodeType inputNode )
throws Exception;
}
private static final String UTF_8 = "UTF-8";
private final Map<Class<?>, Function<Object, Object>> deserializers = new HashMap<>( 16 );
private final Map<Class<?>, ComplexDeserializer<Object, InputType, InputNodeType>> complexDeserializers = new HashMap<>( 2 );
private final Application application;
private final Module module;
private Function<Application, Module> valuesModuleFinder;
private Module valuesModule;
/**
* Register a Plain Value type deserialization Function.
*
* @param <T> Plain Value parametrized Type
* @param type Plain Value Type
* @param deserializer Deserialization Function
*/
@SuppressWarnings( "unchecked" )
protected final <T> void registerDeserializer( Class<T> type, Function<Object, T> deserializer )
{
deserializers.put( type, (Function<Object, Object>) deserializer );
}
@SuppressWarnings( { "UnusedDeclaration", "unchecked" } )
protected final <T> void registerComplexDeserializer( Class<T> type,
ComplexDeserializer<T, InputType, InputNodeType> deserializer )
{
complexDeserializers.put( type, (ComplexDeserializer<Object, InputType, InputNodeType>) deserializer );
}
@SuppressWarnings( "unchecked" )
public ValueDeserializerAdapter( @Structure Application application,
@Structure Module module,
@Service ServiceReference<ValueDeserializer> serviceRef )
{
this( application, module, serviceRef.metaInfo( Function.class ) );
}
protected ValueDeserializerAdapter( Application application,
Module module,
Function<Application, Module> valuesModuleFinder )
{
this.application = application;
this.module = module;
setValuesModuleFinder( valuesModuleFinder );
// Primitive Value types
registerDeserializer( String.class, new Function<Object, String>()
{
@Override
public String map( Object input )
{
return input.toString();
}
} );
registerDeserializer( Character.class, new Function<Object, Character>()
{
@Override
public Character map( Object input )
{
return input.toString().charAt( 0 );
}
} );
registerDeserializer( Boolean.class, new Function<Object, Boolean>()
{
@SuppressWarnings( "UnnecessaryUnboxing" )
@Override
public Boolean map( Object input )
{
return ( input instanceof String )
? Boolean.parseBoolean( (String) input )
: ( (Boolean) input ).booleanValue();
}
} );
registerDeserializer( Integer.class, new Function<Object, Integer>()
{
@Override
public Integer map( Object input )
{
return ( input instanceof String )
? Integer.parseInt( (String) input )
: ( (Number) input ).intValue();
}
} );
registerDeserializer( Long.class, new Function<Object, Long>()
{
@Override
public Long map( Object input )
{
return ( input instanceof String )
? Long.parseLong( (String) input )
: ( (Number) input ).longValue();
}
} );
registerDeserializer( Short.class, new Function<Object, Short>()
{
@Override
public Short map( Object input )
{
return ( input instanceof String )
? Short.parseShort( (String) input )
: ( (Number) input ).shortValue();
}
} );
registerDeserializer( Byte.class, new Function<Object, Byte>()
{
@Override
public Byte map( Object input )
{
return ( input instanceof String )
? Byte.parseByte( (String) input )
: ( (Number) input ).byteValue();
}
} );
registerDeserializer( Float.class, new Function<Object, Float>()
{
@Override
public Float map( Object input )
{
return ( input instanceof String )
? Float.parseFloat( (String) input )
: ( (Number) input ).floatValue();
}
} );
registerDeserializer( Double.class, new Function<Object, Double>()
{
@Override
public Double map( Object input )
{
return ( input instanceof String )
? Double.parseDouble( (String) input )
: ( (Number) input ).doubleValue();
}
} );
// Number types
registerDeserializer( BigDecimal.class, new Function<Object, BigDecimal>()
{
@Override
public BigDecimal map( Object input )
{
return new BigDecimal( input.toString() );
}
} );
registerDeserializer( BigInteger.class, new Function<Object, BigInteger>()
{
@Override
public BigInteger map( Object input )
{
return new BigInteger( input.toString() );
}
} );
// Date types
registerDeserializer( Date.class, new Function<Object, Date>()
{
@Override
public Date map( Object input )
{
return Dates.fromString( input.toString() );
}
} );
registerDeserializer( DateTime.class, new Function<Object, DateTime>()
{
@Override
public DateTime map( Object input )
{
return DateTime.parse( input.toString() );
}
} );
registerDeserializer( LocalDateTime.class, new Function<Object, LocalDateTime>()
{
@Override
public LocalDateTime map( Object input )
{
return new LocalDateTime( input );
}
} );
registerDeserializer( LocalDate.class, new Function<Object, LocalDate>()
{
@Override
public LocalDate map( Object input )
{
return new LocalDate( input );
}
} );
// Other supported types
registerDeserializer( EntityReference.class, new Function<Object, EntityReference>()
{
@Override
public EntityReference map( Object input )
{
return EntityReference.parseEntityReference( input.toString() );
}
} );
}
private void setValuesModuleFinder( Function<Application, Module> valuesModuleFinder )
{
this.valuesModuleFinder = valuesModuleFinder;
this.valuesModule = null;
}
private Module valuesModule()
{
if( valuesModule == null )
{
if( valuesModuleFinder == null )
{
valuesModule = module;
}
else
{
valuesModule = valuesModuleFinder.map( application );
if( valuesModule == null )
{
throw new ValueSerializationException( "Values Module provided by the finder Function was null." );
}
}
}
return valuesModule;
}
@Override
public <T> Function<String, T> deserialize( Class<T> type )
{
if( CollectionType.isCollection( type ) )
{
ValueType objectValueType = new ValueType( Object.class );
return deserialize( new CollectionType( type, objectValueType ) );
}
if( MapType.isMap( type ) )
{
ValueType objectValueType = new ValueType( Object.class );
return deserialize( new MapType( type, objectValueType, objectValueType ) );
}
return deserialize( new ValueType( type ) );
}
@Override
public final <T> Function<String, T> deserialize( final ValueType valueType )
{
return new Function<String, T>()
{
@Override
public T map( String input )
{
return deserialize( valueType, input );
}
};
}
@Override
public final <T> Function2<ValueType, String, T> deserialize()
{
return new Function2<ValueType, String, T>()
{
@Override
public T map( ValueType valueType, String input )
{
return deserialize( valueType, input );
}
};
}
@Override
public final <T> T deserialize( Class<?> type, String input )
throws ValueSerializationException
{
if( CollectionType.isCollection( type ) )
{
ValueType objectValueType = new ValueType( Object.class );
return deserialize( new CollectionType( type, objectValueType ), input );
}
if( MapType.isMap( type ) )
{
ValueType objectValueType = new ValueType( Object.class );
return deserialize( new MapType( type, objectValueType, objectValueType ), input );
}
return deserialize( new ValueType( type ), input );
}
@Override
public final <T> T deserialize( ValueType valueType, String input )
throws ValueSerializationException
{
try
{
return deserializeRoot( valueType, new ByteArrayInputStream( input.getBytes( UTF_8 ) ) );
}
catch( ValueSerializationException ex )
{
throw ex;
}
catch( Exception ex )
{
throw new ValueSerializationException( "Could not deserialize value", ex );
}
}
@Override
public final <T> T deserialize( Class<?> type, InputStream input )
throws ValueSerializationException
{
if( CollectionType.isCollection( type ) )
{
ValueType objectValueType = new ValueType( Object.class );
return deserialize( new CollectionType( type, objectValueType ), input );
}
if( MapType.isMap( type ) )
{
ValueType objectValueType = new ValueType( Object.class );
return deserialize( new MapType( type, objectValueType, objectValueType ), input );
}
return deserialize( new ValueType( type ), input );
}
@Override
public final <T> T deserialize( ValueType valueType, InputStream input )
throws ValueSerializationException
{
try
{
return deserializeRoot( valueType, input );
}
catch( ValueSerializationException ex )
{
throw ex;
}
catch( Exception ex )
{
throw new ValueSerializationException( "Could not deserialize value", ex );
}
}
@SuppressWarnings( "unchecked" )
private <T> T deserializeRoot( ValueType valueType, InputStream input )
throws Exception
{
final Class<?> type = first( valueType.types() );
// Plain ValueType
if( deserializers.get( type ) != null )
{
Scanner scanner = new Scanner( input, UTF_8 ).useDelimiter( "\\A" );
if( !scanner.hasNext() )
{
return String.class.equals( type ) ? (T) "" : null;
}
String string = scanner.next();
return (T) deserializers.get( type ).map( string );
}
else // Array ValueType
if( type.isArray() )
{
Scanner scanner = new Scanner( input, UTF_8 ).useDelimiter( "\\A" );
if( !scanner.hasNext() )
{
return null;
}
String string = scanner.next();
return (T) deserializeBase64Serialized( string );
}
else // Complex ValueType
{
InputType adaptedInput = adaptInput( input );
onDeserializationStart( valueType, adaptedInput );
T deserialized = doDeserialize( valueType, adaptedInput );
onDeserializationEnd( valueType, adaptedInput );
return deserialized;
}
}
@SuppressWarnings( "unchecked" )
private <T> T doDeserialize( ValueType valueType, InputType input )
throws Exception
{
final Class<?> type = first( valueType.types() );
// Registered deserializers
if( deserializers.get( type ) != null )
{
Object value = readPlainValue( input );
if( value == null )
{
return null;
}
return (T) deserializers.get( type ).map( value );
}
else if( complexDeserializers.get( type ) != null )
{
return (T) complexDeserializers.get( type ).deserializePull( input );
}
else // Explicit ValueComposite
if( ValueCompositeType.class.isAssignableFrom( valueType.getClass() ) )
{
return (T) deserializeValueComposite( valueType, input );
}
else // Explicit Collections
if( CollectionType.class.isAssignableFrom( valueType.getClass() ) )
{
return (T) deserializeCollection( (CollectionType) valueType, input );
}
else // Explicit Map
if( MapType.class.isAssignableFrom( valueType.getClass() ) )
{
return (T) deserializeMap( (MapType) valueType, input );
}
else // Enum
if( EnumType.class.isAssignableFrom( valueType.getClass() ) || type.isEnum() )
{
return (T) Enum.valueOf( (Class) type, readPlainValue( input ).toString() );
}
else // Array
if( type.isArray() )
{
return (T) deserializeBase64Serialized( readPlainValue( input ).toString() );
}
// Guessed Deserialization
return (T) deserializeGuessed( valueType, input );
}
private <T> Function<InputType, T> buildDeserializeInputFunction( final ValueType valueType )
{
return new Function<InputType, T>()
{
@Override
public T map( InputType input )
{
try
{
return doDeserialize( valueType, input );
}
catch( ValueSerializationException ex )
{
throw ex;
}
catch( Exception ex )
{
throw new ValueSerializationException( ex );
}
}
};
}
private <T> Collection<T> deserializeCollection( CollectionType collectionType, InputType input )
throws Exception
{
Collection<T> collection;
Class<?> collectionMainType = first( collectionType.types() );
if( Set.class.equals( collectionMainType ) )
{
collection = new LinkedHashSet<>();
}
else
{
collection = new ArrayList<>();
}
return readArrayInCollection( input,
this.<T>buildDeserializeInputFunction( collectionType.collectedType() ),
collection );
}
private <K, V> Map<K, V> deserializeMap( MapType mapType, InputType input )
throws Exception
{
return readMapInMap( input,
this.<K>buildDeserializeInputFunction( mapType.keyType() ),
this.<V>buildDeserializeInputFunction( mapType.valueType() ),
new HashMap<K, V>() );
}
private <T> T deserializeValueComposite( ValueType valueType, InputType input )
throws Exception
{
InputNodeType inputNode = readObjectTree( input );
if( inputNode == null )
{
return null;
}
return deserializeNodeValueComposite( valueType, inputNode );
}
private <T> T deserializeNodeValueComposite( ValueType valueType, InputNodeType inputNode )
throws Exception
{
ValueCompositeType valueCompositeType = (ValueCompositeType) valueType;
Class<?> valueBuilderType = first( valueCompositeType.types() );
String typeInfo = this.getObjectFieldValue(
inputNode,
"_type",
this.<String>buildDeserializeInputNodeFunction( new ValueType( String.class ) ) );
if( typeInfo != null )
{
ValueDescriptor valueDescriptor = valuesModule().valueDescriptor( typeInfo );
if( valueDescriptor == null )
{
throw new ValueSerializationException( "Specified value type could not be resolved: " + typeInfo );
}
valueCompositeType = valueDescriptor.valueType();
valueBuilderType = Class.forName( typeInfo );
}
return deserializeValueComposite( valueCompositeType, valueBuilderType, inputNode );
}
@SuppressWarnings( "unchecked" )
private <T> T deserializeValueComposite( ValueCompositeType valueCompositeType, Class<?> valueBuilderType, InputNodeType inputNode )
throws Exception
{
final Map<String, Object> stateMap = new HashMap<>();
// Properties
for( PropertyDescriptor property : valueCompositeType.properties() )
{
String propertyName = property.qualifiedName().name();
Object value;
if( objectHasField( inputNode, propertyName ) )
{
value = getObjectFieldValue(
inputNode,
propertyName,
buildDeserializeInputNodeFunction( property.valueType() ) );
if( property.isImmutable() )
{
if( value instanceof Set )
{
value = Collections.unmodifiableSet( (Set<?>) value );
}
else if( value instanceof List )
{
value = Collections.unmodifiableList( (List<?>) value );
}
else if( value instanceof Map )
{
value = Collections.unmodifiableMap( (Map<?, ?>) value );
}
}
}
else
{
// Serialized object does not contain the field, try to default it
value = property.initialValue( valuesModule() );
}
stateMap.put( propertyName, value );
}
// Associations
for( AssociationDescriptor association : valueCompositeType.associations() )
{
String associationName = association.qualifiedName().name();
if( objectHasField( inputNode, associationName ) )
{
Object value = getObjectFieldValue(
inputNode,
associationName,
buildDeserializeInputNodeFunction( new ValueType( EntityReference.class ) ) );
stateMap.put( associationName, value );
}
}
// ManyAssociations
for( AssociationDescriptor manyAssociation : valueCompositeType.manyAssociations() )
{
String manyAssociationName = manyAssociation.qualifiedName().name();
if( objectHasField( inputNode, manyAssociationName ) )
{
Object value = getObjectFieldValue(
inputNode,
manyAssociationName,
buildDeserializeInputNodeFunction( new CollectionType( Collection.class,
new ValueType( EntityReference.class ) ) ) );
stateMap.put( manyAssociationName, value );
}
}
// NamedAssociations
for( AssociationDescriptor namedAssociation : valueCompositeType.namedAssociations() )
{
String namedAssociationName = namedAssociation.qualifiedName().name();
if( objectHasField( inputNode, namedAssociationName ) )
{
Object value = getObjectFieldValue(
inputNode,
namedAssociationName,
buildDeserializeInputNodeFunction( MapType.of( String.class, EntityReference.class, Serialization.Variant.object ) ) );
stateMap.put( namedAssociationName, value );
}
}
ValueBuilder<?> valueBuilder = buildNewValueBuilderWithState( valueBuilderType, stateMap );
return (T) valueBuilder.newInstance(); // Unchecked cast because the builder could use a type != T
}
private <T> Function<InputNodeType, T> buildDeserializeInputNodeFunction( final ValueType valueType )
{
return new Function<InputNodeType, T>()
{
@Override
public T map( InputNodeType inputNode )
{
try
{
return doDeserializeInputNodeValue( valueType, inputNode );
}
catch( ValueSerializationException ex )
{
throw ex;
}
catch( Exception ex )
{
throw new ValueSerializationException( ex );
}
}
};
}
@SuppressWarnings( "unchecked" )
private <T> T doDeserializeInputNodeValue( ValueType valueType, InputNodeType inputNode )
throws Exception
{
if( inputNode == null )
{
return null;
}
final Class<?> type = first( valueType.types() );
// Registered deserializers
if( deserializers.get( type ) != null )
{
Object value = asSimpleValue( inputNode );
if( value == null )
{
return null;
}
return (T) deserializers.get( type ).map( value );
}
else if( complexDeserializers.get( type ) != null )
{
return (T) complexDeserializers.get( type ).deserializeTree( inputNode );
}
else // Explicit ValueComposite
if( ValueCompositeType.class.isAssignableFrom( valueType.getClass() ) )
{
return (T) deserializeNodeValueComposite( valueType, inputNode );
}
else // Explicit Collections
if( CollectionType.class.isAssignableFrom( valueType.getClass() ) )
{
return (T) deserializeNodeCollection( (CollectionType) valueType, inputNode );
}
else // Explicit Map
if( MapType.class.isAssignableFrom( valueType.getClass() ) )
{
MapType mapType = (MapType) valueType;
if( mapType.variant().equals( Serialization.Variant.entry ) )
{
return (T) deserializeNodeEntryMap( (MapType) valueType, inputNode );
}
else
{
return (T) deserializeNodeObjectMap( (MapType) valueType, inputNode );
}
}
else // Enum
if( EnumType.class.isAssignableFrom( valueType.getClass() ) || type.isEnum() )
{
Object value = asSimpleValue( inputNode );
if( value == null )
{
return null;
}
return (T) Enum.valueOf( (Class) type, value.toString() );
}
// Guessed deserialization
return (T) deserializeNodeGuessed( valueType, inputNode );
}
private ValueBuilder<?> buildNewValueBuilderWithState( Class<?> type, final Map<String, Object> stateMap )
{
return valuesModule().newValueBuilderWithState(
type,
new Function<PropertyDescriptor, Object>()
{
@Override
public Object map( PropertyDescriptor property )
{
return stateMap.get( property.qualifiedName().name() );
}
},
new Function<AssociationDescriptor, EntityReference>()
{
@Override
public EntityReference map( AssociationDescriptor association )
{
Object entityRef = stateMap.get( association.qualifiedName().name() );
if( entityRef == null )
{
return null;
}
return (EntityReference) entityRef;
}
},
new Function<AssociationDescriptor, Iterable<EntityReference>>()
{
@Override
@SuppressWarnings( "unchecked" )
public Iterable<EntityReference> map( AssociationDescriptor manyAssociation )
{
Object entityRefs = stateMap.get( manyAssociation.qualifiedName().name() );
if( entityRefs == null )
{
return empty();
}
return (Iterable<EntityReference>) entityRefs;
}
},
new Function<AssociationDescriptor, Map<String, EntityReference>>()
{
@Override
@SuppressWarnings( "unchecked" )
public Map<String, EntityReference> map( AssociationDescriptor namedAssociation )
{
Object entityRefs = stateMap.get( namedAssociation.qualifiedName().name() );
if( entityRefs == null )
{
return Collections.emptyMap();
}
return (Map<String, EntityReference>) entityRefs;
}
} );
}
@SuppressWarnings( "unchecked" )
private <T> T deserializeGuessed( ValueType valueType, InputType input )
throws Exception
{
InputNodeType inputNode = readObjectTree( input );
if( inputNode == null )
{
return null;
}
return deserializeNodeGuessed( valueType, inputNode );
}
private <T> Collection<T> deserializeNodeCollection( CollectionType collectionType, InputNodeType inputNode )
throws Exception
{
Collection<T> collection;
Class<?> collectionMainType = first( collectionType.types() );
if( Set.class.equals( collectionMainType ) )
{
collection = new LinkedHashSet<>();
}
else
{
collection = new ArrayList<>();
}
putArrayNodeInCollection( inputNode,
this.<T>buildDeserializeInputNodeFunction( collectionType.collectedType() ),
collection );
return collection;
}
private <K, V> Map<K, V> deserializeNodeEntryMap( MapType mapType, InputNodeType inputNode )
throws Exception
{
Map<K, V> map = new HashMap<>();
putArrayNodeInMap( inputNode,
this.<K>buildDeserializeInputNodeFunction( mapType.keyType() ),
this.<V>buildDeserializeInputNodeFunction( mapType.valueType() ),
map );
return map;
}
private <V> Map<String, V> deserializeNodeObjectMap( MapType mapType, InputNodeType inputNode )
throws Exception
{
Map<String, V> map = new HashMap<>();
putObjectNodeInMap( inputNode,
this.<V>buildDeserializeInputNodeFunction( mapType.valueType() ),
map );
return map;
}
@SuppressWarnings( "unchecked" )
private <T> T deserializeNodeGuessed( ValueType valueType, InputNodeType inputNode )
throws Exception
{
if( isObjectValue( inputNode ) )
{
// Attempt ValueComposite deserialization
ValueCompositeType valueCompositeType;
if( objectHasField( inputNode, "_type" ) ) // with _type info
{
String typeInfo = this.getObjectFieldValue(
inputNode,
"_type",
this.<String>buildDeserializeInputNodeFunction( new ValueType( String.class ) ) );
ValueDescriptor valueDescriptor = valuesModule().valueDescriptor( typeInfo );
if( valueDescriptor == null )
{
throw new ValueSerializationException( "Specified value type could not be resolved: " + typeInfo );
}
valueCompositeType = valueDescriptor.valueType();
}
else // without _type info
{
ValueDescriptor valueDescriptor = valuesModule().valueDescriptor( first( valueType.types() ).getName() );
if( valueDescriptor == null )
{
throw new ValueSerializationException( "Don't know how to deserialize " + inputNode );
}
valueCompositeType = valueDescriptor.valueType();
}
Class<?> valueBuilderType = first( valueCompositeType.types() );
return deserializeValueComposite( valueCompositeType, valueBuilderType, inputNode );
}
// Last resort : base64 java deserialization
return (T) deserializeBase64Serialized( inputNode );
}
@SuppressWarnings( "unchecked" )
private <T> T deserializeBase64Serialized( InputNodeType inputNode )
throws Exception
{
Object value = asSimpleValue( inputNode );
if( value == null )
{
return null;
}
String base64 = value.toString();
return deserializeBase64Serialized( base64 );
}
@SuppressWarnings( "unchecked" )
private <T> T deserializeBase64Serialized( String inputString )
throws Exception
{
byte[] bytes = inputString.getBytes( UTF_8 );
bytes = Base64Encoder.decode( bytes );
Object result;
try( ObjectInputStream oin = new ObjectInputStream( new ByteArrayInputStream( bytes ) ) )
{
result = oin.readObject();
}
return (T) result;
}
//
// Deserialization Extension Points
//
/**
* Called by the adapter on deserialization start, after {@link #adaptInput(java.io.InputStream)}.
*
* @param valueType ValueType
* @param input Input
* @throws Exception that will be wrapped in a {@link ValueSerializationException}
*/
@SuppressWarnings( "UnusedParameters" )
protected void onDeserializationStart( ValueType valueType, InputType input )
throws Exception
{
// NOOP
}
/**
* Called by the adapter on deserialization end.
*
* @param valueType ValueType
* @param input Input
* @throws Exception that will be wrapped in a {@link ValueSerializationException}
*/
protected void onDeserializationEnd( ValueType valueType, InputType input )
throws Exception
{
// NOOP
}
//
// Pull Parsing Deserialization
//
/**
* This method is always called first, this is a chance to wrap the input type.
*
* @param input InputStream to adapt
* @return Adapted input
* @throws Exception that will be wrapped in a {@link ValueSerializationException}
*/
protected abstract InputType adaptInput( InputStream input )
throws Exception;
/**
* @param input Input
* @return a Plain Value read from the input
* @throws Exception that will be wrapped in a {@link ValueSerializationException}
*/
protected abstract Object readPlainValue( InputType input )
throws Exception;
/**
* @param <T> Parameterized collection type
* @param input Input
* @param deserializer Deserialization function
* @param collection Collection
* @return The filled collection or null if no array
* @throws Exception that will be wrapped in a {@link ValueSerializationException}
*/
protected abstract <T> Collection<T> readArrayInCollection( InputType input,
Function<InputType, T> deserializer,
Collection<T> collection )
throws Exception;
/**
* A Map&lt;K,V&gt; is serialized in an array of entries objects.
*
* <p>Here is an example in JSON:</p>
* <pre>
* [
* { "key": "foo", "value": "bar" },
* { "key": "cathedral", "value": "bazar" }
* ]
* </pre>
* <p>And an empty Map:</p>
* <pre>[]</pre>
* <p>
* This allow to use any type as keys and values while keeping the Map order at the cost of having
* non-predictible order of key/value inside an entry object.
* </p>
*
* @param <K> Parameterized map key type
* @param <V> Parameterized map value type
* @param input Input
* @param keyDeserializer Map key deserialization function
* @param valueDeserializer Map value deserialization function
* @param map Map
* @return The filled map or null if no array
* @throws Exception that will be wrapped in a {@link ValueSerializationException}
*/
protected abstract <K, V> Map<K, V> readMapInMap( InputType input,
Function<InputType, K> keyDeserializer,
Function<InputType, V> valueDeserializer,
Map<K, V> map )
throws Exception;
/**
* @param input Input
* @return an InputNodeType or null if the value was null
* @throws Exception that will be wrapped in a {@link ValueSerializationException}
*/
protected abstract InputNodeType readObjectTree( InputType input )
throws Exception;
//
// Tree Parsing Deserialization
//
protected abstract Object asSimpleValue( InputNodeType inputNode )
throws Exception;
protected abstract boolean isObjectValue( InputNodeType inputNode )
throws Exception;
protected abstract boolean objectHasField( InputNodeType inputNode, String key )
throws Exception;
/**
* Return null if the field do not exists.
* @param <T> Parameterized object field value type
* @param inputNode Input Node
* @param key Object key
* @param valueDeserializer Deserialization function
* @return The value of the field.
* @throws Exception that will be wrapped in a {@link ValueSerializationException}
*/
protected abstract <T> T getObjectFieldValue( InputNodeType inputNode,
String key,
Function<InputNodeType, T> valueDeserializer )
throws Exception;
protected abstract <T> void putArrayNodeInCollection( InputNodeType inputNode,
Function<InputNodeType, T> deserializer,
Collection<T> collection )
throws Exception;
protected abstract <K, V> void putArrayNodeInMap( InputNodeType inputNode,
Function<InputNodeType, K> keyDeserializer,
Function<InputNodeType, V> valueDeserializer,
Map<K, V> map
)
throws Exception;
protected abstract <V> void putObjectNodeInMap( InputNodeType inputNode,
Function<InputNodeType, V> valueDeserializer,
Map<String, V> map
)
throws Exception;
}