Package com.github.nmorel.gwtjackson.rebind

Source Code of com.github.nmorel.gwtjackson.rebind.BeanJsonDeserializerCreator

/*
* 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.rebind;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.github.nmorel.gwtjackson.client.JsonDeserializer;
import com.github.nmorel.gwtjackson.client.deser.bean.HasDeserializerAndParameters;
import com.github.nmorel.gwtjackson.client.deser.bean.IdentityDeserializationInfo;
import com.github.nmorel.gwtjackson.client.deser.bean.SimpleStringMap;
import com.github.nmorel.gwtjackson.client.deser.bean.SubtypeDeserializer;
import com.github.nmorel.gwtjackson.client.deser.bean.SubtypeDeserializer.BeanSubtypeDeserializer;
import com.github.nmorel.gwtjackson.client.deser.bean.SubtypeDeserializer.DefaultSubtypeDeserializer;
import com.github.nmorel.gwtjackson.client.stream.JsonReader;
import com.github.nmorel.gwtjackson.client.stream.JsonToken;
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.JDeserializerType;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JConstructor;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.thirdparty.guava.common.base.Function;
import com.google.gwt.thirdparty.guava.common.base.Joiner;
import com.google.gwt.thirdparty.guava.common.base.Optional;
import com.google.gwt.thirdparty.guava.common.collect.Collections2;
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.QUOTED_FUNCTION;
import static com.github.nmorel.gwtjackson.rebind.CreatorUtils.getDefaultValueForType;

/**
* @author Nicolas Morel
*/
public class BeanJsonDeserializerCreator extends AbstractBeanJsonCreator {

    private static final String INSTANCE_BUILDER_CLASS = "com.github.nmorel.gwtjackson.client.deser.bean.InstanceBuilder";

    private static final String INSTANCE_CLASS = "com.github.nmorel.gwtjackson.client.deser.bean.Instance";

    private static final Function<String, String> FORMAT_VARIABLE = new Function<String, String>() {
        @Override
        public String apply( @Nullable String s ) {
            if ( null == s ) {
                return null;
            }
            return "v_" + s;
        }
    };

    private static final String INSTANCE_BUILDER_DESERIALIZER_FORMAT = "deserializer_%s";

    private static final String BEAN_PROPERTY_DESERIALIZER_CLASS = "com.github.nmorel.gwtjackson.client.deser.bean" + "" +
            ".BeanPropertyDeserializer";

    private static final String BACK_REFERENCE_PROPERTY_BEAN_CLASS = "com.github.nmorel.gwtjackson.client.deser.bean.BackReferenceProperty";

    private static final String JSON_DESERIALIZER_PARAMETERS_CLASS = "com.github.nmorel.gwtjackson.client.JsonDeserializerParameters";

    public BeanJsonDeserializerCreator( TreeLogger logger, GeneratorContext context, RebindConfiguration configuration,
                                        JacksonTypeOracle typeOracle ) {
        super( logger, context, configuration, typeOracle );
    }

    @Override
    protected boolean isSerializer() {
        return false;
    }

    @Override
    protected void writeClassBody( SourceWriter source, BeanInfo beanInfo, ImmutableMap<String,
            PropertyInfo> properties ) throws UnableToCompleteException, UnsupportedTypeException {
        source.println();

        TypeParameters typeParameters = generateTypeParameterMapperFields( source, beanInfo, JSON_DESERIALIZER_CLASS,
                TYPE_PARAMETER_DESERIALIZER_FIELD_NAME );
        if ( null != typeParameters ) {
            source.println();
        }

        generateConstructors( source, typeParameters );
        source.println();

        if ( beanInfo.getCreatorMethod().isPresent() ) {
            generateInitInstanceBuilderMethod( source, beanInfo, properties );
            source.println();
        }

        // no need to generate properties for non instantiable class
        if ( !properties.isEmpty() && beanInfo.getCreatorMethod().isPresent() ) {
            generateInitPropertiesMethods( source, beanInfo, properties );
            source.println();
        }

        if ( beanInfo.getIdentityInfo().isPresent() ) {
            try {
                Optional<JDeserializerType> deserializerType = getIdentityDeserializerType( beanInfo.getIdentityInfo().get() );
                generateInitIdentityInfoMethod( source, beanInfo, deserializerType );
                source.println();
            } catch ( UnsupportedTypeException e ) {
                logger.log( Type.WARN, "Identity type is not supported. We ignore it." );
            }
        }

        if ( beanInfo.getTypeInfo().isPresent() ) {
            generateInitTypeInfoMethod( source, beanInfo );
            source.println();
        }

        ImmutableList<JClassType> subtypes = filterSubtypes( beanInfo );
        if ( !subtypes.isEmpty() ) {
            generateInitMapSubtypeClassToDeserializerMethod( source, subtypes );
            source.println();
        }

        if ( beanInfo.isIgnoreUnknown() ) {
            generateIsDefaultIgnoreUnknownMethod( source );
            source.println();
        }

        generateClassGetterMethod( source, beanInfo );
    }

