#65 Some clean-up in BeanMappingMethod

This commit is contained in:
Gunnar Morling 2014-11-05 22:33:11 +01:00
parent 7041361b76
commit d0db16072a
2 changed files with 48 additions and 75 deletions

View File

@ -20,11 +20,12 @@ package org.mapstruct.ap.model;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
@ -45,11 +46,11 @@ import org.mapstruct.ap.util.MapperConfig;
import org.mapstruct.ap.util.Strings; import org.mapstruct.ap.util.Strings;
/** /**
* A {@link MappingMethod} implemented by a {@link Mapper} class which maps one * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one bean type to another, optionally
* bean sourceParameter to another, optionally configured by one or more * configured by one or more {@link PropertyMapping}s.
* {@link PropertyMapping}s.
* *
* @author Gunnar Morling * @author Gunnar Morling
* @author Sjaak Derksen
*/ */
public class BeanMappingMethod extends MappingMethod { public class BeanMappingMethod extends MappingMethod {
@ -62,11 +63,9 @@ public class BeanMappingMethod extends MappingMethod {
private MappingBuilderContext ctx; private MappingBuilderContext ctx;
private SourceMethod method; private SourceMethod method;
private Map<String, ExecutableElement> unprocessedTargetProperties;
private final Map<String, TargetProperty> remainingTargetProperties = new HashMap<String, TargetProperty>();
private final List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>(); private final List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
public Builder mappingContext(MappingBuilderContext mappingContext) { public Builder mappingContext(MappingBuilderContext mappingContext) {
this.ctx = mappingContext; this.ctx = mappingContext;
return this; return this;
@ -74,14 +73,11 @@ public class BeanMappingMethod extends MappingMethod {
public Builder souceMethod(SourceMethod sourceMethod) { public Builder souceMethod(SourceMethod sourceMethod) {
this.method = sourceMethod; this.method = sourceMethod;
this.unprocessedTargetProperties = initTargetPropertyAccessors();
return this; return this;
} }
public BeanMappingMethod build() { public BeanMappingMethod build() {
// init all non ignored targetAccessors
initTargetPropertyAccessors();
// map properties with mapping // map properties with mapping
boolean mappingErrorOccured = handleDefinedSourceMappings(); boolean mappingErrorOccured = handleDefinedSourceMappings();
if ( mappingErrorOccured ) { if ( mappingErrorOccured ) {
@ -92,8 +88,7 @@ public class BeanMappingMethod extends MappingMethod {
applyPropertyNameBasedMapping(); applyPropertyNameBasedMapping();
// report errors on unmapped properties // report errors on unmapped properties
reportErrorForUnmappedTargetPropertiesIfRequired( ); reportErrorForUnmappedTargetPropertiesIfRequired();
MethodReference factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx ); MethodReference factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx );
return new BeanMappingMethod( method, propertyMappings, factoryMethod ); return new BeanMappingMethod( method, propertyMappings, factoryMethod );
@ -102,8 +97,7 @@ public class BeanMappingMethod extends MappingMethod {
/** /**
* This method builds the list of target accessors. * This method builds the list of target accessors.
*/ */
private void initTargetPropertyAccessors() { private Map<String, ExecutableElement> initTargetPropertyAccessors() {
// fetch settings from element to implement // fetch settings from element to implement
CollectionMappingStrategy cmStrategy = getEffectiveCollectionMappingStrategy(); CollectionMappingStrategy cmStrategy = getEffectiveCollectionMappingStrategy();
@ -112,8 +106,9 @@ public class BeanMappingMethod extends MappingMethod {
candidates.addAll( method.getResultType().getSetters() ); candidates.addAll( method.getResultType().getSetters() );
candidates.addAll( method.getResultType().getAlternativeTargetAccessors() ); candidates.addAll( method.getResultType().getAlternativeTargetAccessors() );
for ( ExecutableElement candidate : candidates ) { Map<String, ExecutableElement> targetProperties = new HashMap<String, ExecutableElement>();
for ( ExecutableElement candidate : candidates ) {
String targetPropertyName = Executables.getPropertyName( candidate ); String targetPropertyName = Executables.getPropertyName( candidate );
// A target access is in general a setter method on the target object. However, in case of collections, // A target access is in general a setter method on the target object. However, in case of collections,
@ -143,23 +138,23 @@ public class BeanMappingMethod extends MappingMethod {
} }
} }
remainingTargetProperties.put( targetPropertyName, new TargetProperty(targetPropertyName, candidate ) ); targetProperties.put( targetPropertyName, candidate );
} }
return targetProperties;
} }
/** /**
* Iterates over all defined mapping methods (@Mappings, @Mapping), either direct or via * Iterates over all defined mapping methods ({@code @Mapping(s)}), either directly given or inherited from the
* * inverse mapping method.
* @InheritInverseConfiguration. * <p>
* * If a match is found between a defined source (constant, expression, ignore or source) the mapping is removed
* If a match is found between a defined source (constant, expression, ignore or source, the mapping is * from the remaining target properties.
* removed from the remain target properties. * <p>
* * It is furthermore checked whether the given mappings are correct. When an error occurs, the method continues
* This method should check if the mappings are defined correctly. When an error occurs, the method continues in * in search of more problems.
* search of more problems.
*/ */
private boolean handleDefinedSourceMappings() { private boolean handleDefinedSourceMappings() {
boolean errorOccurred = false; boolean errorOccurred = false;
Set<String> handledTargets = new HashSet<String>(); Set<String> handledTargets = new HashSet<String>();
@ -170,7 +165,7 @@ public class BeanMappingMethod extends MappingMethod {
PropertyMapping propertyMapping = null; PropertyMapping propertyMapping = null;
// fetch the target property // fetch the target property
TargetProperty targetProperty = remainingTargetProperties.get( mapping.getTargetName() ); ExecutableElement targetProperty = unprocessedTargetProperties.get( mapping.getTargetName() );
if ( targetProperty == null ) { if ( targetProperty == null ) {
ctx.getMessager().printMessage( ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR, Diagnostic.Kind.ERROR,
@ -184,7 +179,6 @@ public class BeanMappingMethod extends MappingMethod {
errorOccurred = true; errorOccurred = true;
} }
// check the mapping options // check the mapping options
// its an ignored property mapping // its an ignored property mapping
if ( mapping.isIgnored() ) { if ( mapping.isIgnored() ) {
@ -206,8 +200,8 @@ public class BeanMappingMethod extends MappingMethod {
propertyMapping = new PropertyMappingBuilder() propertyMapping = new PropertyMappingBuilder()
.mappingContext( ctx ) .mappingContext( ctx )
.souceMethod( method ) .souceMethod( method )
.targetAccessor( targetProperty.getAccessor() ) .targetAccessor( targetProperty )
.targetPropertyName( targetProperty.getName() ) .targetPropertyName( mapping.getTargetName() )
.sourceReference( sourceRef ) .sourceReference( sourceRef )
.qualifiers( mapping.getQualifiers() ) .qualifiers( mapping.getQualifiers() )
.dateFormat( mapping.getDateFormat() ) .dateFormat( mapping.getDateFormat() )
@ -218,7 +212,6 @@ public class BeanMappingMethod extends MappingMethod {
else { else {
errorOccurred = true; errorOccurred = true;
} }
} }
// its a constant // its a constant
@ -228,7 +221,7 @@ public class BeanMappingMethod extends MappingMethod {
.mappingContext( ctx ) .mappingContext( ctx )
.sourceMethod( method ) .sourceMethod( method )
.constantExpression( "\"" + mapping.getConstant() + "\"" ) .constantExpression( "\"" + mapping.getConstant() + "\"" )
.targetAccessor( targetProperty.getAccessor() ) .targetAccessor( targetProperty )
.dateFormat( mapping.getDateFormat() ) .dateFormat( mapping.getDateFormat() )
.qualifiers( mapping.getQualifiers() ) .qualifiers( mapping.getQualifiers() )
.build(); .build();
@ -242,7 +235,7 @@ public class BeanMappingMethod extends MappingMethod {
.mappingContext( ctx ) .mappingContext( ctx )
.souceMethod( method ) .souceMethod( method )
.javaExpression( mapping.getJavaExpression() ) .javaExpression( mapping.getJavaExpression() )
.targetAccessor( targetProperty.getAccessor() ) .targetAccessor( targetProperty )
.build(); .build();
handledTargets.add( mapping.getTargetName() ); handledTargets.add( mapping.getTargetName() );
} }
@ -256,25 +249,26 @@ public class BeanMappingMethod extends MappingMethod {
} }
for ( String handledTarget : handledTargets ) { for ( String handledTarget : handledTargets ) {
// In order to avoid: "Unknown property <> in return sourceParameter" in case of duplicate // In order to avoid: "Unknown property foo in return type" in case of duplicate
// target mappings // target mappings
remainingTargetProperties.remove( handledTarget ); unprocessedTargetProperties.remove( handledTarget );
} }
return errorOccurred; return errorOccurred;
} }
/** /**
* Iterates over all target properties and all source parameters. * Iterates over all target properties and all source parameters.
* * <p>
* When a property name match occurs, the remainder will be checked for duplicates. Matches will * When a property name match occurs, the remainder will be checked for duplicates. Matches will be removed from
* be removed from the set of remaining target properties. * the set of remaining target properties.
*/ */
private void applyPropertyNameBasedMapping() { private void applyPropertyNameBasedMapping() {
Iterator<Entry<String, ExecutableElement>> targetProperties =
unprocessedTargetProperties.entrySet().iterator();
Collection<TargetProperty> targetProperties = remainingTargetProperties.values(); while ( targetProperties.hasNext() ) {
for ( TargetProperty targetProperty : new ArrayList<TargetProperty>( targetProperties ) ) { Entry<String, ExecutableElement> targetProperty = targetProperties.next();
PropertyMapping propertyMapping = null; PropertyMapping propertyMapping = null;
if ( propertyMapping == null ) { if ( propertyMapping == null ) {
@ -283,7 +277,7 @@ public class BeanMappingMethod extends MappingMethod {
PropertyMapping newPropertyMapping = null; PropertyMapping newPropertyMapping = null;
for ( ExecutableElement sourceAccessor : sourceParameter.getType().getGetters() ) { for ( ExecutableElement sourceAccessor : sourceParameter.getType().getGetters() ) {
String sourcePropertyName = Executables.getPropertyName( sourceAccessor ); String sourcePropertyName = Executables.getPropertyName( sourceAccessor );
if ( sourcePropertyName.equals( targetProperty.getName() ) ) { if ( sourcePropertyName.equals( targetProperty.getKey() ) ) {
Mapping mapping = method.getSingleMappingByTargetPropertyName( sourcePropertyName ); Mapping mapping = method.getSingleMappingByTargetPropertyName( sourcePropertyName );
@ -297,8 +291,8 @@ public class BeanMappingMethod extends MappingMethod {
newPropertyMapping = new PropertyMappingBuilder() newPropertyMapping = new PropertyMappingBuilder()
.mappingContext( ctx ) .mappingContext( ctx )
.souceMethod( method ) .souceMethod( method )
.targetAccessor( targetProperty.getAccessor() ) .targetAccessor( targetProperty.getValue() )
.targetPropertyName( targetProperty.getName() ) .targetPropertyName( targetProperty.getKey() )
.sourceReference( sourceRef ) .sourceReference( sourceRef )
.qualifiers( mapping != null ? mapping.getQualifiers() : null ) .qualifiers( mapping != null ? mapping.getQualifiers() : null )
.dateFormat( mapping != null ? mapping.getDateFormat() : null ) .dateFormat( mapping != null ? mapping.getDateFormat() : null )
@ -312,7 +306,7 @@ public class BeanMappingMethod extends MappingMethod {
ctx.getMessager().printMessage( ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR, Diagnostic.Kind.ERROR,
"Several possible source properties for target property \"" "Several possible source properties for target property \""
+ targetProperty.getName() + targetProperty.getKey()
+ "\".", + "\".",
method.getExecutable() method.getExecutable()
); );
@ -326,7 +320,7 @@ public class BeanMappingMethod extends MappingMethod {
if ( propertyMapping != null ) { if ( propertyMapping != null ) {
propertyMappings.add( propertyMapping ); propertyMappings.add( propertyMapping );
remainingTargetProperties.remove( targetProperty.getName() ); targetProperties.remove();
} }
} }
} }
@ -337,9 +331,9 @@ public class BeanMappingMethod extends MappingMethod {
* be returned. If that is not set either, the default value from {@code Mapper#unmappedTargetPolicy()} will be * be returned. If that is not set either, the default value from {@code Mapper#unmappedTargetPolicy()} will be
* returned. * returned.
* *
* @param element The sourceParameter declaring the generated mapper sourceParameter * @param element The type declaring the generated mapper type
* *
* @return The effective policy for reporting unmapped getReturnType properties. * @return The effective policy for reporting unmapped target properties.
*/ */
private ReportingPolicy getEffectiveUnmappedTargetPolicy() { private ReportingPolicy getEffectiveUnmappedTargetPolicy() {
MapperConfig mapperSettings = MapperConfig.getInstanceOn( ctx.getMapperTypeElement() ); MapperConfig mapperSettings = MapperConfig.getInstanceOn( ctx.getMapperTypeElement() );
@ -367,14 +361,14 @@ public class BeanMappingMethod extends MappingMethod {
// fetch settings from element to implement // fetch settings from element to implement
ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy(); ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy();
if ( !remainingTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) { if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) {
ctx.getMessager().printMessage( ctx.getMessager().printMessage(
unmappedTargetPolicy.getDiagnosticKind(), unmappedTargetPolicy.getDiagnosticKind(),
MessageFormat.format( MessageFormat.format(
"Unmapped target {0,choice,1#property|1<properties}: \"{1}\"", "Unmapped target {0,choice,1#property|1<properties}: \"{1}\"",
remainingTargetProperties.size(), unprocessedTargetProperties.size(),
Strings.join( remainingTargetProperties.keySet(), ", " ) Strings.join( unprocessedTargetProperties.keySet(), ", " )
), ),
method.getExecutable() method.getExecutable()
); );
@ -388,7 +382,6 @@ public class BeanMappingMethod extends MappingMethod {
super( method ); super( method );
this.propertyMappings = propertyMappings; this.propertyMappings = propertyMappings;
// intialize constant mappings as all mappings, but take out the ones that can be contributed to a // intialize constant mappings as all mappings, but take out the ones that can be contributed to a
// parameter mapping. // parameter mapping.
this.mappingsByParameter = new HashMap<String, List<PropertyMapping>>(); this.mappingsByParameter = new HashMap<String, List<PropertyMapping>>();
@ -432,25 +425,4 @@ public class BeanMappingMethod extends MappingMethod {
public MethodReference getFactoryMethod() { public MethodReference getFactoryMethod() {
return this.factoryMethod; return this.factoryMethod;
} }
public static class TargetProperty {
private final String name;
private final ExecutableElement accessor;
public TargetProperty( String name, ExecutableElement accessor ) {
this.name = name;
this.accessor = accessor;
}
public String getName() {
return name;
}
public ExecutableElement getAccessor() {
return accessor;
}
}
} }

View File

@ -21,6 +21,7 @@ package org.mapstruct.ap.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.model.source.Method;
@ -30,7 +31,7 @@ import org.mapstruct.ap.util.Strings;
/** /**
* This method is used to convert the nested properties as listed in propertyEntries into a method * This method is used to convert the nested properties as listed in propertyEntries into a method
* that creates a mapping from the start of this list to the end of the list. * that creates a mapping from the start of this list to the end of the list.
* * <p>
* So, say that the start of the list is of TypeA and the end of the list is of TypeB than the forged method * So, say that the start of the list is of TypeA and the end of the list is of TypeB than the forged method
* will create a forged mapping method: TypeB methodName( TypeA in ). * will create a forged mapping method: TypeB methodName( TypeA in ).
* *