/*
* 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 );
}
}