    private void generateConstructors( SourceWriter source, TypeParameters typeParameters ) throws UnableToCompleteException {
        source.print( "public %s(", getSimpleClassName() );
        if ( null != typeParameters ) {
            source.print( typeParameters.getJoinedTypeParameterMappersWithType() );
        }
        source.print( ") {" );

        if ( null != typeParameters ) {
            source.println();
            source.indent();
            for ( String parameterizedDeserializer : typeParameters.getTypeParameterMapperNames() ) {
                source.println( "this.%s = %s%s;", parameterizedDeserializer, TYPE_PARAMETER_PREFIX, parameterizedDeserializer );
            }
            source.outdent();
        }

        source.println( "}" );
    }

    private void generateInitInstanceBuilderMethod( SourceWriter source, BeanInfo beanInfo, ImmutableMap<String,
            PropertyInfo> properties ) throws UnableToCompleteException, UnsupportedTypeException {
        source.println( "@Override" );
        source.println( "protected %s<%s> initInstanceBuilder() {", INSTANCE_BUILDER_CLASS, beanInfo.getType()
                .getParameterizedQualifiedSourceName() );
        source.indent();
        source.print( "return " );
        generateInstanceBuilderClass( source, beanInfo, properties );
        source.println( ";" );
        source.outdent();
        source.println( "}" );
    }

    private void generateInstanceBuilderClass( SourceWriter source, BeanInfo beanInfo, ImmutableMap<String,
            PropertyInfo> properties ) throws UnableToCompleteException, UnsupportedTypeException {

        source.println( "new %s<%s>() {", INSTANCE_BUILDER_CLASS, beanInfo.getType().getParameterizedQualifiedSourceName() );
        source.indent();

        if ( null != beanInfo.getCreatorParameters() && !beanInfo.getCreatorParameters().isEmpty() ) {
            // for each constructor parameters, we initialize its deserializer.
            for ( Entry<String, JParameter> entry : beanInfo.getCreatorParameters().entrySet() ) {
                String qualifiedTypeName = getParameterizedQualifiedClassName( entry.getValue().getType() );
                String deserializerClass = String.format( "%s<%s, %s<%s>>", HasDeserializerAndParameters.class
                        .getCanonicalName(), qualifiedTypeName, JSON_DESERIALIZER_CLASS, qualifiedTypeName );
                source.println( "private final %s %s = new %s() {", deserializerClass, String
                        .format( INSTANCE_BUILDER_DESERIALIZER_FORMAT, entry.getKey() ), deserializerClass );

                source.indent();
                generateCommonPropertyDeserializerBody( source, properties.get( entry.getKey() ) );
                source.outdent();
                source.println( "};" );
            }
            source.println();
        }

        source.println( "@Override" );
        source.println( "public %s<%s> newInstance( %s reader, %s ctx, %s<String, String> bufferedProperties ) {", INSTANCE_CLASS, beanInfo
                .getType().getParameterizedQualifiedSourceName(), JsonReader.class
                .getCanonicalName(), JSON_DESERIALIZATION_CONTEXT_CLASS, Map.class.getCanonicalName() );
        source.indent();

        if ( beanInfo.isCreatorDefaultConstructor() ) {
            generateInstanceBuilderForDefaultConstructor( source, beanInfo );
        } else if ( beanInfo.isCreatorDelegation() ) {
            generateInstanceBuilderForConstructorOrFactoryMethodDelegation( source, beanInfo, properties );
        } else {
            generateInstanceBuilderForConstructorOrFactoryMethod( source, beanInfo, properties );
        }

        source.outdent();
        source.println( "}" );
        source.println();

        generateInstanceBuilderCreateMethod( source, beanInfo, properties );

        source.outdent();
        source.print( "}" );
    }

