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

org.mapstruct.ap.internal.model.BeanMappingMethod Maven / Gradle / Ivy

There is a newer version: 1.6.3
Show newest version
/*
 * Copyright MapStruct Authors.
 *
 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
 */
package org.mapstruct.ap.internal.model;

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.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;

import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem;
import org.mapstruct.ap.internal.gem.ReportingPolicyGem;
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.beanmapping.MappingReference;
import org.mapstruct.ap.internal.model.beanmapping.MappingReferences;
import org.mapstruct.ap.internal.model.beanmapping.SourceReference;
import org.mapstruct.ap.internal.model.beanmapping.TargetReference;
import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.FormattingParameters;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.ParameterBinding;
import org.mapstruct.ap.internal.model.common.SourceRHS;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer;
import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder;
import org.mapstruct.ap.internal.model.source.BeanMappingOptions;
import org.mapstruct.ap.internal.model.source.MappingOptions;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.model.source.SubclassMappingOptions;
import org.mapstruct.ap.internal.model.source.selector.SelectedMethod;
import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
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.AccessorType;
import org.mapstruct.ap.internal.util.accessor.ParameterElementAccessor;
import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor;
import org.mapstruct.ap.internal.util.accessor.ReadAccessor;

import static org.mapstruct.ap.internal.model.beanmapping.MappingReferences.forSourceMethod;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Message.BEANMAPPING_ABSTRACT;
import static org.mapstruct.ap.internal.util.Message.BEANMAPPING_NOT_ASSIGNABLE;
import static org.mapstruct.ap.internal.util.Message.GENERAL_ABSTRACT_RETURN_TYPE;
import static org.mapstruct.ap.internal.util.Message.GENERAL_AMBIGUOUS_CONSTRUCTORS;
import static org.mapstruct.ap.internal.util.Message.GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS;
import static org.mapstruct.ap.internal.util.Message.PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET;
import static org.mapstruct.ap.internal.util.Message.PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PROPERTY_FROM_TARGET;

