/*
* Copyright 2013 Nicolas Morel
*
* 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 com.github.nmorel.gwtjackson.client.ser.bean;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.github.nmorel.gwtjackson.client.JsonSerializationContext;
import com.github.nmorel.gwtjackson.client.JsonSerializer;
import com.github.nmorel.gwtjackson.client.JsonSerializerParameters;
import com.github.nmorel.gwtjackson.client.stream.JsonWriter;
/**
* Base implementation of {@link JsonSerializer} for beans.
*
* @author Nicolas Morel
* @version $Id: $
*/
public abstract class AbstractBeanJsonSerializer<T> extends JsonSerializer<T> implements InternalSerializer<T> {
protected final BeanPropertySerializer[] serializers;
private final Map<Class, SubtypeSerializer> subtypeClassToSerializer;
private final IdentitySerializationInfo<T> defaultIdentityInfo;
private final TypeSerializationInfo<T> defaultTypeInfo;
private final AnyGetterPropertySerializer<T> anyGetterPropertySerializer;
/**
* <p>Constructor for AbstractBeanJsonSerializer.</p>
*/
protected AbstractBeanJsonSerializer() {
this.serializers = initSerializers();
this.defaultIdentityInfo = initIdentityInfo();
this.defaultTypeInfo = initTypeInfo();
this.subtypeClassToSerializer = initMapSubtypeClassToSerializer();
this.anyGetterPropertySerializer = initAnyGetterPropertySerializer();
}
/**
* Initialize the {@link Map} containing the property serializers. Returns an empty map if there are no properties to
* serialize.
*
* @return an array of {@link com.github.nmorel.gwtjackson.client.ser.bean.BeanPropertySerializer} objects.
*/
protected BeanPropertySerializer[] initSerializers() {
return new BeanPropertySerializer[0];
}
/**
* Initialize the {@link IdentitySerializationInfo}. Returns null if there is no {@link JsonIdentityInfo} annotation on bean.
*
* @return a {@link com.github.nmorel.gwtjackson.client.ser.bean.IdentitySerializationInfo} object.
*/
protected IdentitySerializationInfo<T> initIdentityInfo() {
return null;
}
/**
* Initialize the {@link TypeSerializationInfo}. Returns null if there is no {@link JsonTypeInfo} annotation on bean.
*
* @return a {@link com.github.nmorel.gwtjackson.client.ser.bean.TypeSerializationInfo} object.
*/
protected TypeSerializationInfo<T> initTypeInfo() {
return null;
}
/**
* Initialize the {@link Map} containing the {@link SubtypeSerializer}. Returns an empty map if the bean has no subtypes.
*
* @return a {@link java.util.Map} object.
*/
protected Map<Class, SubtypeSerializer> initMapSubtypeClassToSerializer() {
return Collections.emptyMap();
}
/**
* Initialize the {@link AnyGetterPropertySerializer}. Returns null if there is no method annoted with {@link JsonAnyGetter} on bean.
*
* @return a {@link com.github.nmorel.gwtjackson.client.ser.bean.AnyGetterPropertySerializer} object.
*/
protected AnyGetterPropertySerializer<T> initAnyGetterPropertySerializer() {
return null;
}
/**
* <p>getSerializedType</p>
*
* @return a {@link java.lang.Class} object.
*/
public abstract Class getSerializedType();
/** {@inheritDoc} */
@Override
public void doSerialize( JsonWriter writer, T value, JsonSerializationContext ctx, JsonSerializerParameters params ) {
getSerializer( writer, value, ctx ).serializeInternally( writer, value, ctx, params, defaultIdentityInfo, defaultTypeInfo );
}
private InternalSerializer<T> getSerializer( JsonWriter writer, T value, JsonSerializationContext ctx ) {
if ( value.getClass() == getSerializedType() ) {
return this;
}
SubtypeSerializer subtypeSerializer = subtypeClassToSerializer.get( value.getClass() );
if ( null == subtypeSerializer ) {
if ( ctx.getLogger().isLoggable( Level.FINE ) ) {
ctx.getLogger().fine( "Cannot find serializer for class " + value
.getClass() + ". Fallback to the serializer of " + getSerializedType() );
}
return this;
}
return subtypeSerializer;
}
/** {@inheritDoc} */
public void serializeInternally( JsonWriter writer, T value, JsonSerializationContext ctx, JsonSerializerParameters params,
IdentitySerializationInfo<T> defaultIdentityInfo, TypeSerializationInfo<T> defaultTypeInfo ) {
// Processing the parameters. We fallback to default if parameter is not present.
final IdentitySerializationInfo identityInfo = null == params.getIdentityInfo() ? defaultIdentityInfo : params.getIdentityInfo();
final TypeSerializationInfo typeInfo = null == params.getTypeInfo() ? defaultTypeInfo : params.getTypeInfo();
final Set<String> ignoredProperties = null == params.getIgnoredProperties() ? Collections.<String>emptySet() : params
.getIgnoredProperties();
if ( params.isUnwrapped() ) {
// if unwrapped, we serialize the properties inside the current object
serializeProperties( writer, value, ctx, ignoredProperties, identityInfo );
return;
}
ObjectIdSerializer<?> idWriter = null;
if ( null != identityInfo ) {
idWriter = ctx.getObjectId( value );
if ( null != idWriter ) {
// the bean has already been serialized, we just serialize the id
idWriter.serializeId( writer, ctx );
return;
}
idWriter = identityInfo.getObjectId( value, ctx );
if ( identityInfo.isAlwaysAsId() ) {
idWriter.serializeId( writer, ctx );
return;
}
ctx.addObjectId( value, idWriter );
}
if ( null != typeInfo ) {
String typeInformation = typeInfo.getTypeInfo( value.getClass() );
if ( null == typeInformation ) {
ctx.getLogger().log( Level.WARNING, "Cannot find type info for class " + value.getClass() );
} else {
switch ( typeInfo.getInclude() ) {
case PROPERTY:
// type info is included as a property of the object
serializeObject( writer, value, ctx, ignoredProperties, identityInfo, idWriter, typeInfo
.getPropertyName(), typeInformation );
return;
case WRAPPER_OBJECT:
// type info is included in a wrapper object that contains only one property. The name of this property is the type
// info and the value the object
writer.beginObject();
writer.name( typeInformation );
serializeObject( writer, value, ctx, ignoredProperties, identityInfo, idWriter );
writer.endObject();
return;
case WRAPPER_ARRAY:
// type info is included in a wrapper array that contains two elements. First one is the type
// info and the second one the object
writer.beginArray();
writer.value( typeInformation );
serializeObject( writer, value, ctx, ignoredProperties, identityInfo, idWriter );
writer.endArray();
return;
default:
ctx.getLogger().log( Level.SEVERE, "JsonTypeInfo.As." + typeInfo.getInclude() + " is not supported" );
}
}
}
serializeObject( writer, value, ctx, ignoredProperties, identityInfo, idWriter );
}
/**
* Serializes all the properties of the bean in a json object.
*
* @param writer writer
* @param value bean to serialize
* @param ctx context of the serialization process
* @param ignoredProperties ignored properties
* @param identityInfo identity info
* @param idWriter identifier writer
*/
private void serializeObject( JsonWriter writer, T value, JsonSerializationContext ctx, Set<String> ignoredProperties,
IdentitySerializationInfo identityInfo, ObjectIdSerializer<?> idWriter ) {
serializeObject( writer, value, ctx, ignoredProperties, identityInfo, idWriter, null, null );
}
/**
* Serializes all the properties of the bean in a json object.
*
* @param writer writer
* @param value bean to serialize
* @param ctx context of the serialization process
* @param ignoredProperties ignored properties
* @param identityInfo identity info
* @param idWriter identifier writer
* @param typeName in case of type info as property, the name of the property
* @param typeInformation in case of type info as property, the type information
*/
protected void serializeObject( JsonWriter writer, T value, JsonSerializationContext ctx, Set<String> ignoredProperties,
IdentitySerializationInfo identityInfo, ObjectIdSerializer<?> idWriter, String typeName, String
typeInformation ) {
writer.beginObject();
if ( null != typeName && null != typeInformation ) {
writer.name( typeName );
writer.value( typeInformation );
}
if ( null != idWriter ) {
writer.name( identityInfo.getPropertyName() );
idWriter.serializeId( writer, ctx );
}
serializeProperties( writer, value, ctx, ignoredProperties, identityInfo );
writer.endObject();
}
private void serializeProperties( JsonWriter writer, T value, JsonSerializationContext ctx, Set<String> ignoredProperties,
IdentitySerializationInfo identityInfo ) {
for ( BeanPropertySerializer<T, ?> propertySerializer : serializers ) {
if ( (null == identityInfo || !identityInfo.isProperty() || !identityInfo.getPropertyName().equals( propertySerializer
.getPropertyName() )) && !ignoredProperties.contains( propertySerializer.getPropertyName() ) ) {
propertySerializer.serializePropertyName( writer, value, ctx );
propertySerializer.serialize( writer, value, ctx );
}
}
if ( null != anyGetterPropertySerializer ) {
anyGetterPropertySerializer.serialize( writer, value, ctx );
}
}
}