    /**
     * Generate the instance builder class body for a default constructor. We directly instantiate the bean at the builder creation and we
     * set the properties to it
     *
     * @param source writer
     * @param beanInfo info on bean
     */
    private void generateInstanceBuilderForDefaultConstructor( SourceWriter source, BeanInfo beanInfo ) {
        source.println( "return new %s<%s>(create(), bufferedProperties);", INSTANCE_CLASS, beanInfo.getType()
                .getParameterizedQualifiedSourceName(), beanInfo.getType().getParameterizedQualifiedSourceName() );
    }

    /**
     * Generate the instance builder class body for a constructor with parameters or factory method. We will declare all the fields and
     * instanciate the bean only on build() method when all properties have been deserialiazed
     *
     * @param source writer
     * @param info info on bean
     * @param properties list of properties
     */
    private void generateInstanceBuilderForConstructorOrFactoryMethod( SourceWriter source, BeanInfo info, ImmutableMap<String,
            PropertyInfo> properties ) throws UnableToCompleteException {

        List<String> requiredProperties = new ArrayList<String>();
        for ( String name : info.getCreatorParameters().keySet() ) {
            PropertyInfo propertyInfo = properties.get( name );

            source.println( "%s %s = %s;", propertyInfo.getType().getParameterizedQualifiedSourceName(), FORMAT_VARIABLE
                    .apply( name ), getDefaultValueForType( propertyInfo.getType() ) );

            if ( propertyInfo.isRequired() ) {
                requiredProperties.add( name );
            }
        }

        source.println();

        source.println( "int nbParamToFind = %d;", info.getCreatorParameters().size() );

        if ( !requiredProperties.isEmpty() ) {
            String requiredList = Joiner.on( ", " ).join( Collections2.transform( requiredProperties, QUOTED_FUNCTION ) );
            source.println( "%s<String> requiredProperties = new %s<String>(%s.asList(%s));", Set.class.getName(), HashSet.class
                    .getName(), Arrays.class.getName(), requiredList );
        }

        source.println();

        source.println( "if(null != bufferedProperties) {" );
        source.indent();
        source.println( "String value;" );
        for ( String name : info.getCreatorParameters().keySet() ) {
            PropertyInfo propertyInfo = properties.get( name );

            source.println();
            source.println( "value = bufferedProperties.remove(\"%s\");", name );
            source.println( "if(null != value) {" );
            source.indent();
            source.println( "%s = %s.deserialize(ctx.newJsonReader(value), ctx);", FORMAT_VARIABLE.apply( name ), String
                    .format( INSTANCE_BUILDER_DESERIALIZER_FORMAT, name ) );
            source.println( "nbParamToFind--;" );
            if ( propertyInfo.isRequired() ) {
                source.println( "requiredProperties.remove(\"%s\");", name );
            }
            source.outdent();
            source.println( "}" );
        }
        source.outdent();
        source.println( "}" );

        source.println();

        source.println( "String name;" );
        source.println( "while (nbParamToFind > 0 && %s.NAME == reader.peek()) {", JsonToken.class.getName() );
        source.indent();

        source.println( "name = reader.nextName();" );
        source.println();

        for ( String name : info.getCreatorParameters().keySet() ) {
            PropertyInfo propertyInfo = properties.get( name );

            source.println( "if(\"%s\".equals(name)) {", name );
            source.indent();
            source.println( "%s = %s.deserialize(reader, ctx);", FORMAT_VARIABLE.apply( name ), String
                    .format( INSTANCE_BUILDER_DESERIALIZER_FORMAT, name ) );
            source.println( "nbParamToFind--;" );
            if ( propertyInfo.isRequired() ) {
                source.println( "requiredProperties.remove(\"%s\");", name );
            }
            source.println( "continue;" );
            source.outdent();
            source.println( "}" );
        }

        source.println();
        source.println( "if(null == bufferedProperties) {" );
        source.indent();
        source.println( "bufferedProperties = new %s<String, String>();", HashMap.class.getName() );
        source.outdent();
        source.println( "}" );
        source.println( "bufferedProperties.put( name, reader.nextValue() );" );

        source.outdent();
        source.println( "}" );

        source.println();

        if ( !requiredProperties.isEmpty() ) {
            source.println( "if(!requiredProperties.isEmpty()) {" );
            source.indent();
            source.println( "throw ctx.traceError( \"Required properties are missing : \" + requiredProperties, reader );" );
            source.outdent();
            source.println( "}" );
            source.println();
        }

        String parameters = Joiner.on( ", " ).join( Collections2.transform( info.getCreatorParameters().keySet(), FORMAT_VARIABLE ) );
        source.println( "return new %s<%s>( create(%s), bufferedProperties );", INSTANCE_CLASS, info.getType()
                .getParameterizedQualifiedSourceName(), parameters );
    }

