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

com.github.nmorel.gwtjackson.rebind.RebindConfiguration 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 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.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
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 :
 * 
 * <extend-configuration-property name="gwtjackson.configuration.extension" value="com.example.MyAbstractConfiguration" />
 * 
* * @author Nicolas Morel */ public final class RebindConfiguration { public enum MapperType { KEY_SERIALIZER( true, true ) { @Override protected Map getMapperTypeConfiguration( AbstractConfiguration configuration ) { return configuration.getMapTypeToKeySerializer(); } }, KEY_DESERIALIZER( false, true ) { @Override protected Map getMapperTypeConfiguration( AbstractConfiguration configuration ) { return configuration.getMapTypeToKeyDeserializer(); } }, JSON_SERIALIZER( true, false ) { @Override protected Map getMapperTypeConfiguration( AbstractConfiguration configuration ) { return configuration.getMapTypeToSerializer(); } }, JSON_DESERIALIZER( false, false ) { @Override protected Map getMapperTypeConfiguration( AbstractConfiguration configuration ) { return configuration.getMapTypeToDeserializer(); } }; private final boolean serializer; private final boolean key; MapperType( boolean serializer, boolean key ) { this.serializer = serializer; this.key = key; } abstract Map getMapperTypeConfiguration( final AbstractConfiguration configuration ); boolean isSerializer() { return serializer; } boolean isKey() { return key; } } public static class MapperInstance { private final JClassType mapperType; private final JAbstractMethod instanceCreationMethod; private final MapperType[] parameters; private MapperInstance( JClassType mapperType, JAbstractMethod instanceCreationMethod, MapperType[] parameters ) { this.mapperType = mapperType; this.instanceCreationMethod = instanceCreationMethod; this.parameters = parameters; } public JClassType getMapperType() { return mapperType; } public JAbstractMethod getInstanceCreationMethod() { return instanceCreationMethod; } public MapperType[] getParameters() { return parameters; } } private static class TypeFilter extends RegexFilter { public TypeFilter( TreeLogger logger, List 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 serializers = new HashMap(); private final Map deserializers = new HashMap(); private final Map keySerializers = new HashMap(); private final Map keyDeserializers = new HashMap(); private final Map mixInAnnotations = new HashMap(); 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 specificTypes = new HashSet(); private final ImmutableSet allSupportedSerializationClass; private final ImmutableSet allSupportedDeserializationClass; private final TypeFilter additionalSupportedTypes; private final JsonAutoDetect.Visibility defaultFieldVisibility; private final JsonAutoDetect.Visibility defaultGetterVisibility; private final JsonAutoDetect.Visibility defaultIsGetterVisibility; private final JsonAutoDetect.Visibility defaultSetterVisibility; private final JsonAutoDetect.Visibility defaultCreatorVisibility; 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 configurations = getAllConfigurations(); Builder allSupportedSerializationClassBuilder = ImmutableSet.builder(); Builder allSupportedDeserializationClassBuilder = ImmutableSet.builder(); List whitelist = new ArrayList(); JsonAutoDetect.Visibility fieldVisibility = JsonAutoDetect.Visibility.DEFAULT; JsonAutoDetect.Visibility getterVisibility = JsonAutoDetect.Visibility.DEFAULT; JsonAutoDetect.Visibility isGetterVisibility = JsonAutoDetect.Visibility.DEFAULT; JsonAutoDetect.Visibility setterVisibility = JsonAutoDetect.Visibility.DEFAULT; JsonAutoDetect.Visibility creatorVisibility = JsonAutoDetect.Visibility.DEFAULT; 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() ); fieldVisibility = configuration.getFieldVisibility(); getterVisibility = configuration.getGetterVisibility(); isGetterVisibility = configuration.getIsGetterVisibility(); setterVisibility = configuration.getSetterVisibility(); creatorVisibility = configuration.getCreatorVisibility(); } this.allSupportedSerializationClass = allSupportedSerializationClassBuilder.build(); this.allSupportedDeserializationClass = allSupportedDeserializationClassBuilder.build(); this.additionalSupportedTypes = new TypeFilter( logger, whitelist ); this.defaultFieldVisibility = fieldVisibility; this.defaultGetterVisibility = getterVisibility; this.defaultIsGetterVisibility = isGetterVisibility; this.defaultSetterVisibility = setterVisibility; this.defaultCreatorVisibility = creatorVisibility; } /** * @return the list of default configuration + user configurations */ private List getAllConfigurations() { ImmutableList.Builder 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 allSupportedSerializationClassBuilder, Builder allSupportedDeserializationClassBuilder ) { Map configuredMapper = mapperType.getMapperTypeConfiguration( configuration ); for ( Entry 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( mappedType, mapperClassType, mapperType.isSerializer() ); 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() && (!isSerializers || !typeOracle.isEnumSupertype( mappedType )) ) { 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() && null != method.getReturnType().isClassOrInterface() && classType.isAssignableTo( method.getReturnType().isClassOrInterface() ) && method.getParameters().length == nbParam && method.isPublic() ) { MapperType[] parameters = getParameters( mappedType, method, isSerializers ); if ( null == parameters ) { continue; } return new MapperInstance( classType, method, parameters ); } } // then we search the default constructor for ( JConstructor constructor : classType.getConstructors() ) { if ( constructor.isPublic() && constructor.getParameters().length == nbParam ) { MapperType[] parameters = getParameters( mappedType, constructor, isSerializers ); if ( null == parameters ) { continue; } return new MapperInstance( classType, constructor, parameters ); } } logger.log( Type.WARN, "Cannot instantiate the custom serializer/deserializer " + classType .getQualifiedSourceName() + ". It will be ignored" ); return null; } private MapperType[] getParameters( JType mappedType, JAbstractMethod method, boolean isSerializers ) { if ( !isSerializers && typeOracle.isEnumSupertype( mappedType ) ) { // For enums, the constructor requires the enum class. We just return an empty array and will handle it later if ( method.getParameters().length == 1 && Class.class.getName().equals( method.getParameters()[0].getType() .getQualifiedSourceName() ) ) { return new MapperType[0]; } else { // Not a valid method to create enum deserializer return null; } } 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( JType mappedType, JClassType classType, boolean isSerializers ) { int nbParam = 0; if ( !isSerializers && typeOracle.isEnumSupertype( mappedType ) ) { nbParam = 1; } // 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() && null != method.getReturnType().isClassOrInterface() && classType.isAssignableTo( method.getReturnType().isClassOrInterface() ) && method.getParameters().length == nbParam && method.isPublic() ) { MapperType[] parameters = getParameters( mappedType, method, isSerializers ); if ( null == parameters ) { continue; } return new MapperInstance( classType, method, parameters ); } } // then we search the default constructor for ( JConstructor constructor : classType.getConstructors() ) { if ( constructor.isPublic() && constructor.getParameters().length == nbParam ) { MapperType[] parameters = getParameters( mappedType, constructor, isSerializers ); if ( null == parameters ) { continue; } return new MapperInstance( classType, constructor, parameters ); } } 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 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 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 getSerializer( JType type ) { return Optional.fromNullable( serializers.get( type.getQualifiedSourceName() ) ); } /** * Return a {@link MapperInstance} instantiating the deserializer for the given type */ public Optional getDeserializer( JType type ) { return Optional.fromNullable( deserializers.get( type.getQualifiedSourceName() ) ); } /** * Return a {@link MapperInstance} instantiating the key serializer for the given type */ public Optional getKeySerializer( JType type ) { return Optional.fromNullable( keySerializers.get( type.getQualifiedSourceName() ) ); } /** * Return a {@link MapperInstance} instantiating the key deserializer for the given type */ public Optional getKeyDeserializer( JType type ) { return Optional.fromNullable( keyDeserializers.get( type.getQualifiedSourceName() ) ); } /** * Return the mixin type for the given type */ public Optional 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() ); } public Visibility getDefaultFieldVisibility() { return defaultFieldVisibility; } public Visibility getDefaultGetterVisibility() { return defaultGetterVisibility; } public Visibility getDefaultIsGetterVisibility() { return defaultIsGetterVisibility; } public Visibility getDefaultSetterVisibility() { return defaultSetterVisibility; } public Visibility getDefaultCreatorVisibility() { return defaultCreatorVisibility; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy