#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. -->
<!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="MethodLength"/>
<module name="MethodLength">
<property name="max" value="200"/>
</module>
<module name="ParameterNumber">
<property name="max" value="10"/>
</module>

View File

@ -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,8 +157,16 @@ 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<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.
* 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 {
* <p>
* 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<String> 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<String> handledTargets) {
private boolean handleDefinedNestedTargetMapping(Set<String> 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<PropertyEntry, Set<MappingReference>> entry : holder.getUnprocessedDefinedTarget()
for ( Entry<String, Set<MappingReference>> 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<String> handledTargets) {
private boolean handleDefinedMapping(MappingReference mappingRef, Type resultTypeToMap,
Set<String> 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<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
// 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() )
@ -724,9 +833,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false );
PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx )
.sourceMethod( method )
.targetWriteAccessor( targetPropertyWriteAccessor )
.targetReadAccessor( targetPropertyReadAccessor )
.targetPropertyName( targetPropertyName )
.target( targetPropertyName, targetPropertyReadAccessor, targetPropertyWriteAccessor )
.sourceReference( sourceRef )
.existingVariableNames( existingVariableNames )
.forgeMethodWithMappingReferences( mappingRefs )
@ -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,6 +971,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) {
if ( !( method instanceof ForgedMethod ) ) {
Message msg = unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ?
Message.BEANMAPPING_UNMAPPED_TARGETS_ERROR : Message.BEANMAPPING_UNMAPPED_TARGETS_WARNING;
Object[] args = new Object[] {
@ -875,8 +981,15 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
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_WARNING;
String sourceErrorMessage = method.getParameters().get( 0 ).getType().toString();
@ -890,19 +1003,23 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
history.createTargetPropertyName()
);
}
args = new Object[] {
args[0],
Object[] args = new Object[] {
MessageFormat.format(
"{0,choice,1#property|1<properties}: \"{1}\"",
unprocessedTargetProperties.size(),
Strings.join( unprocessedTargetProperties.keySet(), ", " )
),
sourceErrorMessage,
targetErrorMessage
};
}
ctx.getMessager().printMessage(
method.getExecutable(),
msg,
args
);
}
}
}
private ReportingPolicyGem getUnmappedSourcePolicy() {

View File

@ -240,4 +240,8 @@ public class MappingBuilderContext {
private boolean canGenerateAutoSubMappingFor(Type type) {
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.TargetReference;
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.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;
@ -37,13 +41,13 @@ public class NestedTargetPropertyMappingHolder {
private final List<Parameter> processedSourceParameters;
private final Set<String> handledTargets;
private final List<PropertyMapping> propertyMappings;
private final Map<PropertyEntry, Set<MappingReference>> unprocessedDefinedTarget;
private final Map<String, Set<MappingReference>> unprocessedDefinedTarget;
private final boolean errorOccurred;
public NestedTargetPropertyMappingHolder(
List<Parameter> processedSourceParameters, Set<String> handledTargets,
List<PropertyMapping> propertyMappings,
Map<PropertyEntry, Set<MappingReference>> unprocessedDefinedTarget, boolean errorOccurred) {
Map<String, Set<MappingReference>> 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<PropertyEntry, Set<MappingReference>> getUnprocessedDefinedTarget() {
public Map<String, Set<MappingReference>> getUnprocessedDefinedTarget() {
return unprocessedDefinedTarget;
}
@ -94,6 +98,9 @@ public class NestedTargetPropertyMappingHolder {
private Set<String> existingVariableNames;
private List<PropertyMapping> propertyMappings;
private Set<String> handledTargets;
private Map<String, Accessor> 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<String, Accessor> targetPropertiesWriteAccessors) {
this.targetPropertiesWriteAccessors = targetPropertiesWriteAccessors;
return this;
}
public Builder targetPropertyType(Type targetType) {
this.targetType = targetType;
return this;
}
public NestedTargetPropertyMappingHolder build() {
List<Parameter> 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<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() ) {
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<MappingReference> 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<PropertyEntry, Set<MappingReference>> mappingsKeyedByProperty = new LinkedHashMap<>();
Map<PropertyEntry, Set<MappingReference>> singleTargetReferences = new LinkedHashMap<>();
boolean errorOccurred = false;
Map<String, Set<MappingReference>> mappingsKeyedByProperty = new LinkedHashMap<>();
Map<String, Set<MappingReference>> 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<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()
.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<PropertyEntry, Set<MappingReference>> poppedTargetReferences;
private final Map<PropertyEntry, Set<MappingReference>> singleTargetReferences;
private final boolean errorOccurred;
private final Map<String, Set<MappingReference>> poppedTargetReferences;
private final Map<String, Set<MappingReference>> singleTargetReferences;
private GroupedTargetReferences(Map<PropertyEntry, Set<MappingReference>> poppedTargetReferences,
Map<PropertyEntry, Set<MappingReference>> singleTargetReferences, boolean errorOccurred) {
private GroupedTargetReferences(Map<String, Set<MappingReference>> poppedTargetReferences,
Map<String, Set<MappingReference>> singleTargetReferences) {
this.poppedTargetReferences = poppedTargetReferences;
this.singleTargetReferences = singleTargetReferences;
this.errorOccurred = errorOccurred;
}
@Override
public String toString() {
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 );
}
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();
}

View File

@ -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

View File

@ -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,7 +30,10 @@ public class MappingReferences {
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) {
Set<MappingReference> references = new LinkedHashSet<>();
@ -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;

View File

@ -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;

View File

@ -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;
* <li>{@code propertyEntries[1]} will describe {@code propB}</li>
* </ul>
*
* 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<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}.
@ -62,21 +123,15 @@ public class TargetReference extends AbstractReference {
private Method method;
private FormattingMessager messager;
private TypeFactory typeFactory;
private Set<String> 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<String> 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<PropertyEntry> entries = getTargetEntries( resultType, targetPropertyNames );
foundEntryMatch = (entries.size() == targetPropertyNames.length);
if ( !foundEntryMatch && segments.length > 1
&& matchesSourceOrTargetParameter( segments[0], parameter, isReversed ) ) {
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 );
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 );
}
}
/**
* 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;
}
List<String> entries = new ArrayList<>( Arrays.asList( targetPropertyNames ) );
/**
* 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 );
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<PropertyEntry> targetPropertyEntries, boolean isValid) {
super( sourceParameter, targetPropertyEntries, isValid );
}
public TargetReference pop() {
if ( getPropertyEntries().size() > 1 ) {
List<PropertyEntry> 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<String> 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<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 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<String> 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,

View File

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

View File

@ -368,10 +368,10 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
.returnTypeBuilder( typeFactory.builderTypeFor( method.getReturnType(), builder ) )
.build();
if ( beanMappingMethod != null ) {
// 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
hasFactoryMethod = true;
if ( beanMappingMethod != null ) {
mappingMethods.add( beanMappingMethod );
}
}

View File

@ -75,4 +75,6 @@ public interface FormattingMessager {
* @param args the arguments
*/
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_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_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_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_TARGET_ACCESSORS( "Found several matching getters for property \"%s\"." ),
BEANMAPPING_UNMAPPED_TARGETS_WARNING( "Unmapped target %s.", Diagnostic.Kind.WARNING ),

View File

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

View File

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

View File

@ -66,13 +66,21 @@ public class SuggestMostSimilarNameTest {
@Diagnostic(type = PersonGarageWrongTargetMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 19,
message = "Unknown property \"garage.colour.rgb\" in result type org.mapstruct.ap.test.namesuggestion" +
".Person. Did you mean \"garage.color\"?"),
message = "Unknown property \"colour\" in type org.mapstruct.ap.test.namesuggestion.Garage for target" +
" 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,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 22,
message = "Unknown property \"garage.colour\" in result type org.mapstruct.ap.test.namesuggestion" +
".Person. Did you mean \"garage.color\"?")
message = "Unknown property \"colour\" in type org.mapstruct.ap.test.namesuggestion.Garage for" +
" 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() {

View File

@ -172,10 +172,7 @@ public class ConversionTest {
})
@ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = {
@Diagnostic(type = ErroneousSourceTargetMapper6.class, kind = javax.tools.Diagnostic.Kind.ERROR,
line = 16, message = "org.mapstruct.ap.test.selection.generics.WildCardSuperWrapper<org.mapstruct.ap.test" +
".selection.generics.TypeA> 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 "

View File

@ -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 <T> WildCardSuperWrapper<T> createWildCardSuperWrapper() {
return new WildCardSuperWrapper<>( null );
}
}