    /**
     * Generate the instance builder class body for a constructor or factory method with delegation.
     *
     * @param source writer
     * @param info info on bean
     * @param properties list of properties
     */
    private void generateInstanceBuilderForConstructorOrFactoryMethodDelegation( SourceWriter source, BeanInfo info, ImmutableMap<String,
            PropertyInfo> properties ) throws UnsupportedTypeException {
        // TODO @JsonCreator with delegation
        String message = "The delegation is not supported yet";
        logger.log( TreeLogger.Type.WARN, message );
        throw new UnsupportedTypeException( message );
    }

    private void generateInstanceBuilderCreateMethod( SourceWriter source, BeanInfo info, ImmutableMap<String, PropertyInfo> properties ) {
        JAbstractMethod method = info.getCreatorMethod().get();

        StringBuilder parametersBuilder = new StringBuilder();
        StringBuilder parametersNameBuilder = new StringBuilder();
        for ( Map.Entry<String, JParameter> parameterEntry : info.getCreatorParameters().entrySet() ) {
            if ( parametersBuilder.length() > 0 ) {
                parametersBuilder.append( ", " );
                parametersNameBuilder.append( ", " );
            }
            PropertyInfo property = properties.get( parameterEntry.getKey() );

            parametersBuilder.append( property.getType().getParameterizedQualifiedSourceName() ).append( " " ).append( property
                    .getPropertyName() );
            parametersNameBuilder.append( property.getPropertyName() );
        }

        if ( method.isPrivate() ) {
            // private method, we use jsni
            source.println( "private native %s create(%s) /*-{", info.getType().getParameterizedQualifiedSourceName(), parametersBuilder
                    .toString() );
            source.indent();

            if ( null != method.isConstructor() ) {
                JConstructor constructor = method.isConstructor();
                source.println( "return %s(%s);", constructor.getJsniSignature(), parametersNameBuilder.toString() );
            } else {
                JMethod factory = method.isMethod();
                source.println( "return %s(%s);", factory.getJsniSignature(), parametersNameBuilder.toString() );
            }

            source.outdent();
            source.println( "}-*/;" );
        } else {
            source.println( "private %s create(%s) {", info.getType().getParameterizedQualifiedSourceName(), parametersBuilder.toString() );
            source.indent();

            if ( null != method.isConstructor() ) {
                source.println( "return new %s(%s);", info.getType().getParameterizedQualifiedSourceName(), parametersNameBuilder
                        .toString() );
            } else {
                source.println( "return %s.%s(%s);", info.getType().getQualifiedSourceName(), method.getName(), parametersNameBuilder
                        .toString() );
            }

            source.outdent();
            source.println( "}" );
        }
    }

