/** * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) * and/or other contributors as indicated by the @authors tag. See the * copyright.txt file in the distribution for a full listing of all * contributors. * * 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 org.mapstruct.ap.internal.model; import static org.mapstruct.ap.internal.util.Collections.first; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.lang.model.type.DeclaredType; import javax.tools.Diagnostic; import org.mapstruct.ap.internal.model.PropertyMapping.ConstantMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.JavaExpressionMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.PropertyMappingBuilder; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder; import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.ForgedMethodHistory; import org.mapstruct.ap.internal.model.source.Mapping; import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.PropertyEntry; import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SourceMethod; import org.mapstruct.ap.internal.model.source.SourceReference; import org.mapstruct.ap.internal.model.source.TargetReference; import org.mapstruct.ap.internal.prism.BeanMappingPrism; import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.prism.ReportingPolicyPrism; import org.mapstruct.ap.internal.util.MapperConfiguration; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one bean type to another, optionally * configured by one or more {@link PropertyMapping}s. * * @author Gunnar Morling */ public class BeanMappingMethod extends NormalTypeMappingMethod { private final List<PropertyMapping> propertyMappings; private final Map<String, List<PropertyMapping>> mappingsByParameter; private final List<PropertyMapping> constantMappings; private final Type resultType; public static class Builder { private MappingBuilderContext ctx; private Method method; private Map<String, Accessor> unprocessedTargetProperties; private Set<String> targetProperties; private final List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>(); private final Set<Parameter> unprocessedSourceParameters = new HashSet<Parameter>(); private NullValueMappingStrategyPrism nullValueMappingStrategy; private SelectionParameters selectionParameters; private final Set<String> existingVariableNames = new HashSet<String>(); private Map<String, List<Mapping>> methodMappings; private SingleMappingByTargetPropertyNameFunction singleMapping; private final Map<String, List<Mapping>> unprocessedDefinedTargets = new HashMap<String, List<Mapping>>(); public Builder mappingContext(MappingBuilderContext mappingContext) { this.ctx = mappingContext; return this; } public Builder souceMethod(SourceMethod sourceMethod) { singleMapping = new SourceMethodSingleMapping( sourceMethod ); return setupMethodWithMapping( sourceMethod ); } public Builder forgedMethod(Method method ) { singleMapping = new EmptySingleMapping(); return setupMethodWithMapping( method ); } private Builder setupMethodWithMapping(Method sourceMethod) { this.method = sourceMethod; this.methodMappings = sourceMethod.getMappingOptions().getMappings(); CollectionMappingStrategyPrism cms = sourceMethod.getMapperConfiguration().getCollectionMappingStrategy(); Map<String, Accessor> accessors = method.getResultType().getPropertyWriteAccessors( cms ); this.targetProperties = accessors.keySet(); this.unprocessedTargetProperties = new LinkedHashMap<String, Accessor>( accessors ); for ( Parameter sourceParameter : method.getSourceParameters() ) { unprocessedSourceParameters.add( sourceParameter ); } existingVariableNames.addAll( method.getParameterNames() ); return this; } public Builder selectionParameters(SelectionParameters selectionParameters) { this.selectionParameters = selectionParameters; return this; } public Builder nullValueMappingStrategy(NullValueMappingStrategyPrism nullValueMappingStrategy) { this.nullValueMappingStrategy = nullValueMappingStrategy; return this; } public BeanMappingMethod build() { // map properties with mapping boolean mappingErrorOccured = handleDefinedMappings(); if ( mappingErrorOccured ) { return null; } if ( !method.getMappingOptions().isRestrictToDefinedMappings() ) { // map properties without a mapping applyPropertyNameBasedMapping(); // map parameters without a mapping applyParameterNameBasedMapping(); } // Process the unprocessed defined targets handleUnprocessedDefinedTargets(); // report errors on unmapped properties reportErrorForUnmappedTargetPropertiesIfRequired(); // mapNullToDefault boolean mapNullToDefault = method.getMapperConfiguration().isMapToDefault( nullValueMappingStrategy ); BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() ); MethodReference factoryMethod = null; if ( !method.isUpdateMethod() ) { factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType(), selectionParameters ); } // if there's no factory method, try the resultType in the @BeanMapping Type resultType = null; if ( factoryMethod == null ) { if ( selectionParameters != null && selectionParameters.getResultType() != null ) { resultType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ); if ( resultType.isAbstract() ) { ctx.getMessager().printMessage( method.getExecutable(), beanMappingPrism.mirror, Message.BEANMAPPING_ABSTRACT, resultType, method.getResultType() ); } else if ( !resultType.isAssignableTo( method.getResultType() ) ) { ctx.getMessager().printMessage( method.getExecutable(), beanMappingPrism.mirror, Message.BEANMAPPING_NOT_ASSIGNABLE, resultType, method.getResultType() ); } } else if ( !method.isUpdateMethod() && method.getReturnType().isAbstract() ) { ctx.getMessager().printMessage( method.getExecutable(), Message.GENERAL_ABSTRACT_RETURN_TYPE, method.getReturnType() ); } } sortPropertyMappingsByDependencies(); List<LifecycleCallbackMethodReference> beforeMappingMethods = LifecycleCallbackFactory.beforeMappingMethods( method, selectionParameters, ctx, existingVariableNames ); List<LifecycleCallbackMethodReference> afterMappingMethods = LifecycleCallbackFactory.afterMappingMethods( method, selectionParameters, ctx, existingVariableNames ); return new BeanMappingMethod( method, existingVariableNames, propertyMappings, factoryMethod, mapNullToDefault, resultType, beforeMappingMethods, afterMappingMethods ); } /** * If there were nested defined targets that have not been handled. Then we need to process them at the end. */ private void handleUnprocessedDefinedTargets() { Iterator<Entry<String, List<Mapping>>> iterator = unprocessedDefinedTargets.entrySet().iterator(); // For each of the unprocessed defined targets forge a mapping for each of the // method source parameters. The generated mappings are not going to use forged name based mappings. while ( iterator.hasNext() ) { Entry<String, List<Mapping>> entry = iterator.next(); String propertyName = entry.getKey(); if ( !unprocessedTargetProperties.containsKey( propertyName ) ) { continue; } List<Parameter> sourceParameters = method.getSourceParameters(); boolean forceUpdateMethod = sourceParameters.size() > 1; for ( Parameter sourceParameter : sourceParameters ) { SourceReference reference = new SourceReference.BuilderFromProperty() .sourceParameter( sourceParameter ) .name( propertyName ) .build(); MappingOptions mappingOptions = extractAdditionalOptions( propertyName, true ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .targetWriteAccessor( unprocessedTargetProperties.get( propertyName ) ) .targetReadAccessor( getTargetPropertyReadAccessor( propertyName ) ) .targetPropertyName( propertyName ) .sourceReference( reference ) .existingVariableNames( existingVariableNames ) .dependsOn( mappingOptions.collectNestedDependsOn() ) .forgeMethodWithMappingOptions( mappingOptions ) .forceUpdateMethod( forceUpdateMethod ) .forgedNamedBased( false ) .build(); if ( propertyMapping != null ) { unprocessedTargetProperties.remove( propertyName ); iterator.remove(); propertyMappings.add( propertyMapping ); } } } } /** * Sources the given mappings as per the dependency relationships given via {@code dependsOn()}. If a cycle is * detected, an error is reported. */ private void sortPropertyMappingsByDependencies() { GraphAnalyzerBuilder graphAnalyzerBuilder = GraphAnalyzer.builder(); for ( PropertyMapping propertyMapping : propertyMappings ) { graphAnalyzerBuilder.withNode( propertyMapping.getName(), propertyMapping.getDependsOn() ); } final GraphAnalyzer graphAnalyzer = graphAnalyzerBuilder.build(); if ( !graphAnalyzer.getCycles().isEmpty() ) { Set<String> cycles = new HashSet<String>(); for ( List<String> cycle : graphAnalyzer.getCycles() ) { cycles.add( Strings.join( cycle, " -> " ) ); } ctx.getMessager().printMessage( method.getExecutable(), Message.BEANMAPPING_CYCLE_BETWEEN_PROPERTIES, Strings.join( cycles, ", " ) ); } else { Collections.sort( propertyMappings, new Comparator<PropertyMapping>() { @Override public int compare(PropertyMapping o1, PropertyMapping o2) { return graphAnalyzer.getTraversalSequence( o1.getName() ) - graphAnalyzer.getTraversalSequence( o2.getName() ); } } ); } } /** * Iterates over all defined mapping methods ({@code @Mapping(s)}), either directly given or inherited from the * inverse mapping method. * <p> * If a match is found between a defined source (constant, expression, ignore or source) the mapping is removed * from the remaining target properties. * <p> * It is furthermore checked whether the given mappings are correct. When an error occurs, the method continues * in search of more problems. */ private boolean handleDefinedMappings() { boolean errorOccurred = false; Set<String> handledTargets = new HashSet<String>(); // first we have to handle nested target mappings if ( method.getMappingOptions().hasNestedTargetReferences() ) { errorOccurred = handleDefinedNestedTargetMapping( handledTargets ); } for ( Map.Entry<String, List<Mapping>> entry : methodMappings.entrySet() ) { for ( Mapping mapping : entry.getValue() ) { TargetReference targetReference = mapping.getTargetReference(); if ( targetReference.isValid() ) { if ( !handledTargets.contains( first( targetReference.getPropertyEntries() ).getFullName() ) ) { if ( handleDefinedMapping( mapping, handledTargets ) ) { errorOccurred = true; } } } else { errorOccurred = true; } } } for ( String handledTarget : handledTargets ) { // In order to avoid: "Unknown property foo in return type" in case of duplicate // target mappings unprocessedTargetProperties.remove( handledTarget ); unprocessedDefinedTargets.remove( handledTarget ); } return errorOccurred; } private boolean handleDefinedNestedTargetMapping(Set<String> handledTargets) { NestedTargetPropertyMappingHolder holder = new NestedTargetPropertyMappingHolder.Builder() .mappingContext( ctx ) .method( method ) .existingVariableNames( existingVariableNames ) .build(); unprocessedSourceParameters.removeAll( holder.getProcessedSourceParameters() ); propertyMappings.addAll( holder.getPropertyMappings() ); handledTargets.addAll( holder.getHandledTargets() ); // Store all the unprocessed defined targets. for ( Entry<PropertyEntry, List<Mapping>> entry : holder.getUnprocessedDefinedTarget().entrySet() ) { if ( entry.getValue().isEmpty() ) { continue; } unprocessedDefinedTargets.put( entry.getKey().getName(), entry.getValue() ); } return holder.hasErrorOccurred(); } private boolean handleDefinedMapping(Mapping mapping, Set<String> handledTargets) { boolean errorOccured = false; PropertyMapping propertyMapping = null; TargetReference targetRef = mapping.getTargetReference(); PropertyEntry targetProperty = first( targetRef.getPropertyEntries() ); String propertyName = targetProperty.getName(); // unknown properties given via dependsOn()? for ( String dependency : mapping.getDependsOn() ) { if ( !targetProperties.contains( dependency ) ) { ctx.getMessager().printMessage( method.getExecutable(), mapping.getMirror(), mapping.getDependsOnAnnotationValue(), Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON, dependency ); errorOccured = true; } } // check the mapping options // its an ignored property mapping if ( mapping.isIgnored() ) { propertyMapping = null; handledTargets.add( mapping.getTargetName() ); } // its a plain-old property mapping else if ( mapping.getSourceName() != null ) { // determine source parameter SourceReference sourceRef = mapping.getSourceReference(); if ( sourceRef.isValid() ) { // targetProperty == null can occur: we arrived here because we want as many errors // as possible before we stop analysing propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .targetProperty( targetProperty ) .targetPropertyName( mapping.getTargetName() ) .sourceReference( sourceRef ) .selectionParameters( mapping.getSelectionParameters() ) .formattingParameters( mapping.getFormattingParameters() ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping.getDependsOn() ) .defaultValue( mapping.getDefaultValue() ) .build(); handledTargets.add( propertyName ); unprocessedSourceParameters.remove( sourceRef.getParameter() ); } else { errorOccured = true; } } // its a constant // if we have an unprocessed target that means that it most probably is nested and we should // not generated any mapping for it now. Eventually it will be done though else if ( mapping.getConstant() != null && !unprocessedDefinedTargets.containsKey( propertyName ) ) { propertyMapping = new ConstantMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .constantExpression( "\"" + mapping.getConstant() + "\"" ) .targetProperty( targetProperty ) .targetPropertyName( mapping.getTargetName() ) .formattingParameters( mapping.getFormattingParameters() ) .selectionParameters( mapping.getSelectionParameters() ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping.getDependsOn() ) .build(); handledTargets.add( mapping.getTargetName() ); } // its an expression // if we have an unprocessed target that means that it most probably is nested and we should // not generated any mapping for it now. Eventually it will be done though else if ( mapping.getJavaExpression() != null && !unprocessedDefinedTargets.containsKey( propertyName ) ) { propertyMapping = new JavaExpressionMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .javaExpression( mapping.getJavaExpression() ) .existingVariableNames( existingVariableNames ) .targetProperty( targetProperty ) .targetPropertyName( mapping.getTargetName() ) .dependsOn( mapping.getDependsOn() ) .build(); handledTargets.add( mapping.getTargetName() ); } // remaining are the mappings without a 'source' so, 'only' a date format or qualifiers if ( propertyMapping != null ) { propertyMappings.add( propertyMapping ); } return errorOccured; } /** * 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 be removed from * the set of remaining target properties. */ private void applyPropertyNameBasedMapping() { Iterator<Entry<String, Accessor>> targetPropertyEntriesIterator = unprocessedTargetProperties.entrySet().iterator(); while ( targetPropertyEntriesIterator.hasNext() ) { Entry<String, Accessor> targetProperty = targetPropertyEntriesIterator.next(); String targetPropertyName = targetProperty.getKey(); PropertyMapping propertyMapping = null; if ( propertyMapping == null ) { for ( Parameter sourceParameter : method.getSourceParameters() ) { Type sourceType = sourceParameter.getType(); if ( sourceType.isPrimitive() ) { continue; } PropertyMapping newPropertyMapping = null; Accessor sourceReadAccessor = sourceParameter.getType().getPropertyReadAccessors().get( targetPropertyName ); ExecutableElementAccessor sourcePresenceChecker = sourceParameter.getType().getPropertyPresenceCheckers().get( targetPropertyName ); if ( sourceReadAccessor != null ) { Mapping mapping = singleMapping.getSingleMappingByTargetPropertyName( targetProperty.getKey() ); DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); SourceReference sourceRef = new SourceReference.BuilderFromProperty() .sourceParameter( sourceParameter ) .type( ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ) ) .readAccessor( sourceReadAccessor ) .presenceChecker( sourcePresenceChecker ) .name( targetProperty.getKey() ) .build(); newPropertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .targetWriteAccessor( targetProperty.getValue() ) .targetReadAccessor( getTargetPropertyReadAccessor( targetPropertyName ) ) .targetPropertyName( targetPropertyName ) .sourceReference( sourceRef ) .formattingParameters( mapping != null ? mapping.getFormattingParameters() : null ) .selectionParameters( mapping != null ? mapping.getSelectionParameters() : null ) .defaultValue( mapping != null ? mapping.getDefaultValue() : null ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.<String>emptyList() ) .forgeMethodWithMappingOptions( extractAdditionalOptions( targetPropertyName, false ) ) .build(); unprocessedSourceParameters.remove( sourceParameter ); } if ( propertyMapping != null && newPropertyMapping != null ) { // TODO improve error message ctx.getMessager().printMessage( method.getExecutable(), Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES, targetPropertyName ); break; } else if ( newPropertyMapping != null ) { propertyMapping = newPropertyMapping; } } } if ( propertyMapping != null ) { propertyMappings.add( propertyMapping ); targetPropertyEntriesIterator.remove(); unprocessedDefinedTargets.remove( targetPropertyName ); } } } private void applyParameterNameBasedMapping() { Iterator<Entry<String, Accessor>> targetPropertyEntriesIterator = unprocessedTargetProperties.entrySet().iterator(); while ( targetPropertyEntriesIterator.hasNext() ) { Entry<String, Accessor> targetProperty = targetPropertyEntriesIterator.next(); Iterator<Parameter> sourceParameters = unprocessedSourceParameters.iterator(); while ( sourceParameters.hasNext() ) { Parameter sourceParameter = sourceParameters.next(); if ( sourceParameter.getName().equals( targetProperty.getKey() ) ) { Mapping mapping = singleMapping.getSingleMappingByTargetPropertyName( targetProperty.getKey() ); SourceReference sourceRef = new SourceReference.BuilderFromProperty() .sourceParameter( sourceParameter ) .name( targetProperty.getKey() ) .build(); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .targetWriteAccessor( targetProperty.getValue() ) .targetReadAccessor( getTargetPropertyReadAccessor( targetProperty.getKey() ) ) .targetPropertyName( targetProperty.getKey() ) .sourceReference( sourceRef ) .formattingParameters( mapping != null ? mapping.getFormattingParameters() : null ) .selectionParameters( mapping != null ? mapping.getSelectionParameters() : null ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.<String>emptyList() ) .forgeMethodWithMappingOptions( extractAdditionalOptions( targetProperty.getKey(), false ) ) .build(); propertyMappings.add( propertyMapping ); targetPropertyEntriesIterator.remove(); sourceParameters.remove(); unprocessedDefinedTargets.remove( targetProperty.getKey() ); } } } } private MappingOptions extractAdditionalOptions(String targetProperty, boolean restrictToDefinedMappings) { MappingOptions additionalOptions = null; if ( unprocessedDefinedTargets.containsKey( targetProperty ) ) { Map<String, List<Mapping>> mappings = new HashMap<String, List<Mapping>>(); mappings.put( targetProperty, unprocessedDefinedTargets.get( targetProperty ) ); additionalOptions = MappingOptions.forMappingsOnly( mappings, restrictToDefinedMappings ); } return additionalOptions; } private Accessor getTargetPropertyReadAccessor(String propertyName) { return method.getResultType().getPropertyReadAccessors().get( propertyName ); } private ReportingPolicyPrism getUnmappedTargetPolicy() { MappingOptions mappingOptions = method.getMappingOptions(); if ( mappingOptions.getBeanMapping() != null && mappingOptions.getBeanMapping().getReportingPolicy() != null ) { return mappingOptions.getBeanMapping().getReportingPolicy(); } MapperConfiguration mapperSettings = MapperConfiguration.getInstanceOn( ctx.getMapperTypeElement() ); return mapperSettings.unmappedTargetPolicy( ctx.getOptions() ); } private void reportErrorForUnmappedTargetPropertiesIfRequired() { // fetch settings from element to implement ReportingPolicyPrism unmappedTargetPolicy = getUnmappedTargetPolicy(); if ( method instanceof ForgedMethod && targetProperties.isEmpty() ) { //TODO until we solve 1140 we report this error when the target properties are empty ForgedMethod forgedMethod = (ForgedMethod) method; if ( forgedMethod.getHistory() == null ) { Type sourceType = this.method.getParameters().get( 0 ).getType(); Type targetType = this.method.getReturnType(); ctx.getMessager().printMessage( this.method.getExecutable(), Message.PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND, sourceType, targetType, targetType, sourceType ); } else { ForgedMethodHistory history = forgedMethod.getHistory(); ctx.getMessager().printMessage( this.method.getExecutable(), Message.PROPERTYMAPPING_MAPPING_NOT_FOUND, history.createSourcePropertyErrorMessage(), history.getTargetType(), history.createTargetPropertyName(), history.getTargetType(), history.getSourceType() ); } } else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) { Message msg = unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ? Message.BEANMAPPING_UNMAPPED_TARGETS_ERROR : Message.BEANMAPPING_UNMAPPED_TARGETS_WARNING; Object[] args = new Object[] { MessageFormat.format( "{0,choice,1#property|1<properties}: \"{1}\"", unprocessedTargetProperties.size(), Strings.join( unprocessedTargetProperties.keySet(), ", " ) ) }; if ( method instanceof ForgedMethod ) { msg = unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ? Message.BEANMAPPING_UNMAPPED_FORGED_TARGETS_ERROR : Message.BEANMAPPING_UNMAPPED_FORGED_TARGETS_WARNING; String sourceErrorMessage = method.getParameters().get( 0 ).getType().toString(); String targetErrorMessage = method.getReturnType().toString(); if ( ( (ForgedMethod) method ).getHistory() != null ) { ForgedMethodHistory history = ( (ForgedMethod) method ).getHistory(); sourceErrorMessage = history.createSourcePropertyErrorMessage(); targetErrorMessage = MessageFormat.format( "\"{0} {1}\"", history.getTargetType(), history.createTargetPropertyName() ); } args = new Object[] { args[0], sourceErrorMessage, targetErrorMessage }; } ctx.getMessager().printMessage( method.getExecutable(), msg, args ); } } } private BeanMappingMethod(Method method, Collection<String> existingVariableNames, List<PropertyMapping> propertyMappings, MethodReference factoryMethod, boolean mapNullToDefault, Type resultType, List<LifecycleCallbackMethodReference> beforeMappingReferences, List<LifecycleCallbackMethodReference> afterMappingReferences) { super( method, existingVariableNames, factoryMethod, mapNullToDefault, beforeMappingReferences, afterMappingReferences ); this.propertyMappings = propertyMappings; // intialize constant mappings as all mappings, but take out the ones that can be contributed to a // parameter mapping. this.mappingsByParameter = new HashMap<String, List<PropertyMapping>>(); this.constantMappings = new ArrayList<PropertyMapping>( propertyMappings ); for ( Parameter sourceParameter : getSourceParameters() ) { ArrayList<PropertyMapping> mappingsOfParameter = new ArrayList<PropertyMapping>(); mappingsByParameter.put( sourceParameter.getName(), mappingsOfParameter ); for ( PropertyMapping mapping : propertyMappings ) { if ( sourceParameter.getName().equals( mapping.getSourceBeanName() ) ) { mappingsOfParameter.add( mapping ); constantMappings.remove( mapping ); } } } this.resultType = resultType; } public List<PropertyMapping> getPropertyMappings() { return propertyMappings; } public List<PropertyMapping> getConstantMappings() { return constantMappings; } public Map<String, List<PropertyMapping>> getPropertyMappingsByParameter() { return mappingsByParameter; } @Override public Type getResultType() { if ( resultType == null ) { return super.getResultType(); } else { return resultType; } } @Override public Set<Type> getImportTypes() { Set<Type> types = super.getImportTypes(); for ( PropertyMapping propertyMapping : propertyMappings ) { types.addAll( propertyMapping.getImportTypes() ); } return types; } public List<Parameter> getSourceParametersExcludingPrimitives() { List<Parameter> sourceParameters = new ArrayList<Parameter>(); for ( Parameter sourceParam : getSourceParameters() ) { if ( !sourceParam.getType().isPrimitive() ) { sourceParameters.add( sourceParam ); } } return sourceParameters; } public List<Parameter> getSourcePrimitiveParameters() { List<Parameter> sourceParameters = new ArrayList<Parameter>(); for ( Parameter sourceParam : getSourceParameters() ) { if ( sourceParam.getType().isPrimitive() ) { sourceParameters.add( sourceParam ); } } return sourceParameters; } @Override public int hashCode() { //Needed for Checkstyle, otherwise it fails due to EqualsHashCode rule return super.hashCode(); } @Override public boolean equals(Object obj) { if ( this == obj ) { return true; } if ( obj == null || getClass() != obj.getClass() ) { return false; } BeanMappingMethod that = (BeanMappingMethod) obj; if ( !super.equals( obj ) ) { return false; } return propertyMappings != null ? propertyMappings.equals( that.propertyMappings ) : that.propertyMappings == null; } private interface SingleMappingByTargetPropertyNameFunction { Mapping getSingleMappingByTargetPropertyName(String targetPropertyName); } private static class EmptySingleMapping implements SingleMappingByTargetPropertyNameFunction { @Override public Mapping getSingleMappingByTargetPropertyName(String targetPropertyName) { return null; } } private static class SourceMethodSingleMapping implements SingleMappingByTargetPropertyNameFunction { private final SourceMethod sourceMethod; private SourceMethodSingleMapping(SourceMethod sourceMethod) { this.sourceMethod = sourceMethod; } @Override public Mapping getSingleMappingByTargetPropertyName(String targetPropertyName) { return sourceMethod.getSingleMappingByTargetPropertyName( targetPropertyName ); } } }