All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.nmorel.gwtjackson.rebind.AbstractBeanJsonCreator Maven / Gradle / Ivy

Go to download

gwt-jackson is a GWT JSON serializer/deserializer mechanism based on Jackson annotations

There is a newer version: 0.15.4
Show newest version
/*
 * 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.lang.model.element.Modifier;
import java.io.PrintWriter;
import java.util.Map.Entry;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.github.nmorel.gwtjackson.client.JsonDeserializer;
import com.github.nmorel.gwtjackson.client.JsonSerializer;
import com.github.nmorel.gwtjackson.client.deser.bean.AbstractBeanJsonDeserializer;
import com.github.nmorel.gwtjackson.client.deser.bean.AbstractDelegationBeanJsonDeserializer;
import com.github.nmorel.gwtjackson.client.deser.bean.AbstractObjectBeanJsonDeserializer;
import com.github.nmorel.gwtjackson.client.deser.bean.AbstractSerializableBeanJsonDeserializer;
import com.github.nmorel.gwtjackson.client.deser.bean.TypeDeserializationInfo;
import com.github.nmorel.gwtjackson.client.ser.bean.AbstractBeanJsonSerializer;
import com.github.nmorel.gwtjackson.client.ser.bean.AbstractValueBeanJsonSerializer;
import com.github.nmorel.gwtjackson.client.ser.bean.TypeSerializationInfo;
import com.github.nmorel.gwtjackson.rebind.bean.BeanInfo;
import com.github.nmorel.gwtjackson.rebind.bean.BeanTypeInfo;
import com.github.nmorel.gwtjackson.rebind.exception.UnsupportedTypeException;
import com.github.nmorel.gwtjackson.rebind.property.PropertyInfo;
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.JClassType;
import com.google.gwt.core.ext.typeinfo.JTypeParameter;
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.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import static com.github.nmorel.gwtjackson.rebind.CreatorUtils.isObject;
import static com.github.nmorel.gwtjackson.rebind.CreatorUtils.isSerializable;
import static com.github.nmorel.gwtjackson.rebind.writer.JTypeName.parameterizedName;
import static com.github.nmorel.gwtjackson.rebind.writer.JTypeName.rawName;
import static com.github.nmorel.gwtjackson.rebind.writer.JTypeName.typeVariableName;

/**
 * @author Nicolas Morel
 */
public abstract class AbstractBeanJsonCreator extends AbstractCreator {

    protected final BeanJsonMapperInfo mapperInfo;

    protected final BeanInfo beanInfo;

    protected final ImmutableMap properties;

    public AbstractBeanJsonCreator( TreeLogger logger, GeneratorContext context, RebindConfiguration configuration, JacksonTypeOracle
            typeOracle, JClassType beanType ) throws UnableToCompleteException {
        super( logger, context, configuration, typeOracle );
        this.mapperInfo = getMapperInfo( beanType );
        this.beanInfo = mapperInfo.getBeanInfo();
        this.properties = mapperInfo.getProperties();
    }

    @Override
    protected final Optional getMapperInfo() {
        return Optional.of( mapperInfo );
    }

    /**
     * Creates an implementation of {@link AbstractBeanJsonSerializer} for the type given in
     * parameter
     *
     * @return the information about the created class
     */
    public final BeanJsonMapperInfo create() throws UnableToCompleteException, UnsupportedTypeException {

        final String simpleClassName = isSerializer() ? mapperInfo
                .getSimpleSerializerClassName() : mapperInfo.getSimpleDeserializerClassName();

        PrintWriter printWriter = getPrintWriter( mapperInfo.getPackageName(), simpleClassName );
        // the class already exists, no need to continue
        if ( printWriter == null ) {
            return mapperInfo;
        }

        try {
            TypeSpec type = buildClass( simpleClassName );
            write( mapperInfo.getPackageName(), type, printWriter );
        } finally {
            printWriter.close();
        }

        return mapperInfo;
    }

    /**
     * @return true if we are creating a serializer, false otherwise
     */
    protected abstract boolean isSerializer();

    /**
     * Build the serializer/deserializer class.
     *
     * @param simpleClassName the name of the class
     *
     * @return the {@link TypeSpec}
     * @throws UnableToCompleteException if an uncoverable exception occured
     * @throws UnsupportedTypeException if the type is not supported
     */
    private TypeSpec buildClass( String simpleClassName ) throws UnableToCompleteException, UnsupportedTypeException {

        TypeSpec.Builder typeBuilder = TypeSpec.classBuilder( simpleClassName )
                .addModifiers( Modifier.PUBLIC, Modifier.FINAL );

        // We add the superclass.
        if ( isSerializer() ) {
            Class clazz;
            if ( beanInfo.getValuePropertyInfo().isPresent() ) {
                clazz = AbstractValueBeanJsonSerializer.class;
            } else {
                clazz = AbstractBeanJsonSerializer.class;
            }
            typeBuilder.superclass( parameterizedName( clazz, beanInfo.getType() ) );
        } else {
            if ( isObject( beanInfo.getType() ) ) {
                typeBuilder.superclass( ClassName.get( AbstractObjectBeanJsonDeserializer.class ) );
            } else if ( isSerializable( beanInfo.getType() ) ) {
                typeBuilder.superclass( ClassName.get( AbstractSerializableBeanJsonDeserializer.class ) );
            } else if ( beanInfo.isCreatorDelegation() ) {
                typeBuilder.superclass( parameterizedName( AbstractDelegationBeanJsonDeserializer.class, beanInfo.getType() ) );
            } else {
                typeBuilder.superclass( parameterizedName( AbstractBeanJsonDeserializer.class, beanInfo.getType() ) );
            }
        }

        // If the type is generic, the mapper must have the same parameters.
        if ( null != beanInfo.getType().isGenericType() ) {
            for ( JTypeParameter typeParameter : beanInfo.getType().isGenericType().getTypeParameters() ) {
                typeBuilder.addTypeVariable( typeVariableName( typeParameter ) );
            }
        }

        buildConstructor( typeBuilder );
        typeBuilder.addMethod( buildClassGetterMethod() );

        buildSpecific( typeBuilder );

        return typeBuilder.build();
    }