    private void generateInitPropertiesMethods( SourceWriter source, BeanInfo beanInfo, Map<String,
            PropertyInfo> properties ) throws UnableToCompleteException {

        List<PropertyInfo> ignoredProperties = new ArrayList<PropertyInfo>();
        List<PropertyInfo> requiredProperties = new ArrayList<PropertyInfo>();
        Map<PropertyInfo, JDeserializerType> deserializerProperties = new LinkedHashMap<PropertyInfo, JDeserializerType>();
        List<PropertyInfo> backReferenceProperties = new ArrayList<PropertyInfo>();

        for ( PropertyInfo property : properties.values() ) {
            if ( null != beanInfo.getCreatorParameters() && beanInfo.getCreatorParameters().containsKey( property.getPropertyName() ) ) {
                // properties used in constructor are deserialized inside instance builder
                continue;
            }

            if ( property.isIgnored() ) {
                ignoredProperties.add( property );
                continue;
            }

            if ( !property.getSetterAccessor().isPresent() ) {
                // there is no setter visible
                continue;
            }

            if ( !property.getBackReference().isPresent() ) {
                try {
                    JDeserializerType deserializerType = getJsonDeserializerFromType( property.getType() );
                    deserializerProperties.put( property, deserializerType );
                    if ( property.isRequired() ) {
                        requiredProperties.add( property );
                    }
                } catch ( UnsupportedTypeException e ) {
                    logger.log( Type.WARN, "Property '" + property.getPropertyName() + "' is ignored" );
                    ignoredProperties.add( property );
                }
            } else {
                backReferenceProperties.add( property );
            }
        }

        if ( !deserializerProperties.isEmpty() ) {
            generateInitDeserializersMethod( source, beanInfo, deserializerProperties );
            source.println();
        }

        if ( !backReferenceProperties.isEmpty() ) {
            generateInitBackReferenceDeserializersMethod( source, beanInfo, backReferenceProperties );
            source.println();
        }

        if ( !ignoredProperties.isEmpty() ) {
            generateInitIgnoredPropertiesMethod( source, ignoredProperties );
            source.println();
        }

        if ( !requiredProperties.isEmpty() ) {
            generateInitRequiredPropertiesMethod( source, requiredProperties );
        }
    }

    private void generateInitDeserializersMethod( SourceWriter source, BeanInfo beanInfo, Map<PropertyInfo,
            JDeserializerType> properties ) throws UnableToCompleteException {
        String resultType = String.format( "%s<%s<%s, ?>>", SimpleStringMap.class
                .getCanonicalName(), BEAN_PROPERTY_DESERIALIZER_CLASS, beanInfo.getType().getParameterizedQualifiedSourceName() );

        source.println( "@Override" );
        source.println( "protected %s initDeserializers() {", resultType );
        source.indent();

        source.println( "%s map = %s.createObject().cast();", resultType, SimpleStringMap.class.getCanonicalName() );
        source.println();

        for ( Entry<PropertyInfo, JDeserializerType> entry : properties.entrySet() ) {
            PropertyInfo property = entry.getKey();

            Accessor accessor = property.getSetterAccessor().get().getAccessor( "bean" );

            source.println( "map.put(\"%s\", new %s<%s, %s>() {", property.getPropertyName(), BEAN_PROPERTY_DESERIALIZER_CLASS, beanInfo
                    .getType().getParameterizedQualifiedSourceName(), getParameterizedQualifiedClassName( property.getType() ) );

            source.indent();

            generateCommonPropertyDeserializerBody( source, property, entry.getValue() );

            source.println();

            source.println( "@Override" );
            source.println( "public void setValue(%s bean, %s value, %s ctx) {", beanInfo.getType()
                    .getParameterizedQualifiedSourceName(), getParameterizedQualifiedClassName( property
                    .getType() ), JSON_DESERIALIZATION_CONTEXT_CLASS );
            source.indent();
            source.println( accessor.getAccessor() + ";", "value" );

            if ( property.getManagedReference().isPresent() ) {
                source.println( "getDeserializer().setBackReference(\"%s\", bean, value, ctx);", property.getManagedReference().get() );
            }

            source.outdent();
            source.println( "}" );

            if ( accessor.getAdditionalMethod().isPresent() ) {
                source.println();
                accessor.getAdditionalMethod().get().write( source );
            }

            source.outdent();
            source.println( "});" );
            source.println();
        }

        source.println( "return map;" );
        source.outdent();
        source.println( "}" );
    }

    private void generateCommonPropertyDeserializerBody( SourceWriter source, PropertyInfo property ) throws UnableToCompleteException,
            UnsupportedTypeException {
        generateCommonPropertyDeserializerBody( source, property, getJsonDeserializerFromType( property.getType() ) );
    }

    private void generateCommonPropertyDeserializerBody( SourceWriter source, PropertyInfo property,
                                                         JDeserializerType deserializerType ) throws UnableToCompleteException {
        source.println( "@Override" );
        source.println( "protected %s<?> newDeserializer() {", JSON_DESERIALIZER_CLASS );
        source.indent();
        source.println( "return %s;", deserializerType.getInstance() );
        source.outdent();
        source.println( "}" );

        generatePropertyDeserializerParameters( source, property, deserializerType );
    }

