Refactoring of BeanMapping and Source/TargetReferences (common base class) (#1903)

This commit is contained in:
Sjaak Derksen 2019-09-13 19:41:06 +02:00 committed by GitHub
parent 6d9a50601e
commit e12f9ffd7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 324 additions and 291 deletions

View File

@ -19,7 +19,6 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.lang.model.type.DeclaredType;
import javax.tools.Diagnostic;
@ -52,7 +51,6 @@ 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.model.source.Mapping.getMappingByTargetName;
import static org.mapstruct.ap.internal.model.beanmapping.MappingReferences.forSourceMethod;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Message.BEANMAPPING_ABSTRACT;
@ -88,7 +86,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private final Set<Parameter> unprocessedSourceParameters = new HashSet<>();
private final Set<String> existingVariableNames = new HashSet<>();
private final Map<String, Set<MappingReference>> unprocessedDefinedTargets = new LinkedHashMap<>();
private Function<String, Mapping> singleMapping;
private MappingReferences mappingReferences;
@ -103,15 +100,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
public Builder sourceMethod(SourceMethod sourceMethod) {
singleMapping =
targetName -> getMappingByTargetName( targetName, sourceMethod.getMappingOptions().getMappings() );
this.method = sourceMethod;
this.mappingReferences = forSourceMethod( sourceMethod, ctx.getMessager(), ctx.getTypeFactory() );
return this;
}
public Builder forgedMethod(ForgedMethod forgedMethod) {
singleMapping = targetPropertyName -> null;
this.method = forgedMethod;
mappingReferences = forgedMethod.getMappingReferences();
Parameter sourceParameter = first( Parameter.getSourceParameters( forgedMethod.getParameters() ) );
@ -320,12 +314,14 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
.name( propertyName )
.build();
Accessor targetPropertyReadAccessor =
method.getResultType().getPropertyReadAccessors().get( propertyName );
MappingReferences mappingRefs = extractMappingReferences( propertyName, true );
PropertyMapping propertyMapping = new PropertyMappingBuilder()
.mappingContext( ctx )
.sourceMethod( method )
.targetWriteAccessor( unprocessedTargetProperties.get( propertyName ) )
.targetReadAccessor( getTargetPropertyReadAccessor( propertyName ) )
.targetReadAccessor( targetPropertyReadAccessor )
.targetPropertyName( propertyName )
.sourceReference( reference )
.existingVariableNames( existingVariableNames )
@ -480,18 +476,16 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
for ( MappingReference mapping : mappingReferences.getMappingReferences() ) {
TargetReference targetReference = mapping.getTargetReference();
if ( targetReference.isValid() ) {
String target = first( targetReference.getPropertyEntries() ).getFullName();
if ( mapping.isValid() ) {
String target = mapping.getTargetReference().getShallowestPropertyName();
if ( !handledTargets.contains( target ) ) {
if ( handleDefinedMapping( mapping, handledTargets ) ) {
errorOccurred = true;
}
}
if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() ) {
List<PropertyEntry> sourceEntries = mapping.getSourceReference().getPropertyEntries();
if ( !sourceEntries.isEmpty() ) {
String source = first( sourceEntries ).getFullName();
if ( mapping.getSourceReference() != null ) {
String source = mapping.getSourceReference().getShallowestPropertyName();
if ( source != null ) {
unprocessedSourceProperties.remove( source );
}
}
@ -558,6 +552,11 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
}
// check if source / expression / constant are not somehow handled already
if ( unprocessedDefinedTargets.containsKey( targetPropertyName ) ) {
return false;
}
// check the mapping options
// its an ignored property mapping
if ( mapping.isIgnored() ) {
@ -565,11 +564,52 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
handledTargets.add( targetProperty.getName() );
}
// its a constant
// if we have an unprocessed target that means that it most probably is nested and we should
// not generated any mapping for it now. Eventually it will be done though
else if ( mapping.getConstant() != null ) {
propertyMapping = new ConstantMappingBuilder()
.mappingContext( ctx )
.sourceMethod( method )
.constantExpression( mapping.getConstant() )
.targetProperty( targetProperty )
.targetPropertyName( targetPropertyName )
.formattingParameters( mapping.getFormattingParameters() )
.selectionParameters( mapping.getSelectionParameters() )
.existingVariableNames( existingVariableNames )
.dependsOn( mapping.getDependsOn() )
.mirror( mapping.getMirror() )
.build();
handledTargets.add( targetPropertyName );
}
// its an expression
// if we have an unprocessed target that means that it most probably is nested and we should
// not generated any mapping for it now. Eventually it will be done though
else if ( mapping.getJavaExpression() != null ) {
propertyMapping = new JavaExpressionMappingBuilder()
.mappingContext( ctx )
.sourceMethod( method )
.javaExpression( mapping.getJavaExpression() )
.existingVariableNames( existingVariableNames )
.targetProperty( targetProperty )
.targetPropertyName( targetPropertyName )
.dependsOn( mapping.getDependsOn() )
.mirror( mapping.getMirror() )
.build();
handledTargets.add( targetPropertyName );
}
// its a plain-old property mapping
else if ( mapping.getSourceName() != null ) {
else {
// determine source parameter
SourceReference sourceRef = mappingRef.getSourceReference();
if ( sourceRef == null && method.getSourceParameters().size() == 1 ) {
sourceRef = getSourceRef( method.getSourceParameters().get( 0 ), targetPropertyName );
}
if ( sourceRef.isValid() ) {
// targetProperty == null can occur: we arrived here because we want as many errors
@ -598,46 +638,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
errorOccured = true;
}
}
// its a constant
// if we have an unprocessed target that means that it most probably is nested and we should
// not generated any mapping for it now. Eventually it will be done though
else if ( mapping.getConstant() != null && !unprocessedDefinedTargets.containsKey( targetPropertyName ) ) {
propertyMapping = new ConstantMappingBuilder()
.mappingContext( ctx )
.sourceMethod( method )
.constantExpression( mapping.getConstant() )
.targetProperty( targetProperty )
.targetPropertyName( targetPropertyName )
.formattingParameters( mapping.getFormattingParameters() )
.selectionParameters( mapping.getSelectionParameters() )
.existingVariableNames( existingVariableNames )
.dependsOn( mapping.getDependsOn() )
.mirror( mapping.getMirror() )
.build();
handledTargets.add( targetPropertyName );
}
// its an expression
// if we have an unprocessed target that means that it most probably is nested and we should
// not generated any mapping for it now. Eventually it will be done though
else if ( mapping.getJavaExpression() != null
&& !unprocessedDefinedTargets.containsKey( targetPropertyName ) ) {
propertyMapping = new JavaExpressionMappingBuilder()
.mappingContext( ctx )
.sourceMethod( method )
.javaExpression( mapping.getJavaExpression() )
.existingVariableNames( existingVariableNames )
.targetProperty( targetProperty )
.targetPropertyName( targetPropertyName )
.dependsOn( mapping.getDependsOn() )
.mirror( mapping.getMirror() )
.build();
handledTargets.add( targetPropertyName );
}
// remaining are the mappings without a 'source' so, 'only' a date format or qualifiers
if ( propertyMapping != null ) {
propertyMappings.add( propertyMapping );
@ -653,91 +653,60 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
* the set of remaining target properties.
*/
private void applyPropertyNameBasedMapping() {
Iterator<Entry<String, Accessor>> targetPropertyEntriesIterator =
unprocessedTargetProperties.entrySet().iterator();
while ( targetPropertyEntriesIterator.hasNext() ) {
Entry<String, Accessor> targetProperty = targetPropertyEntriesIterator.next();
String targetPropertyName = targetProperty.getKey();
PropertyMapping propertyMapping = null;
if ( propertyMapping == null ) {
for ( Parameter sourceParameter : method.getSourceParameters() ) {
Type sourceType = sourceParameter.getType();
if ( sourceType.isPrimitive() || sourceType.isArrayType() ) {
continue;
}
PropertyMapping newPropertyMapping = null;
Accessor sourceReadAccessor =
sourceParameter.getType().getPropertyReadAccessors().get( targetPropertyName );
Accessor sourcePresenceChecker =
sourceParameter.getType().getPropertyPresenceCheckers().get( targetPropertyName );
if ( sourceReadAccessor != null ) {
Mapping mapping = singleMapping.apply( targetProperty.getKey() );
DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror();
SourceReference sourceRef = new SourceReference.BuilderFromProperty()
.sourceParameter( sourceParameter )
.type( ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ) )
.readAccessor( sourceReadAccessor )
.presenceChecker( sourcePresenceChecker )
.name( targetProperty.getKey() )
.build();
MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false );
newPropertyMapping = new PropertyMappingBuilder()
.mappingContext( ctx )
.sourceMethod( method )
.targetWriteAccessor( targetProperty.getValue() )
.targetReadAccessor( getTargetPropertyReadAccessor( targetPropertyName ) )
.targetPropertyName( targetPropertyName )
.sourceReference( sourceRef )
.formattingParameters( mapping != null ? mapping.getFormattingParameters() : null )
.selectionParameters( mapping != null ? mapping.getSelectionParameters() : null )
.defaultValue( mapping != null ? mapping.getDefaultValue() : null )
.existingVariableNames( existingVariableNames )
.dependsOn( mapping != null ? mapping.getDependsOn() : Collections.<String>emptySet() )
.forgeMethodWithMappingReferences( mappingRefs )
.nullValueCheckStrategy( mapping != null ? mapping.getNullValueCheckStrategy() : null )
.nullValuePropertyMappingStrategy( mapping != null ?
mapping.getNullValuePropertyMappingStrategy() : null )
.mirror( mapping != null ? mapping.getMirror() : null )
.build();
unprocessedSourceParameters.remove( sourceParameter );
}
if ( propertyMapping != null && newPropertyMapping != null ) {
// TODO improve error message
ctx.getMessager().printMessage(
method.getExecutable(),
Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES,
targetPropertyName
);
break;
}
else if ( newPropertyMapping != null ) {
propertyMapping = newPropertyMapping;
}
List<SourceReference> sourceReferences = new ArrayList<>();
for ( String targetPropertyName : unprocessedTargetProperties.keySet() ) {
for ( Parameter sourceParameter : method.getSourceParameters() ) {
SourceReference sourceRef = getSourceRef( sourceParameter, targetPropertyName );
if ( sourceRef != null ) {
sourceReferences.add( sourceRef );
}
}
}
applyPropertyNameBasedMapping( sourceReferences );
}
/**
* Iterates over all target properties and all source parameters.
* <p>
* When a property name match occurs, the remainder will be checked for duplicates. Matches will be removed from
* the set of remaining target properties.
*/
private void applyPropertyNameBasedMapping(List<SourceReference> sourceReferences) {
for ( SourceReference sourceRef : sourceReferences ) {
String targetPropertyName = sourceRef.getDeepestPropertyName();
Accessor targetPropertyWriteAccessor = unprocessedTargetProperties.remove( targetPropertyName );
if ( targetPropertyWriteAccessor == null ) {
// TODO improve error message
ctx.getMessager()
.printMessage( method.getExecutable(),
Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES,
targetPropertyName
);
continue;
}
Accessor targetPropertyReadAccessor =
method.getResultType().getPropertyReadAccessors().get( targetPropertyName );
MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false );
PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx )
.sourceMethod( method )
.targetWriteAccessor( targetPropertyWriteAccessor )
.targetReadAccessor( targetPropertyReadAccessor )
.targetPropertyName( targetPropertyName )
.sourceReference( sourceRef )
.existingVariableNames( existingVariableNames )
.forgeMethodWithMappingReferences( mappingRefs )
.build();
unprocessedSourceParameters.remove( sourceRef.getParameter() );
if ( propertyMapping != null ) {
propertyMappings.add( propertyMapping );
targetPropertyEntriesIterator.remove();
unprocessedDefinedTargets.remove( targetPropertyName );
unprocessedSourceProperties.remove( targetPropertyName );
}
unprocessedDefinedTargets.remove( targetPropertyName );
unprocessedSourceProperties.remove( targetPropertyName );
}
}
@ -756,30 +725,24 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
Parameter sourceParameter = sourceParameters.next();
if ( sourceParameter.getName().equals( targetProperty.getKey() ) ) {
Mapping mapping = singleMapping.apply( targetProperty.getKey() );
SourceReference sourceRef = new SourceReference.BuilderFromProperty()
.sourceParameter( sourceParameter )
.name( targetProperty.getKey() )
.build();
Accessor targetPropertyReadAccessor =
method.getResultType().getPropertyReadAccessors().get( targetProperty.getKey() );
MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false );
PropertyMapping propertyMapping = new PropertyMappingBuilder()
.mappingContext( ctx )
.sourceMethod( method )
.targetWriteAccessor( targetProperty.getValue() )
.targetReadAccessor( getTargetPropertyReadAccessor( targetProperty.getKey() ) )
.targetReadAccessor( targetPropertyReadAccessor )
.targetPropertyName( targetProperty.getKey() )
.sourceReference( sourceRef )
.formattingParameters( mapping != null ? mapping.getFormattingParameters() : null )
.selectionParameters( mapping != null ? mapping.getSelectionParameters() : null )
.existingVariableNames( existingVariableNames )
.dependsOn( mapping != null ? mapping.getDependsOn() : Collections.<String>emptySet() )
.forgeMethodWithMappingReferences( mappingRefs )
.nullValueCheckStrategy( mapping != null ? mapping.getNullValueCheckStrategy() : null )
.nullValuePropertyMappingStrategy( mapping != null ?
mapping.getNullValuePropertyMappingStrategy() : null )
.mirror( mapping != null ? mapping.getMirror() : null )
.build();
propertyMappings.add( propertyMapping );
@ -792,6 +755,33 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
}
}
private SourceReference getSourceRef(Parameter sourceParameter, String targetPropertyName) {
SourceReference sourceRef = null;
if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() ) {
return sourceRef;
}
Accessor sourceReadAccessor =
sourceParameter.getType().getPropertyReadAccessors().get( targetPropertyName );
Accessor sourcePresenceChecker =
sourceParameter.getType().getPropertyPresenceCheckers().get( targetPropertyName );
if ( sourceReadAccessor != null ) {
DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror();
Type returnType = ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor );
sourceRef = new SourceReference.BuilderFromProperty().sourceParameter( sourceParameter )
.type( returnType )
.readAccessor( sourceReadAccessor )
.presenceChecker( sourcePresenceChecker )
.name( targetPropertyName )
.build();
}
return sourceRef;
}
private MappingReferences extractMappingReferences(String targetProperty, boolean restrictToDefinedMappings) {
if ( unprocessedDefinedTargets.containsKey( targetProperty ) ) {
Set<MappingReference> mappings = unprocessedDefinedTargets.get( targetProperty );
@ -800,10 +790,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return null;
}
private Accessor getTargetPropertyReadAccessor(String propertyName) {
return method.getResultType().getPropertyReadAccessors().get( propertyName );
}
private ReportingPolicyPrism getUnmappedTargetPolicy() {
if ( mappingReferences.isForForgedMethods() ) {
return ReportingPolicyPrism.IGNORE;
@ -1034,10 +1020,5 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return Objects.equals( propertyMappings, that.propertyMappings );
}
private interface SingleMappingByTargetPropertyNameFunction {
Mapping getSingleMappingByTargetPropertyName(String targetPropertyName);
}
}

View File

@ -51,8 +51,6 @@ import static org.mapstruct.ap.internal.model.ForgedMethod.forElementMapping;
import static org.mapstruct.ap.internal.model.ForgedMethod.forPropertyMapping;
import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_DEFAULT;
import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_NULL;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.last;
/**
* Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to
@ -519,10 +517,10 @@ public class PropertyMapping extends ModelElement {
private SourceRHS getSourceRHS( SourceReference sourceReference ) {
Parameter sourceParam = sourceReference.getParameter();
List<PropertyEntry> propertyEntries = sourceReference.getPropertyEntries();
PropertyEntry propertyEntry = sourceReference.getDeepestProperty();
// parameter reference
if ( propertyEntries.isEmpty() ) {
if ( propertyEntry == null ) {
return new SourceRHS( sourceParam.getName(),
sourceParam.getType(),
existingVariableNames,
@ -530,8 +528,7 @@ public class PropertyMapping extends ModelElement {
);
}
// simple property
else if ( propertyEntries.size() == 1 ) {
PropertyEntry propertyEntry = propertyEntries.get( 0 );
else if ( !sourceReference.isNested() ) {
String sourceRef = sourceParam.getName() + "." + ValueProvider.of( propertyEntry.getReadAccessor() );
return new SourceRHS( sourceParam.getName(),
sourceRef,
@ -543,7 +540,7 @@ public class PropertyMapping extends ModelElement {
}
// nested property given as dot path
else {
Type sourceType = last( propertyEntries ).getType();
Type sourceType = propertyEntry.getType();
if ( sourceType.isPrimitive() && !targetType.isPrimitive() ) {
// Handle null's. If the forged method needs to be mapped to an object, the forged method must be
// able to return null. So in that case primitive types are mapped to their corresponding wrapped
@ -581,7 +578,7 @@ public class PropertyMapping extends ModelElement {
);
// create a local variable to which forged method can be assigned.
String desiredName = last( sourceReference.getPropertyEntries() ).getName();
String desiredName = propertyEntry.getName();
sourceRhs.setSourceLocalVarName( sourceRhs.createUniqueVarName( desiredName ) );
return sourceRhs;
@ -595,7 +592,7 @@ public class PropertyMapping extends ModelElement {
Parameter sourceParam = sourceReference.getParameter();
// TODO is first correct here?? shouldn't it be last since the remainer is checked
// in the forged method?
PropertyEntry propertyEntry = first( sourceReference.getPropertyEntries() );
PropertyEntry propertyEntry = sourceReference.getShallowestProperty();
if ( propertyEntry.getPresenceChecker() != null ) {
sourcePresenceChecker = sourceParam.getName()
+ "." + propertyEntry.getPresenceChecker().getSimpleName() + "()";

View File

@ -0,0 +1,132 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.internal.model.beanmapping;
import java.util.ArrayList;
import java.util.List;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.util.Strings;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.last;
/**
* Class acts as a common base class for {@link TargetReference} and {@link SourceReference}.
*
* @author sjaak
*/
public abstract class AbstractReference {
private final Parameter parameter;
private final List<PropertyEntry> propertyEntries;
private final boolean isValid;
protected AbstractReference(Parameter sourceParameter, List<PropertyEntry> sourcePropertyEntries, boolean isValid) {
this.parameter = sourceParameter;
this.propertyEntries = sourcePropertyEntries;
this.isValid = isValid;
}
public Parameter getParameter() {
return parameter;
}
public List<PropertyEntry> getPropertyEntries() {
return propertyEntries;
}
public boolean isValid() {
return isValid;
}
public List<String> getElementNames() {
List<String> elementNames = new ArrayList<>();
if ( parameter != null ) {
// only relevant for source properties
elementNames.add( parameter.getName() );
}
for ( PropertyEntry propertyEntry : propertyEntries ) {
elementNames.add( propertyEntry.getName() );
}
return elementNames;
}
/**
* returns the property name on the shallowest nesting level
* @return
*/
public PropertyEntry getShallowestProperty() {
if ( propertyEntries.isEmpty() ) {
return null;
}
return first( propertyEntries );
}
/**
* returns the property name on the shallowest nesting level
* @return
*/
public String getShallowestPropertyName() {
if ( propertyEntries.isEmpty() ) {
return null;
}
return first( propertyEntries ).getName();
}
/**
* returns the property name on the deepest nesting level
* @return
*/
public PropertyEntry getDeepestProperty() {
if ( propertyEntries.isEmpty() ) {
return null;
}
return last( propertyEntries );
}
/**
* returns the property name on the deepest nesting level
* @return
*/
public String getDeepestPropertyName() {
if ( propertyEntries.isEmpty() ) {
return null;
}
return last( propertyEntries ).getName();
}
public boolean isNested() {
return propertyEntries.size() > 1;
}
@Override
public String toString() {
String result = "";
if ( !isValid ) {
result = "invalid";
}
else if ( propertyEntries.isEmpty() ) {
if ( parameter != null ) {
result = String.format( "parameter \"%s %s\"", parameter.getType(), parameter.getName() );
}
}
else if ( propertyEntries.size() == 1 ) {
PropertyEntry propertyEntry = propertyEntries.get( 0 );
result = String.format( "property \"%s %s\"", propertyEntry.getType(), propertyEntry.getName() );
}
else {
PropertyEntry lastPropertyEntry = propertyEntries.get( propertyEntries.size() - 1 );
result = String.format(
"property \"%s %s\"",
lastPropertyEntry.getType(),
Strings.join( getElementNames(), "." )
);
}
return result;
}
}

View File

@ -8,7 +8,6 @@ package org.mapstruct.ap.internal.model.beanmapping;
import java.util.Objects;
import org.mapstruct.ap.internal.model.source.Mapping;
import org.mapstruct.ap.internal.util.Strings;
/**
* Represents the intermediate (nesting) state of the {@link Mapping} in this class.
@ -80,16 +79,24 @@ public class MappingReference {
return Objects.hash( mapping );
}
public boolean isValid( ) {
boolean result = false;
if ( targetReference.isValid() ) {
result = sourceReference != null ? sourceReference.isValid() : true;
}
return result;
}
@Override
public String toString() {
String targetRefName = Strings.join( targetReference.getElementNames(), "." );
String sourceRefName = "null";
String targetRefStr = targetReference.toString();
String sourceRefStr = "null";
if ( sourceReference != null ) {
sourceRefName = Strings.join( sourceReference.getElementNames(), "." );
sourceRefStr = sourceReference.toString();
}
return "MappingReference {"
+ "\n sourceRefName='" + sourceRefName + "\',"
+ "\n targetRefName='" + targetRefName + "\',"
+ "\n sourceReference='" + sourceRefStr + "\',"
+ "\n targetReference='" + targetRefStr + "\',"
+ "\n}";
}
}

View File

@ -29,10 +29,9 @@ public class MappingReferences {
public static MappingReferences forSourceMethod(SourceMethod sourceMethod, FormattingMessager messager,
TypeFactory typeFactory) {
Set<Mapping> mappings = sourceMethod.getMappingOptions().getMappings();
Set<MappingReference> references = new LinkedHashSet<>();
Set<MappingReference> targetThisReferences = new LinkedHashSet<>();
for ( Mapping mapping : sourceMethod.getMappingOptions().getMappings() ) {
// handle source reference
@ -52,9 +51,15 @@ public class MappingReferences {
// add when inverse is also valid
MappingReference mappingReference = new MappingReference( mapping, targetReference, sourceReference );
if ( isValidWhenInversed( mappingReference ) ) {
references.add( mappingReference );
if ( targetReference.isTargetThis() ) {
targetThisReferences.add( mappingReference );
}
else {
references.add( mappingReference );
}
}
}
references.addAll( targetThisReferences );
return new MappingReferences( references, false );
}
@ -104,7 +109,7 @@ public class MappingReferences {
for ( MappingReference mappingRef : mappingReferences ) {
TargetReference targetReference = mappingRef.getTargetReference();
if ( targetReference.isValid() && targetReference.getPropertyEntries().size() > 1 ) {
if ( targetReference.isValid() && targetReference.isNested()) {
return true;
}

View File

@ -6,7 +6,6 @@
package org.mapstruct.ap.internal.model.beanmapping;
import static org.mapstruct.ap.internal.model.beanmapping.PropertyEntry.forSourceReference;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.last;
import java.util.ArrayList;
@ -22,7 +21,6 @@ import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.Mapping;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;
@ -50,11 +48,7 @@ import org.mapstruct.ap.internal.util.accessor.Accessor;
*
* @author Sjaak Derksen
*/
public class SourceReference {
private final Parameter parameter;
private final List<PropertyEntry> propertyEntries;
private final boolean isValid;
public class SourceReference extends AbstractReference {
/**
* Builds a {@link SourceReference} from an {@code @Mappping}.
@ -371,88 +365,23 @@ public class SourceReference {
}
public SourceReference build() {
return new SourceReference( sourceParameter, sourceReference.propertyEntries, true );
return new SourceReference( sourceParameter, sourceReference.getPropertyEntries(), true );
}
}
private SourceReference(Parameter sourceParameter, List<PropertyEntry> sourcePropertyEntries, boolean isValid) {
this.parameter = sourceParameter;
this.propertyEntries = sourcePropertyEntries;
this.isValid = isValid;
}
public Parameter getParameter() {
return parameter;
}
public List<PropertyEntry> getPropertyEntries() {
return propertyEntries;
}
public boolean isValid() {
return isValid;
}
public List<String> getElementNames() {
List<String> sourceName = new ArrayList<>();
sourceName.add( parameter.getName() );
for ( PropertyEntry propertyEntry : propertyEntries ) {
sourceName.add( propertyEntry.getName() );
}
return sourceName;
}
/**
* Creates a copy of this reference, which is adapted to the given method
*
* @param method the method to create the copy for
* @return the copy
*/
public SourceReference copyForInheritanceTo(SourceMethod method) {
List<Parameter> replacementParamCandidates = new ArrayList<>();
for ( Parameter sourceParam : method.getSourceParameters() ) {
if ( parameter != null && sourceParam.getType().isAssignableTo( parameter.getType() ) ) {
replacementParamCandidates.add( sourceParam );
}
}
Parameter replacement = parameter;
if ( replacementParamCandidates.size() == 1 ) {
replacement = first( replacementParamCandidates );
}
return new SourceReference( replacement, propertyEntries, isValid );
super( sourceParameter, sourcePropertyEntries, isValid );
}
public SourceReference pop() {
if ( propertyEntries.size() > 1 ) {
if ( getPropertyEntries().size() > 1 ) {
List<PropertyEntry> newPropertyEntries =
new ArrayList<>( propertyEntries.subList( 1, propertyEntries.size() ) );
return new SourceReference( parameter, newPropertyEntries, isValid );
new ArrayList<>( getPropertyEntries().subList( 1, getPropertyEntries().size() ) );
return new SourceReference( getParameter(), newPropertyEntries, isValid() );
}
else {
return null;
}
}
@Override
public String toString() {
if ( propertyEntries.isEmpty() ) {
return String.format( "parameter \"%s %s\"", getParameter().getType(), getParameter().getName() );
}
else if ( propertyEntries.size() == 1 ) {
PropertyEntry propertyEntry = propertyEntries.get( 0 );
return String.format( "property \"%s %s\"", propertyEntry.getType(), propertyEntry.getName() );
}
else {
PropertyEntry lastPropertyEntry = propertyEntries.get( propertyEntries.size() - 1 );
return String.format(
"property \"%s %s\"",
lastPropertyEntry.getType(),
Strings.join( getElementNames(), "." )
);
}
}
}

View File

@ -51,12 +51,7 @@ import static org.mapstruct.ap.internal.util.Collections.first;
*
* @author Sjaak Derksen
*/
public class TargetReference {
private final Parameter parameter;
private final List<PropertyEntry> propertyEntries;
private final boolean isValid;
public class TargetReference extends AbstractReference {
/**
* Builds a {@link TargetReference} from an {@code @Mappping}.
@ -348,46 +343,24 @@ public class TargetReference {
}
}
private TargetReference(Parameter sourceParameter, List<PropertyEntry> sourcePropertyEntries, boolean isValid) {
this.parameter = sourceParameter;
this.propertyEntries = sourcePropertyEntries;
this.isValid = isValid;
private TargetReference(Parameter sourceParameter, List<PropertyEntry> targetPropertyEntries, boolean isValid) {
super( sourceParameter, targetPropertyEntries, isValid );
}
public Parameter getParameter() {
return parameter;
}
public List<PropertyEntry> getPropertyEntries() {
return propertyEntries;
}
public boolean isValid() {
return isValid;
}
public List<String> getElementNames() {
List<String> elementNames = new ArrayList<>();
if ( parameter != null ) {
// only relevant for source properties
elementNames.add( parameter.getName() );
}
for ( PropertyEntry propertyEntry : propertyEntries ) {
elementNames.add( propertyEntry.getName() );
}
return elementNames;
public boolean isTargetThis() {
return getPropertyEntries().isEmpty();
}
public TargetReference pop() {
if ( propertyEntries.size() > 1 ) {
List<PropertyEntry> newPropertyEntries = new ArrayList<>( propertyEntries.size() - 1 );
for ( PropertyEntry propertyEntry : propertyEntries ) {
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 );
return new TargetReference( null, newPropertyEntries, isValid() );
}
else {
return null;

View File

@ -63,8 +63,8 @@ public class OrderingTest {
@Diagnostic(type = ErroneousAddressMapperWithCyclicDependency.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 24,
messageRegExp = "Cycle\\(s\\) between properties given via dependsOn\\(\\): firstName -> lastName -> "
+ "middleName -> firstName"
messageRegExp = "Cycle\\(s\\) between properties given via dependsOn\\(\\): lastName -> middleName -> "
+ "firstName -> lastName"
)
}
)

View File

@ -84,11 +84,11 @@ public class SuggestMostSimilarNameTest {
diagnostics = {
@Diagnostic(type = PersonGarageWrongSourceMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 19,
line = 21,
messageRegExp = "No property named \"garage\\.colour\\.rgb\".*Did you mean \"garage\\.color\"\\?"),
@Diagnostic(type = PersonGarageWrongSourceMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 22,
line = 28,
messageRegExp = "No property named \"garage\\.colour\".*Did you mean \"garage\\.color\"\\?")
}
)

View File

@ -7,6 +7,7 @@ package org.mapstruct.ap.test.namesuggestion.erroneous;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.ap.test.namesuggestion.Person;
import org.mapstruct.ap.test.namesuggestion.PersonDto;
import org.mapstruct.factory.Mappers;
@ -16,10 +17,18 @@ public interface PersonGarageWrongSourceMapper {
PersonGarageWrongSourceMapper MAPPER = Mappers.getMapper( PersonGarageWrongSourceMapper.class );
@Mapping(source = "garage.colour.rgb", target = "garage.color.rgb")
@Mappings( {
@Mapping( target = "garage.color.rgb", source = "garage.colour.rgb" ),
@Mapping( target = "fullAge", source = "age" ),
@Mapping( target = "fullName", source = "name" )
} )
Person mapPerson(PersonDto dto);
@Mapping(source = "garage.colour", target = "garage.color")
@Mappings( {
@Mapping( target = "garage.color", source = "garage.colour" ),
@Mapping( target = "fullAge", source = "age" ),
@Mapping( target = "fullName", source = "name" )
} )
Person mapPersonGarage(PersonDto dto);
}