Package com.github.nmorel.gwtjackson.rebind

Source Code of com.github.nmorel.gwtjackson.rebind.RebindConfiguration$TypeFilter

/*
* 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 java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.github.nmorel.gwtjackson.client.AbstractConfiguration;
import com.github.nmorel.gwtjackson.client.annotation.JsonMixIns;
import com.github.nmorel.gwtjackson.client.annotation.JsonMixIns.JsonMixIn;
import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.ConfigurationProperty;
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.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracleException;
import com.google.gwt.thirdparty.guava.common.base.Optional;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet.Builder;
import com.google.gwt.util.regexfilter.RegexFilter;
import com.google.gwt.util.tools.shared.Md5Utils;

/**
* Wrap the default configuration + user configuration. It reads the configuration from {@link DefaultConfiguration} and any {@link
* AbstractConfiguration} the user defined with :
* <pre>
* &lt;extend-configuration-property name="gwtjackson.configuration.extension" value="com.example.MyAbstractConfiguration" /&gt;
* </pre>
*
* @author Nicolas Morel
*/
public final class RebindConfiguration {

    public enum MapperType {
        KEY_SERIALIZER( true, true ) {
            @Override
            protected Map<Class, Class> getMapperTypeConfiguration( AbstractConfiguration configuration ) {
                return configuration.getMapTypeToKeySerializer();
            }
        }, KEY_DESERIALIZER( false, true ) {
            @Override
            protected Map<Class, Class> getMapperTypeConfiguration( AbstractConfiguration configuration ) {
                return configuration.getMapTypeToKeyDeserializer();
            }
        }, JSON_SERIALIZER( true, false ) {
            @Override
            protected Map<Class, Class> getMapperTypeConfiguration( AbstractConfiguration configuration ) {
                return configuration.getMapTypeToSerializer();
            }
        }, JSON_DESERIALIZER( false, false ) {
            @Override
            protected Map<Class, Class> getMapperTypeConfiguration( AbstractConfiguration configuration ) {
                return configuration.getMapTypeToDeserializer();
            }
        };

        private final boolean serializer;

        private final boolean key;

        private MapperType( boolean serializer, boolean key ) {
            this.serializer = serializer;
            this.key = key;
        }

        abstract Map<Class, Class> getMapperTypeConfiguration( final AbstractConfiguration configuration );

        boolean isSerializer() {
            return serializer;
        }

        boolean isKey() {
            return key;
        }
    }

    public static class MapperInstance {

        private final JClassType mapperType;

        private final String instanceCreation;

        private final MapperType[] parameters;

        private MapperInstance( JClassType mapperType, String instanceCreation ) {
            this( mapperType, instanceCreation, new MapperType[0] );
        }

        private MapperInstance( JClassType mapperType, String instanceCreation, MapperType[] parameters ) {
            this.mapperType = mapperType;
            this.instanceCreation = instanceCreation;
            this.parameters = parameters;
        }

        public JClassType getMapperType() {
            return mapperType;
        }

        public String getInstanceCreation() {
            return instanceCreation;
        }

        public MapperType[] getParameters() {
            return parameters;
        }
    }

    private static class TypeFilter extends RegexFilter {

        public TypeFilter( TreeLogger logger, List<String> values ) throws UnableToCompleteException {
            super( logger, values );
        }

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

        @Override
        protected boolean entriesArePositiveByDefault() {
            return true;
        }
    }

    private static final String CONFIGURATION_EXTENSION_PROPERTY = "gwtjackson.configuration.extension";

    private final TreeLogger logger;

    private final GeneratorContext context;

    private final JacksonTypeOracle typeOracle;

    private final Map<String, MapperInstance> serializers = new HashMap<String, MapperInstance>();

    private final Map<String, MapperInstance> deserializers = new HashMap<String, MapperInstance>();

    private final Map<String, MapperInstance> keySerializers = new HashMap<String, MapperInstance>();

    private final Map<String, MapperInstance> keyDeserializers = new HashMap<String, MapperInstance>();

    private final Map<String, JClassType> mixInAnnotations = new HashMap<String, JClassType>();

    private final JClassType rootMapperClass;

    private final String rootMapperHash;

    // If the user adds an annotation on mapper, we have to make distinct serializer/deserializer for the impacted types.
    // For now, it means any types and associated subtypes targeted by a mix-in annotation
    private final Set<JClassType> specificTypes = new HashSet<JClassType>();

    private final ImmutableSet<JClassType> allSupportedSerializationClass;

    private final ImmutableSet<JClassType> allSupportedDeserializationClass;

    private final TypeFilter additionalSupportedTypes;