    private void generatePropertyDeserializerParameters( SourceWriter source, PropertyInfo property,
                                                         JDeserializerType deserializerType ) throws UnableToCompleteException {

        if ( property.getFormat().isPresent() || property.getIgnoredProperties().isPresent() || property.getIgnoreUnknown().isPresent() ||
                property.getIdentityInfo().isPresent() || property.getTypeInfo().isPresent() ) {

            JClassType annotatedType = findFirstTypeToApplyPropertyAnnotation( deserializerType );

            source.println();

            source.println( "@Override" );
            source.println( "protected %s newParameters() {", JSON_DESERIALIZER_PARAMETERS_CLASS );
            source.indent();
            source.print( "return new %s()", JSON_DESERIALIZER_PARAMETERS_CLASS );

            source.indent();

            generateCommonPropertyParameters( source, property, deserializerType );

            if ( property.getIgnoreUnknown().isPresent() ) {
                source.println();
                source.print( ".setIgnoreUnknown(%s)", Boolean.toString( property.getIgnoreUnknown().get() ) );
            }

            if ( property.getIdentityInfo().isPresent() ) {
                try {
                    Optional<JDeserializerType> identityDeserializerType = getIdentityDeserializerType( property.getIdentityInfo().get() );
                    source.println();
                    source.print( ".setIdentityInfo(" );
                    generateIdentifierDeserializationInfo( source, annotatedType, property.getIdentityInfo()
                            .get(), identityDeserializerType );
                    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(), false );
                source.print( ")" );
            }

            source.println( ";" );
            source.outdent();

            source.outdent();
            source.println( "}" );
        }
    }