    /**
     * Build the constructor and the final fields.
     *
     * @param typeBuilder the type builder
     */
    private void buildConstructor( TypeSpec.Builder typeBuilder ) {
        MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder().addModifiers( Modifier.PUBLIC );

        if ( !beanInfo.getParameterizedTypes().isEmpty() ) {
            Class mapperClass;
            String mapperNameFormat;
            if ( isSerializer() ) {
                mapperClass = JsonSerializer.class;
                mapperNameFormat = TYPE_PARAMETER_SERIALIZER_FIELD_NAME;
            } else {
                mapperClass = JsonDeserializer.class;
                mapperNameFormat = TYPE_PARAMETER_DESERIALIZER_FIELD_NAME;
            }

            for ( int i = 0; i < beanInfo.getParameterizedTypes().size(); i++ ) {
                JClassType argType = beanInfo.getParameterizedTypes().get( i );
                String mapperName = String.format( mapperNameFormat, i );
                TypeName mapperType = parameterizedName( mapperClass, argType );

                FieldSpec field = FieldSpec.builder( mapperType, mapperName, Modifier.PRIVATE, Modifier.FINAL ).build();
                typeBuilder.addField( field );

                ParameterSpec parameter = ParameterSpec.builder( mapperType, mapperName ).build();
                constructorBuilder.addParameter( parameter );
                constructorBuilder.addStatement( "this.$N = $N", field, parameter );
            }
        }

        typeBuilder.addMethod( constructorBuilder.build() );
    }

    /**
     * Build the method that returns the class of the mapped type.
     *
     * @return the method built
     */
    private MethodSpec buildClassGetterMethod() {
        return MethodSpec.methodBuilder( isSerializer() ? "getSerializedType" : "getDeserializedType" )
                .addModifiers( Modifier.PUBLIC )
                .addAnnotation( Override.class )
                .returns( Class.class )
                .addStatement( "return $T.class", rawName( beanInfo.getType() ) )
                .build();
    }

    /**
     * Method implemented by childs to add specific methods to the builder.
     *
     * @param typeBuilder the type builder
     */
    protected abstract void buildSpecific( TypeSpec.Builder typeBuilder ) throws UnableToCompleteException, UnsupportedTypeException;

    /**
     * Build the code to initialize a {@link TypeSerializationInfo} or {@link TypeDeserializationInfo}.
     *
     * @param typeInfo the type information obtained through the {@link JsonTypeInfo} annotation
     *
     * @return the code built
     */
    protected final CodeBlock generateTypeInfo( BeanTypeInfo typeInfo ) {

        Class type;
        ImmutableMap mapTypeToMetadata;
        if ( isSerializer() ) {
            type = TypeSerializationInfo.class;
            mapTypeToMetadata = typeInfo.getMapTypeToSerializationMetadata();
        } else {
            type = TypeDeserializationInfo.class;
            mapTypeToMetadata = typeInfo.getMapTypeToDeserializationMetadata();
        }

        CodeBlock.Builder builder = CodeBlock.builder()
                .add( "new $T($T.$L, $S)", type, As.class, typeInfo.getInclude(), typeInfo.getPropertyName() )
                .indent()
                .indent();

        for ( Entry entry : mapTypeToMetadata.entrySet() ) {
            builder.add( "\n.addTypeInfo($T.class, $S)", rawName( entry.getKey() ), entry.getValue() );
        }

        return builder.unindent().unindent().build();
    }

    /**
     * Add the common property parameters to the code builder.
     *
     * @param paramBuilder the code builder
     * @param property the information about the property
     */
    protected final void buildCommonPropertyParameters( CodeBlock.Builder paramBuilder, PropertyInfo property ) {
        if ( property.getFormat().isPresent() ) {
            JsonFormat format = property.getFormat().get();

            if ( !Strings.isNullOrEmpty( format.pattern() ) ) {
                paramBuilder.add( "\n.setPattern($S)", format.pattern() );
            }

            paramBuilder.add( "\n.setShape($T.$L)", Shape.class, format.shape().name() );

            if ( !Strings.isNullOrEmpty( format.locale() ) && !JsonFormat.DEFAULT_LOCALE.equals( format.locale() ) ) {
                logger.log( Type.WARN, "JsonFormat.locale is not supported by default" );
                paramBuilder.add( "\n.setLocale($S)", format.locale() );
            }
        }

        if ( property.getIgnoredProperties().isPresent() ) {
            for ( String ignoredProperty : property.getIgnoredProperties().get() ) {
                paramBuilder.add( "\n.addIgnoredProperty($S)", ignoredProperty );
            }
        }
    }

    /**
     * @return the filtered subtypes of the mapped type
     */
    protected final ImmutableList filterSubtypes() {
        if ( isSerializer() ) {
            return CreatorUtils.filterSubtypesForSerialization( logger, configuration, beanInfo.getType() );
        } else {
            return CreatorUtils.filterSubtypesForDeserialization( logger, configuration, beanInfo.getType() );
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy