#2069 Refactor target reference to report errors during bean mapping instead of creation of the target reference

With this we can more easily go in the direction of using constructor to map into target beans.
This commit is contained in:
Filip Hrisafov 2020-04-12 13:27:31 +02:00
parent 1bbc4e1ca8
commit c58f80cc5f
19 changed files with 436 additions and 455 deletions

View File

@ -122,7 +122,9 @@
<!-- Checks for Size Violations. --> <!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html --> <!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="MethodLength"/> <module name="MethodLength">
<property name="max" value="200"/>
</module>
<module name="ParameterNumber"> <module name="ParameterNumber">
<property name="max" value="10"/> <property name="max" value="10"/>
</module> </module>

View File

@ -22,12 +22,13 @@ import java.util.stream.Collectors;
import javax.lang.model.type.DeclaredType; import javax.lang.model.type.DeclaredType;
import javax.tools.Diagnostic; 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.ConstantMappingBuilder;
import org.mapstruct.ap.internal.model.PropertyMapping.JavaExpressionMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.JavaExpressionMappingBuilder;
import org.mapstruct.ap.internal.model.PropertyMapping.PropertyMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.PropertyMappingBuilder;
import org.mapstruct.ap.internal.model.beanmapping.MappingReference; import org.mapstruct.ap.internal.model.beanmapping.MappingReference;
import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; 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.SourceReference;
import org.mapstruct.ap.internal.model.beanmapping.TargetReference; import org.mapstruct.ap.internal.model.beanmapping.TargetReference;
import org.mapstruct.ap.internal.model.common.BuilderType; 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.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.SourceMethod; 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.Message;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.Accessor;
@ -96,7 +95,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
public Builder sourceMethod(SourceMethod sourceMethod) { public Builder sourceMethod(SourceMethod sourceMethod) {
this.method = sourceMethod; this.method = sourceMethod;
this.mappingReferences = forSourceMethod( sourceMethod, ctx.getMessager(), ctx.getTypeFactory() );
return this; return this;
} }
@ -131,6 +129,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
MethodReference factoryMethod = null; MethodReference factoryMethod = null;
// determine which return type to construct // determine which return type to construct
boolean cannotConstructReturnType = false;
if ( !method.getReturnType().isVoid() ) { if ( !method.getReturnType().isVoid() ) {
Type returnTypeImpl = getReturnTypeToConstructFromSelectionParameters( selectionParameters ); Type returnTypeImpl = getReturnTypeToConstructFromSelectionParameters( selectionParameters );
if ( returnTypeImpl != null ) { if ( returnTypeImpl != null ) {
@ -138,6 +137,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
if ( factoryMethod != null || canResultTypeFromBeanMappingBeConstructed( returnTypeImpl ) ) { if ( factoryMethod != null || canResultTypeFromBeanMappingBeConstructed( returnTypeImpl ) ) {
returnTypeToConstruct = returnTypeImpl; returnTypeToConstruct = returnTypeImpl;
} }
else {
cannotConstructReturnType = true;
}
} }
else if ( isBuilderRequired() ) { else if ( isBuilderRequired() ) {
returnTypeImpl = returnTypeBuilder.getBuilder(); returnTypeImpl = returnTypeBuilder.getBuilder();
@ -145,6 +147,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) { if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) {
returnTypeToConstruct = returnTypeImpl; returnTypeToConstruct = returnTypeImpl;
} }
else {
cannotConstructReturnType = true;
}
} }
else if ( !method.isUpdateMethod() ) { else if ( !method.isUpdateMethod() ) {
returnTypeImpl = method.getReturnType(); returnTypeImpl = method.getReturnType();
@ -152,8 +157,16 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) { if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) {
returnTypeToConstruct = 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 */ /* the type that needs to be used in the mapping process as target */
Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct; Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct;
@ -187,8 +200,10 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
} }
} }
initializeMappingReferencesIfNeeded( resultTypeToMap );
// map properties with mapping // map properties with mapping
boolean mappingErrorOccurred = handleDefinedMappings(); boolean mappingErrorOccurred = handleDefinedMappings( resultTypeToMap );
if ( mappingErrorOccurred ) { if ( mappingErrorOccurred ) {
return null; return null;
} }
@ -261,6 +276,20 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
); );
} }
private void initializeMappingReferencesIfNeeded(Type resultTypeToMap) {
if ( mappingReferences == null && method instanceof SourceMethod ) {
Set<String> 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. * @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 * 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() PropertyMapping propertyMapping = new PropertyMappingBuilder()
.mappingContext( ctx ) .mappingContext( ctx )
.sourceMethod( method ) .sourceMethod( method )
.targetWriteAccessor( unprocessedTargetProperties.get( propertyName ) ) .target(
.targetReadAccessor( targetPropertyReadAccessor ) propertyName,
.targetPropertyName( propertyName ) targetPropertyReadAccessor,
unprocessedTargetProperties.get( propertyName )
)
.sourceReference( reference ) .sourceReference( reference )
.existingVariableNames( existingVariableNames ) .existingVariableNames( existingVariableNames )
.dependsOn( mappingRefs.collectNestedDependsOn() ) .dependsOn( mappingRefs.collectNestedDependsOn() )
@ -464,22 +495,24 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
* <p> * <p>
* It is furthermore checked whether the given mappings are correct. When an error occurs, the method continues * It is furthermore checked whether the given mappings are correct. When an error occurs, the method continues
* in search of more problems. * 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; boolean errorOccurred = false;
Set<String> handledTargets = new HashSet<>(); Set<String> handledTargets = new HashSet<>();
// first we have to handle nested target mappings // first we have to handle nested target mappings
if ( mappingReferences.hasNestedTargetReferences() ) { if ( mappingReferences.hasNestedTargetReferences() ) {
errorOccurred = handleDefinedNestedTargetMapping( handledTargets ); errorOccurred = handleDefinedNestedTargetMapping( handledTargets, resultTypeToMap );
} }
for ( MappingReference mapping : mappingReferences.getMappingReferences() ) { for ( MappingReference mapping : mappingReferences.getMappingReferences() ) {
if ( mapping.isValid() ) { if ( mapping.isValid() ) {
String target = mapping.getTargetReference().getShallowestPropertyName(); String target = mapping.getTargetReference().getShallowestPropertyName();
if ( !handledTargets.contains( target ) ) { if ( !handledTargets.contains( target ) ) {
if ( handleDefinedMapping( mapping, handledTargets ) ) { if ( handleDefinedMapping( mapping, resultTypeToMap, handledTargets ) ) {
errorOccurred = true; errorOccurred = true;
} }
} }
@ -504,11 +537,13 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return errorOccurred; return errorOccurred;
} }
private boolean handleDefinedNestedTargetMapping(Set<String> handledTargets) { private boolean handleDefinedNestedTargetMapping(Set<String> handledTargets, Type resultTypeToMap) {
NestedTargetPropertyMappingHolder holder = new NestedTargetPropertyMappingHolder.Builder() NestedTargetPropertyMappingHolder holder = new NestedTargetPropertyMappingHolder.Builder()
.mappingContext( ctx ) .mappingContext( ctx )
.method( method ) .method( method )
.targetPropertiesWriteAccessors( unprocessedTargetProperties )
.targetPropertyType( resultTypeToMap )
.mappingReferences( mappingReferences ) .mappingReferences( mappingReferences )
.existingVariableNames( existingVariableNames ) .existingVariableNames( existingVariableNames )
.build(); .build();
@ -517,26 +552,24 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
propertyMappings.addAll( holder.getPropertyMappings() ); propertyMappings.addAll( holder.getPropertyMappings() );
handledTargets.addAll( holder.getHandledTargets() ); handledTargets.addAll( holder.getHandledTargets() );
// Store all the unprocessed defined targets. // Store all the unprocessed defined targets.
for ( Entry<PropertyEntry, Set<MappingReference>> entry : holder.getUnprocessedDefinedTarget() for ( Entry<String, Set<MappingReference>> entry : holder.getUnprocessedDefinedTarget()
.entrySet() ) { .entrySet() ) {
if ( entry.getValue().isEmpty() ) { if ( entry.getValue().isEmpty() ) {
continue; continue;
} }
unprocessedDefinedTargets.put( entry.getKey().getName(), entry.getValue() ); unprocessedDefinedTargets.put( entry.getKey(), entry.getValue() );
} }
return holder.hasErrorOccurred(); return holder.hasErrorOccurred();
} }
private boolean handleDefinedMapping(MappingReference mappingRef, Set<String> handledTargets) { private boolean handleDefinedMapping(MappingReference mappingRef, Type resultTypeToMap,
Set<String> handledTargets) {
boolean errorOccured = false; boolean errorOccured = false;
PropertyMapping propertyMapping = null; PropertyMapping propertyMapping = null;
TargetReference targetRef = mappingRef.getTargetReference(); TargetReference targetRef = mappingRef.getTargetReference();
MappingOptions mapping = mappingRef.getMapping(); MappingOptions mapping = mappingRef.getMapping();
PropertyEntry targetProperty = first( targetRef.getPropertyEntries() );
String targetPropertyName = targetProperty.getName();
// unknown properties given via dependsOn()? // unknown properties given via dependsOn()?
for ( String dependency : mapping.getDependsOn() ) { 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 // check if source / expression / constant are not somehow handled already
if ( unprocessedDefinedTargets.containsKey( targetPropertyName ) ) { if ( unprocessedDefinedTargets.containsKey( targetPropertyName ) ) {
return false; return false;
} }
Accessor targetWriteAccessor = unprocessedTargetProperties.get( targetPropertyName );
Accessor targetReadAccessor = resultTypeToMap.getPropertyReadAccessors().get( targetPropertyName );
if ( targetWriteAccessor == null ) {
if ( targetReadAccessor == null ) {
Set<String> 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<String> 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 // check the mapping options
// its an ignored property mapping // its an ignored property mapping
if ( mapping.isIgnored() ) { if ( mapping.isIgnored() ) {
propertyMapping = null; propertyMapping = null;
handledTargets.add( targetProperty.getName() ); handledTargets.add( targetPropertyName );
} }
// its a constant // its a constant
@ -573,8 +685,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
.mappingContext( ctx ) .mappingContext( ctx )
.sourceMethod( method ) .sourceMethod( method )
.constantExpression( mapping.getConstant() ) .constantExpression( mapping.getConstant() )
.targetProperty( targetProperty ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor )
.targetPropertyName( targetPropertyName )
.formattingParameters( mapping.getFormattingParameters() ) .formattingParameters( mapping.getFormattingParameters() )
.selectionParameters( mapping.getSelectionParameters() ) .selectionParameters( mapping.getSelectionParameters() )
.options( mapping ) .options( mapping )
@ -595,8 +706,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
.sourceMethod( method ) .sourceMethod( method )
.javaExpression( mapping.getJavaExpression() ) .javaExpression( mapping.getJavaExpression() )
.existingVariableNames( existingVariableNames ) .existingVariableNames( existingVariableNames )
.targetProperty( targetProperty ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor )
.targetPropertyName( targetPropertyName )
.dependsOn( mapping.getDependsOn() ) .dependsOn( mapping.getDependsOn() )
.mirror( mapping.getMirror() ) .mirror( mapping.getMirror() )
.build(); .build();
@ -618,8 +728,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
propertyMapping = new PropertyMappingBuilder() propertyMapping = new PropertyMappingBuilder()
.mappingContext( ctx ) .mappingContext( ctx )
.sourceMethod( method ) .sourceMethod( method )
.targetProperty( targetProperty ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor )
.targetPropertyName( targetPropertyName )
.sourcePropertyName( mapping.getSourceName() ) .sourcePropertyName( mapping.getSourceName() )
.sourceReference( sourceRef ) .sourceReference( sourceRef )
.selectionParameters( mapping.getSelectionParameters() ) .selectionParameters( mapping.getSelectionParameters() )
@ -724,9 +833,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false );
PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx )
.sourceMethod( method ) .sourceMethod( method )
.targetWriteAccessor( targetPropertyWriteAccessor ) .target( targetPropertyName, targetPropertyReadAccessor, targetPropertyWriteAccessor )
.targetReadAccessor( targetPropertyReadAccessor )
.targetPropertyName( targetPropertyName )
.sourceReference( sourceRef ) .sourceReference( sourceRef )
.existingVariableNames( existingVariableNames ) .existingVariableNames( existingVariableNames )
.forgeMethodWithMappingReferences( mappingRefs ) .forgeMethodWithMappingReferences( mappingRefs )
@ -770,9 +877,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
PropertyMapping propertyMapping = new PropertyMappingBuilder() PropertyMapping propertyMapping = new PropertyMappingBuilder()
.mappingContext( ctx ) .mappingContext( ctx )
.sourceMethod( method ) .sourceMethod( method )
.targetWriteAccessor( targetProperty.getValue() ) .target( targetProperty.getKey(), targetPropertyReadAccessor, targetProperty.getValue() )
.targetReadAccessor( targetPropertyReadAccessor )
.targetPropertyName( targetProperty.getKey() )
.sourceReference( sourceRef ) .sourceReference( sourceRef )
.existingVariableNames( existingVariableNames ) .existingVariableNames( existingVariableNames )
.forgeMethodWithMappingReferences( mappingRefs ) .forgeMethodWithMappingReferences( mappingRefs )
@ -866,6 +971,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
} }
else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) { else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) {
if ( !( method instanceof ForgedMethod ) ) {
Message msg = unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ? Message msg = unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ?
Message.BEANMAPPING_UNMAPPED_TARGETS_ERROR : Message.BEANMAPPING_UNMAPPED_TARGETS_WARNING; Message.BEANMAPPING_UNMAPPED_TARGETS_ERROR : Message.BEANMAPPING_UNMAPPED_TARGETS_WARNING;
Object[] args = new Object[] { Object[] args = new Object[] {
@ -875,8 +981,15 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
Strings.join( unprocessedTargetProperties.keySet(), ", " ) Strings.join( unprocessedTargetProperties.keySet(), ", " )
) )
}; };
if ( method instanceof ForgedMethod ) {
msg = unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ? ctx.getMessager().printMessage(
method.getExecutable(),
msg,
args
);
}
else if ( !ctx.isErroneous() ) {
Message msg = unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ?
Message.BEANMAPPING_UNMAPPED_FORGED_TARGETS_ERROR : Message.BEANMAPPING_UNMAPPED_FORGED_TARGETS_ERROR :
Message.BEANMAPPING_UNMAPPED_FORGED_TARGETS_WARNING; Message.BEANMAPPING_UNMAPPED_FORGED_TARGETS_WARNING;
String sourceErrorMessage = method.getParameters().get( 0 ).getType().toString(); String sourceErrorMessage = method.getParameters().get( 0 ).getType().toString();
@ -890,19 +1003,23 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
history.createTargetPropertyName() history.createTargetPropertyName()
); );
} }
args = new Object[] { Object[] args = new Object[] {
args[0], MessageFormat.format(
"{0,choice,1#property|1<properties}: \"{1}\"",
unprocessedTargetProperties.size(),
Strings.join( unprocessedTargetProperties.keySet(), ", " )
),
sourceErrorMessage, sourceErrorMessage,
targetErrorMessage targetErrorMessage
}; };
}
ctx.getMessager().printMessage( ctx.getMessager().printMessage(
method.getExecutable(), method.getExecutable(),
msg, msg,
args args
); );
} }
}
} }
private ReportingPolicyGem getUnmappedSourcePolicy() { private ReportingPolicyGem getUnmappedSourcePolicy() {

View File

@ -240,4 +240,8 @@ public class MappingBuilderContext {
private boolean canGenerateAutoSubMappingFor(Type type) { private boolean canGenerateAutoSubMappingFor(Type type) {
return type.getTypeElement() != null && !SUB_MAPPING_EXCLUSION_PROVIDER.isExcluded( type.getTypeElement() ); return type.getTypeElement() != null && !SUB_MAPPING_EXCLUSION_PROVIDER.isExcluded( type.getTypeElement() );
} }
public boolean isErroneous() {
return messager.isErroneous();
}
} }

View File

@ -21,8 +21,12 @@ import org.mapstruct.ap.internal.model.beanmapping.PropertyEntry;
import org.mapstruct.ap.internal.model.beanmapping.SourceReference; import org.mapstruct.ap.internal.model.beanmapping.SourceReference;
import org.mapstruct.ap.internal.model.beanmapping.TargetReference; import org.mapstruct.ap.internal.model.beanmapping.TargetReference;
import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.MappingOptions;
import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.accessor.Accessor;
import static org.mapstruct.ap.internal.util.Collections.first; import static org.mapstruct.ap.internal.util.Collections.first;
@ -37,13 +41,13 @@ public class NestedTargetPropertyMappingHolder {
private final List<Parameter> processedSourceParameters; private final List<Parameter> processedSourceParameters;
private final Set<String> handledTargets; private final Set<String> handledTargets;
private final List<PropertyMapping> propertyMappings; private final List<PropertyMapping> propertyMappings;
private final Map<PropertyEntry, Set<MappingReference>> unprocessedDefinedTarget; private final Map<String, Set<MappingReference>> unprocessedDefinedTarget;
private final boolean errorOccurred; private final boolean errorOccurred;
public NestedTargetPropertyMappingHolder( public NestedTargetPropertyMappingHolder(
List<Parameter> processedSourceParameters, Set<String> handledTargets, List<Parameter> processedSourceParameters, Set<String> handledTargets,
List<PropertyMapping> propertyMappings, List<PropertyMapping> propertyMappings,
Map<PropertyEntry, Set<MappingReference>> unprocessedDefinedTarget, boolean errorOccurred) { Map<String, Set<MappingReference>> unprocessedDefinedTarget, boolean errorOccurred) {
this.processedSourceParameters = processedSourceParameters; this.processedSourceParameters = processedSourceParameters;
this.handledTargets = handledTargets; this.handledTargets = handledTargets;
this.propertyMappings = propertyMappings; 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 * @return a map of all the unprocessed defined targets that can be applied to name forged base methods
*/ */
public Map<PropertyEntry, Set<MappingReference>> getUnprocessedDefinedTarget() { public Map<String, Set<MappingReference>> getUnprocessedDefinedTarget() {
return unprocessedDefinedTarget; return unprocessedDefinedTarget;
} }
@ -94,6 +98,9 @@ public class NestedTargetPropertyMappingHolder {
private Set<String> existingVariableNames; private Set<String> existingVariableNames;
private List<PropertyMapping> propertyMappings; private List<PropertyMapping> propertyMappings;
private Set<String> handledTargets; private Set<String> handledTargets;
private Map<String, Accessor> targetPropertiesWriteAccessors;
private Type targetType;
private boolean errorOccurred;
public Builder mappingReferences(MappingReferences mappingReferences) { public Builder mappingReferences(MappingReferences mappingReferences) {
this.mappingReferences = mappingReferences; this.mappingReferences = mappingReferences;
@ -115,6 +122,16 @@ public class NestedTargetPropertyMappingHolder {
return this; return this;
} }
public Builder targetPropertiesWriteAccessors(Map<String, Accessor> targetPropertiesWriteAccessors) {
this.targetPropertiesWriteAccessors = targetPropertiesWriteAccessors;
return this;
}
public Builder targetPropertyType(Type targetType) {
this.targetType = targetType;
return this;
}
public NestedTargetPropertyMappingHolder build() { public NestedTargetPropertyMappingHolder build() {
List<Parameter> processedSourceParameters = new ArrayList<>(); List<Parameter> processedSourceParameters = new ArrayList<>();
handledTargets = new HashSet<>(); 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 // 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. // properties we get the new mappings as if the first property did not exist.
GroupedTargetReferences groupedByTP = groupByTargetReferences( ); GroupedTargetReferences groupedByTP = groupByTargetReferences( );
Map<PropertyEntry, Set<MappingReference>> unprocessedDefinedTarget = new LinkedHashMap<>(); Map<String, Set<MappingReference>> unprocessedDefinedTarget = new LinkedHashMap<>();
for ( Map.Entry<PropertyEntry, Set<MappingReference>> entryByTP : for ( Map.Entry<String, Set<MappingReference>> entryByTP :
groupedByTP.poppedTargetReferences.entrySet() ) { 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 //Now we are grouping the already popped mappings by the source parameter(s) of the method
GroupedBySourceParameters groupedBySourceParam = groupBySourceParameter( GroupedBySourceParameters groupedBySourceParam = groupBySourceParameter(
entryByTP.getValue(), entryByTP.getValue(),
@ -188,7 +205,7 @@ public class NestedTargetPropertyMappingHolder {
.type( sourceEntry.getType() ) .type( sourceEntry.getType() )
.readAccessor( sourceEntry.getReadAccessor() ) .readAccessor( sourceEntry.getReadAccessor() )
.presenceChecker( sourceEntry.getPresenceChecker() ) .presenceChecker( sourceEntry.getPresenceChecker() )
.name( targetProperty.getName() ) .name( targetProperty )
.build(); .build();
// If we have multiple source parameters that are mapped to the target reference, or // If we have multiple source parameters that are mapped to the target reference, or
@ -205,7 +222,7 @@ public class NestedTargetPropertyMappingHolder {
propertyMappings.add( propertyMapping ); propertyMappings.add( propertyMapping );
} }
handledTargets.add( entryByTP.getKey().getName() ); handledTargets.add( entryByTP.getKey() );
} }
// For the nonNested mappings (assymetric) Mappings we also forge mappings // For the nonNested mappings (assymetric) Mappings we also forge mappings
@ -216,7 +233,7 @@ public class NestedTargetPropertyMappingHolder {
new MappingReferences( groupedSourceReferences.nonNested, true ); new MappingReferences( groupedSourceReferences.nonNested, true );
SourceReference reference = new SourceReference.BuilderFromProperty() SourceReference reference = new SourceReference.BuilderFromProperty()
.sourceParameter( sourceParameter ) .sourceParameter( sourceParameter )
.name( targetProperty.getName() ) .name( targetProperty )
.build(); .build();
boolean forceUpdateMethodForNonNested = boolean forceUpdateMethodForNonNested =
@ -236,7 +253,7 @@ public class NestedTargetPropertyMappingHolder {
propertyMappings.add( propertyMapping ); propertyMappings.add( propertyMapping );
} }
handledTargets.add( entryByTP.getKey().getName() ); handledTargets.add( entryByTP.getKey() );
} }
handleSourceParameterMappings( handleSourceParameterMappings(
@ -255,7 +272,7 @@ public class NestedTargetPropertyMappingHolder {
handledTargets, handledTargets,
propertyMappings, propertyMappings,
unprocessedDefinedTarget, unprocessedDefinedTarget,
groupedByTP.errorOccurred errorOccurred
); );
} }
@ -268,7 +285,7 @@ public class NestedTargetPropertyMappingHolder {
* @param forceUpdateMethod whether we need to force an update method * @param forceUpdateMethod whether we need to force an update method
*/ */
private void handleSourceParameterMappings(Set<MappingReference> sourceParameterMappings, private void handleSourceParameterMappings(Set<MappingReference> sourceParameterMappings,
PropertyEntry targetProperty, Parameter sourceParameter, String targetProperty, Parameter sourceParameter,
boolean forceUpdateMethod) { boolean forceUpdateMethod) {
if ( !sourceParameterMappings.isEmpty() ) { if ( !sourceParameterMappings.isEmpty() ) {
// The source parameter mappings have no mappings, the source name is actually the parameter itself // 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 ); new MappingReferences( Collections.emptySet(), false, true );
SourceReference reference = new SourceReference.BuilderFromProperty() SourceReference reference = new SourceReference.BuilderFromProperty()
.sourceParameter( sourceParameter ) .sourceParameter( sourceParameter )
.name( targetProperty.getName() ) .name( targetProperty )
.build(); .build();
PropertyMapping propertyMapping = createPropertyMappingForNestedTarget( PropertyMapping propertyMapping = createPropertyMappingForNestedTarget(
@ -290,7 +307,7 @@ public class NestedTargetPropertyMappingHolder {
propertyMappings.add( propertyMapping ); propertyMappings.add( propertyMapping );
} }
handledTargets.add( targetProperty.getName() ); handledTargets.add( targetProperty );
} }
} }
@ -340,16 +357,11 @@ public class NestedTargetPropertyMappingHolder {
*/ */
private GroupedTargetReferences groupByTargetReferences( ) { private GroupedTargetReferences groupByTargetReferences( ) {
// group all mappings based on the top level name before popping // group all mappings based on the top level name before popping
Map<PropertyEntry, Set<MappingReference>> mappingsKeyedByProperty = new LinkedHashMap<>(); Map<String, Set<MappingReference>> mappingsKeyedByProperty = new LinkedHashMap<>();
Map<PropertyEntry, Set<MappingReference>> singleTargetReferences = new LinkedHashMap<>(); Map<String, Set<MappingReference>> singleTargetReferences = new LinkedHashMap<>();
boolean errorOccurred = false;
for ( MappingReference mapping : mappingReferences.getMappingReferences() ) { for ( MappingReference mapping : mappingReferences.getMappingReferences() ) {
TargetReference targetReference = mapping.getTargetReference(); TargetReference targetReference = mapping.getTargetReference();
if ( !targetReference.isValid() ) { String property = first( targetReference.getPropertyEntries() );
errorOccurred = true;
continue;
}
PropertyEntry property = first( targetReference.getPropertyEntries() );
MappingReference newMapping = mapping.popTargetReference(); MappingReference newMapping = mapping.popTargetReference();
if ( newMapping != null ) { if ( newMapping != null ) {
// group properties on current name. // 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, private PropertyMapping createPropertyMappingForNestedTarget(MappingReferences mappingReferences,
PropertyEntry targetProperty, String targetPropertyName,
SourceReference sourceReference, SourceReference sourceReference,
boolean forceUpdateMethod) { boolean forceUpdateMethod) {
Accessor targetWriteAccessor = targetPropertiesWriteAccessors.get( targetPropertyName );
Accessor targetReadAccessor = targetType.getPropertyReadAccessors().get( targetPropertyName );
if ( targetWriteAccessor == null ) {
Set<String> readAccessors = targetType.getPropertyReadAccessors().keySet();
String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors );
for ( MappingReference mappingReference : mappingReferences.getMappingReferences() ) {
MappingOptions mapping = mappingReference.getMapping();
List<String> 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() return new PropertyMapping.PropertyMappingBuilder()
.mappingContext( mappingContext ) .mappingContext( mappingContext )
.sourceMethod( method ) .sourceMethod( method )
.targetProperty( targetProperty ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor )
.targetPropertyName( targetProperty.getName() )
.sourceReference( sourceReference ) .sourceReference( sourceReference )
.existingVariableNames( existingVariableNames ) .existingVariableNames( existingVariableNames )
.dependsOn( mappingReferences.collectNestedDependsOn() ) .dependsOn( mappingReferences.collectNestedDependsOn() )
@ -685,21 +728,19 @@ public class NestedTargetPropertyMappingHolder {
* references (target references that were not nested). * references (target references that were not nested).
*/ */
private static class GroupedTargetReferences { private static class GroupedTargetReferences {
private final Map<PropertyEntry, Set<MappingReference>> poppedTargetReferences; private final Map<String, Set<MappingReference>> poppedTargetReferences;
private final Map<PropertyEntry, Set<MappingReference>> singleTargetReferences; private final Map<String, Set<MappingReference>> singleTargetReferences;
private final boolean errorOccurred;
private GroupedTargetReferences(Map<PropertyEntry, Set<MappingReference>> poppedTargetReferences, private GroupedTargetReferences(Map<String, Set<MappingReference>> poppedTargetReferences,
Map<PropertyEntry, Set<MappingReference>> singleTargetReferences, boolean errorOccurred) { Map<String, Set<MappingReference>> singleTargetReferences) {
this.poppedTargetReferences = poppedTargetReferences; this.poppedTargetReferences = poppedTargetReferences;
this.singleTargetReferences = singleTargetReferences; this.singleTargetReferences = singleTargetReferences;
this.errorOccurred = errorOccurred;
} }
@Override @Override
public String toString() { public String toString() {
return "GroupedTargetReferences{" + "poppedTargetReferences=" + poppedTargetReferences return "GroupedTargetReferences{" + "poppedTargetReferences=" + poppedTargetReferences
+ ", singleTargetReferences=" + singleTargetReferences + ", errorOccurred=" + errorOccurred + '}'; + ", singleTargetReferences=" + singleTargetReferences + "}";
} }
} }

View File

@ -94,21 +94,9 @@ public class PropertyMapping extends ModelElement {
return super.method( sourceMethod ); return super.method( sourceMethod );
} }
public T targetProperty(PropertyEntry targetProp) { public T target(String targetPropertyName, Accessor targetReadAccessor, Accessor targetWriteAccessor) {
this.targetReadAccessor = targetProp.getReadAccessor(); this.targetPropertyName = targetPropertyName;
this.targetWriteAccessor = targetProp.getWriteAccessor();
this.targetType = targetProp.getType();
this.targetBuilderType = targetProp.getBuilderType();
this.targetWriteAccessorType = targetWriteAccessor.getAccessorType();
return (T) this;
}
public T targetReadAccessor(Accessor targetReadAccessor) {
this.targetReadAccessor = targetReadAccessor; this.targetReadAccessor = targetReadAccessor;
return (T) this;
}
public T targetWriteAccessor(Accessor targetWriteAccessor) {
this.targetWriteAccessor = targetWriteAccessor; this.targetWriteAccessor = targetWriteAccessor;
this.targetType = ctx.getTypeFactory().getType( targetWriteAccessor.getAccessedType() ); this.targetType = ctx.getTypeFactory().getType( targetWriteAccessor.getAccessedType() );
BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); BuilderGem builder = method.getOptions().getBeanMapping().getBuilder();
@ -122,11 +110,6 @@ public class PropertyMapping extends ModelElement {
return (T) this; return (T) this;
} }
public T targetPropertyName(String targetPropertyName) {
this.targetPropertyName = targetPropertyName;
return (T) this;
}
public T sourcePropertyName(String sourcePropertyName) { public T sourcePropertyName(String sourcePropertyName) {
this.sourcePropertyName = sourcePropertyName; this.sourcePropertyName = sourcePropertyName;
return (T) this; return (T) this;
@ -326,6 +309,11 @@ public class PropertyMapping extends ModelElement {
* Report that a mapping could not be created. * Report that a mapping could not be created.
*/ */
private void reportCannotCreateMapping() { 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 ) { if ( method instanceof ForgedMethod && ( (ForgedMethod) method ).getHistory() != null ) {
// The history that is part of the ForgedMethod misses the information from the current right hand // 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. // 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 ) .existingVariableNames( existingVariableNames )
.mappingContext( ctx ) .mappingContext( ctx )
.sourceMethod( method ) .sourceMethod( method )
.targetPropertyName( targetPropertyName ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor )
.targetReadAccessor( targetReadAccessor )
.targetWriteAccessor( targetWriteAccessor )
.build(); .build();
return build.getAssignment(); return build.getAssignment();
} }
@ -378,9 +364,7 @@ public class PropertyMapping extends ModelElement {
.existingVariableNames( existingVariableNames ) .existingVariableNames( existingVariableNames )
.mappingContext( ctx ) .mappingContext( ctx )
.sourceMethod( method ) .sourceMethod( method )
.targetPropertyName( targetPropertyName ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor )
.targetReadAccessor( targetReadAccessor )
.targetWriteAccessor( targetWriteAccessor )
.build(); .build();
return build.getAssignment(); return build.getAssignment();
} }

View File

@ -80,11 +80,7 @@ public class MappingReference {
} }
public boolean isValid( ) { public boolean isValid( ) {
boolean result = false; return sourceReference == null || sourceReference.isValid();
if ( targetReference.isValid() ) {
result = sourceReference != null ? sourceReference.isValid() : true;
}
return result;
} }
@Override @Override

View File

@ -11,6 +11,7 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; 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.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.MappingOptions;
import org.mapstruct.ap.internal.model.source.SourceMethod; import org.mapstruct.ap.internal.model.source.SourceMethod;
@ -29,7 +30,10 @@ public class MappingReferences {
return EMPTY; return EMPTY;
} }
public static MappingReferences forSourceMethod(SourceMethod sourceMethod, FormattingMessager messager, public static MappingReferences forSourceMethod(SourceMethod sourceMethod,
Type targetType,
Set<String> targetProperties,
FormattingMessager messager,
TypeFactory typeFactory) { TypeFactory typeFactory) {
Set<MappingReference> references = new LinkedHashSet<>(); Set<MappingReference> references = new LinkedHashSet<>();
@ -49,6 +53,8 @@ public class MappingReferences {
.method( sourceMethod ) .method( sourceMethod )
.messager( messager ) .messager( messager )
.typeFactory( typeFactory ) .typeFactory( typeFactory )
.targetProperties( targetProperties )
.targetType( targetType )
.build(); .build();
// add when inverse is also valid // add when inverse is also valid
@ -121,7 +127,7 @@ public class MappingReferences {
for ( MappingReference mappingRef : mappingReferences ) { for ( MappingReference mappingRef : mappingReferences ) {
TargetReference targetReference = mappingRef.getTargetReference(); TargetReference targetReference = mappingRef.getTargetReference();
if ( targetReference.isValid() && targetReference.isNested()) { if ( targetReference.isNested()) {
return true; return true;
} }
@ -141,7 +147,7 @@ public class MappingReferences {
private static boolean isValidWhenInversed(MappingReference mappingRef) { private static boolean isValidWhenInversed(MappingReference mappingRef) {
MappingOptions mapping = mappingRef.getMapping(); MappingOptions mapping = mappingRef.getMapping();
if ( mapping.getInheritContext() != null && mapping.getInheritContext().isReversed() ) { if ( mapping.getInheritContext() != null && mapping.getInheritContext().isReversed() ) {
return mappingRef.getTargetReference().isValid() && ( mappingRef.getSourceReference() == null || return ( mappingRef.getSourceReference() == null ||
mappingRef.getSourceReference().isValid() ); mappingRef.getSourceReference().isValid() );
} }
return true; return true;

View File

@ -7,55 +7,33 @@ package org.mapstruct.ap.internal.model.beanmapping;
import java.util.Arrays; 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.model.common.Type;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.Accessor;
/** /**
* A PropertyEntry contains information on the name, readAccessor (for source), readAccessor and writeAccessor * A PropertyEntry contains information on the name, readAccessor and presenceCheck (for source)
* (for targets) and return type of a property. * and return type of a property.
*/ */
public class PropertyEntry { public class PropertyEntry {
private final String[] fullName; private final String[] fullName;
private final Accessor readAccessor; private final Accessor readAccessor;
private final Accessor writeAccessor;
private final Accessor presenceChecker; private final Accessor presenceChecker;
private final Type type; private final Type type;
private final BuilderType builderType;
/** /**
* Constructor used to create {@link TargetReference} property entries from a mapping * Constructor used to create {@link TargetReference} property entries from a mapping
* *
* @param fullName * @param fullName
* @param readAccessor * @param readAccessor
* @param writeAccessor
* @param type * @param type
*/ */
private PropertyEntry(String[] fullName, Accessor readAccessor, Accessor writeAccessor, private PropertyEntry(String[] fullName, Accessor readAccessor, Accessor presenceChecker, Type type) {
Accessor presenceChecker, Type type, BuilderType builderType) {
this.fullName = fullName; this.fullName = fullName;
this.readAccessor = readAccessor; this.readAccessor = readAccessor;
this.writeAccessor = writeAccessor;
this.presenceChecker = presenceChecker; this.presenceChecker = presenceChecker;
this.type = type; 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, public static PropertyEntry forSourceReference(String[] name, Accessor readAccessor,
Accessor presenceChecker, Type type) { Accessor presenceChecker, Type type) {
return new PropertyEntry( name, readAccessor, null, presenceChecker, type, null ); return new PropertyEntry( name, readAccessor, presenceChecker, type );
} }
public String getName() { public String getName() {
@ -80,10 +58,6 @@ public class PropertyEntry {
return readAccessor; return readAccessor;
} }
public Accessor getWriteAccessor() {
return writeAccessor;
}
public Accessor getPresenceChecker() { public Accessor getPresenceChecker() {
return presenceChecker; return presenceChecker;
} }
@ -92,24 +66,10 @@ public class PropertyEntry {
return type; return type;
} }
public BuilderType getBuilderType() {
return builderType;
}
public String getFullName() { public String getFullName() {
return Strings.join( Arrays.asList( fullName ), "." ); 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 @Override
public int hashCode() { public int hashCode() {
int hash = 7; int hash = 7;

View File

@ -7,26 +7,21 @@ package org.mapstruct.ap.internal.model.beanmapping;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue; 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.Parameter;
import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.MappingOptions;
import org.mapstruct.ap.internal.model.source.Method; 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.FormattingMessager;
import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings; 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; import static org.mapstruct.ap.internal.util.Collections.first;
@ -48,11 +43,77 @@ import static org.mapstruct.ap.internal.util.Collections.first;
* <li>{@code propertyEntries[1]} will describe {@code propB}</li> * <li>{@code propertyEntries[1]} will describe {@code propB}</li>
* </ul> * </ul>
* *
* After building, {@link #isValid()} will return true when when no problems are detected during building.
*
* @author Sjaak Derksen * @author Sjaak Derksen
*/ */
public class TargetReference extends AbstractReference { public class TargetReference {
private final List<String> pathProperties;
private final Parameter parameter;
private final List<String> propertyEntries;
public TargetReference(Parameter parameter, List<String> propertyEntries) {
this( parameter, propertyEntries, Collections.emptyList() );
}
public TargetReference(Parameter parameter, List<String> propertyEntries, List<String> pathProperties) {
this.pathProperties = pathProperties;
this.parameter = parameter;
this.propertyEntries = propertyEntries;
}
public List<String> getPathProperties() {
return pathProperties;
}
public List<String> getPropertyEntries() {
return propertyEntries;
}
public List<String> getElementNames() {
List<String> 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}. * Builds a {@link TargetReference} from an {@code @Mappping}.
@ -62,21 +123,15 @@ public class TargetReference extends AbstractReference {
private Method method; private Method method;
private FormattingMessager messager; private FormattingMessager messager;
private TypeFactory typeFactory; private TypeFactory typeFactory;
private Set<String> targetProperties;
private Type targetType;
// mapping parameters // mapping parameters
private boolean isReversed = false;
private boolean isIgnored = false;
private String targetName = null; private String targetName = null;
private MappingOptions mapping;
private AnnotationMirror annotationMirror = null; private AnnotationMirror annotationMirror = null;
private AnnotationValue targetAnnotationValue = null; private AnnotationValue targetAnnotationValue = null;
private AnnotationValue sourceAnnotationValue = 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) { public Builder messager(FormattingMessager messager) {
this.messager = messager; this.messager = messager;
@ -84,11 +139,7 @@ public class TargetReference extends AbstractReference {
} }
public Builder mapping(MappingOptions mapping) { public Builder mapping(MappingOptions mapping) {
if ( mapping.getInheritContext() != null ) { this.mapping = mapping;
this.isReversed = mapping.getInheritContext().isReversed();
this.templateMethod = mapping.getInheritContext().getTemplateMethod();
}
this.isIgnored = mapping.isIgnored();
this.targetName = mapping.getTargetName(); this.targetName = mapping.getTargetName();
this.annotationMirror = mapping.getMirror(); this.annotationMirror = mapping.getMirror();
this.targetAnnotationValue = mapping.getTargetAnnotationValue(); this.targetAnnotationValue = mapping.getTargetAnnotationValue();
@ -106,11 +157,22 @@ public class TargetReference extends AbstractReference {
return this; return this;
} }
public Builder targetProperties(Set<String> targetProperties) {
this.targetProperties = targetProperties;
return this;
}
public Builder targetType(Type targetType) {
this.targetType = targetType;
return this;
}
public TargetReference build() { public TargetReference build() {
Objects.requireNonNull( method ); Objects.requireNonNull( method );
Objects.requireNonNull( typeFactory ); Objects.requireNonNull( typeFactory );
Objects.requireNonNull( messager ); Objects.requireNonNull( messager );
Objects.requireNonNull( targetType );
if ( targetName == null ) { if ( targetName == null ) {
return null; return null;
@ -130,185 +192,27 @@ public class TargetReference extends AbstractReference {
String[] segments = targetNameTrimmed.split( "\\." ); String[] segments = targetNameTrimmed.split( "\\." );
Parameter parameter = method.getMappingTargetParameter(); Parameter parameter = method.getMappingTargetParameter();
boolean foundEntryMatch;
Type resultType = typeBasedOnMethod( method.getResultType() );
// there can be 4 situations // there can be 4 situations
// 1. Return type // 1. Return type
// 2. An inverse target reference where the source parameter name is used in the original mapping // 2. An inverse target reference where the source parameter name is used in the original mapping
// 3. @MappingTarget, with // 3. @MappingTarget, with
// 4. or without parameter name. // 4. or without parameter name.
String[] targetPropertyNames = segments; String[] targetPropertyNames = segments;
List<PropertyEntry> entries = getTargetEntries( resultType, targetPropertyNames ); if ( segments.length > 1 ) {
foundEntryMatch = (entries.size() == targetPropertyNames.length); String firstTargetProperty = targetPropertyNames[0];
if ( !foundEntryMatch && segments.length > 1 // If the first target property is not within the defined target properties, then check if it matches
&& matchesSourceOrTargetParameter( segments[0], parameter, isReversed ) ) { // the source or target parameter
if ( !targetProperties.contains( firstTargetProperty ) ) {
if ( matchesSourceOrTargetParameter( firstTargetProperty, parameter ) ) {
targetPropertyNames = Arrays.copyOfRange( segments, 1, segments.length ); 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<PropertyEntry> getTargetEntries(Type type, String[] entryNames) {
// initialize
CollectionMappingStrategyGem cms = method.getOptions().getMapper().getCollectionMappingStrategy();
List<PropertyEntry> 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
);
}
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;
}
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 );
}
}
/** List<String> entries = new ArrayList<>( Arrays.asList( targetPropertyNames ) );
* 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;
}
/** return new TargetReference( parameter, entries );
* 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}:
* <ul>
* <li>{@code writeAccessor} is {@code null}</li>
* <li>It is for the last entry</li>
* <li>A read accessor does not exist, or the mapping is not ignored</li>
* </ul>
*
* @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 );
} }
/** /**
@ -317,16 +221,14 @@ public class TargetReference extends AbstractReference {
* *
* @param segment that needs to be checked * @param segment that needs to be checked
* @param targetParameter the target parameter if it exists * @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 * @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 * {@code inverseSourceParameter} when this is a inverse {@link TargetReference} is being built, {@code
* false} otherwise * 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 ); 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. * a inverse is created.
* *
* @param segment that needs to be checked* * @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 * @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; 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 // there is only source parameter by definition when applying @InheritInverseConfiguration
Parameter inverseSourceParameter = first( templateMethod.getSourceParameters() ); Parameter inverseSourceParameter = first( templateMethod.getSourceParameters() );
result = inverseSourceParameter.getName().equals( segment ); result = inverseSourceParameter.getName().equals( segment );
@ -359,87 +264,19 @@ public class TargetReference extends AbstractReference {
} }
} }
private TargetReference(Parameter sourceParameter, List<PropertyEntry> targetPropertyEntries, boolean isValid) {
super( sourceParameter, targetPropertyEntries, isValid );
}
public TargetReference pop() { public TargetReference pop() {
if ( getPropertyEntries().size() > 1 ) { if ( getPropertyEntries().size() > 1 ) {
List<PropertyEntry> newPropertyEntries = new ArrayList<>( getPropertyEntries().size() - 1 ); List<String> newPathProperties = new ArrayList<>( this.pathProperties );
for ( PropertyEntry propertyEntry : getPropertyEntries() ) { newPathProperties.add( getPropertyEntries().get( 0 ) );
PropertyEntry newPropertyEntry = propertyEntry.pop(); return new TargetReference(
if ( newPropertyEntry != null ) { null,
newPropertyEntries.add( newPropertyEntry ); getPropertyEntries().subList( 1, getPropertyEntries().size() ),
} newPathProperties
} );
return new TargetReference( null, newPropertyEntries, isValid() );
} }
else { else {
return null; 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<String> readAccessors = nextType.getPropertyReadAccessors().keySet();
String mostSimilarProperty = Strings.getMostSimilarWord( entryNames[index], readAccessors );
List<String> elements = new ArrayList<>( Arrays.asList( entryNames ).subList( 0, index ) );
elements.add( mostSimilarProperty );
printErrorMessage( Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE, Strings.join( elements, "." ) );
}
}
} }

View File

@ -16,6 +16,7 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.util.Elements; import javax.lang.model.util.Elements;
import javax.lang.model.util.Types; import javax.lang.model.util.Types;
@ -50,6 +51,7 @@ public class MappingOptions extends DelegatingOptions {
private final boolean isIgnored; private final boolean isIgnored;
private final Set<String> dependsOn; private final Set<String> dependsOn;
private final Element element;
private final AnnotationValue sourceAnnotationValue; private final AnnotationValue sourceAnnotationValue;
private final AnnotationValue targetAnnotationValue; private final AnnotationValue targetAnnotationValue;
private final MappingGem mapping; private final MappingGem mapping;
@ -135,6 +137,7 @@ public class MappingOptions extends DelegatingOptions {
MappingOptions options = new MappingOptions( MappingOptions options = new MappingOptions(
mapping.target().getValue(), mapping.target().getValue(),
method,
mapping.target().getAnnotationValue(), mapping.target().getAnnotationValue(),
source, source,
mapping.source().getAnnotationValue(), mapping.source().getAnnotationValue(),
@ -169,6 +172,7 @@ public class MappingOptions extends DelegatingOptions {
null, null,
null, null,
null, null,
null,
true, true,
null, null,
null, null,
@ -249,6 +253,7 @@ public class MappingOptions extends DelegatingOptions {
@SuppressWarnings("checkstyle:parameternumber") @SuppressWarnings("checkstyle:parameternumber")
private MappingOptions(String targetName, private MappingOptions(String targetName,
Element element,
AnnotationValue targetAnnotationValue, AnnotationValue targetAnnotationValue,
String sourceName, String sourceName,
AnnotationValue sourceAnnotationValue, AnnotationValue sourceAnnotationValue,
@ -266,6 +271,7 @@ public class MappingOptions extends DelegatingOptions {
) { ) {
super( next ); super( next );
this.targetName = targetName; this.targetName = targetName;
this.element = element;
this.targetAnnotationValue = targetAnnotationValue; this.targetAnnotationValue = targetAnnotationValue;
this.sourceName = sourceName; this.sourceName = sourceName;
this.sourceAnnotationValue = sourceAnnotationValue; this.sourceAnnotationValue = sourceAnnotationValue;
@ -377,6 +383,10 @@ public class MappingOptions extends DelegatingOptions {
return Optional.ofNullable( mapping ).map( MappingGem::mirror ).orElse( null ); return Optional.ofNullable( mapping ).map( MappingGem::mirror ).orElse( null );
} }
public Element getElement() {
return element;
}
public AnnotationValue getDependsOnAnnotationValue() { public AnnotationValue getDependsOnAnnotationValue() {
return Optional.ofNullable( mapping ) return Optional.ofNullable( mapping )
.map( MappingGem::dependsOn ) .map( MappingGem::dependsOn )
@ -434,6 +444,7 @@ public class MappingOptions extends DelegatingOptions {
MappingOptions mappingOptions = new MappingOptions( MappingOptions mappingOptions = new MappingOptions(
sourceName != null ? sourceName : targetName, sourceName != null ? sourceName : targetName,
templateMethod.getExecutable(),
targetAnnotationValue, targetAnnotationValue,
sourceName != null ? targetName : null, sourceName != null ? targetName : null,
sourceAnnotationValue, sourceAnnotationValue,
@ -462,6 +473,7 @@ public class MappingOptions extends DelegatingOptions {
BeanMappingOptions beanMappingOptions ) { BeanMappingOptions beanMappingOptions ) {
MappingOptions mappingOptions = new MappingOptions( MappingOptions mappingOptions = new MappingOptions(
targetName, targetName,
templateMethod.getExecutable(),
targetAnnotationValue, targetAnnotationValue,
sourceName, sourceName,
sourceAnnotationValue, sourceAnnotationValue,

View File

@ -167,6 +167,7 @@ public class DefaultModelElementProcessorContext implements ProcessorContext {
} }
} }
@Override
public boolean isErroneous() { public boolean isErroneous() {
return isErroneous; return isErroneous;
} }

View File

@ -368,10 +368,10 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
.returnTypeBuilder( typeFactory.builderTypeFor( method.getReturnType(), builder ) ) .returnTypeBuilder( typeFactory.builderTypeFor( method.getReturnType(), builder ) )
.build(); .build();
if ( beanMappingMethod != null ) {
// We can consider that the bean mapping method can always be constructed. If there is a problem // We can consider that the bean mapping method can always be constructed. If there is a problem
// it would have been reported in its build // it would have been reported in its build
hasFactoryMethod = true; hasFactoryMethod = true;
if ( beanMappingMethod != null ) {
mappingMethods.add( beanMappingMethod ); mappingMethods.add( beanMappingMethod );
} }
} }

View File

@ -75,4 +75,6 @@ public interface FormattingMessager {
* @param args the arguments * @param args the arguments
*/ */
void note(int level, Message log, Object... args); void note(int level, Message log, Object... args);
boolean isErroneous();
} }

View File

@ -23,7 +23,9 @@ public enum Message {
BEANMAPPING_NOT_ASSIGNABLE( "%s not assignable to: %s." ), BEANMAPPING_NOT_ASSIGNABLE( "%s not assignable to: %s." ),
BEANMAPPING_ABSTRACT( "The result type %s may not be an abstract class nor interface." ), BEANMAPPING_ABSTRACT( "The result type %s may not be an abstract class nor interface." ),
BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE( "Unknown property \"%s\" in result type %s. Did you mean \"%s\"?" ), BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE( "Unknown property \"%s\" in result type %s. Did you mean \"%s\"?" ),
BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE( "Unknown property \"%s\" in type %s for target name \"%s\". Did you mean \"%s\"?" ),
BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE( "Property \"%s\" has no write accessor in %s." ), BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE( "Property \"%s\" has no write accessor in %s." ),
BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_TYPE( "Property \"%s\" has no write accessor in %s for target name \"%s\"." ),
BEANMAPPING_SEVERAL_POSSIBLE_SOURCES( "Several possible source properties for target property \"%s\"." ), BEANMAPPING_SEVERAL_POSSIBLE_SOURCES( "Several possible source properties for target property \"%s\"." ),
BEANMAPPING_SEVERAL_POSSIBLE_TARGET_ACCESSORS( "Found several matching getters for property \"%s\"." ), BEANMAPPING_SEVERAL_POSSIBLE_TARGET_ACCESSORS( "Found several matching getters for property \"%s\"." ),
BEANMAPPING_UNMAPPED_TARGETS_WARNING( "Unmapped target %s.", Diagnostic.Kind.WARNING ), BEANMAPPING_UNMAPPED_TARGETS_WARNING( "Unmapped target %s.", Diagnostic.Kind.WARNING ),

View File

@ -161,5 +161,9 @@ public class DefaultConversionContextTest {
return lastKindPrinted; return lastKindPrinted;
} }
@Override
public boolean isErroneous() {
return false;
}
} }
} }

View File

@ -32,14 +32,14 @@ public class Issue1153Test {
@Diagnostic(type = ErroneousIssue1153Mapper.class, @Diagnostic(type = ErroneousIssue1153Mapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR, kind = javax.tools.Diagnostic.Kind.ERROR,
line = 20, line = 20,
message = "Property \"nestedTarget.readOnly\" has no write accessor in " + message = "Property \"readOnly\" has no write accessor in org.mapstruct.ap.test.bugs._1153" +
"org.mapstruct.ap.test.bugs._1153.ErroneousIssue1153Mapper.Target."), ".ErroneousIssue1153Mapper.Target.NestedTarget for target name \"nestedTarget.readOnly\"."),
@Diagnostic(type = ErroneousIssue1153Mapper.class, @Diagnostic(type = ErroneousIssue1153Mapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR, kind = javax.tools.Diagnostic.Kind.ERROR,
line = 23, line = 23,
message = "Unknown property \"nestedTarget2.writable2\" in result type " + message = "Unknown property \"writable2\" in type org.mapstruct.ap.test.bugs._1153" +
"org.mapstruct.ap.test.bugs._1153.ErroneousIssue1153Mapper.Target. " + ".ErroneousIssue1153Mapper.Target.NestedTarget for target name \"nestedTarget2.writable2\". Did " +
"Did you mean \"nestedTarget2.writable\"?") "you mean \"nestedTarget2.writable\"?")
}) })
@Test @Test
public void shouldReportErrorsCorrectly() { public void shouldReportErrorsCorrectly() {

View File

@ -66,13 +66,21 @@ public class SuggestMostSimilarNameTest {
@Diagnostic(type = PersonGarageWrongTargetMapper.class, @Diagnostic(type = PersonGarageWrongTargetMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR, kind = javax.tools.Diagnostic.Kind.ERROR,
line = 19, line = 19,
message = "Unknown property \"garage.colour.rgb\" in result type org.mapstruct.ap.test.namesuggestion" + message = "Unknown property \"colour\" in type org.mapstruct.ap.test.namesuggestion.Garage for target" +
".Person. Did you mean \"garage.color\"?"), " name \"garage.colour.rgb\". Did you mean \"garage.color\"?"),
@Diagnostic(type = PersonGarageWrongTargetMapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 20,
messageRegExp = "Unmapped target properties: \"fullName, fullAge\"\\."),
@Diagnostic(type = PersonGarageWrongTargetMapper.class, @Diagnostic(type = PersonGarageWrongTargetMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR, kind = javax.tools.Diagnostic.Kind.ERROR,
line = 22, line = 22,
message = "Unknown property \"garage.colour\" in result type org.mapstruct.ap.test.namesuggestion" + message = "Unknown property \"colour\" in type org.mapstruct.ap.test.namesuggestion.Garage for" +
".Person. Did you mean \"garage.color\"?") " target name \"garage.colour\". Did you mean \"garage.color\"?"),
@Diagnostic(type = PersonGarageWrongTargetMapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 23,
messageRegExp = "Unmapped target properties: \"fullName, fullAge\"\\."),
} }
) )
public void testGarageTargetSuggestion() { public void testGarageTargetSuggestion() {

View File

@ -172,10 +172,7 @@ public class ConversionTest {
}) })
@ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = {
@Diagnostic(type = ErroneousSourceTargetMapper6.class, kind = javax.tools.Diagnostic.Kind.ERROR, @Diagnostic(type = ErroneousSourceTargetMapper6.class, kind = javax.tools.Diagnostic.Kind.ERROR,
line = 16, message = "org.mapstruct.ap.test.selection.generics.WildCardSuperWrapper<org.mapstruct.ap.test" + line = 17, message =
".selection.generics.TypeA> does not have an accessible parameterless constructor."),
@Diagnostic(type = ErroneousSourceTargetMapper6.class, kind = javax.tools.Diagnostic.Kind.ERROR,
line = 16, message =
"No target bean properties found: can't map property \"org.mapstruct.ap.test.NoProperties " "No target bean properties found: can't map property \"org.mapstruct.ap.test.NoProperties "
+ "foo.wrapped\" to" + "foo.wrapped\" to"
+ " \"org.mapstruct.ap.test.selection.generics.TypeA " + " \"org.mapstruct.ap.test.selection.generics.TypeA "

View File

@ -6,6 +6,7 @@
package org.mapstruct.ap.test.selection.generics; package org.mapstruct.ap.test.selection.generics;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.ObjectFactory;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@Mapper( uses = GenericTypeMapper.class ) @Mapper( uses = GenericTypeMapper.class )
@ -14,4 +15,11 @@ public interface ErroneousSourceTargetMapper6 {
ErroneousSourceTargetMapper6 INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapper6.class ); ErroneousSourceTargetMapper6 INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapper6.class );
ErroneousTarget6 sourceToTarget(ErroneousSource6 source); 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 <T> WildCardSuperWrapper<T> createWildCardSuperWrapper() {
return new WildCardSuperWrapper<>( null );
}
} }