    public RebindConfiguration( TreeLogger logger, GeneratorContext context, JacksonTypeOracle typeOracle,
                                JClassType rootMapperClass ) throws UnableToCompleteException {
        this.logger = logger;
        this.context = context;
        this.typeOracle = typeOracle;
        this.rootMapperClass = rootMapperClass;
        this.rootMapperHash = new BigInteger( 1, Md5Utils.getMd5Digest( rootMapperClass.getQualifiedSourceName().getBytes() ) )
                .toString( 16 );

        List<AbstractConfiguration> configurations = getAllConfigurations();

        Builder<JClassType> allSupportedSerializationClassBuilder = ImmutableSet.builder();
        Builder<JClassType> allSupportedDeserializationClassBuilder = ImmutableSet.builder();
        List<String> whitelist = new ArrayList<String>();

        for ( AbstractConfiguration configuration : configurations ) {
            for ( MapperType mapperType : MapperType.values() ) {
                addMappers( configuration, mapperType, allSupportedSerializationClassBuilder, allSupportedDeserializationClassBuilder );
            }
            addMixInAnnotations( configuration.getMapMixInAnnotations(), rootMapperClass.getAnnotation( JsonMixIns.class ) );
            whitelist.addAll( configuration.getWhitelist() );
        }

        this.allSupportedSerializationClass = allSupportedSerializationClassBuilder.build();
        this.allSupportedDeserializationClass = allSupportedDeserializationClassBuilder.build();
        this.additionalSupportedTypes = new TypeFilter( logger, whitelist );
    }

    /**
     * @return the list of default configuration + user configurations
     */
    private List<AbstractConfiguration> getAllConfigurations() {
        ImmutableList.Builder<AbstractConfiguration> builder = ImmutableList.builder();
        builder.add( new DefaultConfiguration() );

        ConfigurationProperty property = null;
        try {
            property = context.getPropertyOracle().getConfigurationProperty( CONFIGURATION_EXTENSION_PROPERTY );
        } catch ( BadPropertyValueException e ) {
            logger.log( Type.WARN, "Cannot find the property " + CONFIGURATION_EXTENSION_PROPERTY );
        }

        if ( null != property && !property.getValues().isEmpty() ) {
            for ( String value : property.getValues() ) {
                try {
                    builder.add( (AbstractConfiguration) Class.forName( value ).newInstance() );
                } catch ( Exception e ) {
                    logger.log( Type.WARN, "Cannot instantiate the configuration class " + value );
                }
            }
        }

        return builder.build();
    }

    /**
     * Parse the configured serializer/deserializer configuration and put them into the corresponding map
     *
     * @param configuration configuration
     * @param mapperType type of the mapper
     * @param allSupportedSerializationClassBuilder builder aggregating all the types that have a serializer
     * @param allSupportedDeserializationClassBuilder builder aggregating all the types that have a deserializer
     */
    private void addMappers( final AbstractConfiguration configuration, final MapperType mapperType,
                             Builder<JClassType> allSupportedSerializationClassBuilder,
                             Builder<JClassType> allSupportedDeserializationClassBuilder ) {
        Map<Class, Class> configuredMapper = mapperType.getMapperTypeConfiguration( configuration );

        for ( Entry<Class, Class> entry : configuredMapper.entrySet() ) {

            JType mappedType = findType( entry.getKey() );
            if ( null == mappedType ) {
                continue;
            }

            JClassType mapperClassType = findClassType( entry.getValue() );
            if ( null == mapperClassType ) {
                continue;
            }

            if ( mapperType.isKey() ) {
                MapperInstance keyMapperInstance = getKeyInstance( mapperClassType );
                if ( mapperType.isSerializer() ) {
                    keySerializers.put( mappedType.getQualifiedSourceName(), keyMapperInstance );
                } else {
                    keyDeserializers.put( mappedType.getQualifiedSourceName(), keyMapperInstance );
                }
            } else {
                MapperInstance mapperInstance = getInstance( mappedType, mapperClassType, mapperType.isSerializer() );
                if ( null != mapperInstance ) {
                    if ( mapperType.isSerializer() ) {
                        serializers.put( mappedType.getQualifiedSourceName(), mapperInstance );
                        if ( null != mappedType.isClass() ) {
                            allSupportedSerializationClassBuilder.add( mappedType.isClass() );
                        }
                    } else {
                        deserializers.put( mappedType.getQualifiedSourceName(), mapperInstance );
                        if ( null != mappedType.isClass() ) {
                            allSupportedDeserializationClassBuilder.add( mappedType.isClass() );
                        }
                    }
                }
            }
        }
    }

    /**
     * @param clazz class to find the type
     *
     * @return the {@link JType} denoted by the class given in parameter
     */

