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
package com.github.nmorel.gwtjackson.rebind;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.github.nmorel.gwtjackson.client.ser.bean.AbstractBeanJsonSerializer;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.user.rebind.SourceWriter;

import static com.github.nmorel.gwtjackson.rebind.CreatorUtils.findFirstEncounteredAnnotationsOnAllHierarchy;

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

    protected static class TypeParameters {

        private final List typeParameterMapperNames;

        private final String joinedTypeParameterMappersWithType;

        private final String joinedTypeParameterMappersWithoutType;

        public TypeParameters( List typeParameterMapperNames, String joinedTypeParameterMappersWithType,
                               String joinedTypeParameterMappersWithoutType ) {
            this.typeParameterMapperNames = typeParameterMapperNames;
            this.joinedTypeParameterMappersWithType = joinedTypeParameterMappersWithType;
            this.joinedTypeParameterMappersWithoutType = joinedTypeParameterMappersWithoutType;
        }

        public List getTypeParameterMapperNames() {
            return typeParameterMapperNames;
        }

        public String getJoinedTypeParameterMappersWithType() {
            return joinedTypeParameterMappersWithType;
        }

        public String getJoinedTypeParameterMappersWithoutType() {
            return joinedTypeParameterMappersWithoutType;
        }
    }

    protected static final String ABSTRACT_BEAN_JSON_DESERIALIZER_CLASS = "com.github.nmorel.gwtjackson.client.deser.bean" + "" +
        ".AbstractBeanJsonDeserializer";

    protected static final String ABSTRACT_BEAN_JSON_SERIALIZER_CLASS = "com.github.nmorel.gwtjackson.client.ser.bean" + "" +
        ".AbstractBeanJsonSerializer";

    protected BeanJsonMapperInfo mapperInfo;

    public AbstractBeanJsonCreator( TreeLogger logger, GeneratorContext context, JacksonTypeOracle typeOracle ) {
        super( logger, context, typeOracle );
    }

    /**
     * Creates an implementation of {@link AbstractBeanJsonSerializer} for the type given in
     * parameter
     *
     * @param beanType type of the bean
     *
     * @return the fully qualified name of the created class
     * @throws com.google.gwt.core.ext.UnableToCompleteException
     */
    public BeanJsonMapperInfo create( JClassType beanType ) throws UnableToCompleteException {

        mapperInfo = typeOracle.getBeanJsonMapperInfo( beanType );

        String packageName = beanType.getPackage().getName();

        if ( null == mapperInfo ) {

            // we concatenate the name of all the enclosing class
            StringBuilder builder = new StringBuilder( beanType.getSimpleSourceName() );
            JClassType enclosingType = beanType.getEnclosingType();
            while ( null != enclosingType ) {
                builder.insert( 0, enclosingType.getSimpleSourceName() + "_" );
                enclosingType = enclosingType.getEnclosingType();
            }

            String simpleSerializerClassName = builder.toString() + "BeanJsonSerializerImpl";
            String qualifiedSerializerClassName = packageName + "." + simpleSerializerClassName;
            String simpleDeserializerClassName = builder.toString() + "BeanJsonDeserializerImpl";
            String qualifiedDeserializerClassName = packageName + "." + simpleDeserializerClassName;

            mapperInfo = new BeanJsonMapperInfo( beanType, qualifiedSerializerClassName, simpleSerializerClassName,
                qualifiedDeserializerClassName, simpleDeserializerClassName );

            // retrieve the informations on the beans and its properties
            BeanInfo info = BeanInfo.process( logger, typeOracle, mapperInfo );
            mapperInfo.setBeanInfo( info );

            Map properties = findAllProperties( info );
            mapperInfo.setProperties( properties );

            typeOracle.addBeanJsonMapperInfo( beanType, mapperInfo );
        }

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

        String parameterizedTypes = beanType.getParameterizedQualifiedSourceName();
        if ( !isSerializer() ) {
            parameterizedTypes = parameterizedTypes + ", " + mapperInfo.getBeanInfo()
                .getInstanceBuilderQualifiedName() + getGenericClassParameters();
        }

        SourceWriter source = getSourceWriter( printWriter, packageName, getSimpleClassName() + getGenericClassParameters(),
            getSuperclass() + "<" +
            parameterizedTypes + ">" );

        writeClassBody( source, mapperInfo.getBeanInfo(), mapperInfo.getProperties() );

        return mapperInfo;
    }

    protected abstract boolean isSerializer();

    protected String getSimpleClassName() {
        if ( isSerializer() ) {
            return mapperInfo.getSimpleSerializerClassName();
        } else {
            return mapperInfo.getSimpleDeserializerClassName();
        }
    }

    protected String getQualifiedClassName() {
        if ( isSerializer() ) {
            return mapperInfo.getQualifiedSerializerClassName();
        } else {
            return mapperInfo.getQualifiedDeserializerClassName();
        }
    }

    protected String getGenericClassParameters() {
        return mapperInfo.getGenericClassParameters();
    }

    protected String getSuperclass() {
        if ( isSerializer() ) {
            return ABSTRACT_BEAN_JSON_SERIALIZER_CLASS;
        } else {
            return ABSTRACT_BEAN_JSON_DESERIALIZER_CLASS;
        }
    }

    protected abstract void writeClassBody( SourceWriter source, BeanInfo info, Map properties ) throws UnableToCompleteException;

    protected String extractTypeMetadata( BeanInfo info, JClassType subtype ) throws UnableToCompleteException {
        switch ( info.getTypeInfo().use() ) {
            case NAME:
                JsonSubTypes jsonSubTypes = findFirstEncounteredAnnotationsOnAllHierarchy( info.getType(), JsonSubTypes.class );
                if ( null != jsonSubTypes && jsonSubTypes.value().length > 0 ) {
                    for ( JsonSubTypes.Type type : jsonSubTypes.value() ) {
                        if ( !type.name().isEmpty() && type.value().getName().equals( subtype.getQualifiedBinaryName() ) ) {
                            return type.name();
                        }
                    }
                }
                JsonTypeName typeName = findFirstEncounteredAnnotationsOnAllHierarchy( subtype, JsonTypeName.class );
                if ( null != typeName && null != typeName.value() && !typeName.value().isEmpty() ) {
                    return typeName.value();
                } else {
                    String simpleBinaryName = subtype.getQualifiedBinaryName();
                    int indexLastDot = simpleBinaryName.lastIndexOf( '.' );
                    if ( indexLastDot != -1 ) {
                        simpleBinaryName = simpleBinaryName.substring( indexLastDot + 1 );
                    }
                    return simpleBinaryName;
                }
            case MINIMAL_CLASS:
                if ( !info.getType().getPackage().isDefault() ) {
                    String basePackage = info.getType().getPackage().getName();
                    if ( subtype.getQualifiedBinaryName().startsWith( basePackage + "." ) ) {
                        return subtype.getQualifiedBinaryName().substring( basePackage.length() );
                    }
                }
            case CLASS:
                return subtype.getQualifiedBinaryName();
            default:
                logger.log( TreeLogger.Type.ERROR, "JsonTypeInfo.Id." + info.getTypeInfo().use() + " is not supported" );
                throw new UnableToCompleteException();
        }
    }

    private Map findAllProperties( BeanInfo info ) throws UnableToCompleteException {
        Map result = new LinkedHashMap();
        if ( null != info.getType().isInterface() ) {
            // no properties on interface
            return result;
        }

        Map fieldsMap = new LinkedHashMap();
        parseFields( info.getType(), fieldsMap );
        parseMethods( info.getType(), fieldsMap );

        // Processing all the properties accessible via field, getter or setter
        Map propertiesMap = new LinkedHashMap();
        for ( FieldAccessors field : fieldsMap.values() ) {
            PropertyInfo property = PropertyInfo.process( logger, typeOracle, field, mapperInfo );
            if ( !property.isVisible() ) {
                logger.log( TreeLogger.Type.DEBUG, "Field " + field.getFieldName() + " of type " + info.getType() + " is not visible" );
            } else {
                propertiesMap.put( property.getPropertyName(), property );
            }
        }

        // We look if there is any constructor parameters not found yet
        if ( !info.getCreatorParameters().isEmpty() ) {
            for ( Map.Entry entry : info.getCreatorParameters().entrySet() ) {
                PropertyInfo property = propertiesMap.get( entry.getKey() );
                if ( null == property ) {
                    propertiesMap.put( entry.getKey(), PropertyInfo.process( logger, typeOracle, entry.getKey(), entry.getValue(), info ) );
                } else if ( entry.getValue().getAnnotation( JsonProperty.class ).required() ) {
                    property.setRequired( true );
                }
            }
        }

        // we first add the properties defined in order
        for ( String orderedProperty : info.getPropertyOrderList() ) {
            // we remove the entry to have the map with only properties with natural or alphabetic order
            PropertyInfo property = propertiesMap.remove( orderedProperty );
            if ( null != property ) {
                result.put( property.getPropertyName(), property );
            }
        }

        // if the user asked for an alphbetic order, we sort the rest of the properties
        if ( info.isPropertyOrderAlphabetic() ) {
            List> entries = new ArrayList>( propertiesMap.entrySet() );
            Collections.sort( entries, new Comparator>() {
                public int compare( Map.Entry a, Map.Entry b ) {
                    return a.getKey().compareTo( b.getKey() );
                }
            } );
            for ( Map.Entry entry : entries ) {
                result.put( entry.getKey(), entry.getValue() );
            }
        } else {
            for ( Map.Entry entry : propertiesMap.entrySet() ) {
                result.put( entry.getKey(), entry.getValue() );
            }
        }

        findIdPropertyInfo( result, info.getIdentityInfo() );

        return result;
    }

    private void parseFields( JClassType type, Map propertiesMap ) {
        if ( null == type || type.getQualifiedSourceName().equals( "java.lang.Object" ) ) {
            return;
        }

        for ( JField field : type.getFields() ) {
            String fieldName = field.getName();
            FieldAccessors property = propertiesMap.get( fieldName );
            if ( null == property ) {
                property = new FieldAccessors( fieldName );
                propertiesMap.put( fieldName, property );
            }
            if ( null == property.getField() ) {
                property.setField( field );
            } else {
                // we found an other field with the same name on a superclass. we ignore it
                logger.log( TreeLogger.Type.WARN, "A field with the same name as " + field
                    .getName() + " has already been found on child class" );
            }
        }
        parseFields( type.getSuperclass(), propertiesMap );
    }

    private void parseMethods( JClassType type, Map propertiesMap ) {
        if ( null == type || type.getQualifiedSourceName().equals( "java.lang.Object" ) ) {
            return;
        }

        for ( JMethod method : type.getMethods() ) {
            if ( null != method.isConstructor() || method.isStatic() ) {
                continue;
            }

            JType returnType = method.getReturnType();
            if ( null != returnType.isPrimitive() && JPrimitiveType.VOID.equals( returnType.isPrimitive() ) ) {
                // might be a setter
                if ( method.getParameters().length == 1 ) {
                    String methodName = method.getName();
                    if ( methodName.startsWith( "set" ) && methodName.length() > 3 ) {
                        // it's a setter method
                        String fieldName = extractFieldNameFromGetterSetterMethodName( methodName );
                        FieldAccessors property = propertiesMap.get( fieldName );
                        if ( null == property ) {
                            property = new FieldAccessors( fieldName );
                            propertiesMap.put( fieldName, property );
                        }
                        property.addSetter( method );
                    }
                }
            } else {
                // might be a getter
                if ( method.getParameters().length == 0 ) {
                    String methodName = method.getName();
                    if ( (methodName.startsWith( "get" ) && methodName.length() > 3) || (methodName.startsWith( "is" ) && methodName
                        .length() > 2 && null != returnType.isPrimitive() && JPrimitiveType.BOOLEAN.equals( returnType.isPrimitive() )) ) {
                        // it's a getter method
                        String fieldName = extractFieldNameFromGetterSetterMethodName( methodName );
                        FieldAccessors property = propertiesMap.get( fieldName );
                        if ( null == property ) {
                            property = new FieldAccessors( fieldName );
                            propertiesMap.put( fieldName, property );
                        }
                        property.addGetter( method );
                    }
                }
            }
        }

        for ( JClassType interf : type.getImplementedInterfaces() ) {
            parseMethods( interf, propertiesMap );
        }

        parseMethods( type.getSuperclass(), propertiesMap );
    }

    private String extractFieldNameFromGetterSetterMethodName( String methodName ) {
        if ( methodName.startsWith( "is" ) ) {
            return methodName.substring( 2, 3 ).toLowerCase() + methodName.substring( 3 );
        } else {
            return methodName.substring( 3, 4 ).toLowerCase() + methodName.substring( 4 );
        }
    }

    protected TypeParameters generateTypeParameterMapperFields( SourceWriter source, BeanInfo beanInfo, String mapperClass,
                                                                String mapperNameFormat ) throws UnableToCompleteException {
        if ( null == beanInfo.getParameterizedTypes() || beanInfo.getParameterizedTypes().length == 0 ) {
            return null;
        }

        List typeParameterMapperNames = new ArrayList();
        StringBuilder joinedTypeParameterMappersWithType = new StringBuilder();
        StringBuilder joinedTypeParameterMappersWithoutType = new StringBuilder();

        for ( int i = 0; i < beanInfo.getParameterizedTypes().length; i++ ) {
            if ( i > 0 ) {
                joinedTypeParameterMappersWithType.append( ", " );
                joinedTypeParameterMappersWithoutType.append( ", " );
            }

            JClassType argType = beanInfo.getParameterizedTypes()[i];
            String mapperType = String.format( "%s<%s>", mapperClass, argType.getName() );
            String mapperName = String.format( mapperNameFormat, i );

            source.println( "private final %s %s;", mapperType, mapperName );

            typeParameterMapperNames.add( mapperName );
            joinedTypeParameterMappersWithType.append( String.format( "%s %s%s", mapperType, TYPE_PARAMETER_PREFIX, mapperName ) );
            joinedTypeParameterMappersWithoutType.append( TYPE_PARAMETER_PREFIX ).append( mapperName );
        }

        return new TypeParameters( typeParameterMapperNames, joinedTypeParameterMappersWithType
            .toString(), joinedTypeParameterMappersWithoutType.toString() );
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy