diff --git a/examples/benchmark/src/main/java/com/github/nmorel/gwtjackson/benchmark/client/BenchmarkView.java b/examples/benchmark/src/main/java/com/github/nmorel/gwtjackson/benchmark/client/BenchmarkView.java index 198be976..9e7b8621 100644 --- a/examples/benchmark/src/main/java/com/github/nmorel/gwtjackson/benchmark/client/BenchmarkView.java +++ b/examples/benchmark/src/main/java/com/github/nmorel/gwtjackson/benchmark/client/BenchmarkView.java @@ -48,8 +48,6 @@ */ public class BenchmarkView extends Composite implements Editor { - public static interface PersonMapper extends ObjectMapper> {} - interface CriteriaDriver extends SimpleBeanEditorDriver {} interface BenchmarkViewUiBinder extends UiBinder {} diff --git a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/AbstractBeanJsonSerializer.java b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/AbstractBeanJsonSerializer.java index 48aa93ad..a395a849 100644 --- a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/AbstractBeanJsonSerializer.java +++ b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/AbstractBeanJsonSerializer.java @@ -37,7 +37,7 @@ */ public abstract class AbstractBeanJsonSerializer extends JsonSerializer implements InternalSerializer { - protected final Map> serializers; + protected final BeanPropertySerializer[] serializers; private final Map subtypeClassToSerializer; @@ -59,8 +59,8 @@ protected AbstractBeanJsonSerializer() { * Initialize the {@link Map} containing the property serializers. Returns an empty map if there are no properties to * serialize. */ - protected Map> initSerializers() { - return Collections.emptyMap(); + protected BeanPropertySerializer[] initSerializers() { + return new BeanPropertySerializer[0]; } /** @@ -95,8 +95,7 @@ protected AnyGetterPropertySerializer initAnyGetterPropertySerializer() { @Override public void doSerialize( JsonWriter writer, @Nonnull T value, JsonSerializationContext ctx, JsonSerializerParameters params ) { - getSerializer( writer, value, ctx ) - .serializeInternally( writer, value, ctx, params, defaultIdentityInfo, defaultTypeInfo, serializers ); + getSerializer( writer, value, ctx ).serializeInternally( writer, value, ctx, params, defaultIdentityInfo, defaultTypeInfo ); } private InternalSerializer getSerializer( JsonWriter writer, T value, JsonSerializationContext ctx ) { @@ -115,8 +114,7 @@ private InternalSerializer getSerializer( JsonWriter writer, T value, JsonSer } public void serializeInternally( JsonWriter writer, T value, JsonSerializationContext ctx, JsonSerializerParameters params, - IdentitySerializationInfo defaultIdentityInfo, TypeSerializationInfo defaultTypeInfo, - Map> serializers ) { + IdentitySerializationInfo defaultIdentityInfo, TypeSerializationInfo defaultTypeInfo ) { // Processing the parameters. We fallback to default if parameter is not present. final IdentitySerializationInfo identityInfo = null == params.getIdentityInfo() ? defaultIdentityInfo : params.getIdentityInfo(); @@ -133,12 +131,7 @@ public void serializeInternally( JsonWriter writer, T value, JsonSerializationCo return; } - if ( identityInfo.isProperty() ) { - BeanPropertySerializer propertySerializer = serializers.get( identityInfo.getPropertyName() ); - idWriter = new ObjectIdSerializer( propertySerializer.getValue( value, ctx ), propertySerializer.getSerializer() ); - } else { - idWriter = identityInfo.getObjectId( value, ctx ); - } + idWriter = identityInfo.getObjectId( value, ctx ); if ( identityInfo.isAlwaysAsId() ) { idWriter.serializeId( writer, ctx ); return; @@ -227,11 +220,11 @@ protected void serializeObject( JsonWriter writer, T value, JsonSerializationCon idWriter.serializeId( writer, ctx ); } - for ( Map.Entry> entry : serializers.entrySet() ) { - if ( (null == identityInfo || !identityInfo.isProperty() || !identityInfo.getPropertyName().equals( entry - .getKey() )) && !ignoredProperties.contains( entry.getKey() ) ) { - writer.name( entry.getKey() ); - entry.getValue().serialize( writer, value, ctx ); + for ( BeanPropertySerializer 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 ); } } diff --git a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/AnyGetterPropertySerializer.java b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/AnyGetterPropertySerializer.java index 0909b81a..d74fa89a 100644 --- a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/AnyGetterPropertySerializer.java +++ b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/AnyGetterPropertySerializer.java @@ -31,6 +31,14 @@ public abstract class AnyGetterPropertySerializer extends BeanPropertySeriali protected abstract MapJsonSerializer newSerializer(); + public AnyGetterPropertySerializer() { + super( null ); + } + + public void serializePropertyName( JsonWriter writer, T bean, JsonSerializationContext ctx ) { + // no-op + } + /** * Serializes the property defined for this instance. * @@ -40,7 +48,7 @@ public abstract class AnyGetterPropertySerializer extends BeanPropertySeriali */ public void serialize( JsonWriter writer, T bean, JsonSerializationContext ctx ) { Map map = getValue( bean, ctx ); - if(null != map) { + if ( null != map ) { ((MapJsonSerializer) getSerializer()).serializeValues( writer, map, ctx, getParameters() ); } } diff --git a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/BeanPropertySerializer.java b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/BeanPropertySerializer.java index dd31e590..32284427 100644 --- a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/BeanPropertySerializer.java +++ b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/BeanPropertySerializer.java @@ -28,8 +28,14 @@ */ public abstract class BeanPropertySerializer extends HasSerializer> { + protected final String propertyName; + private JsonSerializerParameters parameters; + protected BeanPropertySerializer( String propertyName ) { + this.propertyName = propertyName; + } + protected JsonSerializerParameters getParameters() { if ( null == parameters ) { parameters = newParameters(); @@ -41,6 +47,21 @@ protected JsonSerializerParameters newParameters() { return JsonSerializerParameters.DEFAULT; } + public String getPropertyName() { + return propertyName; + } + + /** + * Serializes the property name + * + * @param writer writer + * @param bean bean containing the property to serialize + * @param ctx context of the serialization process + */ + public void serializePropertyName( JsonWriter writer, T bean, JsonSerializationContext ctx ) { + writer.unescapeName( propertyName ); + } + /** * @param bean bean containing the property to serialize * @param ctx context of the serialization process diff --git a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/InternalSerializer.java b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/InternalSerializer.java index f39b0fe5..8acaf87a 100644 --- a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/InternalSerializer.java +++ b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/InternalSerializer.java @@ -16,8 +16,6 @@ package com.github.nmorel.gwtjackson.client.ser.bean; -import java.util.Map; - import com.github.nmorel.gwtjackson.client.JsonSerializationContext; import com.github.nmorel.gwtjackson.client.JsonSerializerParameters; import com.github.nmorel.gwtjackson.client.stream.JsonWriter; @@ -30,8 +28,7 @@ interface InternalSerializer { void serializeInternally( JsonWriter writer, T value, JsonSerializationContext ctx, JsonSerializerParameters params, - IdentitySerializationInfo defaultIdentityInfo, TypeSerializationInfo defaultTypeInfo, Map> serializers ); + IdentitySerializationInfo defaultIdentityInfo, TypeSerializationInfo defaultTypeInfo ); } diff --git a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/PropertyIdentitySerializationInfo.java b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/PropertyIdentitySerializationInfo.java index df384515..067342c9 100644 --- a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/PropertyIdentitySerializationInfo.java +++ b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/PropertyIdentitySerializationInfo.java @@ -23,21 +23,16 @@ * * @author Nicolas Morel */ -public class PropertyIdentitySerializationInfo implements IdentitySerializationInfo { +public abstract class PropertyIdentitySerializationInfo extends BeanPropertySerializer implements IdentitySerializationInfo { /** * if we always serialize the bean as an id even for the first encounter. */ private final boolean alwaysAsId; - /** - * Name of the property holding the identity - */ - private final String propertyName; - public PropertyIdentitySerializationInfo( boolean alwaysAsId, String propertyName ) { + super( propertyName ); this.alwaysAsId = alwaysAsId; - this.propertyName = propertyName; } @Override @@ -50,13 +45,8 @@ public boolean isProperty() { return true; } - @Override - public String getPropertyName() { - return propertyName; - } - @Override public ObjectIdSerializer getObjectId( T bean, JsonSerializationContext ctx ) { - throw ctx.traceError( bean, "getObjectId() is not supported by PropertyIdentitySerializationInfo" ); + return new ObjectIdSerializer( getValue( bean, ctx ), getSerializer() ); } } diff --git a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/SubtypeSerializer.java b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/SubtypeSerializer.java index dee457e5..7259016b 100644 --- a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/SubtypeSerializer.java +++ b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/ser/bean/SubtypeSerializer.java @@ -16,8 +16,6 @@ package com.github.nmorel.gwtjackson.client.ser.bean; -import java.util.Map; - import com.github.nmorel.gwtjackson.client.JsonSerializationContext; import com.github.nmorel.gwtjackson.client.JsonSerializer; import com.github.nmorel.gwtjackson.client.JsonSerializerParameters; @@ -39,9 +37,8 @@ public abstract static class BeanSubtypeSerializer extends SubtypeSerializer< @Override public void serializeInternally( JsonWriter writer, T value, JsonSerializationContext ctx, JsonSerializerParameters params, - IdentitySerializationInfo defaultIdentityInfo, TypeSerializationInfo defaultTypeInfo, - Map> serializers ) { - getSerializer().serializeInternally( writer, value, ctx, params, defaultIdentityInfo, defaultTypeInfo, serializers ); + IdentitySerializationInfo defaultIdentityInfo, TypeSerializationInfo defaultTypeInfo ) { + getSerializer().serializeInternally( writer, value, ctx, params, defaultIdentityInfo, defaultTypeInfo ); } } @@ -54,8 +51,7 @@ public abstract static class DefaultSubtypeSerializer extends SubtypeSerializ @Override public void serializeInternally( JsonWriter writer, T value, JsonSerializationContext ctx, JsonSerializerParameters params, - IdentitySerializationInfo defaultIdentityInfo, TypeSerializationInfo defaultTypeInfo, - Map> serializers ) { + IdentitySerializationInfo defaultIdentityInfo, TypeSerializationInfo defaultTypeInfo ) { final TypeSerializationInfo typeInfo = null == params.getTypeInfo() ? defaultTypeInfo : params.getTypeInfo(); diff --git a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/stream/impl/DefaultJsonWriter.java b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/stream/impl/DefaultJsonWriter.java index 2f092e02..c92dd974 100644 --- a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/stream/impl/DefaultJsonWriter.java +++ b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/client/stream/impl/DefaultJsonWriter.java @@ -467,8 +467,13 @@ public void close() { } private void string(String value) { - String[] replacements = REPLACEMENT_CHARS; out.append("\""); + encodeString( value, out ); + out.append("\""); + } + + private static void encodeString(final String value, final StringBuilder out) { + String[] replacements = REPLACEMENT_CHARS; int last = 0; int length = value.length(); for (int i = 0; i < length; i++) { @@ -495,7 +500,12 @@ private void string(String value) { if (last < length) { out.append(value, last, length); } - out.append("\""); + } + + public static String encodeString(final String value) { + StringBuilder out = new StringBuilder(); + encodeString( value, out ); + return out.toString(); } private void newline() { diff --git a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/AbstractBeanJsonCreator.java b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/AbstractBeanJsonCreator.java index 9728499a..01e859cf 100644 --- a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/AbstractBeanJsonCreator.java +++ b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/AbstractBeanJsonCreator.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat.Shape; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.annotation.ObjectIdGenerator; import com.github.nmorel.gwtjackson.client.deser.bean.AbstractDelegationBeanJsonDeserializer; @@ -36,11 +37,13 @@ import com.github.nmorel.gwtjackson.client.ser.bean.AbstractValueBeanJsonSerializer; import com.github.nmorel.gwtjackson.client.ser.bean.ObjectIdSerializer; import com.github.nmorel.gwtjackson.client.ser.bean.PropertyIdentitySerializationInfo; +import com.github.nmorel.gwtjackson.client.ser.map.MapJsonSerializer; import com.github.nmorel.gwtjackson.rebind.bean.BeanIdentityInfo; import com.github.nmorel.gwtjackson.rebind.bean.BeanInfo; import com.github.nmorel.gwtjackson.rebind.bean.BeanProcessor; import com.github.nmorel.gwtjackson.rebind.bean.BeanTypeInfo; import com.github.nmorel.gwtjackson.rebind.exception.UnsupportedTypeException; +import com.github.nmorel.gwtjackson.rebind.property.FieldAccessor.Accessor; import com.github.nmorel.gwtjackson.rebind.property.PropertiesContainer; import com.github.nmorel.gwtjackson.rebind.property.PropertyInfo; import com.github.nmorel.gwtjackson.rebind.property.processor.PropertyProcessor; @@ -53,6 +56,7 @@ import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.i18n.client.TimeZone; import com.google.gwt.thirdparty.guava.common.base.Optional; import com.google.gwt.thirdparty.guava.common.base.Strings; import com.google.gwt.thirdparty.guava.common.collect.ImmutableList; @@ -102,6 +106,8 @@ public String getJoinedTypeParameterMappersWithType() { protected static final String TYPE_SERIALIZATION_INFO_CLASS = "com.github.nmorel.gwtjackson.client.ser.bean" + "" + ".TypeSerializationInfo"; + protected static final String JSON_SERIALIZER_PARAMETERS_CLASS = "com.github.nmorel.gwtjackson.client.JsonSerializerParameters"; + protected BeanJsonMapperInfo mapperInfo; public AbstractBeanJsonCreator( TreeLogger logger, GeneratorContext context, RebindConfiguration configuration, JacksonTypeOracle @@ -172,7 +178,8 @@ public String create( JClassType beanType ) throws UnableToCompleteException, Un beanInfo = BeanProcessor.processProperties( configuration, logger, typeOracle, beanInfo, properties ); mapperInfo = new BeanJsonMapperInfo( beanType, qualifiedSerializerClassName, simpleSerializerClassName, - qualifiedDeserializerClassName, simpleDeserializerClassName, beanInfo, properties.getProperties() ); + qualifiedDeserializerClassName, simpleDeserializerClassName, beanInfo, properties + .getProperties() ); typeOracle.addBeanJsonMapperInfo( beanType, mapperInfo ); } @@ -274,11 +281,25 @@ protected Optional getIdentitySerializerType( BeanIdentityInfo } protected void generateIdentifierSerializationInfo( SourceWriter source, JClassType type, BeanIdentityInfo identityInfo, - Optional serializerType ) throws UnableToCompleteException { + Optional serializerType ) throws UnableToCompleteException, + UnsupportedTypeException { if ( identityInfo.isIdABeanProperty() ) { - source.print( "new %s<%s>(%s, \"%s\")", PropertyIdentitySerializationInfo.class.getName(), type - .getParameterizedQualifiedSourceName(), identityInfo.isAlwaysAsId(), identityInfo.getPropertyName() ); + BeanJsonMapperInfo mapperInfo = typeOracle.getBeanJsonMapperInfo( type ); + PropertyInfo propertyInfo = mapperInfo.getProperties().get( identityInfo.getPropertyName() ); + JSerializerType propertySerializerType = getJsonSerializerFromType( propertyInfo.getType() ); + + source.println( "new %s<%s, %s>(%s, \"%s\") {", PropertyIdentitySerializationInfo.class.getName(), type + .getParameterizedQualifiedSourceName(), getParameterizedQualifiedClassName( propertyInfo.getType()), identityInfo + .isAlwaysAsId(), identityInfo.getPropertyName() ); + + source.indent(); + + generateBeanPropertySerializerBody( source, type, propertyInfo, propertySerializerType ); + + source.outdent(); + source.print( "}" ); + } else { String qualifiedType = getParameterizedQualifiedClassName( identityInfo.getType().get() ); String identityPropertyClass = String.format( "%s<%s, %s>", AbstractIdentitySerializationInfo.class.getName(), type @@ -436,4 +457,102 @@ protected ImmutableList filterSubtypes( BeanInfo beanInfo ) { return CreatorUtils.filterSubtypesForDeserialization( logger, configuration, beanInfo.getType() ); } } + + protected void generateBeanPropertySerializerBody( SourceWriter source, JClassType beanType, PropertyInfo property, JSerializerType + serializerType ) throws UnableToCompleteException { + Accessor getterAccessor = property.getGetterAccessor().get().getAccessor( "bean" ); + + source.println( "@Override" ); + String returnType; + if ( property.isAnyGetter() ) { + returnType = MapJsonSerializer.class.getCanonicalName(); + } else { + returnType = String.format( "%s", JSON_SERIALIZER_CLASS ); + } + source.println( "protected %s newSerializer() {", returnType ); + source.indent(); + source.println( "return %s;", serializerType.getInstance() ); + source.outdent(); + source.println( "}" ); + + generatePropertySerializerParameters( source, property, serializerType ); + + source.println(); + + source.println( "@Override" ); + source.println( "public %s getValue(%s bean, %s ctx) {", getParameterizedQualifiedClassName( property + .getType() ), getParameterizedQualifiedClassName( beanType ), JSON_SERIALIZATION_CONTEXT_CLASS ); + source.indent(); + source.println( "return %s;", getterAccessor.getAccessor() ); + source.outdent(); + source.println( "}" ); + + if ( getterAccessor.getAdditionalMethod().isPresent() ) { + source.println(); + getterAccessor.getAdditionalMethod().get().write( source ); + } + } + + protected void generatePropertySerializerParameters( SourceWriter source, PropertyInfo property, JSerializerType serializerType ) + throws UnableToCompleteException { + if ( property.getFormat().isPresent() || property.getIgnoredProperties().isPresent() || property.getIgnoreUnknown().isPresent() || + property.getIdentityInfo().isPresent() || property.getTypeInfo().isPresent() || property.getInclude().isPresent() ) { + + JClassType annotatedType = findFirstTypeToApplyPropertyAnnotation( serializerType ); + + source.println(); + + source.println( "@Override" ); + source.println( "protected %s newParameters() {", JSON_SERIALIZER_PARAMETERS_CLASS ); + source.indent(); + source.print( "return new %s()", JSON_SERIALIZER_PARAMETERS_CLASS ); + + source.indent(); + + generateCommonPropertyParameters( source, property, serializerType ); + + if ( property.getFormat().isPresent() ) { + JsonFormat format = property.getFormat().get(); + if ( !Strings.isNullOrEmpty( format.timezone() ) && !JsonFormat.DEFAULT_TIMEZONE.equals( format.timezone() ) ) { + source.println(); + java.util.TimeZone timeZoneJdk = java.util.TimeZone.getTimeZone( format.timezone() ); + // in java the offset is in milliseconds from timezone to GMT + // in gwt the offset is in minutes from GMT to timezone + // so we convert the milliseconds in minutes and invert the sign + int timeZoneOffsetGwt = (timeZoneJdk.getRawOffset() / 1000 / 60) * -1; + source.print( ".setTimezone(%s.createTimeZone( %d ))", TimeZone.class.getCanonicalName(), timeZoneOffsetGwt ); + } + } + + if ( property.getInclude().isPresent() ) { + source.println(); + source.print( ".setInclude(%s.%s)", Include.class.getCanonicalName(), property.getInclude().get().name() ); + } + + if ( property.getIdentityInfo().isPresent() ) { + try { + Optional identitySerializerType = getIdentitySerializerType( property.getIdentityInfo().get() ); + source.println(); + source.print( ".setIdentityInfo(" ); + generateIdentifierSerializationInfo( source, annotatedType, property.getIdentityInfo().get(), identitySerializerType ); + source.print( ")" ); + } catch ( UnsupportedTypeException e ) { + logger.log( Type.WARN, "Identity type is not supported. We ignore it." ); + } + } + + if ( property.getTypeInfo().isPresent() ) { + source.println(); + source.print( ".setTypeInfo(" ); + generateTypeInfo( source, property.getTypeInfo().get(), true ); + source.print( ")" ); + } + + source.println( ";" ); + source.outdent(); + + source.outdent(); + source.println( "}" ); + } + } } diff --git a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/BeanJsonDeserializerCreator.java b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/BeanJsonDeserializerCreator.java index 5c25e596..c4760f0b 100644 --- a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/BeanJsonDeserializerCreator.java +++ b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/BeanJsonDeserializerCreator.java @@ -61,6 +61,7 @@ import com.google.gwt.user.rebind.SourceWriter; import static com.github.nmorel.gwtjackson.rebind.CreatorUtils.QUOTED_FUNCTION; +import static com.github.nmorel.gwtjackson.rebind.CreatorUtils.escapeString; import static com.github.nmorel.gwtjackson.rebind.CreatorUtils.getDefaultValueForType; /** @@ -537,7 +538,7 @@ private void generateInitDeserializersMethod( SourceWriter source, BeanInfo bean PropertyInfo property = entry.getKey(); JDeserializerType deserializerType = entry.getValue(); - source.print( "map.put(\"%s\", ", property.getPropertyName() ); + source.print( "map.put(\"%s\", ", escapeString( property.getPropertyName() ) ); generateDeserializer( source, beanInfo, property, property.getType(), deserializerType ); @@ -680,8 +681,8 @@ private void generateInitBackReferenceDeserializersMethod( SourceWriter source, Accessor accessor = property.getSetterAccessor().get().getAccessor( "bean" ); // this is a back reference, we add the special back reference property that will be called by the parent - source.println( "map.put(\"%s\", new %s<%s, %s>() {", property.getBackReference() - .get(), BACK_REFERENCE_PROPERTY_BEAN_CLASS, beanInfo.getType() + source.println( "map.put(\"%s\", new %s<%s, %s>() {", escapeString( property.getBackReference() + .get() ), BACK_REFERENCE_PROPERTY_BEAN_CLASS, beanInfo.getType() .getParameterizedQualifiedSourceName(), getParameterizedQualifiedClassName( property.getType() ) ); source.indent(); diff --git a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/BeanJsonSerializerCreator.java b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/BeanJsonSerializerCreator.java index 21e0d034..ecec8cf3 100644 --- a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/BeanJsonSerializerCreator.java +++ b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/BeanJsonSerializerCreator.java @@ -21,8 +21,6 @@ import java.util.Map; import java.util.Map.Entry; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.github.nmorel.gwtjackson.client.JsonSerializer; import com.github.nmorel.gwtjackson.client.ser.RawValueJsonSerializer; import com.github.nmorel.gwtjackson.client.ser.bean.AnyGetterPropertySerializer; @@ -30,10 +28,9 @@ import com.github.nmorel.gwtjackson.client.ser.bean.SubtypeSerializer; import com.github.nmorel.gwtjackson.client.ser.bean.SubtypeSerializer.BeanSubtypeSerializer; import com.github.nmorel.gwtjackson.client.ser.bean.SubtypeSerializer.DefaultSubtypeSerializer; -import com.github.nmorel.gwtjackson.client.ser.map.MapJsonSerializer; +import com.github.nmorel.gwtjackson.client.stream.JsonWriter; import com.github.nmorel.gwtjackson.rebind.bean.BeanInfo; import com.github.nmorel.gwtjackson.rebind.exception.UnsupportedTypeException; -import com.github.nmorel.gwtjackson.rebind.property.FieldAccessor.Accessor; import com.github.nmorel.gwtjackson.rebind.property.PropertyInfo; import com.github.nmorel.gwtjackson.rebind.type.JSerializerType; import com.google.gwt.core.ext.GeneratorContext; @@ -41,13 +38,13 @@ import com.google.gwt.core.ext.TreeLogger.Type; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; -import com.google.gwt.i18n.client.TimeZone; import com.google.gwt.thirdparty.guava.common.base.Optional; -import com.google.gwt.thirdparty.guava.common.base.Strings; import com.google.gwt.thirdparty.guava.common.collect.ImmutableList; import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap; import com.google.gwt.user.rebind.SourceWriter; +import static com.github.nmorel.gwtjackson.rebind.CreatorUtils.escapeString; + /** * @author Nicolas Morel */ @@ -55,8 +52,6 @@ public class BeanJsonSerializerCreator extends AbstractBeanJsonCreator { private static final String BEAN_PROPERTY_SERIALIZER_CLASS = "com.github.nmorel.gwtjackson.client.ser.bean.BeanPropertySerializer"; - private static final String JSON_SERIALIZER_PARAMETERS_CLASS = "com.github.nmorel.gwtjackson.client.JsonSerializerParameters"; - public BeanJsonSerializerCreator( TreeLogger logger, GeneratorContext context, RebindConfiguration configuration, JacksonTypeOracle typeOracle ) { super( logger, context, configuration, typeOracle ); @@ -184,25 +179,24 @@ private void generateInitValueSerializerMethod( SourceWriter source, BeanInfo be private void generateInitSerializersMethod( SourceWriter source, BeanInfo beanInfo, Map properties ) throws UnableToCompleteException { - String mapType = String.format( "<%s, %s<%s, ?>>", String.class.getCanonicalName(), BEAN_PROPERTY_SERIALIZER_CLASS, beanInfo - .getType().getParameterizedQualifiedSourceName() ); - String resultType = String.format( "%s%s", Map.class.getCanonicalName(), mapType ); + String arrayType = String.format( "%s", BEAN_PROPERTY_SERIALIZER_CLASS ); source.println( "@Override" ); - source.println( "protected %s initSerializers() {", resultType ); + source.println( "protected %s[] initSerializers() {", arrayType ); source.indent(); - source.println( "%s map = new %s%s(%s);", resultType, LinkedHashMap.class.getCanonicalName(), mapType, properties.size() ); + source.println( "%s[] result = new %s[%s];", arrayType, arrayType, properties.size() ); source.println(); + int i = 0; for ( Entry entry : properties.entrySet() ) { - source.print( "map.put(\"%s\", ", entry.getKey().getPropertyName() ); + source.print( "result[%s] = ", i++ ); generateSerializer( source, beanInfo, entry.getKey(), entry.getValue() ); - source.println( ");" ); + source.println( ";" ); source.println(); } - source.println( "return map;" ); + source.println( "return result;" ); source.outdent(); source.println( "}" ); } @@ -225,116 +219,39 @@ private void generateInitAnyGetterPropertySerializerMethod( SourceWriter source, private void generateSerializer( SourceWriter source, BeanInfo beanInfo, PropertyInfo property, JSerializerType serializerType ) throws UnableToCompleteException { - Accessor getterAccessor = property.getGetterAccessor().get().getAccessor( "bean" ); + + String escapedPropertyName = escapeString( property.getPropertyName() ); if ( property.isAnyGetter() ) { source.println( "new %s<%s>() {", AnyGetterPropertySerializer.class .getCanonicalName(), getParameterizedQualifiedClassName( beanInfo.getType() ) ); } else { - source.println( "new %s<%s, %s>() {", BEAN_PROPERTY_SERIALIZER_CLASS, getParameterizedQualifiedClassName( beanInfo - .getType() ), getParameterizedQualifiedClassName( property.getType() ) ); + source.println( "new %s<%s, %s>(\"%s\") {", BEAN_PROPERTY_SERIALIZER_CLASS, getParameterizedQualifiedClassName( beanInfo + .getType() ), getParameterizedQualifiedClassName( property.getType() ), escapedPropertyName ); } source.indent(); - source.println( "@Override" ); - String returnType; - if ( property.isAnyGetter() ) { - returnType = MapJsonSerializer.class.getCanonicalName(); - } else { - returnType = String.format( "%s", JSON_SERIALIZER_CLASS ); - } - source.println( "protected %s newSerializer() {", returnType ); - source.indent(); - source.println( "return %s;", serializerType.getInstance() ); - source.outdent(); - source.println( "}" ); - - generatePropertySerializerParameters( source, property, serializerType ); - - source.println(); - - source.println( "@Override" ); - source.println( "public %s getValue(%s bean, %s ctx) {", getParameterizedQualifiedClassName( property - .getType() ), getParameterizedQualifiedClassName( beanInfo.getType() ), JSON_SERIALIZATION_CONTEXT_CLASS ); - source.indent(); - source.println( "return %s;", getterAccessor.getAccessor() ); - source.outdent(); - source.println( "}" ); - - if ( getterAccessor.getAdditionalMethod().isPresent() ) { - source.println(); - getterAccessor.getAdditionalMethod().get().write( source ); - } - - source.outdent(); - source.print( "}" ); - } - - private void generatePropertySerializerParameters( SourceWriter source, PropertyInfo property, JSerializerType serializerType ) - throws UnableToCompleteException { - if ( property.getFormat().isPresent() || property.getIgnoredProperties().isPresent() || property.getIgnoreUnknown().isPresent() || - property.getIdentityInfo().isPresent() || property.getTypeInfo().isPresent() || property.getInclude().isPresent() ) { - JClassType annotatedType = findFirstTypeToApplyPropertyAnnotation( serializerType ); + generateBeanPropertySerializerBody( source, beanInfo.getType(), property, serializerType ); + boolean requireEscaping = !property.getPropertyName().equals( escapedPropertyName ); + if ( requireEscaping ) { source.println(); - source.println( "@Override" ); - source.println( "protected %s newParameters() {", JSON_SERIALIZER_PARAMETERS_CLASS ); - source.indent(); - source.print( "return new %s()", JSON_SERIALIZER_PARAMETERS_CLASS ); - + source.println( "public void serializePropertyName(%s writer, %s bean, %s ctx) {", JsonWriter.class + .getCanonicalName(), getParameterizedQualifiedClassName( beanInfo.getType() ), JSON_SERIALIZATION_CONTEXT_CLASS ); source.indent(); - - generateCommonPropertyParameters( source, property, serializerType ); - - if ( property.getFormat().isPresent() ) { - JsonFormat format = property.getFormat().get(); - if ( !Strings.isNullOrEmpty( format.timezone() ) && !JsonFormat.DEFAULT_TIMEZONE.equals( format.timezone() ) ) { - source.println(); - java.util.TimeZone timeZoneJdk = java.util.TimeZone.getTimeZone( format.timezone() ); - // in java the offset is in milliseconds from timezone to GMT - // in gwt the offset is in minutes from GMT to timezone - // so we convert the milliseconds in minutes and invert the sign - int timeZoneOffsetGwt = (timeZoneJdk.getRawOffset() / 1000 / 60) * -1; - source.print( ".setTimezone(%s.createTimeZone( %d ))", TimeZone.class.getCanonicalName(), timeZoneOffsetGwt ); - } - } - - if ( property.getInclude().isPresent() ) { - source.println(); - source.print( ".setInclude(%s.%s)", Include.class.getCanonicalName(), property.getInclude().get().name() ); - } - - if ( property.getIdentityInfo().isPresent() ) { - try { - Optional identitySerializerType = getIdentitySerializerType( property.getIdentityInfo().get() ); - source.println(); - source.print( ".setIdentityInfo(" ); - generateIdentifierSerializationInfo( source, annotatedType, property.getIdentityInfo().get(), identitySerializerType ); - source.print( ")" ); - } catch ( UnsupportedTypeException e ) { - logger.log( Type.WARN, "Identity type is not supported. We ignore it." ); - } - } - - if ( property.getTypeInfo().isPresent() ) { - source.println(); - source.print( ".setTypeInfo(" ); - generateTypeInfo( source, property.getTypeInfo().get(), true ); - source.print( ")" ); - } - - source.println( ";" ); - source.outdent(); - + source.println( "writer.name( propertyName );" ); source.outdent(); source.println( "}" ); } + + source.outdent(); + source.print( "}" ); } private void generateInitIdentityInfoMethod( SourceWriter source, BeanInfo beanInfo, Optional serializerType ) - throws UnableToCompleteException { + throws UnableToCompleteException, UnsupportedTypeException { source.println( "@Override" ); source.println( "protected %s<%s> initIdentityInfo() {", IdentitySerializationInfo.class.getCanonicalName(), beanInfo.getType() .getParameterizedQualifiedSourceName() ); diff --git a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/CreatorUtils.java b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/CreatorUtils.java index 38544dbb..d5cf6440 100644 --- a/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/CreatorUtils.java +++ b/gwt-jackson/src/main/java/com/github/nmorel/gwtjackson/rebind/CreatorUtils.java @@ -21,6 +21,7 @@ import java.lang.annotation.Annotation; import java.util.List; +import com.github.nmorel.gwtjackson.client.stream.impl.DefaultJsonWriter; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.typeinfo.HasAnnotations; import com.google.gwt.core.ext.typeinfo.JClassType; @@ -76,8 +77,8 @@ public static Optional findFirstEncounteredAnnotations return Optional.absent(); } - public static boolean isAnnotationPresent( Class annotation, - List hasAnnotationsList ) { + public static boolean isAnnotationPresent( Class annotation, List + hasAnnotationsList ) { for ( HasAnnotations accessor : hasAnnotationsList ) { if ( accessor.isAnnotationPresent( annotation ) ) { return true; @@ -86,8 +87,8 @@ public static boolean isAnnotationPresent( Class annot return false; } - public static Optional getAnnotation( Class annotation, - List hasAnnotationsList ) { + public static Optional getAnnotation( Class annotation, List + hasAnnotationsList ) { for ( HasAnnotations accessor : hasAnnotationsList ) { if ( accessor.isAnnotationPresent( annotation ) ) { return Optional.of( accessor.getAnnotation( annotation ) ); @@ -177,6 +178,17 @@ public static ImmutableList filterSubtypesForDeserialization( TreeLo return builder.build(); } + /** + * Escapes the {@link String} given in parameter + * + * @param value the {@link String} + * + * @return the escaped {@link String} + */ + public static String escapeString( String value ) { + return DefaultJsonWriter.encodeString( value ); + } + private CreatorUtils() { } } diff --git a/gwt-jackson/src/test/java/com/github/nmorel/gwtjackson/shared/annotations/JsonAutoDetectTester.java b/gwt-jackson/src/test/java/com/github/nmorel/gwtjackson/shared/annotations/JsonAutoDetectTester.java index 3fd08c8d..058b3812 100644 --- a/gwt-jackson/src/test/java/com/github/nmorel/gwtjackson/shared/annotations/JsonAutoDetectTester.java +++ b/gwt-jackson/src/test/java/com/github/nmorel/gwtjackson/shared/annotations/JsonAutoDetectTester.java @@ -27,7 +27,7 @@ */ public final class JsonAutoDetectTester extends AbstractTester { - @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC, getterVisibility = JsonAutoDetect.Visibility.NONE) + @JsonAutoDetect( fieldVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC, getterVisibility = JsonAutoDetect.Visibility.NONE ) public static class BeanOne { public String publicFieldVisible; @@ -38,7 +38,7 @@ public static class BeanOne { private String visibleWithSetter; - @JsonProperty("@fieldWithJsonProperty") + @JsonProperty( "\"fieldWithJsonProperty\"" ) private String fieldWithJsonProperty; public String getVisibleWithSetter() { @@ -73,7 +73,7 @@ public void testSerializeAutoDetection( ObjectWriterTester writer ) { String expected = "{\"publicFieldVisible\":\"publicField\"," + "\"protectedFieldVisible\":\"protectedField\"," + - "\"@fieldWithJsonProperty\":\"jsonProperty\"}"; + "\"\\\"fieldWithJsonProperty\\\"\":\"jsonProperty\"}"; String result = writer.write( bean ); assertEquals( expected, result ); @@ -84,7 +84,7 @@ public void testDeserializeAutoDetection( ObjectReaderTester reader ) { "\"publicFieldVisible\":\"publicField\"," + "\"protectedFieldVisible\":\"protectedField\"," + "\"notVisibleField\":2," + - "\"@fieldWithJsonProperty\":\"jsonProperty\"}"; + "\"\\\"fieldWithJsonProperty\\\"\":\"jsonProperty\"}"; BeanOne result = reader.read( input );