    private JType findType( Class<?> clazz ) {
        if ( clazz.isPrimitive() ) {

            return JPrimitiveType.parse( clazz.getCanonicalName() );

        } else if ( clazz.isArray() ) {

            try {
                return context.getTypeOracle().parse( clazz.getCanonicalName() );
            } catch ( TypeOracleException e ) {
                logger.log( TreeLogger.WARN, "Cannot find the array denoted by the class " + clazz.getCanonicalName() );
                return null;
            }

        } else {
            return findClassType( clazz );
        }
    }

    /**
     * @param clazz class to find the type
     *
     * @return the {@link JClassType} denoted by the class given in parameter
     */
    private JClassType findClassType( Class<?> clazz ) {
        JClassType mapperType = context.getTypeOracle().findType( clazz.getCanonicalName() );
        if ( null == mapperType ) {
            logger.log( Type.WARN, "Cannot find the type denoted by the class " + clazz.getCanonicalName() );
            return null;
        }
        return mapperType;
    }

    /**
     * Search a static method or constructor to instantiate the mapper and return a {@link String} calling it.
     */
    private MapperInstance getInstance( JType mappedType, JClassType classType, boolean isSerializers ) {
        int nbParam = 0;
        if ( null != mappedType.isGenericType() ) {
            nbParam = mappedType.isGenericType().getTypeParameters().length;
        }

        // we first look at static method
        for ( JMethod method : classType.getMethods() ) {
            // method must be public static, return the instance type and take no parameters
            if ( method.isStatic() && method.getReturnType().getQualifiedSourceName().equals( classType.getQualifiedSourceName() ) && method
                    .getParameters().length == nbParam && method.isPublic() ) {
                MapperType[] parameters = getParameters( method, isSerializers );
                if ( null == parameters ) {
                    continue;
                }

                StringBuilder builder = new StringBuilder();
                builder.append( classType.getQualifiedSourceName() ).append( '.' ).append( method.getName() ).append( '(' );
                for ( int i = 0; i < nbParam; i++ ) {
                    if ( i > 0 ) {
                        builder.append( ", %s" );
                    } else {
                        builder.append( "%s" );
                    }
                }
                builder.append( ')' );
                return new MapperInstance( classType, builder.toString(), parameters );
            }
        }

        // then we search the default constructor
        for ( JConstructor constructor : classType.getConstructors() ) {
            if ( constructor.isPublic() && constructor.getParameters().length == nbParam ) {
                MapperType[] parameters = getParameters( constructor, isSerializers );
                if ( null == parameters ) {
                    continue;
                }

                StringBuilder builder = new StringBuilder();
                builder.append( "new " ).append( classType.getQualifiedSourceName() ).append( '(' );
                for ( int i = 0; i < nbParam; i++ ) {
                    if ( i > 0 ) {
                        builder.append( ", %s" );
                    } else {
                        builder.append( "%s" );
                    }
                }
                builder.append( ')' );
                return new MapperInstance( classType, builder.toString(), parameters );
            }
        }

        logger.log( Type.WARN, "Cannot instantiate the custom serializer/deserializer " + classType
                .getQualifiedSourceName() + ". It will be ignored" );
        return null;
    }

    private MapperType[] getParameters( JAbstractMethod method, boolean isSerializers ) {
        MapperType[] parameters = new MapperType[method.getParameters().length];
        for ( int i = 0; i < method.getParameters().length; i++ ) {
            JParameter parameter = method.getParameters()[i];
            if ( isSerializers ) {
                if ( typeOracle.isKeySerializer( parameter.getType() ) ) {
                    parameters[i] = MapperType.KEY_SERIALIZER;
                } else if ( typeOracle.isJsonSerializer( parameter.getType() ) ) {
                    parameters[i] = MapperType.JSON_SERIALIZER;
                } else {
                    // the parameter is unknown, we ignore this method
                    return null;
                }
            } else {
                if ( typeOracle.isKeyDeserializer( parameter.getType() ) ) {
                    parameters[i] = MapperType.KEY_DESERIALIZER;
                } else if ( typeOracle.isJsonDeserializer( parameter.getType() ) ) {
                    parameters[i] = MapperType.JSON_DESERIALIZER;
                } else {
                    // the parameter is unknown, we ignore this method
                    return null;
                }
            }
        }
        return parameters;
    }