/**
 * 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 propertyMappings;
    private final Map> mappingsByParameter;
    private final Map> constructorMappingsByParameter;
    private final List constantMappings;
    private final List constructorConstantMappings;
    private final List subclassMappings;
    private final Type returnTypeToConstruct;
    private final BuilderType returnTypeBuilder;
    private final MethodReference finalizerMethod;

    private final MappingReferences mappingReferences;

    public static class Builder extends AbstractMappingMethodBuilder {

        private Type userDefinedReturnType;

        /* returnType to construct can have a builder */
        private BuilderType returnTypeBuilder;
        private Map unprocessedConstructorProperties;
        private Map unprocessedTargetProperties;
        private Map unprocessedSourceProperties;
        private Set missingIgnoredSourceProperties;
        private Set targetProperties;
        private final List propertyMappings = new ArrayList<>();
        private final Set unprocessedSourceParameters = new HashSet<>();
        private final Set existingVariableNames = new HashSet<>();
        private final Map> unprocessedDefinedTargets = new LinkedHashMap<>();

        private MappingReferences mappingReferences;
        private MethodReference factoryMethod;
        private boolean hasFactoryMethod;

        public Builder() {
            super( Builder.class );
        }

        @Override
        protected boolean shouldUsePropertyNamesInHistory() {
            return true;
        }

        public Builder userDefinedReturnType(Type userDefinedReturnType) {
            this.userDefinedReturnType = userDefinedReturnType;
            return this;
        }

        public Builder returnTypeBuilder( BuilderType returnTypeBuilder ) {
            this.returnTypeBuilder = returnTypeBuilder;
            return this;
        }

        public Builder sourceMethod(SourceMethod sourceMethod) {
            method( sourceMethod );
            return this;
        }

        public Builder forgedMethod(ForgedMethod forgedMethod) {
            method( forgedMethod );
            mappingReferences = forgedMethod.getMappingReferences();
            Parameter sourceParameter = first( Parameter.getSourceParameters( forgedMethod.getParameters() ) );
            for ( MappingReference mappingReference: mappingReferences.getMappingReferences() ) {
                SourceReference sourceReference = mappingReference.getSourceReference();
                if ( sourceReference != null ) {
                    mappingReference.setSourceReference( new SourceReference.BuilderFromSourceReference()
                        .sourceParameter( sourceParameter )
                        .sourceReference( sourceReference )
                        .build() );
                }
            }
            return this;
        }

        @Override
        public BeanMappingMethod build() {

            BeanMappingOptions beanMapping = method.getOptions().getBeanMapping();
            SelectionParameters selectionParameters = beanMapping != null ? beanMapping.getSelectionParameters() : null;

            /* the return type that needs to be constructed (new or factorized), so for instance: */
            /*  1) the return type of a non-update method */
            /*  2) or the implementation type that needs to be used when the return type is abstract */
            /*  3) or the builder whenever the return type is immutable */
            Type returnTypeToConstruct = null;

            // determine which return type to construct
            boolean cannotConstructReturnType = false;
            if ( !method.getReturnType().isVoid() ) {
                Type returnTypeImpl = null;
                if ( isBuilderRequired() ) {
                    // the userDefinedReturn type can also require a builder. That buildertype is already set
                    returnTypeImpl = returnTypeBuilder.getBuilder();
                    initializeFactoryMethod( returnTypeImpl, selectionParameters );
                    if ( factoryMethod != null
                        || allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed( returnTypeImpl )
                        || doesNotAllowAbstractReturnTypeAndCanBeConstructed( returnTypeImpl ) ) {
                        returnTypeToConstruct = returnTypeImpl;
                    }
                    else {
                        cannotConstructReturnType = true;
                    }
                }
                else if ( userDefinedReturnType != null ) {
                    returnTypeImpl = userDefinedReturnType;
                    initializeFactoryMethod( returnTypeImpl, selectionParameters );
                    if ( factoryMethod != null || canResultTypeFromBeanMappingBeConstructed( returnTypeImpl ) ) {
                        returnTypeToConstruct = returnTypeImpl;
                    }
                    else {
                        cannotConstructReturnType = true;
                    }
                }
                else if ( !method.isUpdateMethod() ) {
                    returnTypeImpl = method.getReturnType();
                    initializeFactoryMethod( returnTypeImpl, selectionParameters );
                    if ( factoryMethod != null
                        || allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed( returnTypeImpl )
                        || doesNotAllowAbstractReturnTypeAndCanBeConstructed( returnTypeImpl ) ) {
                        returnTypeToConstruct = returnTypeImpl;
                    }
                    else {
                        cannotConstructReturnType = true;
                    }
                }
            }

            if ( cannotConstructReturnType ) {
                // If the return type cannot be constructed then no need to try to create mappings
                return null;
            }

            /* the type that needs to be used in the mapping process as target */
            Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct;

            existingVariableNames.addAll( method.getParameterNames() );

            CollectionMappingStrategyGem cms = this.method.getOptions().getMapper().getCollectionMappingStrategy();

            // determine accessors
            Map accessors = resultTypeToMap.getPropertyWriteAccessors( cms );
            this.targetProperties = new LinkedHashSet<>( accessors.keySet() );

            this.unprocessedTargetProperties = new LinkedHashMap<>( accessors );

            if ( !method.isUpdateMethod() && !hasFactoryMethod ) {
                ConstructorAccessor constructorAccessor = getConstructorAccessor( resultTypeToMap );
                if ( constructorAccessor != null ) {

                    this.unprocessedConstructorProperties = constructorAccessor.constructorAccessors;

                    factoryMethod = MethodReference.forConstructorInvocation(
                        resultTypeToMap,
                        constructorAccessor.parameterBindings
                    );

                }
                else {
                    this.unprocessedConstructorProperties = new LinkedHashMap<>();
                }

                this.targetProperties.addAll( this.unprocessedConstructorProperties.keySet() );
                this.unprocessedTargetProperties.putAll( this.unprocessedConstructorProperties );
            }
            else {
                unprocessedConstructorProperties = new LinkedHashMap<>();
            }

            this.unprocessedSourceProperties = new LinkedHashMap<>();
            for ( Parameter sourceParameter : method.getSourceParameters() ) {
                unprocessedSourceParameters.add( sourceParameter );

                if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() ||
                    sourceParameter.getType().isMapType() ) {
                    continue;
                }

                Map readAccessors = sourceParameter.getType().getPropertyReadAccessors();

                for ( Entry entry : readAccessors.entrySet() ) {
                    unprocessedSourceProperties.put( entry.getKey(), entry.getValue() );
                }
            }

            // get bean mapping (when specified as annotation )
            this.missingIgnoredSourceProperties = new HashSet<>();
            if ( beanMapping != null ) {
                for ( String ignoreUnmapped : beanMapping.getIgnoreUnmappedSourceProperties() ) {
                    if ( unprocessedSourceProperties.remove( ignoreUnmapped ) == null ) {
                        missingIgnoredSourceProperties.add( ignoreUnmapped );
                    }
                }
            }

            initializeMappingReferencesIfNeeded( resultTypeToMap );

            // map properties with mapping
            boolean mappingErrorOccurred = handleDefinedMappings( resultTypeToMap );
            if ( mappingErrorOccurred ) {
                return null;
            }

            if ( !mappingReferences.isRestrictToDefinedMappings() ) {

                // apply name based mapping from a source reference
                applyTargetThisMapping();

                // map properties without a mapping
                applyPropertyNameBasedMapping();

                // map parameters without a mapping
                applyParameterNameBasedMapping();

            }

            // Process the unprocessed defined targets
            handleUnprocessedDefinedTargets();

            // Initialize unprocessed constructor properties
            handleUnmappedConstructorProperties();

            // report errors on unmapped properties
            reportErrorForUnmappedTargetPropertiesIfRequired();
            reportErrorForUnmappedSourcePropertiesIfRequired();
            reportErrorForMissingIgnoredSourceProperties();
            reportErrorForUnusedSourceParameters();

            // mapNullToDefault
            boolean mapNullToDefault = method.getOptions()
                .getBeanMapping()
                .getNullValueMappingStrategy()
                .isReturnDefault();

            // sort
            sortPropertyMappingsByDependencies();

            // before / after mappings
            List beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods(
                            method,
                            resultTypeToMap,
                            selectionParameters,
                            ctx,
                            existingVariableNames
            );
            List afterMappingMethods = LifecycleMethodResolver.afterMappingMethods(
                            method,
                            resultTypeToMap,
                            selectionParameters,
                            ctx,
                            existingVariableNames
            );

            if ( method instanceof ForgedMethod ) {
                ForgedMethod forgedMethod = (ForgedMethod) method;
                if ( factoryMethod != null ) {
                    forgedMethod.addThrownTypes( factoryMethod.getThrownTypes() );
                }
                for ( LifecycleCallbackMethodReference beforeMappingMethod : beforeMappingMethods ) {
                    forgedMethod.addThrownTypes( beforeMappingMethod.getThrownTypes() );
                }

                for ( LifecycleCallbackMethodReference afterMappingMethod : afterMappingMethods ) {
                    forgedMethod.addThrownTypes( afterMappingMethod.getThrownTypes() );
                }


                for ( PropertyMapping propertyMapping : propertyMappings ) {
                    if ( propertyMapping.getAssignment() != null ) {
                        forgedMethod.addThrownTypes( propertyMapping.getAssignment().getThrownTypes() );
                    }
                }

            }

            List subclasses = new ArrayList<>();
            for ( SubclassMappingOptions subclassMappingOptions : method.getOptions().getSubclassMappings() ) {
                subclasses.add( createSubclassMapping( subclassMappingOptions ) );
            }

            MethodReference finalizeMethod = null;

            if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) {
                finalizeMethod = getFinalizerMethod();
            }

            return new BeanMappingMethod(
                method,
                existingVariableNames,
                propertyMappings,
                factoryMethod,
                mapNullToDefault,
                returnTypeToConstruct,
                returnTypeBuilder,
                beforeMappingMethods,
                afterMappingMethods,
                finalizeMethod,
                mappingReferences,
                subclasses
            );
        }

        private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) {
            return !isAbstractReturnTypeAllowed()
                && canReturnTypeBeConstructed( returnTypeImpl );
        }

        private boolean allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed(Type returnTypeImpl) {
            return isAbstractReturnTypeAllowed()
                && isReturnTypeAbstractOrCanBeConstructed( returnTypeImpl );
        }

        private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMappingOptions) {
            TypeFactory typeFactory = ctx.getTypeFactory();
            Type sourceType = typeFactory.getType( subclassMappingOptions.getSource() );
            Type targetType = typeFactory.getType( subclassMappingOptions.getTarget() );

            SourceRHS rightHandSide = new SourceRHS(
                "subclassMapping",
                sourceType,
                Collections.emptySet(),
                "SubclassMapping for " + sourceType.getFullyQualifiedName() );
            SelectionCriteria criteria =
                SelectionCriteria
                                 .forSubclassMappingMethods(
                                     new SelectionParameters(
                                         Collections.emptyList(),
                                         Collections.emptyList(),
                                         subclassMappingOptions.getTarget(),
                                         ctx.getTypeUtils() ).withSourceRHS( rightHandSide ),
                                     subclassMappingOptions.getMappingControl( ctx.getElementUtils() ) );
            Assignment assignment = ctx
                                   .getMappingResolver()
                                   .getTargetAssignment(
                                       method,
                                       null,
                                       targetType,
                                       FormattingParameters.EMPTY,
                                       criteria,
                                       rightHandSide,
                                       null,
                                           () -> forgeSubclassMapping(
                                               rightHandSide,
                                               sourceType,
                                               targetType,
                                               mappingReferences ) );
            String sourceArgument = null;
            for ( Parameter parameter : method.getSourceParameters() ) {
                if ( ctx
                        .getTypeUtils()
                        .isAssignable( sourceType.getTypeMirror(), parameter.getType().getTypeMirror() ) ) {
                    sourceArgument = parameter.getName();
                    assignment.setSourceLocalVarName( "(" + sourceType.createReferenceName() + ") " + sourceArgument );
                }
            }
            return new SubclassMapping( sourceType, sourceArgument, targetType, assignment );
        }

        private boolean isAbstractReturnTypeAllowed() {
            return method.getOptions().getBeanMapping().getSubclassExhaustiveStrategy().isAbstractReturnTypeAllowed()
                && !method.getOptions().getSubclassMappings().isEmpty();
        }

        private void initializeMappingReferencesIfNeeded(Type resultTypeToMap) {
            if ( mappingReferences == null && method instanceof SourceMethod ) {
                Set readAndWriteTargetProperties = new HashSet<>( unprocessedTargetProperties.keySet() );
                readAndWriteTargetProperties.addAll( resultTypeToMap.getPropertyReadAccessors().keySet() );
                mappingReferences = forSourceMethod(
                    (SourceMethod) method,
                    resultTypeToMap,
                    readAndWriteTargetProperties,
                    ctx.getMessager(),
                    ctx.getTypeFactory()
                );
            }
        }

        /**
         * @return builder is required when there is a returnTypeBuilder and the mapping method is not update method.
         * However, builder is also required when there is a returnTypeBuilder, the mapping target is the builder and
         * builder is not assignable to the return type (so without building).
         */
        private boolean isBuilderRequired() {
            return returnTypeBuilder != null
                    && ( !method.isUpdateMethod() || !method.isMappingTargetAssignableToReturnType() );
        }

        private boolean shouldCallFinalizerMethod(Type returnTypeToConstruct ) {
            if ( returnTypeToConstruct == null ) {
                return false;
            }
            else if ( returnTypeToConstruct.isAssignableTo( method.getReturnType() ) ) {
                // If the mapping type can be assigned to the return type then we
                // don't need a finalizer method
                return false;
            }

            return returnTypeBuilder != null;
        }

        private MethodReference getFinalizerMethod() {
            return BuilderFinisherMethodResolver.getBuilderFinisherMethod(
                method,
                returnTypeBuilder,
                ctx
            );
        }

        /**
         * If there were nested defined targets that have not been handled. Then we need to process them at the end.
         */
        private void handleUnprocessedDefinedTargets() {
            Iterator>> 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> entry = iterator.next();
                String propertyName = entry.getKey();
                if ( !unprocessedTargetProperties.containsKey( propertyName ) ) {
                    continue;
                }
                List sourceParameters = method.getSourceParameters();
                boolean forceUpdateMethod = sourceParameters.size() > 1;
                for ( Parameter sourceParameter : sourceParameters ) {
                    SourceReference reference = new SourceReference.BuilderFromProperty()
                        .sourceParameter( sourceParameter )
                        .name( propertyName )
                        .build();

                    ReadAccessor targetPropertyReadAccessor =
                        method.getResultType().getReadAccessor( propertyName );
                    MappingReferences mappingRefs = extractMappingReferences( propertyName, true );
                    PropertyMapping propertyMapping = new PropertyMappingBuilder()
                        .mappingContext( ctx )
                        .sourceMethod( method )
                        .target(
                            propertyName,
                            targetPropertyReadAccessor,
                            unprocessedTargetProperties.get( propertyName )
                        )
                        .sourceReference( reference )
                        .existingVariableNames( existingVariableNames )
                        .dependsOn( mappingRefs.collectNestedDependsOn() )
                        .forgeMethodWithMappingReferences( mappingRefs )
                        .forceUpdateMethod( forceUpdateMethod )
                        .forgedNamedBased( false )
                        .build();

                    if ( propertyMapping != null ) {
                        unprocessedTargetProperties.remove( propertyName );
                        unprocessedConstructorProperties.remove( propertyName );
                        unprocessedSourceProperties.remove( propertyName );
                        iterator.remove();
                        propertyMappings.add( propertyMapping );
                        // If we found a mapping for the unprocessed property then stop
                        break;
                    }
                }
            }
        }

        private void handleUnmappedConstructorProperties() {
            for ( Entry entry : unprocessedConstructorProperties.entrySet() ) {
                Accessor accessor = entry.getValue();
                Type accessedType = ctx.getTypeFactory()
                    .getType( accessor.getAccessedType() );
                String targetPropertyName = entry.getKey();

                propertyMappings.add( new JavaExpressionMappingBuilder()
                    .mappingContext( ctx )
                    .sourceMethod( method )
                    .javaExpression( accessedType.getNull() )
                    .existingVariableNames( existingVariableNames )
                    .target( targetPropertyName, null, accessor )
                    .dependsOn( Collections.emptySet() )
                    .mirror( null )
                    .build()
                );
            }

            unprocessedConstructorProperties.clear();
        }

        /**
         * 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 cycles = new HashSet<>();
                for ( List cycle : graphAnalyzer.getCycles() ) {
                    cycles.add( Strings.join( cycle, " -> " ) );
                }

                ctx.getMessager().printMessage(
                    method.getExecutable(),
                    Message.BEANMAPPING_CYCLE_BETWEEN_PROPERTIES, Strings.join( cycles, ", " )
                );
            }
            else {
                propertyMappings.sort( Comparator.comparingInt( propertyMapping ->
                    graphAnalyzer.getTraversalSequence( propertyMapping.getName() ) ) );
            }
        }

        private boolean canResultTypeFromBeanMappingBeConstructed(Type resultType) {

            boolean error = true;
            if ( resultType.isAbstract() ) {
                ctx.getMessager().printMessage(
                    method.getExecutable(),
                    method.getOptions().getBeanMapping().getMirror(),
                    BEANMAPPING_ABSTRACT,
                    resultType.describe(),
                    method.getResultType().describe()
                );
                error = false;
            }
            else if ( !resultType.isAssignableTo( method.getResultType() ) ) {
                ctx.getMessager().printMessage(
                    method.getExecutable(),
                    method.getOptions().getBeanMapping().getMirror(),
                    BEANMAPPING_NOT_ASSIGNABLE,
                    resultType.describe(),
                    method.getResultType().describe()
                );
                error = false;
            }
            else if ( !resultType.hasAccessibleConstructor() ) {
                ctx.getMessager().printMessage(
                    method.getExecutable(),
                    method.getOptions().getBeanMapping().getMirror(),
                    Message.GENERAL_NO_SUITABLE_CONSTRUCTOR,
                    resultType.describe()
                );
                error = false;
            }
            return error;
        }

        private boolean canReturnTypeBeConstructed(Type returnType) {
            boolean error = true;
            if ( returnType.isAbstract() ) {
                ctx.getMessager().printMessage(
                    method.getExecutable(),
                    GENERAL_ABSTRACT_RETURN_TYPE,
                    returnType.describe()
                );
                error = false;
            }
            else if ( !returnType.hasAccessibleConstructor() ) {
                ctx.getMessager().printMessage(
                    method.getExecutable(),
                    Message.GENERAL_NO_SUITABLE_CONSTRUCTOR,
                    returnType.describe()
                );
                error = false;
            }
            return error;
        }

        private boolean isReturnTypeAbstractOrCanBeConstructed(Type returnType) {
            boolean error = true;
            if ( !returnType.isAbstract() && !returnType.hasAccessibleConstructor() ) {
                ctx
                   .getMessager()
                   .printMessage(
                       method.getExecutable(),
                       Message.GENERAL_NO_SUITABLE_CONSTRUCTOR,
                       returnType.describe() );
                error = false;
            }
            return error;
        }

        /**
         * Find a factory method for a return type or for a builder.
         * @param returnTypeImpl the return type implementation to construct
         * @param @selectionParameters
         * @return
         */
        private void initializeFactoryMethod(Type returnTypeImpl, SelectionParameters selectionParameters) {
            List> matchingFactoryMethods =
                ObjectFactoryMethodResolver.getMatchingFactoryMethods(
                    method,
                    returnTypeImpl,
                    selectionParameters,
                    ctx
                );

            if ( matchingFactoryMethods.isEmpty() ) {
                if ( factoryMethod == null && returnTypeBuilder != null ) {
                    factoryMethod = ObjectFactoryMethodResolver.getBuilderFactoryMethod( method, returnTypeBuilder );
                    hasFactoryMethod = factoryMethod != null;
                }
            }
            else if ( matchingFactoryMethods.size() == 1 ) {
                factoryMethod = ObjectFactoryMethodResolver.getFactoryMethodReference(
                    method,
                    first( matchingFactoryMethods ),
                    ctx
                );
                hasFactoryMethod = true;
            }
            else {
                ctx.getMessager().printMessage(
                    method.getExecutable(),
                    Message.GENERAL_AMBIGUOUS_FACTORY_METHOD,
                    returnTypeImpl.describe(),
                    matchingFactoryMethods.stream()
                        .map( SelectedMethod::getMethod )
                        .map( Method::describe )
                        .collect( Collectors.joining( ", " ) )
                );
                hasFactoryMethod = true;
            }
        }

        private ConstructorAccessor getConstructorAccessor(Type type) {
            if ( type.isAbstract() ) {
                // We cannot construct abstract classes.
                // Usually we won't reach here,
                // but if SubclassMapping is used with SubclassExhaustiveStrategy#RUNTIME_EXCEPTION
                // then we will still generate the code.
                // We shouldn't generate anything for those abstract types
                return null;
            }

            if ( type.isRecord() ) {
                // If the type is a record then just get the record components and use then
                List recordComponents = type.getRecordComponents();
                List parameterBindings = new ArrayList<>( recordComponents.size() );
                Map constructorAccessors = new LinkedHashMap<>();
                for ( Element recordComponent : recordComponents ) {
                    TypeMirror recordComponentMirror = ctx.getTypeUtils()
                        .asMemberOf( (DeclaredType) type.getTypeMirror(), recordComponent );
                    String parameterName = recordComponent.getSimpleName().toString();
                    Accessor accessor = createConstructorAccessor(
                        recordComponent,
                        recordComponentMirror,
                        parameterName
                    );
                    constructorAccessors.put(
                        parameterName,
                        accessor
                    );

                    parameterBindings.add( ParameterBinding.fromTypeAndName(
                        ctx.getTypeFactory().getType( recordComponentMirror ),
                        accessor.getSimpleName()
                    ) );
                }
                return new ConstructorAccessor( parameterBindings, constructorAccessors );
            }

            List constructors = ElementFilter.constructorsIn( type.getTypeElement()
                .getEnclosedElements() );

            // The rules for picking a constructor are the following:
            // 1. Constructor annotated with @Default (from any package) has highest precedence
            // 2. If there is a single public constructor then it would be used to construct the object
            // 3. If a parameterless constructor exists then it would be used to construct the object, and the other
            // constructors will be ignored
            ExecutableElement defaultAnnotatedConstructor = null;
            ExecutableElement parameterLessConstructor = null;
            List accessibleConstructors = new ArrayList<>( constructors.size() );
            List publicConstructors = new ArrayList<>( );

            for ( ExecutableElement constructor : constructors ) {
                if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) {
                    continue;
                }

                if ( hasDefaultAnnotationFromAnyPackage( constructor ) ) {
                    // We found a constructor annotated with @Default everything else is irrelevant
                    defaultAnnotatedConstructor = constructor;
                    break;
                }

                if ( constructor.getParameters().isEmpty() ) {
                    parameterLessConstructor = constructor;
                }
                else {
                    accessibleConstructors.add( constructor );
                }

                if ( constructor.getModifiers().contains( Modifier.PUBLIC ) ) {
                    publicConstructors.add( constructor );
                }
            }

            if ( defaultAnnotatedConstructor != null ) {
                // If a default annotated constructor exists it will be used, it has highest precedence
                return getConstructorAccessor( type, defaultAnnotatedConstructor );
            }

            if ( publicConstructors.size() == 1 ) {
                // If there is a single public constructor then use that one
                ExecutableElement publicConstructor = publicConstructors.get( 0 );
                if ( publicConstructor.getParameters().isEmpty() ) {
                    // The public parameterless constructor
                    return null;
                }

                return getConstructorAccessor( type, publicConstructor );
            }

            if ( parameterLessConstructor != null ) {
                // If there is a constructor without parameters use it
                return null;
            }

            if ( accessibleConstructors.isEmpty() ) {
                return null;
            }

            if ( accessibleConstructors.size() > 1 ) {

                ctx.getMessager().printMessage(
                    method.getExecutable(),
                    GENERAL_AMBIGUOUS_CONSTRUCTORS,
                    type,
                    constructors.stream()
                        .map( ExecutableElement::getParameters )
                        .map( ps -> ps.stream()
                            .map( VariableElement::asType )
                            .map( String::valueOf )
                            .collect( Collectors.joining( ", ", type.getName() + "(", ")" ) )
                        )
                        .collect( Collectors.joining( ", " ) )
                );
                return null;
            }
            else {
                return getConstructorAccessor( type, accessibleConstructors.get( 0 ) );
            }

        }

        private ConstructorAccessor getConstructorAccessor(Type type, ExecutableElement constructor) {
            List constructorParameters = ctx.getTypeFactory()
                .getParameters( (DeclaredType) type.getTypeMirror(), constructor );

            List constructorProperties = null;
            for ( AnnotationMirror annotationMirror : constructor.getAnnotationMirrors() ) {
                if ( annotationMirror.getAnnotationType()
                    .asElement()
                    .getSimpleName()
                    .contentEquals( "ConstructorProperties" ) ) {
                    for ( Entry entry : annotationMirror
                        .getElementValues()
                        .entrySet() ) {
                        if ( entry.getKey().getSimpleName().contentEquals( "value" ) ) {
                            constructorProperties = getArrayValues( entry.getValue() );
                            break;
                        }
                    }
                    break;
                }
            }

            if ( constructorProperties == null ) {
                Map constructorAccessors = new LinkedHashMap<>();
                List parameterBindings = new ArrayList<>( constructorParameters.size() );
                for ( Parameter constructorParameter : constructorParameters ) {
                    String parameterName = constructorParameter.getName();
                    Element parameterElement = constructorParameter.getElement();
                    Accessor constructorAccessor = createConstructorAccessor(
                        parameterElement,
                        constructorParameter.getType().getTypeMirror(),
                        parameterName
                    );
                    constructorAccessors.put(
                        parameterName,
                        constructorAccessor
                    );
                    parameterBindings.add( ParameterBinding.fromTypeAndName(
                        constructorParameter.getType(),
                        constructorAccessor.getSimpleName()
                    ) );
                }

                return new ConstructorAccessor( parameterBindings, constructorAccessors );
            }
            else if ( constructorProperties.size() != constructorParameters.size() ) {
                ctx.getMessager().printMessage(
                    method.getExecutable(),
                    GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS,
                    type
                );
                return null;
            }
            else {
                Map constructorAccessors = new LinkedHashMap<>();
                List parameterBindings = new ArrayList<>( constructorProperties.size() );
                for ( int i = 0; i < constructorProperties.size(); i++ ) {
                    String parameterName = constructorProperties.get( i );
                    Parameter constructorParameter = constructorParameters.get( i );
                    Element parameterElement = constructorParameter.getElement();
                    Accessor constructorAccessor = createConstructorAccessor(
                        parameterElement,
                        constructorParameter.getType().getTypeMirror(),
                        parameterName
                    );
                    constructorAccessors.put(
                        parameterName,
                        constructorAccessor
                    );
                    parameterBindings.add( ParameterBinding.fromTypeAndName(
                        constructorParameter.getType(),
                        constructorAccessor.getSimpleName()
                    ) );
                }

                return new ConstructorAccessor( parameterBindings, constructorAccessors );
            }
        }

        private Accessor createConstructorAccessor(Element element, TypeMirror accessedType, String parameterName) {
            String safeParameterName = Strings.getSafeVariableName(
                parameterName,
                existingVariableNames
            );
            existingVariableNames.add( safeParameterName );
            return new ParameterElementAccessor( element, accessedType, safeParameterName );
        }

        private boolean hasDefaultAnnotationFromAnyPackage(Element element) {
            for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) {
                if ( annotationMirror.getAnnotationType()
                    .asElement()
                    .getSimpleName()
                    .contentEquals( "Default" ) ) {
                    return true;
                }
            }

            return false;
        }

        private List getArrayValues(AnnotationValue av) {

            if ( av.getValue() instanceof List ) {
                List result = new ArrayList<>();
                for ( AnnotationValue v : getValueAsList( av ) ) {
                    Object value = v.getValue();
                    if ( value instanceof String ) {
                        result.add( (String) value );
                    }
                    else {
                        return null;
                    }
                }
                return result;
            }
            else {
                return null;
            }
        }

        @SuppressWarnings("unchecked")
        private List getValueAsList(AnnotationValue av) {
            return (List) av.getValue();
        }

        /**
         * Iterates over all defined mapping methods ({@code @Mapping(s)}), either directly given or inherited from the
         * inverse mapping method.
         * 

* If a match is found between a defined source (constant, expression, ignore or source) the mapping is removed * from the remaining target properties. *

* It is furthermore checked whether the given mappings are correct. When an error occurs, the method continues * in search of more problems. * * @param resultTypeToMap the type in which the defined target properties are defined */ private boolean handleDefinedMappings(Type resultTypeToMap) { boolean errorOccurred = false; Set handledTargets = new HashSet<>(); // first we have to handle nested target mappings if ( mappingReferences.hasNestedTargetReferences() ) { errorOccurred = handleDefinedNestedTargetMapping( handledTargets, resultTypeToMap ); } for ( MappingReference mapping : mappingReferences.getMappingReferences() ) { if ( mapping.isValid() ) { String target = mapping.getTargetReference().getShallowestPropertyName(); if ( !handledTargets.contains( target ) ) { if ( handleDefinedMapping( mapping, resultTypeToMap, handledTargets ) ) { errorOccurred = true; } } if ( mapping.getSourceReference() != null ) { String source = mapping.getSourceReference().getShallowestPropertyName(); if ( source != null ) { unprocessedSourceProperties.remove( source ); } } } else { errorOccurred = true; } } // remove the remaining name based properties for ( String handledTarget : handledTargets ) { unprocessedTargetProperties.remove( handledTarget ); unprocessedConstructorProperties.remove( handledTarget ); unprocessedDefinedTargets.remove( handledTarget ); } return errorOccurred; } private boolean handleDefinedNestedTargetMapping(Set handledTargets, Type resultTypeToMap) { NestedTargetPropertyMappingHolder holder = new NestedTargetPropertyMappingHolder.Builder() .mappingContext( ctx ) .method( method ) .targetPropertiesWriteAccessors( unprocessedTargetProperties ) .targetPropertyType( resultTypeToMap ) .mappingReferences( mappingReferences ) .existingVariableNames( existingVariableNames ) .build(); unprocessedSourceParameters.removeAll( holder.getProcessedSourceParameters() ); propertyMappings.addAll( holder.getPropertyMappings() ); handledTargets.addAll( holder.getHandledTargets() ); // Store all the unprocessed defined targets. for ( Entry> entry : holder.getUnprocessedDefinedTarget() .entrySet() ) { if ( entry.getValue().isEmpty() ) { continue; } unprocessedDefinedTargets.put( entry.getKey(), entry.getValue() ); } return holder.hasErrorOccurred(); } private boolean handleDefinedMapping(MappingReference mappingRef, Type resultTypeToMap, Set handledTargets) { boolean errorOccured = false; PropertyMapping propertyMapping = null; TargetReference targetRef = mappingRef.getTargetReference(); MappingOptions mapping = mappingRef.getMapping(); // 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; } } String targetPropertyName = first( targetRef.getPropertyEntries() ); // check if source / expression / constant are not somehow handled already if ( unprocessedDefinedTargets.containsKey( targetPropertyName ) ) { return false; } Accessor targetWriteAccessor = unprocessedTargetProperties.get( targetPropertyName ); ReadAccessor targetReadAccessor = resultTypeToMap.getReadAccessor( targetPropertyName ); if ( targetWriteAccessor == null ) { if ( targetReadAccessor == null ) { MappingOptions.InheritContext inheritContext = mapping.getInheritContext(); if ( inheritContext != null ) { if ( inheritContext.isForwarded() && inheritContext.getTemplateMethod().isUpdateMethod() != method.isUpdateMethod() ) { // When a configuration is inherited and the template method is not same type as the current // method then we can safely ignore this mapping. // This means that a property which is inherited might be present for a direct mapping // via the Builder, but not for an update mapping (directly on the object itself), // or vice versa return false; } else if ( inheritContext.isReversed() ) { // When a configuration is reverse inherited and there are no read or write accessor // then we should ignore this mapping. // This most likely means that we were mapping the source parameter to the target. // If the error is due to something else it will be reported on the original mapping return false; } } Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); Message msg; String[] args; if ( targetRef.getPathProperties().isEmpty() ) { msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; args = new String[] { targetPropertyName, resultTypeToMap.describe(), mostSimilarProperty }; } else { List pathProperties = new ArrayList<>( targetRef.getPathProperties() ); pathProperties.add( mostSimilarProperty ); msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE; args = new String[] { targetPropertyName, resultTypeToMap.describe(), mapping.getTargetName(), Strings.join( pathProperties, "." ) }; } ctx.getMessager() .printMessage( mapping.getElement(), mapping.getMirror(), mapping.getTargetAnnotationValue(), msg, args ); return true; } else if ( mapping.getInheritContext() != null && mapping.getInheritContext().isReversed() ) { // read only reversed mappings are implicitly ignored return false; } else if ( !mapping.isIgnored() ) { // report an error for read only mappings Message msg; Object[] args; if ( Objects.equals( targetPropertyName, mapping.getTargetName() ) ) { msg = Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE; args = new Object[] { mapping.getTargetName(), resultTypeToMap.describe() }; } else { msg = Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_TYPE; args = new Object[] { targetPropertyName, resultTypeToMap.describe(), mapping.getTargetName() }; } ctx.getMessager() .printMessage( mapping.getElement(), mapping.getMirror(), mapping.getTargetAnnotationValue(), msg, args ); return true; } } // check the mapping options // its an ignored property mapping if ( mapping.isIgnored() ) { if ( targetWriteAccessor != null && targetWriteAccessor.getAccessorType() == AccessorType.PARAMETER ) { // Even though the property is ignored this is a constructor parameter. // Therefore we have to initialize it Type accessedType = ctx.getTypeFactory() .getType( targetWriteAccessor.getAccessedType() ); propertyMapping = new JavaExpressionMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .javaExpression( accessedType.getNull() ) .existingVariableNames( existingVariableNames ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .dependsOn( mapping.getDependsOn() ) .mirror( mapping.getMirror() ) .build(); } handledTargets.add( targetPropertyName ); } // 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 ) { propertyMapping = new ConstantMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .constantExpression( mapping.getConstant() ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .formattingParameters( mapping.getFormattingParameters() ) .selectionParameters( mapping.getSelectionParameters() ) .options( mapping ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping.getDependsOn() ) .mirror( mapping.getMirror() ) .build(); handledTargets.add( targetPropertyName ); } // 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 ) { propertyMapping = new JavaExpressionMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .javaExpression( mapping.getJavaExpression() ) .existingVariableNames( existingVariableNames ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .dependsOn( mapping.getDependsOn() ) .mirror( mapping.getMirror() ) .build(); handledTargets.add( targetPropertyName ); } // its a plain-old property mapping else { SourceReference sourceRef = mappingRef.getSourceReference(); // sourceRef is not defined, check if a source property has the same name if ( sourceRef == null ) { // Here we follow the same rules as when we implicitly map // When we implicitly map we first do property name based mapping // i.e. look for matching properties in the source types // and then do parameter name based mapping for ( Parameter sourceParameter : method.getSourceParameters() ) { SourceReference matchingSourceRef = getSourceRefByTargetName( sourceParameter, targetPropertyName ); if ( matchingSourceRef != null ) { if ( sourceRef != null ) { errorOccured = true; // This can only happen when the target property matches multiple properties // within the different source parameters ctx.getMessager() .printMessage( method.getExecutable(), mappingRef.getMapping().getMirror(), Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES, targetPropertyName ); break; } // We can't break here since it is possible that the same property exists in multiple // source parameters sourceRef = matchingSourceRef; } } } if ( sourceRef == null ) { // still no match. Try if one of the parameters has the same name sourceRef = method.getSourceParameters() .stream() .filter( p -> targetPropertyName.equals( p.getName() ) ) .findAny() .map( p -> new SourceReference.BuilderFromProperty() .sourceParameter( p ) .name( targetPropertyName ) .build() ) .orElse( null ); } if ( sourceRef != null ) { // sourceRef == null is not considered an error here 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 ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .sourcePropertyName( mapping.getSourceName() ) .sourceReference( sourceRef ) .selectionParameters( mapping.getSelectionParameters() ) .formattingParameters( mapping.getFormattingParameters() ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping.getDependsOn() ) .defaultValue( mapping.getDefaultValue() ) .defaultJavaExpression( mapping.getDefaultJavaExpression() ) .conditionJavaExpression( mapping.getConditionJavaExpression() ) .mirror( mapping.getMirror() ) .options( mapping ) .build(); handledTargets.add( targetPropertyName ); unprocessedSourceParameters.remove( sourceRef.getParameter() ); unprocessedSourceProperties.remove( sourceRef.getShallowestPropertyName() ); } else { errorOccured = true; } } else { errorOccured = true; if ( method.getSourceParameters().size() == 1 ) { ctx.getMessager() .printMessage( method.getExecutable(), mapping.getMirror(), mapping.getTargetAnnotationValue(), PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PROPERTY_FROM_TARGET, method.getSourceParameters().get( 0 ).getName(), targetPropertyName ); } else { ctx.getMessager() .printMessage( method.getExecutable(), mapping.getMirror(), mapping.getTargetAnnotationValue(), PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET, targetPropertyName ); } } } // remaining are the mappings without a 'source' so, 'only' a date format or qualifiers if ( propertyMapping != null ) { propertyMappings.add( propertyMapping ); } return errorOccured; } /** * When target this mapping present, iterates over unprocessed targets. *

* When a target property matches its name with the (nested) source property, it is added to the list if and * only if it is an unprocessed target property. * * duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)} */ private void applyTargetThisMapping() { Set handledTargetProperties = new HashSet<>(); for ( MappingReference targetThis : mappingReferences.getTargetThisReferences() ) { // handle all prior unprocessed target properties, but let duplicates fall through List sourceRefs = targetThis .getSourceReference() .push( ctx.getTypeFactory(), ctx.getMessager(), method ) .stream() .filter( sr -> unprocessedTargetProperties.containsKey( sr.getDeepestPropertyName() ) || handledTargetProperties.contains( sr.getDeepestPropertyName() ) ) .collect( Collectors.toList() ); // apply name based mapping applyPropertyNameBasedMapping( sourceRefs ); // add handled target properties handledTargetProperties.addAll( sourceRefs.stream() .map( SourceReference::getDeepestPropertyName ) .collect( Collectors.toList() ) ); } } /** * Iterates over all target properties and all source parameters. *

* 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() { List sourceReferences = new ArrayList<>(); for ( String targetPropertyName : unprocessedTargetProperties.keySet() ) { for ( Parameter sourceParameter : method.getSourceParameters() ) { SourceReference sourceRef = getSourceRefByTargetName( sourceParameter, targetPropertyName ); if ( sourceRef != null ) { sourceReferences.add( sourceRef ); } } } applyPropertyNameBasedMapping( sourceReferences ); } /** * Iterates over all target properties and all source parameters. *

* 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(List sourceReferences) { for ( SourceReference sourceRef : sourceReferences ) { String targetPropertyName = sourceRef.getDeepestPropertyName(); Accessor targetPropertyWriteAccessor = unprocessedTargetProperties.remove( targetPropertyName ); unprocessedConstructorProperties.remove( targetPropertyName ); if ( targetPropertyWriteAccessor == null ) { // TODO improve error message ctx.getMessager() .printMessage( method.getExecutable(), Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES, targetPropertyName ); continue; } ReadAccessor targetPropertyReadAccessor = method.getResultType().getReadAccessor( targetPropertyName ); MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) .sourceMethod( method ) .target( targetPropertyName, targetPropertyReadAccessor, targetPropertyWriteAccessor ) .sourceReference( sourceRef ) .existingVariableNames( existingVariableNames ) .forgeMethodWithMappingReferences( mappingRefs ) .options( method.getOptions().getBeanMapping() ) .build(); unprocessedSourceParameters.remove( sourceRef.getParameter() ); if ( propertyMapping != null ) { propertyMappings.add( propertyMapping ); } unprocessedDefinedTargets.remove( targetPropertyName ); unprocessedSourceProperties.remove( targetPropertyName ); } } private void applyParameterNameBasedMapping() { Iterator> targetPropertyEntriesIterator = unprocessedTargetProperties.entrySet().iterator(); while ( targetPropertyEntriesIterator.hasNext() ) { Entry targetProperty = targetPropertyEntriesIterator.next(); Iterator sourceParameters = unprocessedSourceParameters.iterator(); while ( sourceParameters.hasNext() ) { Parameter sourceParameter = sourceParameters.next(); if ( sourceParameter.getName().equals( targetProperty.getKey() ) ) { SourceReference sourceRef = new SourceReference.BuilderFromProperty() .sourceParameter( sourceParameter ) .name( targetProperty.getKey() ) .build(); ReadAccessor targetPropertyReadAccessor = method.getResultType().getReadAccessor( targetProperty.getKey() ); MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .target( targetProperty.getKey(), targetPropertyReadAccessor, targetProperty.getValue() ) .sourceReference( sourceRef ) .existingVariableNames( existingVariableNames ) .forgeMethodWithMappingReferences( mappingRefs ) .options( method.getOptions().getBeanMapping() ) .build(); propertyMappings.add( propertyMapping ); targetPropertyEntriesIterator.remove(); sourceParameters.remove(); unprocessedDefinedTargets.remove( targetProperty.getKey() ); unprocessedSourceProperties.remove( targetProperty.getKey() ); // The source parameter was directly mapped so ignore all of its source properties completely if ( !sourceParameter.getType().isPrimitive() && !sourceParameter.getType().isArrayType() ) { // We explicitly ignore source properties from primitives or array types Map readAccessors = sourceParameter.getType() .getPropertyReadAccessors(); for ( String sourceProperty : readAccessors.keySet() ) { unprocessedSourceProperties.remove( sourceProperty ); } } unprocessedConstructorProperties.remove( targetProperty.getKey() ); } } } } private SourceReference getSourceRefByTargetName(Parameter sourceParameter, String targetPropertyName) { SourceReference sourceRef = null; if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() ) { return sourceRef; } ReadAccessor sourceReadAccessor = sourceParameter.getType().getReadAccessor( targetPropertyName ); if ( sourceReadAccessor != null ) { // property mapping PresenceCheckAccessor sourcePresenceChecker = sourceParameter.getType().getPresenceChecker( targetPropertyName ); DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); Type returnType = ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ); sourceRef = new SourceReference.BuilderFromProperty().sourceParameter( sourceParameter ) .type( returnType ) .readAccessor( sourceReadAccessor ) .presenceChecker( sourcePresenceChecker ) .name( targetPropertyName ) .build(); } return sourceRef; } private MappingReferences extractMappingReferences(String targetProperty, boolean restrictToDefinedMappings) { if ( unprocessedDefinedTargets.containsKey( targetProperty ) ) { Set mappings = unprocessedDefinedTargets.get( targetProperty ); return new MappingReferences( mappings, restrictToDefinedMappings ); } return null; } private ReportingPolicyGem getUnmappedTargetPolicy() { if ( mappingReferences.isForForgedMethods() ) { return ReportingPolicyGem.IGNORE; } if ( method.getOptions().getBeanMapping() != null ) { return method.getOptions().getBeanMapping().unmappedTargetPolicy(); } return method.getOptions().getMapper().unmappedTargetPolicy(); } private void reportErrorForUnmappedTargetPropertiesIfRequired() { // fetch settings from element to implement ReportingPolicyGem 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.describe(), targetType.describe(), targetType.describe(), sourceType.describe() ); } else { ForgedMethodHistory history = forgedMethod.getHistory(); ctx.getMessager().printMessage( this.method.getExecutable(), Message.PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND, history.createSourcePropertyErrorMessage(), history.getTargetType().describe(), history.createTargetPropertyName(), history.getTargetType().describe(), history.getSourceType().describe() ); } } else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) { if ( !( method instanceof ForgedMethod ) ) { 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 typeParameters = parameterType.getTypeParameters(); if ( typeParameters.size() != 2 || !typeParameters.get( 0 ).isString() ) { Message message = typeParameters.isEmpty() ? Message.MAPTOBEANMAPPING_RAW_MAP : Message.MAPTOBEANMAPPING_WRONG_KEY_TYPE; ctx.getMessager() .printMessage( method.getExecutable(), message, sourceParameter.getName(), String.format( "Map<%s,%s>", !typeParameters.isEmpty() ? typeParameters.get( 0 ).describe() : "", typeParameters.size() > 1 ? typeParameters.get( 1 ).describe() : "" ) ); } } } } } private static class ConstructorAccessor { private final List parameterBindings; private final Map constructorAccessors; private ConstructorAccessor( List parameterBindings, Map constructorAccessors) { this.parameterBindings = parameterBindings; this.constructorAccessors = constructorAccessors; } } //CHECKSTYLE:OFF private BeanMappingMethod(Method method, Collection existingVariableNames, List propertyMappings, MethodReference factoryMethod, boolean mapNullToDefault, Type returnTypeToConstruct, BuilderType returnTypeBuilder, List beforeMappingReferences, List afterMappingReferences, MethodReference finalizerMethod, MappingReferences mappingReferences, List subclassMappings) { super( method, existingVariableNames, factoryMethod, mapNullToDefault, beforeMappingReferences, afterMappingReferences ); //CHECKSTYLE:ON this.propertyMappings = propertyMappings; this.returnTypeBuilder = returnTypeBuilder; this.finalizerMethod = finalizerMethod; this.mappingReferences = mappingReferences; // intialize constant mappings as all mappings, but take out the ones that can be contributed to a // parameter mapping. this.mappingsByParameter = new HashMap<>(); this.constantMappings = new ArrayList<>( propertyMappings.size() ); this.constructorMappingsByParameter = new LinkedHashMap<>(); this.constructorConstantMappings = new ArrayList<>(); Set sourceParameterNames = getSourceParameters().stream() .map( Parameter::getName ) .collect( Collectors.toSet() ); for ( PropertyMapping mapping : propertyMappings ) { if ( mapping.isConstructorMapping() ) { if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { constructorMappingsByParameter.computeIfAbsent( mapping.getSourceBeanName(), key -> new ArrayList<>() ).add( mapping ); } else { constructorConstantMappings.add( mapping ); } } else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { mappingsByParameter.computeIfAbsent( mapping.getSourceBeanName(), key -> new ArrayList<>() ) .add( mapping ); } else { constantMappings.add( mapping ); } } this.returnTypeToConstruct = returnTypeToConstruct; this.subclassMappings = subclassMappings; } public List getConstantMappings() { return constantMappings; } public List getConstructorConstantMappings() { return constructorConstantMappings; } public List getSubclassMappings() { return subclassMappings; } public List propertyMappingsByParameter(Parameter parameter) { // issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value return mappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() ); } public List constructorPropertyMappingsByParameter(Parameter parameter) { // issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value return constructorMappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() ); } public Type getReturnTypeToConstruct() { return returnTypeToConstruct; } public boolean hasSubclassMappings() { return !subclassMappings.isEmpty(); } public boolean isAbstractReturnType() { return getFactoryMethod() == null && returnTypeToConstruct != null && returnTypeToConstruct.isAbstract(); } public boolean hasConstructorMappings() { return !constructorMappingsByParameter.isEmpty() || !constructorConstantMappings.isEmpty(); } public MethodReference getFinalizerMethod() { return finalizerMethod; } @Override public Set getImportTypes() { Set types = super.getImportTypes(); for ( PropertyMapping propertyMapping : propertyMappings ) { types.addAll( propertyMapping.getImportTypes() ); if ( propertyMapping.isConstructorMapping() ) { // We need to add the target type imports for a constructor mapper since we define its parameters types.addAll( propertyMapping.getTargetType().getImportTypes() ); } } for ( SubclassMapping subclassMapping : subclassMappings ) { types.addAll( subclassMapping.getImportTypes() ); } if ( returnTypeToConstruct != null ) { types.addAll( returnTypeToConstruct.getImportTypes() ); } if ( returnTypeBuilder != null ) { types.add( returnTypeBuilder.getOwningType() ); } return types; } public List getSourceParametersExcludingPrimitives() { return getSourceParameters().stream() .filter( parameter -> !parameter.getType().isPrimitive() ) .collect( Collectors.toList() ); } public List getSourceParametersNeedingNullCheck() { return getSourceParameters().stream() .filter( this::needsNullCheck ) .collect( Collectors.toList() ); } public List getSourceParametersNotNeedingNullCheck() { return getSourceParameters().stream() .filter( parameter -> !needsNullCheck( parameter ) ) .collect( Collectors.toList() ); } private boolean needsNullCheck(Parameter parameter) { if ( parameter.getType().isPrimitive() ) { return false; } List mappings = propertyMappingsByParameter( parameter ); if ( mappings.size() == 1 && doesNotNeedNullCheckForSourceParameter( mappings.get( 0 ) ) ) { return false; } mappings = constructorPropertyMappingsByParameter( parameter ); if ( mappings.size() == 1 && doesNotNeedNullCheckForSourceParameter( mappings.get( 0 ) ) ) { return false; } return true; } private boolean doesNotNeedNullCheckForSourceParameter(PropertyMapping mapping) { if ( mapping.getAssignment().isCallingUpdateMethod() ) { // If the mapping assignment is calling an update method then we should do a null check // in the bean mapping return false; } return mapping.getAssignment().isSourceReferenceParameter(); } @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; } if ( !Objects.equals( propertyMappings, that.propertyMappings ) ) { return false; } if ( !Objects.equals( mappingReferences, that.mappingReferences ) ) { return false; } return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy