/* * Copyright 2014 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.property; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.github.nmorel.gwtjackson.rebind.RebindConfiguration; import com.github.nmorel.gwtjackson.rebind.bean.BeanInfo; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.TreeLogger.Type; 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.thirdparty.guava.common.base.Function; import com.google.gwt.thirdparty.guava.common.base.Optional; import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap; import com.google.gwt.thirdparty.guava.common.collect.Maps; /** * Utility class used to parse a bean looking for all its properties * * @author Nicolas Morel. * @version $Id: $ */ public final class PropertyParser { /** * <p>findPropertyAccessors</p> * * @param configuration a {@link com.github.nmorel.gwtjackson.rebind.RebindConfiguration} object. * @param logger a {@link com.google.gwt.core.ext.TreeLogger} object. * @param beanInfo a {@link com.github.nmorel.gwtjackson.rebind.bean.BeanInfo} object. * @return a {@link com.google.gwt.thirdparty.guava.common.collect.ImmutableMap} object. */ public static ImmutableMap<String, PropertyAccessors> findPropertyAccessors( final RebindConfiguration configuration, TreeLogger logger, BeanInfo beanInfo ) { Map<String, PropertyAccessorsBuilder> fieldsAndMethodsMap = new LinkedHashMap<String, PropertyAccessorsBuilder>(); parse( configuration, logger, beanInfo.getType(), fieldsAndMethodsMap, false ); Map<String, PropertyAccessorsBuilder> propertyAccessors = new LinkedHashMap<String, PropertyAccessorsBuilder>(); for ( PropertyAccessorsBuilder fieldAccessors : fieldsAndMethodsMap.values() ) { propertyAccessors.put( fieldAccessors.computePropertyName(), fieldAccessors ); } if ( !beanInfo.getCreatorParameters().isEmpty() ) { for ( Entry<String, JParameter> entry : beanInfo.getCreatorParameters().entrySet() ) { // If there is a constructor parameter referencing this property, we add it to the accessors PropertyAccessorsBuilder fieldAccessors = propertyAccessors.get( entry.getKey() ); if ( null == fieldAccessors ) { fieldAccessors = new PropertyAccessorsBuilder( entry.getKey() ); fieldAccessors.setParameter( entry.getValue() ); propertyAccessors.put( fieldAccessors.computePropertyName(), fieldAccessors ); } else { fieldAccessors.setParameter( entry.getValue() ); } } } return ImmutableMap.copyOf( Maps.transformValues( propertyAccessors, new Function<PropertyAccessorsBuilder, PropertyAccessors>() { @Override public PropertyAccessors apply( PropertyAccessorsBuilder propertyAccessorsBuilder ) { return null == propertyAccessorsBuilder ? null : propertyAccessorsBuilder.build(); } } ) ); } private static void parse( RebindConfiguration configuration, TreeLogger logger, JClassType type, Map<String, PropertyAccessorsBuilder> propertiesMap, boolean mixin ) { if ( null == type ) { return; } if ( !mixin ) { Optional<JClassType> mixinAnnotation = configuration.getMixInAnnotations( type ); if ( mixinAnnotation.isPresent() ) { parse( configuration, logger, mixinAnnotation.get(), propertiesMap, true ); } } parseFields( logger, type, propertiesMap, mixin ); parseMethods( logger, type, propertiesMap, mixin ); for ( JClassType interf : type.getImplementedInterfaces() ) { parse( configuration, logger, interf, propertiesMap, mixin ); } parse( configuration, logger, type.getSuperclass(), propertiesMap, mixin ); } private static void parseFields( TreeLogger logger, JClassType type, Map<String, PropertyAccessorsBuilder> propertiesMap, boolean mixin ) { if ( type.getQualifiedSourceName().equals( "java.lang.Object" ) ) { return; } for ( JField field : type.getFields() ) { if ( field.isStatic() ) { continue; } String fieldName = field.getName(); PropertyAccessorsBuilder property = propertiesMap.get( fieldName ); if ( null == property ) { property = new PropertyAccessorsBuilder( fieldName ); propertiesMap.put( fieldName, property ); } if ( property.getField().isPresent() && !mixin ) { // we found an other field with the same name on a superclass. we ignore it logger.log( Type.INFO, "A field with the same name as '" + field .getName() + "' has already been found on child class" ); } else { property.addField( field, mixin ); } } } private static void parseMethods( TreeLogger logger, JClassType type, Map<String, PropertyAccessorsBuilder> propertiesMap, boolean mixin ) { for ( JMethod method : type.getMethods() ) { if ( null != method.isConstructor() || method.isStatic() || (type.getQualifiedSourceName() .equals( "java.lang.Object" ) && method.getName().equals( "getClass" )) ) { continue; } if ( method.getParameters().length == 0 ) { // might be a getter String fieldName = extractFieldNameFromGetterSetterMethodName( method.getName() ); PropertyAccessorsBuilder property = propertiesMap.get( fieldName ); if ( null == property ) { property = new PropertyAccessorsBuilder( fieldName ); propertiesMap.put( fieldName, property ); } property.addGetter( method, mixin ); } else if ( method.getParameters().length == 1 || (method.getParameters().length == 2 && method.isAnnotationPresent( JsonAnySetter.class )) ) { // might be a setter String fieldName = extractFieldNameFromGetterSetterMethodName( method.getName() ); PropertyAccessorsBuilder property = propertiesMap.get( fieldName ); if ( null == property ) { property = new PropertyAccessorsBuilder( fieldName ); propertiesMap.put( fieldName, property ); } property.addSetter( method, mixin ); } } } private static String extractFieldNameFromGetterSetterMethodName( String methodName ) { String fieldName; if ( methodName.startsWith( "is" ) && methodName.length() > 2 ) { fieldName = methodName.substring( 2 ); } else if ( (methodName.startsWith( "get" ) || methodName.startsWith( "set" )) && methodName.length() > 3 ) { fieldName = methodName.substring( 3 ); } else { fieldName = methodName; } int index = 0; while ( Character.isUpperCase( fieldName.charAt( index++ ) ) && index < fieldName.length() ) { } return fieldName.substring( 0, index ).toLowerCase() + fieldName.substring( index ); } }