    /**
     * Search a static method or constructor to instantiate the key mapper and return a {@link String} calling it.
     */
    private MapperInstance getKeyInstance( JClassType classType ) {
        // we first look at static method
        for ( JMethod method : classType.getMethods() ) {
            // method must be public static, return the instance type and take no parameters
            if ( method.isStatic() && method.getReturnType().getQualifiedSourceName().equals( classType.getQualifiedSourceName() ) && method
                    .getParameters().length == 0 && method.isPublic() ) {
                StringBuilder builder = new StringBuilder();
                builder.append( classType.getQualifiedSourceName() ).append( '.' ).append( method.getName() ).append( "()" );
                return new MapperInstance( classType, builder.toString() );
            }
        }

        // then we search the default constructor
        for ( JConstructor constructor : classType.getConstructors() ) {
            if ( constructor.isPublic() && constructor.getParameters().length == 0 ) {
                StringBuilder builder = new StringBuilder();
                builder.append( "new " ).append( classType.getQualifiedSourceName() ).append( "()" );
                return new MapperInstance( classType, builder.toString() );
            }
        }

        logger.log( Type.WARN, "Cannot instantiate the custom key serializer/deserializer " + classType
                .getQualifiedSourceName() + ". It will be ignored" );
        return null;
    }

    /**
     * Adds to {@link #mixInAnnotations} the configured mix-in annotations passed in parameters
     *
     * @param mapMixInAnnotations mix-ins annotations to add
     * @param mapperMixIns Annotation defined on mapper
     */
    private void addMixInAnnotations( Map<Class, Class> mapMixInAnnotations, JsonMixIns mapperMixIns ) {
        if ( null != mapperMixIns ) {
            for ( JsonMixIn jsonMixIn : mapperMixIns.value() ) {
                JClassType targetType = findClassType( jsonMixIn.target() );
                if ( null == targetType ) {
                    continue;
                }
                specificTypes.add( targetType );
                specificTypes.addAll( Arrays.asList( targetType.getSubtypes() ) );

                mapMixInAnnotations.put( jsonMixIn.target(), jsonMixIn.mixIn() );
            }
        }

        if ( !mapMixInAnnotations.isEmpty() ) {
            for ( Entry<Class, Class> entry : mapMixInAnnotations.entrySet() ) {
                JClassType targetType = findClassType( entry.getKey() );
                if ( null == targetType ) {
                    continue;
                }

                JClassType mixInType = findClassType( entry.getValue() );
                if ( null == mixInType ) {
                    continue;
                }

                mixInAnnotations.put( targetType.getQualifiedSourceName(), mixInType );
            }
        }
    }

    /**
     * Return a {@link MapperInstance} instantiating the serializer for the given type
     */
    public Optional<MapperInstance> getSerializer( JType type ) {
        return Optional.fromNullable( serializers.get( type.getQualifiedSourceName() ) );
    }

    /**
     * Return a {@link MapperInstance} instantiating the deserializer for the given type
     */
    public Optional<MapperInstance> getDeserializer( JType type ) {
        return Optional.fromNullable( deserializers.get( type.getQualifiedSourceName() ) );
    }

    /**
     * Return a {@link MapperInstance} instantiating the key serializer for the given type
     */
    public Optional<MapperInstance> getKeySerializer( JType type ) {
        return Optional.fromNullable( keySerializers.get( type.getQualifiedSourceName() ) );
    }

    /**
     * Return a {@link MapperInstance} instantiating the key deserializer for the given type
     */
    public Optional<MapperInstance> getKeyDeserializer( JType type ) {
        return Optional.fromNullable( keyDeserializers.get( type.getQualifiedSourceName() ) );
    }

    /**
     * Return the mixin type for the given type
     */
    public Optional<JClassType> getMixInAnnotations( JType type ) {
        return Optional.fromNullable( mixInAnnotations.get( type.getQualifiedSourceName() ) );
    }

    /**
     * @return the root mapper class that is currently generated
     */
    public JClassType getRootMapperClass() {
        return rootMapperClass;
    }

    public String getRootMapperHash() {
        return rootMapperHash;
    }

    /**
     * @param beanType type
     *
     * @return true if beanType is specific to the mapper
     */
    public boolean isSpecificToMapper( JClassType beanType ) {
        return specificTypes.contains( beanType );
    }

    public boolean isTypeSupportedForSerialization( TreeLogger logger, JClassType classType ) {
        return allSupportedSerializationClass.contains( classType ) || additionalSupportedTypes.isIncluded( logger, classType
                .getQualifiedSourceName() );
    }

    public boolean isTypeSupportedForDeserialization( TreeLogger logger, JClassType classType ) {
        return allSupportedDeserializationClass.contains( classType ) || additionalSupportedTypes.isIncluded( logger, classType
                .getQualifiedSourceName() );
    }
}
TOP

Related Classes of com.github.nmorel.gwtjackson.rebind.RebindConfiguration$TypeFilter

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.