mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#304 Allowing to configure dependencies between properties via @Mapping#dependsOn()
This commit is contained in:
parent
9b888847ea
commit
2d7ab089ff
@ -137,10 +137,21 @@ public @interface Mapping {
|
|||||||
*/
|
*/
|
||||||
Class<? extends Annotation>[] qualifiedBy() default { };
|
Class<? extends Annotation>[] qualifiedBy() default { };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the result type of the mapping method to be used in case multiple mapping methods qualify.
|
* Specifies the result type of the mapping method to be used in case multiple mapping methods qualify.
|
||||||
*
|
*
|
||||||
* @return the resultType to select
|
* @return the resultType to select
|
||||||
*/
|
*/
|
||||||
Class<?> resultType() default void.class;
|
Class<?> resultType() default void.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One or more properties of the result type on which the mapped property depends. The generated method
|
||||||
|
* implementation will invoke the setters of the result type ordered so that the given dependency relationship(s)
|
||||||
|
* are satisfied. Useful in case one property setter depends on the state of another property of the result type.
|
||||||
|
* <p>
|
||||||
|
* An error will be raised in case a cycle in the dependency relationships is detected.
|
||||||
|
*
|
||||||
|
* @return the dependencies of the mapped property
|
||||||
|
*/
|
||||||
|
String[] dependsOn() default { };
|
||||||
}
|
}
|
||||||
|
@ -141,4 +141,15 @@ public @interface Mapping {
|
|||||||
* @return the resultType to select
|
* @return the resultType to select
|
||||||
*/
|
*/
|
||||||
Class<?> resultType() default void.class;
|
Class<?> resultType() default void.class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One or more properties of the result type on which the mapped property depends. The generated method
|
||||||
|
* implementation will invoke the setters of the result type ordered so that the given dependency relationship(s)
|
||||||
|
* are satisfied. Useful in case one property setter depends on the state of another property of the result type.
|
||||||
|
* <p>
|
||||||
|
* An error will be raised in case a cycle in the dependency relationships is detected.
|
||||||
|
*
|
||||||
|
* @return the dependencies of the mapped property
|
||||||
|
*/
|
||||||
|
String[] dependsOn() default { };
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ package org.mapstruct.ap.model;
|
|||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -38,6 +40,7 @@ import org.mapstruct.ap.model.PropertyMapping.JavaExpressionMappingBuilder;
|
|||||||
import org.mapstruct.ap.model.PropertyMapping.PropertyMappingBuilder;
|
import org.mapstruct.ap.model.PropertyMapping.PropertyMappingBuilder;
|
||||||
import org.mapstruct.ap.model.common.Parameter;
|
import org.mapstruct.ap.model.common.Parameter;
|
||||||
import org.mapstruct.ap.model.common.Type;
|
import org.mapstruct.ap.model.common.Type;
|
||||||
|
import org.mapstruct.ap.model.dependency.GraphAnalyzer;
|
||||||
import org.mapstruct.ap.model.source.Mapping;
|
import org.mapstruct.ap.model.source.Mapping;
|
||||||
import org.mapstruct.ap.model.source.SourceMethod;
|
import org.mapstruct.ap.model.source.SourceMethod;
|
||||||
import org.mapstruct.ap.model.source.SourceReference;
|
import org.mapstruct.ap.model.source.SourceReference;
|
||||||
@ -146,11 +149,14 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
if ( !resultType.isAssignableTo( method.getResultType() ) ) {
|
if ( !resultType.isAssignableTo( method.getResultType() ) ) {
|
||||||
ctx.getMessager().printMessage( method.getExecutable(),
|
ctx.getMessager().printMessage( method.getExecutable(),
|
||||||
beanMappingPrism.mirror,
|
beanMappingPrism.mirror,
|
||||||
Message.BEANMAPPING_NOT_ASSIGNABLE, resultType, method.getResultType());
|
Message.BEANMAPPING_NOT_ASSIGNABLE, resultType, method.getResultType()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortPropertyMappingsByDependencies();
|
||||||
|
|
||||||
return new BeanMappingMethod(
|
return new BeanMappingMethod(
|
||||||
method,
|
method,
|
||||||
propertyMappings,
|
propertyMappings,
|
||||||
@ -161,6 +167,34 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sortPropertyMappingsByDependencies() {
|
||||||
|
final GraphAnalyzer graphAnalyzer = new GraphAnalyzer();
|
||||||
|
|
||||||
|
for ( PropertyMapping propertyMapping : propertyMappings ) {
|
||||||
|
graphAnalyzer.addNode( propertyMapping.getName(), propertyMapping.getDependsOn() );
|
||||||
|
}
|
||||||
|
|
||||||
|
graphAnalyzer.analyze();
|
||||||
|
|
||||||
|
Collections.sort(
|
||||||
|
propertyMappings, new Comparator<PropertyMapping>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(PropertyMapping o1, PropertyMapping o2) {
|
||||||
|
if ( graphAnalyzer.getAllDescendants( o1.getName() ).contains( o2.getName() ) ) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if ( graphAnalyzer.getAllDescendants( o2.getName() ).contains( o1.getName() ) ) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterates over all defined mapping methods ({@code @Mapping(s)}), either directly given or inherited from the
|
* Iterates over all defined mapping methods ({@code @Mapping(s)}), either directly given or inherited from the
|
||||||
* inverse mapping method.
|
* inverse mapping method.
|
||||||
@ -221,6 +255,7 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
.resultType( mapping.getResultType() )
|
.resultType( mapping.getResultType() )
|
||||||
.dateFormat( mapping.getDateFormat() )
|
.dateFormat( mapping.getDateFormat() )
|
||||||
.existingVariableNames( existingVariableNames )
|
.existingVariableNames( existingVariableNames )
|
||||||
|
.dependsOn( mapping.getDependsOn() )
|
||||||
.build();
|
.build();
|
||||||
handledTargets.add( mapping.getTargetName() );
|
handledTargets.add( mapping.getTargetName() );
|
||||||
unprocessedSourceParameters.remove( sourceRef.getParameter() );
|
unprocessedSourceParameters.remove( sourceRef.getParameter() );
|
||||||
@ -239,10 +274,12 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
.sourceMethod( method )
|
.sourceMethod( method )
|
||||||
.constantExpression( "\"" + mapping.getConstant() + "\"" )
|
.constantExpression( "\"" + mapping.getConstant() + "\"" )
|
||||||
.targetAccessor( targetProperty )
|
.targetAccessor( targetProperty )
|
||||||
|
.targetPropertyName( mapping.getTargetName() )
|
||||||
.dateFormat( mapping.getDateFormat() )
|
.dateFormat( mapping.getDateFormat() )
|
||||||
.qualifiers( mapping.getQualifiers() )
|
.qualifiers( mapping.getQualifiers() )
|
||||||
.resultType( mapping.getResultType() )
|
.resultType( mapping.getResultType() )
|
||||||
.existingVariableNames( existingVariableNames )
|
.existingVariableNames( existingVariableNames )
|
||||||
|
.dependsOn( mapping.getDependsOn() )
|
||||||
.build();
|
.build();
|
||||||
handledTargets.add( mapping.getTargetName() );
|
handledTargets.add( mapping.getTargetName() );
|
||||||
}
|
}
|
||||||
@ -256,6 +293,8 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
.javaExpression( mapping.getJavaExpression() )
|
.javaExpression( mapping.getJavaExpression() )
|
||||||
.targetAccessor( targetProperty )
|
.targetAccessor( targetProperty )
|
||||||
.existingVariableNames( existingVariableNames )
|
.existingVariableNames( existingVariableNames )
|
||||||
|
.targetPropertyName( mapping.getTargetName() )
|
||||||
|
.dependsOn( mapping.getDependsOn() )
|
||||||
.build();
|
.build();
|
||||||
handledTargets.add( mapping.getTargetName() );
|
handledTargets.add( mapping.getTargetName() );
|
||||||
}
|
}
|
||||||
@ -333,6 +372,7 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
.resultType( mapping != null ? mapping.getResultType() : null )
|
.resultType( mapping != null ? mapping.getResultType() : null )
|
||||||
.dateFormat( mapping != null ? mapping.getDateFormat() : null )
|
.dateFormat( mapping != null ? mapping.getDateFormat() : null )
|
||||||
.existingVariableNames( existingVariableNames )
|
.existingVariableNames( existingVariableNames )
|
||||||
|
.dependsOn( mapping != null ? mapping.getDependsOn() : Collections.<String>emptyList() )
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
unprocessedSourceParameters.remove( sourceParameter );
|
unprocessedSourceParameters.remove( sourceParameter );
|
||||||
@ -394,6 +434,7 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
.resultType( mapping != null ? mapping.getResultType() : null )
|
.resultType( mapping != null ? mapping.getResultType() : null )
|
||||||
.dateFormat( mapping != null ? mapping.getDateFormat() : null )
|
.dateFormat( mapping != null ? mapping.getDateFormat() : null )
|
||||||
.existingVariableNames( existingVariableNames )
|
.existingVariableNames( existingVariableNames )
|
||||||
|
.dependsOn( mapping != null ? mapping.getDependsOn() : Collections.<String>emptyList() )
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
propertyMappings.add( propertyMapping );
|
propertyMappings.add( propertyMapping );
|
||||||
|
@ -20,8 +20,10 @@ package org.mapstruct.ap.model;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.lang.model.element.ExecutableElement;
|
import javax.lang.model.element.ExecutableElement;
|
||||||
import javax.lang.model.type.TypeMirror;
|
import javax.lang.model.type.TypeMirror;
|
||||||
|
|
||||||
@ -31,8 +33,8 @@ import org.mapstruct.ap.model.assignment.Assignment;
|
|||||||
import org.mapstruct.ap.model.assignment.GetterWrapperForCollectionsAndMaps;
|
import org.mapstruct.ap.model.assignment.GetterWrapperForCollectionsAndMaps;
|
||||||
import org.mapstruct.ap.model.assignment.NewCollectionOrMapWrapper;
|
import org.mapstruct.ap.model.assignment.NewCollectionOrMapWrapper;
|
||||||
import org.mapstruct.ap.model.assignment.NullCheckWrapper;
|
import org.mapstruct.ap.model.assignment.NullCheckWrapper;
|
||||||
import org.mapstruct.ap.model.assignment.SetterWrapperForCollectionsAndMaps;
|
|
||||||
import org.mapstruct.ap.model.assignment.SetterWrapper;
|
import org.mapstruct.ap.model.assignment.SetterWrapper;
|
||||||
|
import org.mapstruct.ap.model.assignment.SetterWrapperForCollectionsAndMaps;
|
||||||
import org.mapstruct.ap.model.common.ModelElement;
|
import org.mapstruct.ap.model.common.ModelElement;
|
||||||
import org.mapstruct.ap.model.common.Parameter;
|
import org.mapstruct.ap.model.common.Parameter;
|
||||||
import org.mapstruct.ap.model.common.Type;
|
import org.mapstruct.ap.model.common.Type;
|
||||||
@ -41,12 +43,12 @@ import org.mapstruct.ap.model.source.SourceMethod;
|
|||||||
import org.mapstruct.ap.model.source.SourceReference;
|
import org.mapstruct.ap.model.source.SourceReference;
|
||||||
import org.mapstruct.ap.model.source.SourceReference.PropertyEntry;
|
import org.mapstruct.ap.model.source.SourceReference.PropertyEntry;
|
||||||
import org.mapstruct.ap.util.Executables;
|
import org.mapstruct.ap.util.Executables;
|
||||||
|
import org.mapstruct.ap.util.Message;
|
||||||
import org.mapstruct.ap.util.Strings;
|
import org.mapstruct.ap.util.Strings;
|
||||||
|
|
||||||
import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.DIRECT;
|
import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.DIRECT;
|
||||||
import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED;
|
import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED;
|
||||||
import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED_MAPPED;
|
import static org.mapstruct.ap.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED_MAPPED;
|
||||||
import org.mapstruct.ap.util.Message;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to
|
* Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to
|
||||||
@ -57,10 +59,12 @@ import org.mapstruct.ap.util.Message;
|
|||||||
*/
|
*/
|
||||||
public class PropertyMapping extends ModelElement {
|
public class PropertyMapping extends ModelElement {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
private final String sourceBeanName;
|
private final String sourceBeanName;
|
||||||
private final String targetAccessorName;
|
private final String targetAccessorName;
|
||||||
private final Type targetType;
|
private final Type targetType;
|
||||||
private final Assignment assignment;
|
private final Assignment assignment;
|
||||||
|
private final List<String> dependsOn;
|
||||||
|
|
||||||
public static class PropertyMappingBuilder {
|
public static class PropertyMappingBuilder {
|
||||||
|
|
||||||
@ -74,6 +78,7 @@ public class PropertyMapping extends ModelElement {
|
|||||||
private TypeMirror resultType;
|
private TypeMirror resultType;
|
||||||
private SourceReference sourceReference;
|
private SourceReference sourceReference;
|
||||||
private Collection<String> existingVariableNames;
|
private Collection<String> existingVariableNames;
|
||||||
|
private List<String> dependsOn;
|
||||||
|
|
||||||
public PropertyMappingBuilder mappingContext(MappingBuilderContext mappingContext) {
|
public PropertyMappingBuilder mappingContext(MappingBuilderContext mappingContext) {
|
||||||
this.ctx = mappingContext;
|
this.ctx = mappingContext;
|
||||||
@ -120,6 +125,11 @@ public class PropertyMapping extends ModelElement {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PropertyMappingBuilder dependsOn(List<String> dependsOn) {
|
||||||
|
this.dependsOn = dependsOn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
private enum TargetAccessorType {
|
private enum TargetAccessorType {
|
||||||
|
|
||||||
GETTER,
|
GETTER,
|
||||||
@ -195,10 +205,12 @@ public class PropertyMapping extends ModelElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new PropertyMapping(
|
return new PropertyMapping(
|
||||||
|
targetPropertyName,
|
||||||
sourceReference.getParameter().getName(),
|
sourceReference.getParameter().getName(),
|
||||||
targetAccessor.getSimpleName().toString(),
|
targetAccessor.getSimpleName().toString(),
|
||||||
targetType,
|
targetType,
|
||||||
assignment
|
assignment,
|
||||||
|
dependsOn
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,10 +477,12 @@ public class PropertyMapping extends ModelElement {
|
|||||||
private SourceMethod method;
|
private SourceMethod method;
|
||||||
private String constantExpression;
|
private String constantExpression;
|
||||||
private ExecutableElement targetAccessor;
|
private ExecutableElement targetAccessor;
|
||||||
|
private String targetPropertyName;
|
||||||
private String dateFormat;
|
private String dateFormat;
|
||||||
private List<TypeMirror> qualifiers;
|
private List<TypeMirror> qualifiers;
|
||||||
private TypeMirror resultType;
|
private TypeMirror resultType;
|
||||||
private Collection<String> existingVariableNames;
|
private Collection<String> existingVariableNames;
|
||||||
|
private List<String> dependsOn;
|
||||||
|
|
||||||
public ConstantMappingBuilder mappingContext(MappingBuilderContext mappingContext) {
|
public ConstantMappingBuilder mappingContext(MappingBuilderContext mappingContext) {
|
||||||
this.ctx = mappingContext;
|
this.ctx = mappingContext;
|
||||||
@ -490,6 +504,11 @@ public class PropertyMapping extends ModelElement {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ConstantMappingBuilder targetPropertyName(String targetPropertyName) {
|
||||||
|
this.targetPropertyName = targetPropertyName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ConstantMappingBuilder dateFormat(String dateFormat) {
|
public ConstantMappingBuilder dateFormat(String dateFormat) {
|
||||||
this.dateFormat = dateFormat;
|
this.dateFormat = dateFormat;
|
||||||
return this;
|
return this;
|
||||||
@ -510,6 +529,11 @@ public class PropertyMapping extends ModelElement {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ConstantMappingBuilder dependsOn(List<String> dependsOn) {
|
||||||
|
this.dependsOn = dependsOn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public PropertyMapping build() {
|
public PropertyMapping build() {
|
||||||
|
|
||||||
// source
|
// source
|
||||||
@ -525,8 +549,6 @@ public class PropertyMapping extends ModelElement {
|
|||||||
targetType = ctx.getTypeFactory().getReturnType( targetAccessor );
|
targetType = ctx.getTypeFactory().getReturnType( targetAccessor );
|
||||||
}
|
}
|
||||||
|
|
||||||
String targetPropertyName = Executables.getPropertyName( targetAccessor );
|
|
||||||
|
|
||||||
Assignment assignment = ctx.getMappingResolver().getTargetAssignment(
|
Assignment assignment = ctx.getMappingResolver().getTargetAssignment(
|
||||||
method,
|
method,
|
||||||
mappedElement,
|
mappedElement,
|
||||||
@ -565,7 +587,13 @@ public class PropertyMapping extends ModelElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PropertyMapping( targetAccessor.getSimpleName().toString(), targetType, assignment );
|
return new PropertyMapping(
|
||||||
|
targetPropertyName,
|
||||||
|
targetAccessor.getSimpleName().toString(),
|
||||||
|
targetType,
|
||||||
|
assignment,
|
||||||
|
dependsOn
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -576,6 +604,8 @@ public class PropertyMapping extends ModelElement {
|
|||||||
private String javaExpression;
|
private String javaExpression;
|
||||||
private ExecutableElement targetAccessor;
|
private ExecutableElement targetAccessor;
|
||||||
private Collection<String> existingVariableNames;
|
private Collection<String> existingVariableNames;
|
||||||
|
private String targetPropertyName;
|
||||||
|
private List<String> dependsOn;
|
||||||
|
|
||||||
public JavaExpressionMappingBuilder mappingContext(MappingBuilderContext mappingContext) {
|
public JavaExpressionMappingBuilder mappingContext(MappingBuilderContext mappingContext) {
|
||||||
this.ctx = mappingContext;
|
this.ctx = mappingContext;
|
||||||
@ -602,6 +632,16 @@ public class PropertyMapping extends ModelElement {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JavaExpressionMappingBuilder targetPropertyName(String targetPropertyName) {
|
||||||
|
this.targetPropertyName = targetPropertyName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JavaExpressionMappingBuilder dependsOn(List<String> dependsOn) {
|
||||||
|
this.dependsOn = dependsOn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public PropertyMapping build() {
|
public PropertyMapping build() {
|
||||||
|
|
||||||
Assignment assignment = AssignmentFactory.createDirect( javaExpression );
|
Assignment assignment = AssignmentFactory.createDirect( javaExpression );
|
||||||
@ -623,24 +663,38 @@ public class PropertyMapping extends ModelElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PropertyMapping( targetAccessor.getSimpleName().toString(), targetType, assignment );
|
return new PropertyMapping(
|
||||||
|
targetPropertyName,
|
||||||
|
targetAccessor.getSimpleName().toString(),
|
||||||
|
targetType,
|
||||||
|
assignment,
|
||||||
|
dependsOn
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor for creating mappings of constant expressions.
|
// Constructor for creating mappings of constant expressions.
|
||||||
private PropertyMapping(String targetAccessorName, Type targetType, Assignment propertyAssignment) {
|
private PropertyMapping(String name, String targetAccessorName, Type targetType, Assignment propertyAssignment,
|
||||||
this( null, targetAccessorName, targetType, propertyAssignment );
|
List<String> dependsOn) {
|
||||||
|
this( name, null, targetAccessorName, targetType, propertyAssignment, dependsOn );
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertyMapping(String sourceBeanName, String targetAccessorName, Type targetType, Assignment assignment) {
|
private PropertyMapping(String name, String sourceBeanName, String targetAccessorName, Type targetType,
|
||||||
|
Assignment assignment, List<String> dependsOn) {
|
||||||
|
this.name = name;
|
||||||
this.sourceBeanName = sourceBeanName;
|
this.sourceBeanName = sourceBeanName;
|
||||||
|
|
||||||
this.targetAccessorName = targetAccessorName;
|
this.targetAccessorName = targetAccessorName;
|
||||||
this.targetType = targetType;
|
this.targetType = targetType;
|
||||||
|
|
||||||
this.assignment = assignment;
|
this.assignment = assignment;
|
||||||
|
this.dependsOn = dependsOn != null ? dependsOn : Collections.<String>emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of this mapping (property name on the target side)
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSourceBeanName() {
|
public String getSourceBeanName() {
|
||||||
@ -664,12 +718,17 @@ public class PropertyMapping extends ModelElement {
|
|||||||
return assignment.getImportTypes();
|
return assignment.getImportTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getDependsOn() {
|
||||||
|
return dependsOn;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "PropertyMapping {"
|
return "PropertyMapping {"
|
||||||
+ "\n targetName='" + targetAccessorName + "\',"
|
+ "\n name='" + name + "\',"
|
||||||
+ "\n targetType=" + targetType + ","
|
+ "\n targetType=" + targetType + ","
|
||||||
+ "\n propertyAssignment=" + assignment
|
+ "\n propertyAssignment=" + assignment + ","
|
||||||
|
+ "\n dependsOn=" + dependsOn
|
||||||
+ "\n}";
|
+ "\n}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||||
|
* and/or other contributors as indicated by the @authors tag. See the
|
||||||
|
* copyright.txt file in the distribution for a full listing of all
|
||||||
|
* contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.model.dependency;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyzes graphs: Discovers all descendants of given nodes and detects cyclic dependencies between nodes if present.
|
||||||
|
*
|
||||||
|
* @author Gunnar Morling
|
||||||
|
*/
|
||||||
|
public class GraphAnalyzer {
|
||||||
|
|
||||||
|
private final Map<String, Node> nodes = new HashMap<String, Node>();
|
||||||
|
private final Set<List<String>> cycles = new HashSet<List<String>>();
|
||||||
|
|
||||||
|
private final Stack<Node> currentPath = new Stack<Node>();
|
||||||
|
|
||||||
|
public void addNode(String name, List<String> descendants) {
|
||||||
|
Node node = getNode( name );
|
||||||
|
|
||||||
|
for ( String descendant : descendants ) {
|
||||||
|
node.addDescendant( getNode( descendant ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNode(String name, String... descendants) {
|
||||||
|
Node node = getNode( name );
|
||||||
|
|
||||||
|
for ( String descendant : descendants ) {
|
||||||
|
node.addDescendant( getNode( descendant ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a full traversal of the graph, detecting potential cycles and calculates the full list of descendants of
|
||||||
|
* the nodes.
|
||||||
|
*/
|
||||||
|
public void analyze() {
|
||||||
|
for ( Node node : nodes.values() ) {
|
||||||
|
depthFirstSearch( node );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the descendants of the given node, either direct or transitive ones.
|
||||||
|
* <p>
|
||||||
|
* <b>Note</b>:The list will only be complete if the graph contains no cycles.
|
||||||
|
*/
|
||||||
|
public Set<String> getAllDescendants(String name) {
|
||||||
|
Node node = nodes.get( name );
|
||||||
|
return node != null ? node.getAllDescendants() : Collections.<String>emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<List<String>> getCycles() {
|
||||||
|
return cycles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void depthFirstSearch(Node node) {
|
||||||
|
if ( node.isProcessed() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPath.push( node );
|
||||||
|
|
||||||
|
// the node is on the stack already -> cycle
|
||||||
|
if ( node.isVisited() ) {
|
||||||
|
cycles.add( getCurrentCycle( node ) );
|
||||||
|
currentPath.pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.setVisited( true );
|
||||||
|
|
||||||
|
for ( Node descendant : node.getDescendants() ) {
|
||||||
|
depthFirstSearch( descendant );
|
||||||
|
node.getAllDescendants().addAll( descendant.getAllDescendants() );
|
||||||
|
}
|
||||||
|
|
||||||
|
node.setProcessed( true );
|
||||||
|
currentPath.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getCurrentCycle(Node start) {
|
||||||
|
List<String> cycle = new ArrayList<String>();
|
||||||
|
boolean inCycle = false;
|
||||||
|
|
||||||
|
for ( Node n : currentPath ) {
|
||||||
|
if ( n.getName().equals( start.getName() ) ) {
|
||||||
|
inCycle = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( inCycle ) {
|
||||||
|
cycle.add( n.getName() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cycle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node getNode(String name) {
|
||||||
|
Node node = nodes.get( name );
|
||||||
|
|
||||||
|
if ( node == null ) {
|
||||||
|
node = new Node( name );
|
||||||
|
nodes.put( name, node );
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||||
|
* and/or other contributors as indicated by the @authors tag. See the
|
||||||
|
* copyright.txt file in the distribution for a full listing of all
|
||||||
|
* contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.model.dependency;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node of a directed graph.
|
||||||
|
*
|
||||||
|
* @author Gunnar Morling
|
||||||
|
*/
|
||||||
|
class Node {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private boolean visited;
|
||||||
|
private boolean processed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The direct descendants of this node.
|
||||||
|
*/
|
||||||
|
private final List<Node> descendants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All descendants of this node, direct and transitive ones, as discovered through graph traversal.
|
||||||
|
*/
|
||||||
|
private final Set<String> allDescendants;
|
||||||
|
|
||||||
|
public Node(String name) {
|
||||||
|
this.name = name;
|
||||||
|
descendants = new ArrayList<Node>();
|
||||||
|
allDescendants = new HashSet<String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVisited() {
|
||||||
|
return visited;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVisited(boolean visited) {
|
||||||
|
this.visited = visited;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProcessed() {
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProcessed(boolean processed) {
|
||||||
|
this.processed = processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDescendant(Node node) {
|
||||||
|
descendants.add( node );
|
||||||
|
allDescendants.add( node.getName() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Node> getDescendants() {
|
||||||
|
return descendants;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getAllDescendants() {
|
||||||
|
return allDescendants;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ( ( name == null ) ? 0 : name.hashCode() );
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if ( this == obj ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( obj == null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( getClass() != obj.getClass() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Node other = (Node) obj;
|
||||||
|
if ( name == null ) {
|
||||||
|
if ( other.name != null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( !name.equals( other.name ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -19,12 +19,12 @@
|
|||||||
package org.mapstruct.ap.model.source;
|
package org.mapstruct.ap.model.source;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.lang.model.element.AnnotationMirror;
|
import javax.lang.model.element.AnnotationMirror;
|
||||||
import javax.lang.model.element.AnnotationValue;
|
import javax.lang.model.element.AnnotationValue;
|
||||||
import javax.lang.model.element.ElementKind;
|
import javax.lang.model.element.ElementKind;
|
||||||
@ -57,6 +57,7 @@ public class Mapping {
|
|||||||
private final List<TypeMirror> qualifiers;
|
private final List<TypeMirror> qualifiers;
|
||||||
private final TypeMirror resultType;
|
private final TypeMirror resultType;
|
||||||
private final boolean isIgnored;
|
private final boolean isIgnored;
|
||||||
|
private final List<String> dependsOn;
|
||||||
|
|
||||||
private final AnnotationMirror mirror;
|
private final AnnotationMirror mirror;
|
||||||
private final AnnotationValue sourceAnnotationValue;
|
private final AnnotationValue sourceAnnotationValue;
|
||||||
@ -120,6 +121,8 @@ public class Mapping {
|
|||||||
|
|
||||||
boolean resultTypeIsDefined = !TypeKind.VOID.equals( mappingPrism.resultType().getKind() );
|
boolean resultTypeIsDefined = !TypeKind.VOID.equals( mappingPrism.resultType().getKind() );
|
||||||
TypeMirror resultType = resultTypeIsDefined ? mappingPrism.resultType() : null;
|
TypeMirror resultType = resultTypeIsDefined ? mappingPrism.resultType() : null;
|
||||||
|
List<String> dependsOn =
|
||||||
|
mappingPrism.dependsOn() != null ? mappingPrism.dependsOn() : Collections.<String>emptyList();
|
||||||
|
|
||||||
return new Mapping(
|
return new Mapping(
|
||||||
source,
|
source,
|
||||||
@ -132,7 +135,8 @@ public class Mapping {
|
|||||||
mappingPrism.mirror,
|
mappingPrism.mirror,
|
||||||
mappingPrism.values.source(),
|
mappingPrism.values.source(),
|
||||||
mappingPrism.values.target(),
|
mappingPrism.values.target(),
|
||||||
resultType
|
resultType,
|
||||||
|
dependsOn
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +145,7 @@ public class Mapping {
|
|||||||
String dateFormat, List<TypeMirror> qualifiers,
|
String dateFormat, List<TypeMirror> qualifiers,
|
||||||
boolean isIgnored, AnnotationMirror mirror,
|
boolean isIgnored, AnnotationMirror mirror,
|
||||||
AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue,
|
AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue,
|
||||||
TypeMirror resultType ) {
|
TypeMirror resultType, List<String> dependsOn) {
|
||||||
this.sourceName = sourceName;
|
this.sourceName = sourceName;
|
||||||
this.constant = constant;
|
this.constant = constant;
|
||||||
this.javaExpression = javaExpression;
|
this.javaExpression = javaExpression;
|
||||||
@ -153,6 +157,7 @@ public class Mapping {
|
|||||||
this.sourceAnnotationValue = sourceAnnotationValue;
|
this.sourceAnnotationValue = sourceAnnotationValue;
|
||||||
this.targetAnnotationValue = targetAnnotationValue;
|
this.targetAnnotationValue = targetAnnotationValue;
|
||||||
this.resultType = resultType;
|
this.resultType = resultType;
|
||||||
|
this.dependsOn = dependsOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getExpression(MappingPrism mappingPrism, ExecutableElement element,
|
private static String getExpression(MappingPrism mappingPrism, ExecutableElement element,
|
||||||
@ -243,6 +248,10 @@ public class Mapping {
|
|||||||
return resultType;
|
return resultType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getDependsOn() {
|
||||||
|
return dependsOn;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasPropertyInReverseMethod(String name, SourceMethod method) {
|
private boolean hasPropertyInReverseMethod(String name, SourceMethod method) {
|
||||||
CollectionMappingStrategyPrism cms = method.getMapperConfiguration().getCollectionMappingStrategy();
|
CollectionMappingStrategyPrism cms = method.getMapperConfiguration().getCollectionMappingStrategy();
|
||||||
return method.getResultType().getTargetAccessors( cms ).containsKey( name );
|
return method.getResultType().getTargetAccessors( cms ).containsKey( name );
|
||||||
@ -287,7 +296,8 @@ public class Mapping {
|
|||||||
mirror,
|
mirror,
|
||||||
sourceAnnotationValue,
|
sourceAnnotationValue,
|
||||||
targetAnnotationValue,
|
targetAnnotationValue,
|
||||||
null
|
null,
|
||||||
|
Collections.<String>emptyList()
|
||||||
);
|
);
|
||||||
|
|
||||||
reverse.init( method, messager, typeFactory );
|
reverse.init( method, messager, typeFactory );
|
||||||
@ -311,8 +321,9 @@ public class Mapping {
|
|||||||
mirror,
|
mirror,
|
||||||
sourceAnnotationValue,
|
sourceAnnotationValue,
|
||||||
targetAnnotationValue,
|
targetAnnotationValue,
|
||||||
resultType
|
resultType,
|
||||||
);
|
dependsOn
|
||||||
|
);
|
||||||
|
|
||||||
if ( sourceReference != null ) {
|
if ( sourceReference != null ) {
|
||||||
mapping.sourceReference = sourceReference.copyForInheritanceTo( method );
|
mapping.sourceReference = sourceReference.copyForInheritanceTo( method );
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||||
|
* and/or other contributors as indicated by the @authors tag. See the
|
||||||
|
* copyright.txt file in the distribution for a full listing of all
|
||||||
|
* contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.dependency;
|
||||||
|
|
||||||
|
public class Address {
|
||||||
|
|
||||||
|
private String firstName;
|
||||||
|
private String middleName;
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMiddleName() {
|
||||||
|
return middleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMiddleName(String middleName) {
|
||||||
|
this.middleName = middleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||||
|
* and/or other contributors as indicated by the @authors tag. See the
|
||||||
|
* copyright.txt file in the distribution for a full listing of all
|
||||||
|
* contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.dependency;
|
||||||
|
|
||||||
|
public class AddressDto {
|
||||||
|
|
||||||
|
private String givenName;
|
||||||
|
private String middleName;
|
||||||
|
private String surName;
|
||||||
|
private String fullName;
|
||||||
|
|
||||||
|
public String getGivenName() {
|
||||||
|
return givenName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGivenName(String givenName) {
|
||||||
|
this.givenName = givenName;
|
||||||
|
this.fullName = givenName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMiddleName() {
|
||||||
|
return middleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMiddleName(String middleName) {
|
||||||
|
this.middleName = middleName;
|
||||||
|
this.fullName += " " + middleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSurName() {
|
||||||
|
return surName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSurName(String surName) {
|
||||||
|
this.surName = surName;
|
||||||
|
this.fullName += " " + surName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullName() {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||||
|
* and/or other contributors as indicated by the @authors tag. See the
|
||||||
|
* copyright.txt file in the distribution for a full listing of all
|
||||||
|
* contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.dependency;
|
||||||
|
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.Mappings;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface AddressMapper {
|
||||||
|
|
||||||
|
AddressMapper INSTANCE = Mappers.getMapper( AddressMapper.class );
|
||||||
|
|
||||||
|
@Mappings({
|
||||||
|
@Mapping(target = "surName", source = "lastName", dependsOn = "middleName"),
|
||||||
|
@Mapping(target = "middleName", dependsOn = "givenName"),
|
||||||
|
@Mapping(target = "givenName", source = "firstName")
|
||||||
|
})
|
||||||
|
AddressDto addressToDto(Address address);
|
||||||
|
|
||||||
|
@Mappings({
|
||||||
|
@Mapping(target = "lastName", dependsOn = { "firstName", "middleName" })
|
||||||
|
})
|
||||||
|
PersonDto personToDto(Person person);
|
||||||
|
}
|
@ -0,0 +1,224 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||||
|
* and/or other contributors as indicated by the @authors tag. See the
|
||||||
|
* copyright.txt file in the distribution for a full listing of all
|
||||||
|
* contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.dependency;
|
||||||
|
|
||||||
|
import static org.fest.assertions.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mapstruct.ap.model.dependency.GraphAnalyzer;
|
||||||
|
import org.mapstruct.ap.util.Strings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for {@link GraphAnalyzer}.
|
||||||
|
*
|
||||||
|
* @author Gunnar Morling
|
||||||
|
*/
|
||||||
|
public class GraphAnalyzerTest {
|
||||||
|
|
||||||
|
private GraphAnalyzer detector;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUpDetector() {
|
||||||
|
detector = new GraphAnalyzer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptyGraph() {
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( detector.getCycles() ).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void singleNode() {
|
||||||
|
detector.addNode( "a" );
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( detector.getCycles() ).isEmpty();
|
||||||
|
assertThat( detector.getAllDescendants( "a" ) ).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void twoNodesWithoutCycle() {
|
||||||
|
detector.addNode( "a", "b" );
|
||||||
|
detector.addNode( "b" );
|
||||||
|
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( detector.getCycles() ).isEmpty();
|
||||||
|
assertThat( detector.getAllDescendants( "a" ) ).containsOnly( "b" );
|
||||||
|
assertThat( detector.getAllDescendants( "b" ) ).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void twoNodesWithCycle() {
|
||||||
|
detector.addNode( "a", "b" );
|
||||||
|
detector.addNode( "b", "a" );
|
||||||
|
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( asStrings( detector.getCycles() ) ).containsOnly( "a -> b -> a" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void threeNodesWithCycleBetweenTwo() {
|
||||||
|
detector.addNode( "a", "b" );
|
||||||
|
detector.addNode( "b", "a", "c" );
|
||||||
|
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( asStrings( detector.getCycles() ) ).containsOnly( "a -> b -> a" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void twoNodesWithSharedDescendantWithoutCycle() {
|
||||||
|
detector.addNode( "a", "b" );
|
||||||
|
detector.addNode( "b", "c" );
|
||||||
|
detector.addNode( "a", "c" );
|
||||||
|
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( asStrings( detector.getCycles() ) ).isEmpty();
|
||||||
|
assertThat( detector.getAllDescendants( "a" ) ).containsOnly( "b", "c" );
|
||||||
|
assertThat( detector.getAllDescendants( "b" ) ).containsOnly( "c" );
|
||||||
|
assertThat( detector.getAllDescendants( "c" ) ).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void threeNodesWithoutCycle() {
|
||||||
|
detector.addNode( "a", "b" );
|
||||||
|
detector.addNode( "c", "b" );
|
||||||
|
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( asStrings( detector.getCycles() ) ).isEmpty();
|
||||||
|
|
||||||
|
assertThat( detector.getAllDescendants( "a" ) ).containsOnly( "b" );
|
||||||
|
assertThat( detector.getAllDescendants( "b" ) ).isEmpty();
|
||||||
|
assertThat( detector.getAllDescendants( "c" ) ).containsOnly( "b" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fourNodesWithCycleBetweenThree() {
|
||||||
|
detector.addNode( "a", "b" );
|
||||||
|
detector.addNode( "b", "c" );
|
||||||
|
detector.addNode( "c", "d" );
|
||||||
|
detector.addNode( "d", "b" );
|
||||||
|
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( asStrings( detector.getCycles() ) ).containsOnly( "b -> c -> d -> b" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fourNodesWithTwoCycles() {
|
||||||
|
detector.addNode( "a", "b" );
|
||||||
|
detector.addNode( "b", "a" );
|
||||||
|
detector.addNode( "c", "d" );
|
||||||
|
detector.addNode( "d", "c" );
|
||||||
|
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( asStrings( detector.getCycles() ) ).containsOnly( "a -> b -> a", "c -> d -> c" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fourNodesWithoutCycle() {
|
||||||
|
detector.addNode( "a", "b1" );
|
||||||
|
detector.addNode( "a", "b2" );
|
||||||
|
detector.addNode( "b1", "c" );
|
||||||
|
detector.addNode( "b2", "c" );
|
||||||
|
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( asStrings( detector.getCycles() ) ).isEmpty();
|
||||||
|
assertThat( detector.getAllDescendants( "a" ) ).containsOnly( "b1", "b2", "c" );
|
||||||
|
assertThat( detector.getAllDescendants( "b1" ) ).containsOnly( "c" );
|
||||||
|
assertThat( detector.getAllDescendants( "b2" ) ).containsOnly( "c" );
|
||||||
|
assertThat( detector.getAllDescendants( "c" ) ).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fourNodesWithCycle() {
|
||||||
|
detector.addNode( "a", "b1" );
|
||||||
|
detector.addNode( "a", "b2" );
|
||||||
|
detector.addNode( "b1", "c" );
|
||||||
|
detector.addNode( "b2", "c" );
|
||||||
|
detector.addNode( "c", "a" );
|
||||||
|
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( asStrings( detector.getCycles() ) ).containsOnly( "a -> b1 -> c -> a", "a -> b2 -> c -> a" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void eightNodesWithoutCycle() {
|
||||||
|
detector.addNode( "a", "b1" );
|
||||||
|
detector.addNode( "a", "b2" );
|
||||||
|
detector.addNode( "b1", "c1" );
|
||||||
|
detector.addNode( "b1", "c2" );
|
||||||
|
detector.addNode( "b2", "c3" );
|
||||||
|
detector.addNode( "b2", "c4" );
|
||||||
|
|
||||||
|
detector.analyze();
|
||||||
|
|
||||||
|
assertThat( detector.getCycles() ).isEmpty();
|
||||||
|
|
||||||
|
assertThat( detector.getAllDescendants( "a" ) ).containsOnly( "b1", "b2", "c1", "c2", "c3", "c4" );
|
||||||
|
assertThat( detector.getAllDescendants( "b1" ) ).containsOnly( "c1", "c2" );
|
||||||
|
assertThat( detector.getAllDescendants( "b2" ) ).containsOnly( "c3", "c4" );
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> asStrings(Set<List<String>> cycles) {
|
||||||
|
Set<String> asStrings = new HashSet<String>();
|
||||||
|
|
||||||
|
for ( List<String> cycle : cycles ) {
|
||||||
|
asStrings.add( asString( cycle ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return asStrings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String asString(List<String> cycle) {
|
||||||
|
return Strings.join( normalize( cycle ), " -> " );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Normalizes" a cycle so that the minimum element comes first. E.g. both the cycles {@code b -> c -> a -> b} and
|
||||||
|
* {@code c -> a -> b -> c} would be normalized to {@code a -> b -> c -> a}.
|
||||||
|
*/
|
||||||
|
private List<String> normalize(List<String> cycle) {
|
||||||
|
// remove the first element
|
||||||
|
cycle = cycle.subList( 1, cycle.size() );
|
||||||
|
|
||||||
|
// rotate the cycle so the minimum element comes first
|
||||||
|
Collections.rotate( cycle, -cycle.indexOf( Collections.min( cycle ) ) );
|
||||||
|
|
||||||
|
// add the first element add the end to re-close the cycle
|
||||||
|
cycle.add( cycle.get( 0 ) );
|
||||||
|
|
||||||
|
return cycle;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||||
|
* and/or other contributors as indicated by the @authors tag. See the
|
||||||
|
* copyright.txt file in the distribution for a full listing of all
|
||||||
|
* contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.dependency;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.ap.testutil.IssueKey;
|
||||||
|
import org.mapstruct.ap.testutil.WithClasses;
|
||||||
|
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
|
||||||
|
|
||||||
|
import static org.fest.assertions.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for ordering mapped attributes by means of {@link Mapping#dependsOn()}.
|
||||||
|
*
|
||||||
|
* @author Gunnar Morling
|
||||||
|
*/
|
||||||
|
@WithClasses({ Person.class, PersonDto.class, Address.class, AddressDto.class, AddressMapper.class })
|
||||||
|
@RunWith(AnnotationProcessorTestRunner.class)
|
||||||
|
public class OrderingTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@IssueKey("304")
|
||||||
|
public void shouldApplyChainOfDependencies() {
|
||||||
|
Address source = new Address();
|
||||||
|
source.setFirstName( "Bob" );
|
||||||
|
source.setMiddleName( "J." );
|
||||||
|
source.setLastName( "McRobb" );
|
||||||
|
|
||||||
|
AddressDto target = AddressMapper.INSTANCE.addressToDto( source );
|
||||||
|
|
||||||
|
assertThat( target ).isNotNull();
|
||||||
|
assertThat( target.getFullName() ).isEqualTo( "Bob J. McRobb" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@IssueKey("304")
|
||||||
|
public void shouldApplySeveralDependenciesConfiguredForOneProperty() {
|
||||||
|
Person source = new Person();
|
||||||
|
source.setFirstName( "Bob" );
|
||||||
|
source.setMiddleName( "J." );
|
||||||
|
source.setLastName( "McRobb" );
|
||||||
|
|
||||||
|
PersonDto target = AddressMapper.INSTANCE.personToDto( source );
|
||||||
|
|
||||||
|
assertThat( target ).isNotNull();
|
||||||
|
assertThat( target.getFullName() ).isEqualTo( "Bob J. McRobb" );
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||||
|
* and/or other contributors as indicated by the @authors tag. See the
|
||||||
|
* copyright.txt file in the distribution for a full listing of all
|
||||||
|
* contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.dependency;
|
||||||
|
|
||||||
|
public class Person {
|
||||||
|
|
||||||
|
private String firstName;
|
||||||
|
private String middleName;
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMiddleName() {
|
||||||
|
return middleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMiddleName(String middleName) {
|
||||||
|
this.middleName = middleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
|
||||||
|
* and/or other contributors as indicated by the @authors tag. See the
|
||||||
|
* copyright.txt file in the distribution for a full listing of all
|
||||||
|
* contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.dependency;
|
||||||
|
|
||||||
|
public class PersonDto {
|
||||||
|
|
||||||
|
private String firstName;
|
||||||
|
private String middleName;
|
||||||
|
private String lastName;
|
||||||
|
private String fullName;
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMiddleName() {
|
||||||
|
return middleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMiddleName(String middleName) {
|
||||||
|
this.middleName = middleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
this.fullName = firstName + " " + middleName + " " + lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullName() {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user