    private void generateInitBackReferenceDeserializersMethod( SourceWriter source, BeanInfo beanInfo,
                                                               List<PropertyInfo> properties ) throws UnableToCompleteException {
        String resultType = String.format( "%s<%s<%s, ?>>", SimpleStringMap.class
                .getCanonicalName(), BACK_REFERENCE_PROPERTY_BEAN_CLASS, beanInfo.getType().getParameterizedQualifiedSourceName() );

        source.println( "@Override" );
        source.println( "protected %s initBackReferenceDeserializers() {", resultType );
        source.indent();
        source.println( "%s map = %s.createObject().cast();", resultType, SimpleStringMap.class.getCanonicalName() );
        for ( PropertyInfo property : properties ) {

            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()
                    .getParameterizedQualifiedSourceName(), getParameterizedQualifiedClassName( property.getType() ) );

            source.indent();
            source.println( "@Override" );
            source.println( "public void setBackReference(%s bean, %s reference, %s ctx) {", beanInfo.getType()
                    .getParameterizedQualifiedSourceName(), property.getType()
                    .getParameterizedQualifiedSourceName(), JSON_DESERIALIZATION_CONTEXT_CLASS );
            source.indent();

            source.println( accessor.getAccessor() + ";", "reference" );

            source.outdent();
            source.println( "}" );

            if ( accessor.getAdditionalMethod().isPresent() ) {
                source.println();
                accessor.getAdditionalMethod().get().write( source );
            }

            source.outdent();
            source.println( "});" );
            source.println();
        }
        source.println( "return map;" );
        source.outdent();
        source.println( "}" );
    }

    private void generateInitIgnoredPropertiesMethod( SourceWriter source, List<PropertyInfo> properties ) {
        source.println( "@Override" );
        source.println( "protected %s<%s> initIgnoredProperties() {", Set.class.getCanonicalName(), String.class.getCanonicalName() );
        source.indent();
        source.println( "%s<%s> col = new %s<%s>(%s);", HashSet.class.getCanonicalName(), String.class.getCanonicalName(), HashSet.class
                .getCanonicalName(), String.class.getCanonicalName(), properties.size() );
        for ( PropertyInfo property : properties ) {
            source.println( "col.add(\"%s\");", property.getPropertyName() );
        }
        source.println( "return col;" );
        source.outdent();
        source.println( "}" );
    }

    private void generateInitRequiredPropertiesMethod( SourceWriter source, List<PropertyInfo> properties ) {
        source.println( "@Override" );
        source.println( "protected %s<%s> initRequiredProperties() {", Set.class.getCanonicalName(), String.class.getCanonicalName() );
        source.indent();
        source.println( "%s<%s> col = new %s<%s>(%s);", HashSet.class.getCanonicalName(), String.class.getCanonicalName(), HashSet.class
                .getCanonicalName(), String.class.getCanonicalName(), properties.size() );
        for ( PropertyInfo property : properties ) {
            source.println( "col.add(\"%s\");", property.getPropertyName() );
        }
        source.println( "return col;" );
        source.outdent();
        source.println( "}" );
    }

    private void generateInitIdentityInfoMethod( SourceWriter source, BeanInfo beanInfo, Optional<JDeserializerType> deserializerType )
            throws UnableToCompleteException {
        source.println( "@Override" );
        source.println( "protected %s<%s> initIdentityInfo() {", IdentityDeserializationInfo.class.getCanonicalName(), beanInfo.getType()
                .getParameterizedQualifiedSourceName() );
        source.indent();
        source.print( "return " );
        generateIdentifierDeserializationInfo( source, beanInfo.getType(), beanInfo.getIdentityInfo().get(), deserializerType );
        source.println( ";" );
        source.outdent();
        source.println( "}" );
    }

    private void generateInitTypeInfoMethod( SourceWriter source, BeanInfo beanInfo ) throws UnableToCompleteException {
        source.println( "@Override" );
        source.println( "protected %s<%s> initTypeInfo() {", TYPE_DESERIALIZATION_INFO_CLASS, beanInfo.getType()
                .getParameterizedQualifiedSourceName() );
        source.indent();
        source.print( "return " );
        generateTypeInfo( source, beanInfo.getTypeInfo().get(), false );
        source.println( ";" );
        source.outdent();
        source.println( "}" );
    }

    private void generateInitMapSubtypeClassToDeserializerMethod( SourceWriter source, ImmutableList<JClassType> subtypes ) throws
            UnableToCompleteException {

        String mapTypes = String.format( "<%s, %s>", Class.class.getCanonicalName(), SubtypeDeserializer.class.getName() );
        String resultType = String.format( "%s%s", Map.class.getCanonicalName(), mapTypes );

        source.println( "@Override" );
        source.println( "protected %s initMapSubtypeClassToDeserializer() {", resultType );

        source.indent();

        source.println( "%s map = new %s%s(%s);", resultType, IdentityHashMap.class.getCanonicalName(), mapTypes, subtypes.size() );
        source.println();

        for ( JClassType subtype : subtypes ) {

            JDeserializerType deserializerType;
            try {
                deserializerType = getJsonDeserializerFromType( subtype, true );
            } catch ( UnsupportedTypeException e ) {
                logger.log( Type.WARN, "Subtype '" + subtype.getQualifiedSourceName() + "' is not supported. We ignore it." );
                continue;
            }

            String subtypeClass;
            String deserializerClass;
            if ( configuration.getDeserializer( subtype ).isPresent() || null != subtype.isEnum() ) {
                subtypeClass = DefaultSubtypeDeserializer.class.getCanonicalName();
                deserializerClass = String.format( "%s<?>", JsonDeserializer.class.getName() );
            } else {
                subtypeClass = BeanSubtypeDeserializer.class.getCanonicalName();
                deserializerClass = String.format( "%s<?>", ABSTRACT_BEAN_JSON_DESERIALIZER_CLASS );
            }

            source.println( "map.put( %s.class, new %s() {", subtype.getQualifiedSourceName(), subtypeClass );
            source.indent();

            source.println( "@Override" );
            source.println( "protected %s newDeserializer() {", deserializerClass );
            source.indent();
            source.println( "return %s;", deserializerType.getInstance() );
            source.outdent();
            source.println( "}" );

            source.outdent();
            source.println( "});" );
            source.println();
        }

        source.println( "return map;" );

        source.outdent();
        source.println( "}" );
    }

    private void generateIsDefaultIgnoreUnknownMethod( SourceWriter source ) {
        source.println( "@Override" );
        source.println( "protected boolean isDefaultIgnoreUnknown() {" );
        source.indent();
        source.println( "return true;" );
        source.outdent();
        source.println( "}" );
    }

    private void generateClassGetterMethod( SourceWriter source, BeanInfo beanInfo ) {
        source.println( "@Override" );
        source.println( "public %s getDeserializedType() {", Class.class.getName() );
        source.indent();
        source.println( "return %s.class;", beanInfo.getType().getQualifiedSourceName() );
        source.outdent();
        source.println( "}" );
    }
}
TOP

Related Classes of com.github.nmorel.gwtjackson.rebind.BeanJsonDeserializerCreator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.