diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml index 5ae53dc46..945015d5c 100644 --- a/build-config/src/main/resources/build-config/checkstyle.xml +++ b/build-config/src/main/resources/build-config/checkstyle.xml @@ -122,7 +122,9 @@ - + + + diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 1fcf8e4ce..5cb2192aa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -22,12 +22,13 @@ import java.util.stream.Collectors; import javax.lang.model.type.DeclaredType; 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.PropertyEntry; import org.mapstruct.ap.internal.model.beanmapping.SourceReference; import org.mapstruct.ap.internal.model.beanmapping.TargetReference; import org.mapstruct.ap.internal.model.common.BuilderType; @@ -40,8 +41,6 @@ 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.gem.CollectionMappingStrategyGem; -import org.mapstruct.ap.internal.gem.ReportingPolicyGem; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; @@ -96,7 +95,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { public Builder sourceMethod(SourceMethod sourceMethod) { this.method = sourceMethod; - this.mappingReferences = forSourceMethod( sourceMethod, ctx.getMessager(), ctx.getTypeFactory() ); return this; } @@ -131,6 +129,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { MethodReference factoryMethod = null; // determine which return type to construct + boolean cannotConstructReturnType = false; if ( !method.getReturnType().isVoid() ) { Type returnTypeImpl = getReturnTypeToConstructFromSelectionParameters( selectionParameters ); if ( returnTypeImpl != null ) { @@ -138,6 +137,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { if ( factoryMethod != null || canResultTypeFromBeanMappingBeConstructed( returnTypeImpl ) ) { returnTypeToConstruct = returnTypeImpl; } + else { + cannotConstructReturnType = true; + } } else if ( isBuilderRequired() ) { returnTypeImpl = returnTypeBuilder.getBuilder(); @@ -145,6 +147,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) { returnTypeToConstruct = returnTypeImpl; } + else { + cannotConstructReturnType = true; + } } else if ( !method.isUpdateMethod() ) { returnTypeImpl = method.getReturnType(); @@ -152,9 +157,17 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { if ( factoryMethod != null || canReturnTypeBeConstructed( 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; @@ -187,8 +200,10 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } } + initializeMappingReferencesIfNeeded( resultTypeToMap ); + // map properties with mapping - boolean mappingErrorOccurred = handleDefinedMappings(); + boolean mappingErrorOccurred = handleDefinedMappings( resultTypeToMap ); if ( mappingErrorOccurred ) { return null; } @@ -261,6 +276,20 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { ); } + 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 @@ -320,9 +349,11 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) - .targetWriteAccessor( unprocessedTargetProperties.get( propertyName ) ) - .targetReadAccessor( targetPropertyReadAccessor ) - .targetPropertyName( propertyName ) + .target( + propertyName, + targetPropertyReadAccessor, + unprocessedTargetProperties.get( propertyName ) + ) .sourceReference( reference ) .existingVariableNames( existingVariableNames ) .dependsOn( mappingRefs.collectNestedDependsOn() ) @@ -464,22 +495,24 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { *

* 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() { + 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 ); + errorOccurred = handleDefinedNestedTargetMapping( handledTargets, resultTypeToMap ); } for ( MappingReference mapping : mappingReferences.getMappingReferences() ) { if ( mapping.isValid() ) { String target = mapping.getTargetReference().getShallowestPropertyName(); if ( !handledTargets.contains( target ) ) { - if ( handleDefinedMapping( mapping, handledTargets ) ) { + if ( handleDefinedMapping( mapping, resultTypeToMap, handledTargets ) ) { errorOccurred = true; } } @@ -504,11 +537,13 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { return errorOccurred; } - private boolean handleDefinedNestedTargetMapping(Set handledTargets) { + 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(); @@ -517,26 +552,24 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { propertyMappings.addAll( holder.getPropertyMappings() ); handledTargets.addAll( holder.getHandledTargets() ); // Store all the unprocessed defined targets. - for ( Entry> entry : holder.getUnprocessedDefinedTarget() + for ( Entry> entry : holder.getUnprocessedDefinedTarget() .entrySet() ) { if ( entry.getValue().isEmpty() ) { continue; } - unprocessedDefinedTargets.put( entry.getKey().getName(), entry.getValue() ); + unprocessedDefinedTargets.put( entry.getKey(), entry.getValue() ); } return holder.hasErrorOccurred(); } - private boolean handleDefinedMapping(MappingReference mappingRef, Set handledTargets) { - + private boolean handleDefinedMapping(MappingReference mappingRef, Type resultTypeToMap, + Set handledTargets) { boolean errorOccured = false; PropertyMapping propertyMapping = null; TargetReference targetRef = mappingRef.getTargetReference(); MappingOptions mapping = mappingRef.getMapping(); - PropertyEntry targetProperty = first( targetRef.getPropertyEntries() ); - String targetPropertyName = targetProperty.getName(); // unknown properties given via dependsOn()? for ( String dependency : mapping.getDependsOn() ) { @@ -552,16 +585,95 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } } + 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 ); + Accessor targetReadAccessor = resultTypeToMap.getPropertyReadAccessors().get( targetPropertyName ); + + if ( targetWriteAccessor == null ) { + if ( targetReadAccessor == null ) { + Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); + String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); + + Message msg; + Object[] args; + + if ( targetRef.getPathProperties().isEmpty() ) { + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; + args = new Object[] { + targetPropertyName, + resultTypeToMap, + mostSimilarProperty + }; + } + else { + List pathProperties = new ArrayList<>( targetRef.getPathProperties() ); + pathProperties.add( mostSimilarProperty ); + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE; + args = new Object[] { + targetPropertyName, + resultTypeToMap, + 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 + }; + } + else { + msg = Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_TYPE; + args = new Object[] { + targetPropertyName, + resultTypeToMap, + 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() ) { propertyMapping = null; - handledTargets.add( targetProperty.getName() ); + handledTargets.add( targetPropertyName ); } // its a constant @@ -573,8 +685,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { .mappingContext( ctx ) .sourceMethod( method ) .constantExpression( mapping.getConstant() ) - .targetProperty( targetProperty ) - .targetPropertyName( targetPropertyName ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .formattingParameters( mapping.getFormattingParameters() ) .selectionParameters( mapping.getSelectionParameters() ) .options( mapping ) @@ -595,8 +706,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { .sourceMethod( method ) .javaExpression( mapping.getJavaExpression() ) .existingVariableNames( existingVariableNames ) - .targetProperty( targetProperty ) - .targetPropertyName( targetPropertyName ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .dependsOn( mapping.getDependsOn() ) .mirror( mapping.getMirror() ) .build(); @@ -618,8 +728,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) - .targetProperty( targetProperty ) - .targetPropertyName( targetPropertyName ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .sourcePropertyName( mapping.getSourceName() ) .sourceReference( sourceRef ) .selectionParameters( mapping.getSelectionParameters() ) @@ -723,15 +832,13 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { method.getResultType().getPropertyReadAccessors().get( targetPropertyName ); MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) - .sourceMethod( method ) - .targetWriteAccessor( targetPropertyWriteAccessor ) - .targetReadAccessor( targetPropertyReadAccessor ) - .targetPropertyName( targetPropertyName ) - .sourceReference( sourceRef ) - .existingVariableNames( existingVariableNames ) - .forgeMethodWithMappingReferences( mappingRefs ) - .options( method.getOptions().getBeanMapping() ) - .build(); + .sourceMethod( method ) + .target( targetPropertyName, targetPropertyReadAccessor, targetPropertyWriteAccessor ) + .sourceReference( sourceRef ) + .existingVariableNames( existingVariableNames ) + .forgeMethodWithMappingReferences( mappingRefs ) + .options( method.getOptions().getBeanMapping() ) + .build(); unprocessedSourceParameters.remove( sourceRef.getParameter() ); @@ -770,9 +877,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) - .targetWriteAccessor( targetProperty.getValue() ) - .targetReadAccessor( targetPropertyReadAccessor ) - .targetPropertyName( targetProperty.getKey() ) + .target( targetProperty.getKey(), targetPropertyReadAccessor, targetProperty.getValue() ) .sourceReference( sourceRef ) .existingVariableNames( existingVariableNames ) .forgeMethodWithMappingReferences( mappingRefs ) @@ -866,17 +971,25 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } 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 processedSourceParameters; private final Set handledTargets; private final List propertyMappings; - private final Map> unprocessedDefinedTarget; + private final Map> unprocessedDefinedTarget; private final boolean errorOccurred; public NestedTargetPropertyMappingHolder( List processedSourceParameters, Set handledTargets, List propertyMappings, - Map> unprocessedDefinedTarget, boolean errorOccurred) { + Map> unprocessedDefinedTarget, boolean errorOccurred) { this.processedSourceParameters = processedSourceParameters; this.handledTargets = handledTargets; this.propertyMappings = propertyMappings; @@ -75,7 +79,7 @@ public class NestedTargetPropertyMappingHolder { /** * @return a map of all the unprocessed defined targets that can be applied to name forged base methods */ - public Map> getUnprocessedDefinedTarget() { + public Map> getUnprocessedDefinedTarget() { return unprocessedDefinedTarget; } @@ -94,6 +98,9 @@ public class NestedTargetPropertyMappingHolder { private Set existingVariableNames; private List propertyMappings; private Set handledTargets; + private Map targetPropertiesWriteAccessors; + private Type targetType; + private boolean errorOccurred; public Builder mappingReferences(MappingReferences mappingReferences) { this.mappingReferences = mappingReferences; @@ -115,6 +122,16 @@ public class NestedTargetPropertyMappingHolder { return this; } + public Builder targetPropertiesWriteAccessors(Map targetPropertiesWriteAccessors) { + this.targetPropertiesWriteAccessors = targetPropertiesWriteAccessors; + return this; + } + + public Builder targetPropertyType(Type targetType) { + this.targetType = targetType; + return this; + } + public NestedTargetPropertyMappingHolder build() { List processedSourceParameters = new ArrayList<>(); handledTargets = new HashSet<>(); @@ -123,11 +140,11 @@ public class NestedTargetPropertyMappingHolder { // first we group by the first property in the target properties and for each of those // properties we get the new mappings as if the first property did not exist. GroupedTargetReferences groupedByTP = groupByTargetReferences( ); - Map> unprocessedDefinedTarget = new LinkedHashMap<>(); + Map> unprocessedDefinedTarget = new LinkedHashMap<>(); - for ( Map.Entry> entryByTP : + for ( Map.Entry> entryByTP : groupedByTP.poppedTargetReferences.entrySet() ) { - PropertyEntry targetProperty = entryByTP.getKey(); + String targetProperty = entryByTP.getKey(); //Now we are grouping the already popped mappings by the source parameter(s) of the method GroupedBySourceParameters groupedBySourceParam = groupBySourceParameter( entryByTP.getValue(), @@ -188,7 +205,7 @@ public class NestedTargetPropertyMappingHolder { .type( sourceEntry.getType() ) .readAccessor( sourceEntry.getReadAccessor() ) .presenceChecker( sourceEntry.getPresenceChecker() ) - .name( targetProperty.getName() ) + .name( targetProperty ) .build(); // If we have multiple source parameters that are mapped to the target reference, or @@ -205,7 +222,7 @@ public class NestedTargetPropertyMappingHolder { propertyMappings.add( propertyMapping ); } - handledTargets.add( entryByTP.getKey().getName() ); + handledTargets.add( entryByTP.getKey() ); } // For the nonNested mappings (assymetric) Mappings we also forge mappings @@ -216,7 +233,7 @@ public class NestedTargetPropertyMappingHolder { new MappingReferences( groupedSourceReferences.nonNested, true ); SourceReference reference = new SourceReference.BuilderFromProperty() .sourceParameter( sourceParameter ) - .name( targetProperty.getName() ) + .name( targetProperty ) .build(); boolean forceUpdateMethodForNonNested = @@ -236,7 +253,7 @@ public class NestedTargetPropertyMappingHolder { propertyMappings.add( propertyMapping ); } - handledTargets.add( entryByTP.getKey().getName() ); + handledTargets.add( entryByTP.getKey() ); } handleSourceParameterMappings( @@ -255,7 +272,7 @@ public class NestedTargetPropertyMappingHolder { handledTargets, propertyMappings, unprocessedDefinedTarget, - groupedByTP.errorOccurred + errorOccurred ); } @@ -268,7 +285,7 @@ public class NestedTargetPropertyMappingHolder { * @param forceUpdateMethod whether we need to force an update method */ private void handleSourceParameterMappings(Set sourceParameterMappings, - PropertyEntry targetProperty, Parameter sourceParameter, + String targetProperty, Parameter sourceParameter, boolean forceUpdateMethod) { if ( !sourceParameterMappings.isEmpty() ) { // The source parameter mappings have no mappings, the source name is actually the parameter itself @@ -276,7 +293,7 @@ public class NestedTargetPropertyMappingHolder { new MappingReferences( Collections.emptySet(), false, true ); SourceReference reference = new SourceReference.BuilderFromProperty() .sourceParameter( sourceParameter ) - .name( targetProperty.getName() ) + .name( targetProperty ) .build(); PropertyMapping propertyMapping = createPropertyMappingForNestedTarget( @@ -290,7 +307,7 @@ public class NestedTargetPropertyMappingHolder { propertyMappings.add( propertyMapping ); } - handledTargets.add( targetProperty.getName() ); + handledTargets.add( targetProperty ); } } @@ -340,16 +357,11 @@ public class NestedTargetPropertyMappingHolder { */ private GroupedTargetReferences groupByTargetReferences( ) { // group all mappings based on the top level name before popping - Map> mappingsKeyedByProperty = new LinkedHashMap<>(); - Map> singleTargetReferences = new LinkedHashMap<>(); - boolean errorOccurred = false; + Map> mappingsKeyedByProperty = new LinkedHashMap<>(); + Map> singleTargetReferences = new LinkedHashMap<>(); for ( MappingReference mapping : mappingReferences.getMappingReferences() ) { TargetReference targetReference = mapping.getTargetReference(); - if ( !targetReference.isValid() ) { - errorOccurred = true; - continue; - } - PropertyEntry property = first( targetReference.getPropertyEntries() ); + String property = first( targetReference.getPropertyEntries() ); MappingReference newMapping = mapping.popTargetReference(); if ( newMapping != null ) { // group properties on current name. @@ -362,7 +374,7 @@ public class NestedTargetPropertyMappingHolder { } } - return new GroupedTargetReferences( mappingsKeyedByProperty, singleTargetReferences, errorOccurred ); + return new GroupedTargetReferences( mappingsKeyedByProperty, singleTargetReferences ); } /** @@ -625,14 +637,45 @@ public class NestedTargetPropertyMappingHolder { } private PropertyMapping createPropertyMappingForNestedTarget(MappingReferences mappingReferences, - PropertyEntry targetProperty, + String targetPropertyName, SourceReference sourceReference, boolean forceUpdateMethod) { + + Accessor targetWriteAccessor = targetPropertiesWriteAccessors.get( targetPropertyName ); + Accessor targetReadAccessor = targetType.getPropertyReadAccessors().get( targetPropertyName ); + if ( targetWriteAccessor == null ) { + Set readAccessors = targetType.getPropertyReadAccessors().keySet(); + String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); + + for ( MappingReference mappingReference : mappingReferences.getMappingReferences() ) { + MappingOptions mapping = mappingReference.getMapping(); + List pathProperties = new ArrayList<>( mappingReference.getTargetReference() + .getPathProperties() ); + if ( !pathProperties.isEmpty() ) { + pathProperties.set( pathProperties.size() - 1, mostSimilarProperty ); + } + + mappingContext.getMessager() + .printMessage( + mapping.getElement(), + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE, + targetPropertyName, + targetType, + mapping.getTargetName(), + Strings.join( pathProperties, "." ) + ); + } + + errorOccurred = true; + return null; + } + return new PropertyMapping.PropertyMappingBuilder() .mappingContext( mappingContext ) .sourceMethod( method ) - .targetProperty( targetProperty ) - .targetPropertyName( targetProperty.getName() ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .sourceReference( sourceReference ) .existingVariableNames( existingVariableNames ) .dependsOn( mappingReferences.collectNestedDependsOn() ) @@ -685,21 +728,19 @@ public class NestedTargetPropertyMappingHolder { * references (target references that were not nested). */ private static class GroupedTargetReferences { - private final Map> poppedTargetReferences; - private final Map> singleTargetReferences; - private final boolean errorOccurred; + private final Map> poppedTargetReferences; + private final Map> singleTargetReferences; - private GroupedTargetReferences(Map> poppedTargetReferences, - Map> singleTargetReferences, boolean errorOccurred) { + private GroupedTargetReferences(Map> poppedTargetReferences, + Map> singleTargetReferences) { this.poppedTargetReferences = poppedTargetReferences; this.singleTargetReferences = singleTargetReferences; - this.errorOccurred = errorOccurred; } @Override public String toString() { return "GroupedTargetReferences{" + "poppedTargetReferences=" + poppedTargetReferences - + ", singleTargetReferences=" + singleTargetReferences + ", errorOccurred=" + errorOccurred + '}'; + + ", singleTargetReferences=" + singleTargetReferences + "}"; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 06de2aa39..24facce72 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -94,21 +94,9 @@ public class PropertyMapping extends ModelElement { return super.method( sourceMethod ); } - public T targetProperty(PropertyEntry targetProp) { - this.targetReadAccessor = targetProp.getReadAccessor(); - this.targetWriteAccessor = targetProp.getWriteAccessor(); - this.targetType = targetProp.getType(); - this.targetBuilderType = targetProp.getBuilderType(); - this.targetWriteAccessorType = targetWriteAccessor.getAccessorType(); - return (T) this; - } - - public T targetReadAccessor(Accessor targetReadAccessor) { + public T target(String targetPropertyName, Accessor targetReadAccessor, Accessor targetWriteAccessor) { + this.targetPropertyName = targetPropertyName; this.targetReadAccessor = targetReadAccessor; - return (T) this; - } - - public T targetWriteAccessor(Accessor targetWriteAccessor) { this.targetWriteAccessor = targetWriteAccessor; this.targetType = ctx.getTypeFactory().getType( targetWriteAccessor.getAccessedType() ); BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); @@ -122,11 +110,6 @@ public class PropertyMapping extends ModelElement { return (T) this; } - public T targetPropertyName(String targetPropertyName) { - this.targetPropertyName = targetPropertyName; - return (T) this; - } - public T sourcePropertyName(String sourcePropertyName) { this.sourcePropertyName = sourcePropertyName; return (T) this; @@ -326,6 +309,11 @@ public class PropertyMapping extends ModelElement { * Report that a mapping could not be created. */ private void reportCannotCreateMapping() { + if ( forgeMethodWithMappingReferences != null && ctx.isErroneous() ) { + // If we arrived here, there is an error it means that we couldn't forge a mapping method + // so skip the cannot create mapping + return; + } if ( method instanceof ForgedMethod && ( (ForgedMethod) method ).getHistory() != null ) { // The history that is part of the ForgedMethod misses the information from the current right hand // side. Therefore we need to extract the most relevant history and use that in the error reporting. @@ -363,9 +351,7 @@ public class PropertyMapping extends ModelElement { .existingVariableNames( existingVariableNames ) .mappingContext( ctx ) .sourceMethod( method ) - .targetPropertyName( targetPropertyName ) - .targetReadAccessor( targetReadAccessor ) - .targetWriteAccessor( targetWriteAccessor ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .build(); return build.getAssignment(); } @@ -378,9 +364,7 @@ public class PropertyMapping extends ModelElement { .existingVariableNames( existingVariableNames ) .mappingContext( ctx ) .sourceMethod( method ) - .targetPropertyName( targetPropertyName ) - .targetReadAccessor( targetReadAccessor ) - .targetWriteAccessor( targetWriteAccessor ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .build(); return build.getAssignment(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java index 2675e2900..e2501ea95 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java @@ -80,11 +80,7 @@ public class MappingReference { } public boolean isValid( ) { - boolean result = false; - if ( targetReference.isValid() ) { - result = sourceReference != null ? sourceReference.isValid() : true; - } - return result; + return sourceReference == null || sourceReference.isValid(); } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java index 63af65f2e..06f0ed06c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java @@ -11,6 +11,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.SourceMethod; @@ -29,8 +30,11 @@ public class MappingReferences { return EMPTY; } - public static MappingReferences forSourceMethod(SourceMethod sourceMethod, FormattingMessager messager, - TypeFactory typeFactory) { + public static MappingReferences forSourceMethod(SourceMethod sourceMethod, + Type targetType, + Set targetProperties, + FormattingMessager messager, + TypeFactory typeFactory) { Set references = new LinkedHashSet<>(); List targetThisReferences = new ArrayList<>( ); @@ -49,6 +53,8 @@ public class MappingReferences { .method( sourceMethod ) .messager( messager ) .typeFactory( typeFactory ) + .targetProperties( targetProperties ) + .targetType( targetType ) .build(); // add when inverse is also valid @@ -121,7 +127,7 @@ public class MappingReferences { for ( MappingReference mappingRef : mappingReferences ) { TargetReference targetReference = mappingRef.getTargetReference(); - if ( targetReference.isValid() && targetReference.isNested()) { + if ( targetReference.isNested()) { return true; } @@ -141,7 +147,7 @@ public class MappingReferences { private static boolean isValidWhenInversed(MappingReference mappingRef) { MappingOptions mapping = mappingRef.getMapping(); if ( mapping.getInheritContext() != null && mapping.getInheritContext().isReversed() ) { - return mappingRef.getTargetReference().isValid() && ( mappingRef.getSourceReference() == null || + return ( mappingRef.getSourceReference() == null || mappingRef.getSourceReference().isValid() ); } return true; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java index f2d087cd9..8ac5e4667 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java @@ -7,55 +7,33 @@ package org.mapstruct.ap.internal.model.beanmapping; import java.util.Arrays; -import org.mapstruct.ap.internal.model.common.BuilderType; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; /** - * A PropertyEntry contains information on the name, readAccessor (for source), readAccessor and writeAccessor - * (for targets) and return type of a property. + * A PropertyEntry contains information on the name, readAccessor and presenceCheck (for source) + * and return type of a property. */ public class PropertyEntry { private final String[] fullName; private final Accessor readAccessor; - private final Accessor writeAccessor; private final Accessor presenceChecker; private final Type type; - private final BuilderType builderType; /** * Constructor used to create {@link TargetReference} property entries from a mapping * * @param fullName * @param readAccessor - * @param writeAccessor * @param type */ - private PropertyEntry(String[] fullName, Accessor readAccessor, Accessor writeAccessor, - Accessor presenceChecker, Type type, BuilderType builderType) { + private PropertyEntry(String[] fullName, Accessor readAccessor, Accessor presenceChecker, Type type) { this.fullName = fullName; this.readAccessor = readAccessor; - this.writeAccessor = writeAccessor; this.presenceChecker = presenceChecker; this.type = type; - this.builderType = builderType; - } - - /** - * Constructor used to create {@link TargetReference} property entries - * - * @param fullName name of the property (dot separated) - * @param readAccessor its read accessor - * @param writeAccessor its write accessor - * @param type type of the property - * @param builderType the builder for the type - * @return the property entry for given parameters. - */ - public static PropertyEntry forTargetReference(String[] fullName, Accessor readAccessor, - Accessor writeAccessor, Type type, BuilderType builderType ) { - return new PropertyEntry( fullName, readAccessor, writeAccessor, null, type, builderType ); } /** @@ -69,7 +47,7 @@ public class PropertyEntry { */ public static PropertyEntry forSourceReference(String[] name, Accessor readAccessor, Accessor presenceChecker, Type type) { - return new PropertyEntry( name, readAccessor, null, presenceChecker, type, null ); + return new PropertyEntry( name, readAccessor, presenceChecker, type ); } public String getName() { @@ -80,10 +58,6 @@ public class PropertyEntry { return readAccessor; } - public Accessor getWriteAccessor() { - return writeAccessor; - } - public Accessor getPresenceChecker() { return presenceChecker; } @@ -92,24 +66,10 @@ public class PropertyEntry { return type; } - public BuilderType getBuilderType() { - return builderType; - } - public String getFullName() { return Strings.join( Arrays.asList( fullName ), "." ); } - public PropertyEntry pop() { - if ( fullName.length > 1 ) { - String[] newFullName = Arrays.copyOfRange( fullName, 1, fullName.length ); - return new PropertyEntry(newFullName, readAccessor, writeAccessor, presenceChecker, type, builderType ); - } - else { - return null; - } - } - @Override public int hashCode() { int hash = 7; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/TargetReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/TargetReference.java index 8a2a39823..fee97f840 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/TargetReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/TargetReference.java @@ -7,26 +7,21 @@ package org.mapstruct.ap.internal.model.beanmapping; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; -import javax.lang.model.type.DeclaredType; -import org.mapstruct.ap.internal.model.common.BuilderType; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.gem.BuilderGem; -import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.util.FormattingMessager; 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 static org.mapstruct.ap.internal.util.Collections.first; @@ -48,11 +43,77 @@ import static org.mapstruct.ap.internal.util.Collections.first; *

  • {@code propertyEntries[1]} will describe {@code propB}
  • * * - * After building, {@link #isValid()} will return true when when no problems are detected during building. - * * @author Sjaak Derksen */ -public class TargetReference extends AbstractReference { +public class TargetReference { + + private final List pathProperties; + private final Parameter parameter; + private final List propertyEntries; + + public TargetReference(Parameter parameter, List propertyEntries) { + this( parameter, propertyEntries, Collections.emptyList() ); + } + + public TargetReference(Parameter parameter, List propertyEntries, List pathProperties) { + this.pathProperties = pathProperties; + this.parameter = parameter; + this.propertyEntries = propertyEntries; + } + + public List getPathProperties() { + return pathProperties; + } + + public List getPropertyEntries() { + return propertyEntries; + } + + public List getElementNames() { + List elementNames = new ArrayList<>(); + if ( parameter != null ) { + // only relevant for source properties + elementNames.add( parameter.getName() ); + } + elementNames.addAll( propertyEntries ); + return elementNames; + } + + /** + * returns the property name on the shallowest nesting level + */ + public String getShallowestPropertyName() { + if ( propertyEntries.isEmpty() ) { + return null; + } + return first( propertyEntries ); + } + + public boolean isNested() { + return propertyEntries.size() > 1; + } + + @Override + public String toString() { + + String result = ""; + if ( propertyEntries.isEmpty() ) { + if ( parameter != null ) { + result = String.format( "parameter \"%s %s\"", parameter.getType(), parameter.getName() ); + } + } + else if ( propertyEntries.size() == 1 ) { + String propertyEntry = propertyEntries.get( 0 ); + result = String.format( "property \"%s\"", propertyEntry ); + } + else { + result = String.format( + "property \"%s\"", + Strings.join( getElementNames(), "." ) + ); + } + return result; + } /** * Builds a {@link TargetReference} from an {@code @Mappping}. @@ -62,21 +123,15 @@ public class TargetReference extends AbstractReference { private Method method; private FormattingMessager messager; private TypeFactory typeFactory; + private Set targetProperties; + private Type targetType; // mapping parameters - private boolean isReversed = false; - private boolean isIgnored = false; private String targetName = null; + private MappingOptions mapping; private AnnotationMirror annotationMirror = null; private AnnotationValue targetAnnotationValue = null; private AnnotationValue sourceAnnotationValue = null; - private Method templateMethod = null; - /** - * During {@link #getTargetEntries(Type, String[])} an error can occur. However, we are invoking - * that multiple times because the first entry can also be the name of the parameter. Therefore we keep - * the error message until the end when we can report it. - */ - private MappingErrorMessage errorMessage; public Builder messager(FormattingMessager messager) { this.messager = messager; @@ -84,11 +139,7 @@ public class TargetReference extends AbstractReference { } public Builder mapping(MappingOptions mapping) { - if ( mapping.getInheritContext() != null ) { - this.isReversed = mapping.getInheritContext().isReversed(); - this.templateMethod = mapping.getInheritContext().getTemplateMethod(); - } - this.isIgnored = mapping.isIgnored(); + this.mapping = mapping; this.targetName = mapping.getTargetName(); this.annotationMirror = mapping.getMirror(); this.targetAnnotationValue = mapping.getTargetAnnotationValue(); @@ -106,11 +157,22 @@ public class TargetReference extends AbstractReference { return this; } + public Builder targetProperties(Set targetProperties) { + this.targetProperties = targetProperties; + return this; + } + + public Builder targetType(Type targetType) { + this.targetType = targetType; + return this; + } + public TargetReference build() { Objects.requireNonNull( method ); Objects.requireNonNull( typeFactory ); Objects.requireNonNull( messager ); + Objects.requireNonNull( targetType ); if ( targetName == null ) { return null; @@ -130,185 +192,27 @@ public class TargetReference extends AbstractReference { String[] segments = targetNameTrimmed.split( "\\." ); Parameter parameter = method.getMappingTargetParameter(); - boolean foundEntryMatch; - Type resultType = typeBasedOnMethod( method.getResultType() ); - // there can be 4 situations // 1. Return type // 2. An inverse target reference where the source parameter name is used in the original mapping // 3. @MappingTarget, with // 4. or without parameter name. String[] targetPropertyNames = segments; - List entries = getTargetEntries( resultType, targetPropertyNames ); - foundEntryMatch = (entries.size() == targetPropertyNames.length); - if ( !foundEntryMatch && segments.length > 1 - && matchesSourceOrTargetParameter( segments[0], parameter, isReversed ) ) { - targetPropertyNames = Arrays.copyOfRange( segments, 1, segments.length ); - entries = getTargetEntries( resultType, targetPropertyNames ); - foundEntryMatch = (entries.size() == targetPropertyNames.length); - } - - if ( !foundEntryMatch && errorMessage != null && !isReversed ) { - // This is called only for reporting errors - errorMessage.report( ); - } - - // foundEntryMatch = isValid, errors are handled here, and the BeanMapping uses that to ignore - // the creation of mapping for invalid TargetReferences - return new TargetReference( parameter, entries, foundEntryMatch ); - } - - private List getTargetEntries(Type type, String[] entryNames) { - - // initialize - CollectionMappingStrategyGem cms = method.getOptions().getMapper().getCollectionMappingStrategy(); - List targetEntries = new ArrayList<>(); - Type nextType = type; - - // iterate, establish for each entry the target write accessors. Other than setter is only allowed for - // last entry - for ( int i = 0; i < entryNames.length; i++ ) { - - Type mappingType = typeBasedOnMethod( nextType ); - Accessor targetReadAccessor = mappingType.getPropertyReadAccessors().get( entryNames[i] ); - Accessor targetWriteAccessor = mappingType.getPropertyWriteAccessors( cms ).get( entryNames[i] ); - boolean isLast = i == entryNames.length - 1; - boolean isNotLast = i < entryNames.length - 1; - if ( isWriteAccessorNotValidWhenNotLast( targetWriteAccessor, isNotLast ) - || isWriteAccessorNotValidWhenLast( targetWriteAccessor, targetReadAccessor, isIgnored, isLast ) ) { - // there should always be a write accessor (except for the last when the mapping is ignored and - // there is a read accessor) and there should be read accessor mandatory for all but the last - setErrorMessage( targetWriteAccessor, targetReadAccessor, entryNames, i, nextType ); - break; - } - - if ( isLast || ( targetWriteAccessor.getAccessorType() == AccessorType.SETTER || - targetWriteAccessor.getAccessorType() == AccessorType.FIELD ) ) { - // only intermediate nested properties when they are a true setter or field accessor - // the last may be other readAccessor (setter / getter / adder). - - nextType = findNextType( nextType, targetWriteAccessor, targetReadAccessor ); - - // check if an entry alread exists, otherwise create - String[] fullName = Arrays.copyOfRange( entryNames, 0, i + 1 ); - BuilderType builderType; - PropertyEntry propertyEntry = null; - if ( method.isUpdateMethod() ) { - propertyEntry = PropertyEntry.forTargetReference( fullName, - targetReadAccessor, - targetWriteAccessor, - nextType, - null - ); + if ( segments.length > 1 ) { + String firstTargetProperty = targetPropertyNames[0]; + // If the first target property is not within the defined target properties, then check if it matches + // the source or target parameter + if ( !targetProperties.contains( firstTargetProperty ) ) { + if ( matchesSourceOrTargetParameter( firstTargetProperty, parameter ) ) { + targetPropertyNames = Arrays.copyOfRange( segments, 1, segments.length ); } - else { - BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); - builderType = typeFactory.builderTypeFor( nextType, builder ); - propertyEntry = PropertyEntry.forTargetReference( fullName, - targetReadAccessor, - targetWriteAccessor, - nextType, - builderType - ); - - } - targetEntries.add( propertyEntry ); } - } - return targetEntries; - } - /** - * Finds the next type based on the initial type. - * - * @param initial for which a next type should be found - * @param targetWriteAccessor the write accessor - * @param targetReadAccessor the read accessor - * @return the next type that should be used for finding a property entry - */ - private Type findNextType(Type initial, Accessor targetWriteAccessor, Accessor targetReadAccessor) { - Type nextType; - Accessor toUse = targetWriteAccessor != null ? targetWriteAccessor : targetReadAccessor; - if ( toUse.getAccessorType() == AccessorType.GETTER || toUse.getAccessorType() == AccessorType.FIELD ) { - nextType = typeFactory.getReturnType( - (DeclaredType) typeBasedOnMethod( initial ).getTypeMirror(), - toUse - ); - } - else { - nextType = typeFactory.getSingleParameter( - (DeclaredType) typeBasedOnMethod( initial ).getTypeMirror(), - toUse - ).getType(); - } - return nextType; - } + List entries = new ArrayList<>( Arrays.asList( targetPropertyNames ) ); - private void setErrorMessage(Accessor targetWriteAccessor, Accessor targetReadAccessor, String[] entryNames, - int index, Type nextType) { - if ( targetWriteAccessor == null && targetReadAccessor == null ) { - errorMessage = new NoPropertyErrorMessage( this, entryNames, index, nextType ); - } - else if ( targetWriteAccessor == null ) { - errorMessage = new NoWriteAccessorErrorMessage(this ); - } - else { - //TODO there is no read accessor. What should we do here? - errorMessage = new NoPropertyErrorMessage( this, entryNames, index, nextType ); - } - } - - /** - * When we are in an update method, i.e. source parameter with {@code @MappingTarget} then the type should - * be itself, otherwise, we always get the effective type. The reason is that when doing updates we always - * search for setters and getters within the updating type. - */ - private Type typeBasedOnMethod(Type type) { - if ( method.isUpdateMethod() ) { - return type; - } - else { - BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); - return typeFactory.effectiveResultTypeFor( type, builder ); - } - } - - /** - * A write accessor is not valid if it is {@code null} and it is not last. i.e. for nested target mappings - * there must be a write accessor for all entries except the last one. - * - * @param accessor that needs to be checked - * @param isNotLast whether or not this is the last write accessor in the entry chain - * - * @return {@code true} if the accessor is not valid, {@code false} otherwise - */ - private static boolean isWriteAccessorNotValidWhenNotLast(Accessor accessor, boolean isNotLast) { - return accessor == null && isNotLast; - } - - /** - * For a last accessor to be valid, a read accessor should exist and the mapping should be ignored. All other - * cases represent an invalid write accessor. This method will evaluate to {@code true} if the following is - * {@code true}: - *
      - *
    • {@code writeAccessor} is {@code null}
    • - *
    • It is for the last entry
    • - *
    • A read accessor does not exist, or the mapping is not ignored
    • - *
    - * - * @param writeAccessor that needs to be checked - * @param readAccessor that is used - * @param isIgnored true when ignored - * @param isLast whether or not this is the last write accessor in the entry chain - * - * @return {@code true} if the write accessor is not valid, {@code false} otherwise. See description for more - * information - */ - private static boolean isWriteAccessorNotValidWhenLast(Accessor writeAccessor, Accessor readAccessor, - boolean isIgnored, boolean isLast) { - return writeAccessor == null && isLast && ( readAccessor == null || !isIgnored ); + return new TargetReference( parameter, entries ); } /** @@ -317,16 +221,14 @@ public class TargetReference extends AbstractReference { * * @param segment that needs to be checked * @param targetParameter the target parameter if it exists - - * @param isInverse whether a inverse {@link TargetReference} is being built * * @return {@code true} if the segment matches the name of the {@code targetParameter} or the name of the * {@code inverseSourceParameter} when this is a inverse {@link TargetReference} is being built, {@code * false} otherwise */ - private boolean matchesSourceOrTargetParameter(String segment, Parameter targetParameter, boolean isInverse) { + private boolean matchesSourceOrTargetParameter(String segment, Parameter targetParameter) { boolean matchesTargetParameter = targetParameter != null && targetParameter.getName().equals( segment ); - return matchesTargetParameter || matchesSourceOnInverseSourceParameter( segment, isInverse ); + return matchesTargetParameter || matchesSourceOnInverseSourceParameter( segment ); } /** @@ -344,13 +246,16 @@ public class TargetReference extends AbstractReference { * a inverse is created. * * @param segment that needs to be checked* - * @param isInverse whether a inverse {@link TargetReference} is being built * * @return on match when inverse and segment matches the one and only source parameter */ - private boolean matchesSourceOnInverseSourceParameter( String segment, boolean isInverse ) { + private boolean matchesSourceOnInverseSourceParameter(String segment) { + boolean result = false; - if ( isInverse ) { + MappingOptions.InheritContext inheritContext = mapping.getInheritContext(); + if ( inheritContext != null && inheritContext.isReversed() ) { + + Method templateMethod = inheritContext.getTemplateMethod(); // there is only source parameter by definition when applying @InheritInverseConfiguration Parameter inverseSourceParameter = first( templateMethod.getSourceParameters() ); result = inverseSourceParameter.getName().equals( segment ); @@ -359,87 +264,19 @@ public class TargetReference extends AbstractReference { } } - private TargetReference(Parameter sourceParameter, List targetPropertyEntries, boolean isValid) { - super( sourceParameter, targetPropertyEntries, isValid ); - } - public TargetReference pop() { if ( getPropertyEntries().size() > 1 ) { - List newPropertyEntries = new ArrayList<>( getPropertyEntries().size() - 1 ); - for ( PropertyEntry propertyEntry : getPropertyEntries() ) { - PropertyEntry newPropertyEntry = propertyEntry.pop(); - if ( newPropertyEntry != null ) { - newPropertyEntries.add( newPropertyEntry ); - } - } - return new TargetReference( null, newPropertyEntries, isValid() ); + List newPathProperties = new ArrayList<>( this.pathProperties ); + newPathProperties.add( getPropertyEntries().get( 0 ) ); + return new TargetReference( + null, + getPropertyEntries().subList( 1, getPropertyEntries().size() ), + newPathProperties + ); } else { return null; } } - private abstract static class MappingErrorMessage { - private final Builder builder; - - private MappingErrorMessage(Builder builder) { - this.builder = builder; - } - - abstract void report(); - - protected void printErrorMessage(Message message, Object... args) { - Object[] errorArgs = new Object[args.length + 2]; - errorArgs[0] = builder.targetName; - errorArgs[1] = builder.method.getResultType(); - System.arraycopy( args, 0, errorArgs, 2, args.length ); - AnnotationMirror annotationMirror = builder.annotationMirror; - builder.messager.printMessage( builder.method.getExecutable(), - annotationMirror, - builder.sourceAnnotationValue, - message, - errorArgs - ); - } - } - - private static class NoWriteAccessorErrorMessage extends MappingErrorMessage { - - private NoWriteAccessorErrorMessage(Builder builder) { - super( builder ); - } - - @Override - public void report() { - printErrorMessage( Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE ); - } - } - - private static class NoPropertyErrorMessage extends MappingErrorMessage { - - private final String[] entryNames; - private final int index; - private final Type nextType; - - private NoPropertyErrorMessage(Builder builder, String[] entryNames, int index, - Type nextType) { - super( builder ); - this.entryNames = entryNames; - this.index = index; - this.nextType = nextType; - } - - @Override - public void report() { - - Set readAccessors = nextType.getPropertyReadAccessors().keySet(); - String mostSimilarProperty = Strings.getMostSimilarWord( entryNames[index], readAccessors ); - - List elements = new ArrayList<>( Arrays.asList( entryNames ).subList( 0, index ) ); - elements.add( mostSimilarProperty ); - - printErrorMessage( Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE, Strings.join( elements, "." ) ); - } - } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index ed88e5107..b826295f7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -16,6 +16,7 @@ import java.util.regex.Pattern; 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.util.Elements; import javax.lang.model.util.Types; @@ -50,6 +51,7 @@ public class MappingOptions extends DelegatingOptions { private final boolean isIgnored; private final Set dependsOn; + private final Element element; private final AnnotationValue sourceAnnotationValue; private final AnnotationValue targetAnnotationValue; private final MappingGem mapping; @@ -135,6 +137,7 @@ public class MappingOptions extends DelegatingOptions { MappingOptions options = new MappingOptions( mapping.target().getValue(), + method, mapping.target().getAnnotationValue(), source, mapping.source().getAnnotationValue(), @@ -169,6 +172,7 @@ public class MappingOptions extends DelegatingOptions { null, null, null, + null, true, null, null, @@ -249,6 +253,7 @@ public class MappingOptions extends DelegatingOptions { @SuppressWarnings("checkstyle:parameternumber") private MappingOptions(String targetName, + Element element, AnnotationValue targetAnnotationValue, String sourceName, AnnotationValue sourceAnnotationValue, @@ -266,6 +271,7 @@ public class MappingOptions extends DelegatingOptions { ) { super( next ); this.targetName = targetName; + this.element = element; this.targetAnnotationValue = targetAnnotationValue; this.sourceName = sourceName; this.sourceAnnotationValue = sourceAnnotationValue; @@ -377,6 +383,10 @@ public class MappingOptions extends DelegatingOptions { return Optional.ofNullable( mapping ).map( MappingGem::mirror ).orElse( null ); } + public Element getElement() { + return element; + } + public AnnotationValue getDependsOnAnnotationValue() { return Optional.ofNullable( mapping ) .map( MappingGem::dependsOn ) @@ -434,6 +444,7 @@ public class MappingOptions extends DelegatingOptions { MappingOptions mappingOptions = new MappingOptions( sourceName != null ? sourceName : targetName, + templateMethod.getExecutable(), targetAnnotationValue, sourceName != null ? targetName : null, sourceAnnotationValue, @@ -462,6 +473,7 @@ public class MappingOptions extends DelegatingOptions { BeanMappingOptions beanMappingOptions ) { MappingOptions mappingOptions = new MappingOptions( targetName, + templateMethod.getExecutable(), targetAnnotationValue, sourceName, sourceAnnotationValue, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java index bc7374d39..a9f9eff4a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java @@ -167,6 +167,7 @@ public class DefaultModelElementProcessorContext implements ProcessorContext { } } + @Override public boolean isErroneous() { return isErroneous; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 197d9fa63..7746dd677 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -368,10 +368,10 @@ public class MapperCreationProcessor implements ModelElementProcessor does not have an accessible parameterless constructor."), - @Diagnostic(type = ErroneousSourceTargetMapper6.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 16, message = + line = 17, message = "No target bean properties found: can't map property \"org.mapstruct.ap.test.NoProperties " + "foo.wrapped\" to" + " \"org.mapstruct.ap.test.selection.generics.TypeA " diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/ErroneousSourceTargetMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/ErroneousSourceTargetMapper6.java index 731b743b8..210cc3692 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/ErroneousSourceTargetMapper6.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/ErroneousSourceTargetMapper6.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.selection.generics; import org.mapstruct.Mapper; +import org.mapstruct.ObjectFactory; import org.mapstruct.factory.Mappers; @Mapper( uses = GenericTypeMapper.class ) @@ -14,4 +15,11 @@ public interface ErroneousSourceTargetMapper6 { ErroneousSourceTargetMapper6 INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapper6.class ); ErroneousTarget6 sourceToTarget(ErroneousSource6 source); + + // We are testing that we can't create the nested + // not whether or not we can instantiate the WildCardSuperWrapper + @ObjectFactory + default WildCardSuperWrapper createWildCardSuperWrapper() { + return new WildCardSuperWrapper<>( null ); + } }