mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
2696: Invert @SubclassMappings
with @InheritInverseConfiguration
. (#2708)
* #2696: Added support for '@InheritInverseConfiguration' with '@SubclassMappings'. * #2696: Overriding of inverse inheritence implemented. New order has preference over inherited order.
This commit is contained in:
parent
2a2c11e871
commit
7bb85d05c0
@ -157,6 +157,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanMappingMethod build() {
|
||||
|
||||
BeanMappingOptions beanMapping = method.getOptions().getBeanMapping();
|
||||
|
@ -11,6 +11,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
|
||||
import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem;
|
||||
import org.mapstruct.ap.internal.model.common.Type;
|
||||
@ -35,7 +36,8 @@ public class MappingMethodOptions {
|
||||
null,
|
||||
null,
|
||||
Collections.emptyList(),
|
||||
Collections.emptySet()
|
||||
Collections.emptySet(),
|
||||
null
|
||||
);
|
||||
|
||||
private MapperOptions mapper;
|
||||
@ -46,14 +48,16 @@ public class MappingMethodOptions {
|
||||
private EnumMappingOptions enumMappingOptions;
|
||||
private List<ValueMappingOptions> valueMappings;
|
||||
private boolean fullyInitialized;
|
||||
private Set<SubclassMappingOptions> subclassMapping;
|
||||
private Set<SubclassMappingOptions> subclassMappings;
|
||||
|
||||
private SubclassValidator subclassValidator;
|
||||
|
||||
public MappingMethodOptions(MapperOptions mapper, Set<MappingOptions> mappings,
|
||||
IterableMappingOptions iterableMapping,
|
||||
MapMappingOptions mapMapping, BeanMappingOptions beanMapping,
|
||||
EnumMappingOptions enumMappingOptions,
|
||||
List<ValueMappingOptions> valueMappings,
|
||||
Set<SubclassMappingOptions> subclassMapping) {
|
||||
Set<SubclassMappingOptions> subclassMappings, SubclassValidator subclassValidator) {
|
||||
this.mapper = mapper;
|
||||
this.mappings = mappings;
|
||||
this.iterableMapping = iterableMapping;
|
||||
@ -61,7 +65,8 @@ public class MappingMethodOptions {
|
||||
this.beanMapping = beanMapping;
|
||||
this.enumMappingOptions = enumMappingOptions;
|
||||
this.valueMappings = valueMappings;
|
||||
this.subclassMapping = subclassMapping;
|
||||
this.subclassMappings = subclassMappings;
|
||||
this.subclassValidator = subclassValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,7 +107,7 @@ public class MappingMethodOptions {
|
||||
}
|
||||
|
||||
public Set<SubclassMappingOptions> getSubclassMappings() {
|
||||
return subclassMapping;
|
||||
return subclassMappings;
|
||||
}
|
||||
|
||||
public void setIterableMapping(IterableMappingOptions iterableMapping) {
|
||||
@ -144,10 +149,13 @@ public class MappingMethodOptions {
|
||||
/**
|
||||
* Merges in all the mapping options configured, giving the already defined options precedence.
|
||||
*
|
||||
* @param sourceMethod the method which inherits the options.
|
||||
* @param templateMethod the template method with the options to inherit, may be {@code null}
|
||||
* @param isInverse if {@code true}, the specified options are from an inverse method
|
||||
* @param annotationMirror the annotation on which the compile errors will be shown.
|
||||
*/
|
||||
public void applyInheritedOptions(SourceMethod templateMethod, boolean isInverse) {
|
||||
public void applyInheritedOptions(SourceMethod sourceMethod, SourceMethod templateMethod, boolean isInverse,
|
||||
AnnotationMirror annotationMirror) {
|
||||
MappingMethodOptions templateOptions = templateMethod.getOptions();
|
||||
if ( null != templateOptions ) {
|
||||
if ( !getIterableMapping().hasAnnotation() && templateOptions.getIterableMapping().hasAnnotation() ) {
|
||||
@ -184,7 +192,7 @@ public class MappingMethodOptions {
|
||||
}
|
||||
else {
|
||||
if ( templateOptions.getValueMappings() != null ) {
|
||||
// iff there are also inherited mappings, we inverse and add them.
|
||||
// if there are also inherited mappings, we inverse and add them.
|
||||
for ( ValueMappingOptions inheritedValueMapping : templateOptions.getValueMappings() ) {
|
||||
ValueMappingOptions valueMapping =
|
||||
isInverse ? inheritedValueMapping.inverse() : inheritedValueMapping;
|
||||
@ -196,7 +204,13 @@ public class MappingMethodOptions {
|
||||
}
|
||||
}
|
||||
|
||||
// Do NOT inherit subclass mapping options!!!
|
||||
if ( isInverse ) {
|
||||
// normal inheritence of subclass mappings will result runtime in infinite loops.
|
||||
List<SubclassMappingOptions> inheritedMappings = SubclassMappingOptions.copyForInverseInheritance(
|
||||
templateOptions.getSubclassMappings(),
|
||||
getBeanMapping() );
|
||||
addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings );
|
||||
}
|
||||
|
||||
Set<MappingOptions> newMappings = new LinkedHashSet<>();
|
||||
for ( MappingOptions mapping : templateOptions.getMappings() ) {
|
||||
@ -218,6 +232,21 @@ public class MappingMethodOptions {
|
||||
}
|
||||
}
|
||||
|
||||
private void addAllNonRedefined(SourceMethod sourceMethod, AnnotationMirror annotationMirror,
|
||||
List<SubclassMappingOptions> inheritedMappings) {
|
||||
Set<SubclassMappingOptions> redefinedSubclassMappings = new HashSet<>( subclassMappings );
|
||||
for ( SubclassMappingOptions subclassMappingOption : inheritedMappings ) {
|
||||
if ( !redefinedSubclassMappings.contains( subclassMappingOption ) ) {
|
||||
if ( subclassValidator.isValidUsage(
|
||||
sourceMethod.getExecutable(),
|
||||
annotationMirror,
|
||||
subclassMappingOption.getSource() ) ) {
|
||||
subclassMappings.add( subclassMappingOption );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addAllNonRedefined(Set<MappingOptions> inheritedMappings) {
|
||||
Set<String> redefinedSources = new HashSet<>();
|
||||
Set<String> redefinedTargets = new HashSet<>();
|
||||
@ -328,7 +357,7 @@ public class MappingMethodOptions {
|
||||
/**
|
||||
* SubclassMappingOptions are not inherited to forged methods. They would result in an infinite loop if they were.
|
||||
*
|
||||
* @return a MappingMethodOptions without SubclassMappingOptions.
|
||||
* @return a MappingMethodOptions without SubclassMappingOptions or SubclassValidator.
|
||||
*/
|
||||
public static MappingMethodOptions getForgedMethodInheritedOptions(MappingMethodOptions options) {
|
||||
return new MappingMethodOptions(
|
||||
@ -339,7 +368,8 @@ public class MappingMethodOptions {
|
||||
options.beanMapping,
|
||||
options.enumMappingOptions,
|
||||
options.valueMappings,
|
||||
Collections.emptySet() );
|
||||
Collections.emptySet(),
|
||||
null );
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,15 +16,14 @@ import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.mapstruct.ap.internal.gem.ConditionGem;
|
||||
import org.mapstruct.ap.internal.util.TypeUtils;
|
||||
|
||||
import org.mapstruct.ap.internal.gem.ObjectFactoryGem;
|
||||
import org.mapstruct.ap.internal.model.common.Accessibility;
|
||||
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.gem.ObjectFactoryGem;
|
||||
import org.mapstruct.ap.internal.util.Executables;
|
||||
import org.mapstruct.ap.internal.util.Strings;
|
||||
import org.mapstruct.ap.internal.util.TypeUtils;
|
||||
|
||||
import static org.mapstruct.ap.internal.model.source.MappingMethodUtils.isEnumMapping;
|
||||
import static org.mapstruct.ap.internal.util.Collections.first;
|
||||
@ -98,6 +97,7 @@ public class SourceMethod implements Method {
|
||||
private Set<SubclassMappingOptions> subclassMappings;
|
||||
|
||||
private boolean verboseLogging;
|
||||
private SubclassValidator subclassValidator;
|
||||
|
||||
public Builder setDeclaringMapper(Type declaringMapper) {
|
||||
this.declaringMapper = declaringMapper;
|
||||
@ -159,6 +159,11 @@ public class SourceMethod implements Method {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSubclassValidator(SubclassValidator subclassValidator) {
|
||||
this.subclassValidator = subclassValidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTypeUtils(TypeUtils typeUtils) {
|
||||
this.typeUtils = typeUtils;
|
||||
return this;
|
||||
@ -212,7 +217,8 @@ public class SourceMethod implements Method {
|
||||
beanMapping,
|
||||
enumMappingOptions,
|
||||
valueMappings,
|
||||
subclassMappings
|
||||
subclassMappings,
|
||||
subclassValidator
|
||||
);
|
||||
|
||||
this.typeParameters = this.executable.getTypeParameters()
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
package org.mapstruct.ap.internal.model.source;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
@ -31,11 +32,13 @@ public class SubclassMappingOptions extends DelegatingOptions {
|
||||
|
||||
private final TypeMirror source;
|
||||
private final TypeMirror target;
|
||||
private final TypeUtils typeUtils;
|
||||
|
||||
public SubclassMappingOptions(TypeMirror source, TypeMirror target, DelegatingOptions next) {
|
||||
public SubclassMappingOptions(TypeMirror source, TypeMirror target, TypeUtils typeUtils, DelegatingOptions next) {
|
||||
super( next );
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.typeUtils = typeUtils;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -84,7 +87,9 @@ public class SubclassMappingOptions extends DelegatingOptions {
|
||||
targetSubclass.toString() );
|
||||
isConsistent = false;
|
||||
}
|
||||
subclassValidator.isInCorrectOrder( method, gem.mirror(), targetSubclass );
|
||||
if ( !subclassValidator.isValidUsage( method, gem.mirror(), sourceSubclass ) ) {
|
||||
isConsistent = false;
|
||||
}
|
||||
return isConsistent;
|
||||
}
|
||||
|
||||
@ -115,10 +120,10 @@ public class SubclassMappingOptions extends DelegatingOptions {
|
||||
public static void addInstances(SubclassMappingsGem gem, ExecutableElement method,
|
||||
BeanMappingOptions beanMappingOptions, FormattingMessager messager,
|
||||
TypeUtils typeUtils, Set<SubclassMappingOptions> mappings,
|
||||
List<Parameter> sourceParameters, Type resultType) {
|
||||
SubclassValidator subclassValidator = new SubclassValidator( messager, typeUtils );
|
||||
List<Parameter> sourceParameters, Type resultType,
|
||||
SubclassValidator subclassValidator) {
|
||||
for ( SubclassMappingGem subclassMappingGem : gem.value().get() ) {
|
||||
addAndValidateInstance(
|
||||
addInstance(
|
||||
subclassMappingGem,
|
||||
method,
|
||||
beanMappingOptions,
|
||||
@ -134,24 +139,8 @@ public class SubclassMappingOptions extends DelegatingOptions {
|
||||
public static void addInstance(SubclassMappingGem subclassMapping, ExecutableElement method,
|
||||
BeanMappingOptions beanMappingOptions, FormattingMessager messager,
|
||||
TypeUtils typeUtils, Set<SubclassMappingOptions> mappings,
|
||||
List<Parameter> sourceParameters, Type resultType) {
|
||||
addAndValidateInstance(
|
||||
subclassMapping,
|
||||
method,
|
||||
beanMappingOptions,
|
||||
messager,
|
||||
typeUtils,
|
||||
mappings,
|
||||
sourceParameters,
|
||||
resultType,
|
||||
new SubclassValidator( messager, typeUtils ) );
|
||||
}
|
||||
|
||||
private static void addAndValidateInstance(SubclassMappingGem subclassMapping, ExecutableElement method,
|
||||
BeanMappingOptions beanMappingOptions, FormattingMessager messager,
|
||||
TypeUtils typeUtils, Set<SubclassMappingOptions> mappings,
|
||||
List<Parameter> sourceParameters, Type resultType,
|
||||
SubclassValidator subclassValidator) {
|
||||
List<Parameter> sourceParameters, Type resultType,
|
||||
SubclassValidator subclassValidator) {
|
||||
if ( !isConsistent(
|
||||
subclassMapping,
|
||||
method,
|
||||
@ -166,6 +155,41 @@ public class SubclassMappingOptions extends DelegatingOptions {
|
||||
TypeMirror sourceSubclass = subclassMapping.source().getValue();
|
||||
TypeMirror targetSubclass = subclassMapping.target().getValue();
|
||||
|
||||
mappings.add( new SubclassMappingOptions( sourceSubclass, targetSubclass, beanMappingOptions ) );
|
||||
mappings
|
||||
.add(
|
||||
new SubclassMappingOptions(
|
||||
sourceSubclass,
|
||||
targetSubclass,
|
||||
typeUtils,
|
||||
beanMappingOptions ) );
|
||||
}
|
||||
|
||||
public static List<SubclassMappingOptions> copyForInverseInheritance(Set<SubclassMappingOptions> subclassMappings,
|
||||
BeanMappingOptions beanMappingOptions) {
|
||||
// we are not interested in keeping it unique at this point.
|
||||
List<SubclassMappingOptions> mappings = new ArrayList<>();
|
||||
for ( SubclassMappingOptions subclassMapping : subclassMappings ) {
|
||||
mappings.add(
|
||||
new SubclassMappingOptions(
|
||||
subclassMapping.target,
|
||||
subclassMapping.source,
|
||||
subclassMapping.typeUtils,
|
||||
beanMappingOptions ) );
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if ( obj == null || !( obj instanceof SubclassMappingOptions ) ) {
|
||||
return false;
|
||||
}
|
||||
SubclassMappingOptions other = (SubclassMappingOptions) obj;
|
||||
return typeUtils.isSameType( source, other.source );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 1; // use a stable value because TypeMirror is not safe to use for hashCode.
|
||||
}
|
||||
}
|
||||
|
@ -20,19 +20,28 @@ import org.mapstruct.ap.internal.util.TypeUtils;
|
||||
*
|
||||
* @author Ben Zegveld
|
||||
*/
|
||||
class SubclassValidator {
|
||||
public class SubclassValidator {
|
||||
|
||||
private final FormattingMessager messager;
|
||||
private final List<TypeMirror> handledSubclasses = new ArrayList<>();
|
||||
private final TypeUtils typeUtils;
|
||||
|
||||
SubclassValidator(FormattingMessager messager, TypeUtils typeUtils) {
|
||||
public SubclassValidator(FormattingMessager messager, TypeUtils typeUtils) {
|
||||
this.messager = messager;
|
||||
this.typeUtils = typeUtils;
|
||||
}
|
||||
|
||||
public boolean isInCorrectOrder(Element e, AnnotationMirror annotation, TypeMirror sourceType) {
|
||||
public boolean isValidUsage(Element e, AnnotationMirror annotation, TypeMirror sourceType) {
|
||||
for ( TypeMirror typeMirror : handledSubclasses ) {
|
||||
if ( typeUtils.isSameType( sourceType, typeMirror ) ) {
|
||||
messager
|
||||
.printMessage(
|
||||
e,
|
||||
annotation,
|
||||
Message.SUBCLASSMAPPING_DOUBLE_SOURCE_SUBCLASS,
|
||||
sourceType );
|
||||
return false;
|
||||
}
|
||||
if ( typeUtils.isAssignable( sourceType, typeMirror ) ) {
|
||||
messager
|
||||
.printMessage(
|
||||
@ -43,11 +52,11 @@ class SubclassValidator {
|
||||
typeMirror,
|
||||
sourceType,
|
||||
typeMirror );
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
handledSubclasses.add( sourceType );
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
@ -320,7 +321,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
||||
continue;
|
||||
}
|
||||
|
||||
mergeInheritedOptions( method, mapperAnnotation, methods, new ArrayList<>() );
|
||||
mergeInheritedOptions( method, mapperAnnotation, methods, new ArrayList<>(), null );
|
||||
|
||||
MappingMethodOptions mappingOptions = method.getOptions();
|
||||
|
||||
@ -462,7 +463,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
||||
}
|
||||
|
||||
private void mergeInheritedOptions(SourceMethod method, MapperOptions mapperConfig,
|
||||
List<SourceMethod> availableMethods, List<SourceMethod> initializingMethods) {
|
||||
List<SourceMethod> availableMethods, List<SourceMethod> initializingMethods,
|
||||
AnnotationMirror annotationMirror) {
|
||||
if ( initializingMethods.contains( method ) ) {
|
||||
// cycle detected
|
||||
|
||||
@ -497,10 +499,10 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
||||
|
||||
// apply defined (@InheritConfiguration, @InheritInverseConfiguration) mappings
|
||||
if ( forwardTemplateMethod != null ) {
|
||||
mappingOptions.applyInheritedOptions( forwardTemplateMethod, false );
|
||||
mappingOptions.applyInheritedOptions( method, forwardTemplateMethod, false, annotationMirror );
|
||||
}
|
||||
if ( inverseTemplateMethod != null ) {
|
||||
mappingOptions.applyInheritedOptions( inverseTemplateMethod, true );
|
||||
mappingOptions.applyInheritedOptions( method, inverseTemplateMethod, true, annotationMirror );
|
||||
}
|
||||
|
||||
// apply auto inherited options
|
||||
@ -510,7 +512,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
||||
// but.. there should not be an @InheritedConfiguration
|
||||
if ( forwardTemplateMethod == null && inheritanceStrategy.isApplyForward() ) {
|
||||
if ( applicablePrototypeMethods.size() == 1 ) {
|
||||
mappingOptions.applyInheritedOptions( first( applicablePrototypeMethods ), false );
|
||||
mappingOptions.applyInheritedOptions( method, first( applicablePrototypeMethods ), false,
|
||||
annotationMirror );
|
||||
}
|
||||
else if ( applicablePrototypeMethods.size() > 1 ) {
|
||||
messager.printMessage(
|
||||
@ -523,7 +526,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
||||
// or no @InheritInverseConfiguration
|
||||
if ( inverseTemplateMethod == null && inheritanceStrategy.isApplyReverse() ) {
|
||||
if ( applicableReversePrototypeMethods.size() == 1 ) {
|
||||
mappingOptions.applyInheritedOptions( first( applicableReversePrototypeMethods ), true );
|
||||
mappingOptions.applyInheritedOptions( method, first( applicableReversePrototypeMethods ), true,
|
||||
annotationMirror );
|
||||
}
|
||||
else if ( applicableReversePrototypeMethods.size() > 1 ) {
|
||||
messager.printMessage(
|
||||
@ -607,16 +611,23 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
||||
}
|
||||
}
|
||||
|
||||
return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods );
|
||||
return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods,
|
||||
getAnnotationMirror( inverseConfiguration ) );
|
||||
}
|
||||
|
||||
private AnnotationMirror getAnnotationMirror(InheritInverseConfigurationGem inverseConfiguration) {
|
||||
return inverseConfiguration == null ? null : inverseConfiguration.mirror();
|
||||
}
|
||||
|
||||
private SourceMethod extractInitializedOptions(SourceMethod resultMethod,
|
||||
List<SourceMethod> rawMethods,
|
||||
MapperOptions mapperConfig,
|
||||
List<SourceMethod> initializingMethods) {
|
||||
List<SourceMethod> initializingMethods,
|
||||
AnnotationMirror annotationMirror) {
|
||||
if ( resultMethod != null ) {
|
||||
if ( !resultMethod.getOptions().isFullyInitialized() ) {
|
||||
mergeInheritedOptions( resultMethod, mapperConfig, rawMethods, initializingMethods );
|
||||
mergeInheritedOptions( resultMethod, mapperConfig, rawMethods, initializingMethods,
|
||||
annotationMirror );
|
||||
}
|
||||
|
||||
return resultMethod;
|
||||
@ -684,7 +695,12 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
||||
}
|
||||
}
|
||||
|
||||
return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods );
|
||||
return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods,
|
||||
getAnnotationMirror( inheritConfiguration ) );
|
||||
}
|
||||
|
||||
private AnnotationMirror getAnnotationMirror(InheritConfigurationGem inheritConfiguration) {
|
||||
return inheritConfiguration == null ? null : inheritConfiguration.mirror();
|
||||
}
|
||||
|
||||
private void reportErrorWhenAmbigousReverseMapping(List<SourceMethod> candidates, SourceMethod method,
|
||||
|
@ -45,6 +45,7 @@ import org.mapstruct.ap.internal.model.source.MappingOptions;
|
||||
import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods;
|
||||
import org.mapstruct.ap.internal.model.source.SourceMethod;
|
||||
import org.mapstruct.ap.internal.model.source.SubclassMappingOptions;
|
||||
import org.mapstruct.ap.internal.model.source.SubclassValidator;
|
||||
import org.mapstruct.ap.internal.model.source.ValueMappingOptions;
|
||||
import org.mapstruct.ap.internal.option.Options;
|
||||
import org.mapstruct.ap.internal.util.AccessorNamingUtils;
|
||||
@ -307,11 +308,13 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
|
||||
// We want to get as much error reporting as possible.
|
||||
// If targetParameter is not null it means we have an update method
|
||||
SubclassValidator subclassValidator = new SubclassValidator( messager, typeUtils );
|
||||
Set<SubclassMappingOptions> subclassMappingOptions = getSubclassMappings(
|
||||
sourceParameters,
|
||||
targetParameter != null ? null : resultType,
|
||||
method,
|
||||
beanMappingOptions
|
||||
beanMappingOptions,
|
||||
subclassValidator
|
||||
);
|
||||
|
||||
return new SourceMethod.Builder()
|
||||
@ -327,6 +330,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
.setValueMappingOptionss( getValueMappings( method ) )
|
||||
.setEnumMappingOptions( enumMappingOptions )
|
||||
.setSubclassMappings( subclassMappingOptions )
|
||||
.setSubclassValidator( subclassValidator )
|
||||
.setTypeUtils( typeUtils )
|
||||
.setTypeFactory( typeFactory )
|
||||
.setPrototypeMethods( prototypeMethods )
|
||||
@ -601,8 +605,10 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
* @return The subclass mappings for the given method
|
||||
*/
|
||||
private Set<SubclassMappingOptions> getSubclassMappings(List<Parameter> sourceParameters, Type resultType,
|
||||
ExecutableElement method, BeanMappingOptions beanMapping) {
|
||||
return new RepeatableSubclassMappings( sourceParameters, resultType ).getMappings( method, beanMapping );
|
||||
ExecutableElement method, BeanMappingOptions beanMapping,
|
||||
SubclassValidator validator) {
|
||||
return new RepeatableSubclassMappings( sourceParameters, resultType, validator )
|
||||
.getMappings( method, beanMapping );
|
||||
}
|
||||
|
||||
private class RepeatableMappings extends RepeatableMappingAnnotations<MappingGem, MappingsGem, MappingOptions> {
|
||||
@ -637,11 +643,13 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
extends RepeatableMappingAnnotations<SubclassMappingGem, SubclassMappingsGem, SubclassMappingOptions> {
|
||||
private final List<Parameter> sourceParameters;
|
||||
private final Type resultType;
|
||||
private SubclassValidator validator;
|
||||
|
||||
RepeatableSubclassMappings(List<Parameter> sourceParameters, Type resultType) {
|
||||
RepeatableSubclassMappings(List<Parameter> sourceParameters, Type resultType, SubclassValidator validator) {
|
||||
super( SUB_CLASS_MAPPING_FQN, SUB_CLASS_MAPPINGS_FQN );
|
||||
this.sourceParameters = sourceParameters;
|
||||
this.resultType = resultType;
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -666,7 +674,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
typeUtils,
|
||||
mappings,
|
||||
sourceParameters,
|
||||
resultType );
|
||||
resultType,
|
||||
validator );
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -681,7 +690,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
typeUtils,
|
||||
mappings,
|
||||
sourceParameters,
|
||||
resultType );
|
||||
resultType,
|
||||
validator );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,7 @@ public enum Message {
|
||||
ENUMMAPPING_NO_ELEMENTS( "'nameTransformationStrategy', 'configuration' and 'unexpectedValueMappingException' are undefined in @EnumMapping, define at least one of them." ),
|
||||
ENUMMAPPING_ILLEGAL_TRANSFORMATION( "Illegal transformation for '%s' EnumTransformationStrategy. Error: '%s'." ),
|
||||
|
||||
SUBCLASSMAPPING_DOUBLE_SOURCE_SUBCLASS( "Subclass '%s' is already defined as a source." ),
|
||||
SUBCLASSMAPPING_ILLEGAL_SUBCLASS( "Class '%s' is not a subclass of '%s'." ),
|
||||
SUBCLASSMAPPING_NO_VALID_SUPERCLASS( "Could not find a parameter that is a superclass for '%s'." ),
|
||||
SUBCLASSMAPPING_UPDATE_METHODS_NOT_SUPPORTED( "SubclassMapping annotation can not be used for update methods." ),
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.test.subclassmapping;
|
||||
|
||||
import org.mapstruct.InheritInverseConfiguration;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.SubclassMapping;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.Bike;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.Car;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface ErroneousInverseSubclassMapper {
|
||||
ErroneousInverseSubclassMapper INSTANCE = Mappers.getMapper( ErroneousInverseSubclassMapper.class );
|
||||
|
||||
@SubclassMapping( source = Bike.class, target = VehicleDto.class )
|
||||
@SubclassMapping( source = Car.class, target = VehicleDto.class )
|
||||
@Mapping( target = "maker", source = "vehicleManufacturingCompany" )
|
||||
VehicleDto map(Vehicle vehicle);
|
||||
|
||||
@InheritInverseConfiguration
|
||||
Vehicle mapInverse(VehicleDto dto);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.test.subclassmapping;
|
||||
|
||||
import org.mapstruct.InheritInverseConfiguration;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.SubclassMapping;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.Bike;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.BikeDto;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.Car;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.CarDto;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.HatchBack;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollection;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollectionDto;
|
||||
import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface InverseOrderSubclassMapper {
|
||||
InverseOrderSubclassMapper INSTANCE = Mappers.getMapper( InverseOrderSubclassMapper.class );
|
||||
|
||||
VehicleCollectionDto map(VehicleCollection vehicles);
|
||||
|
||||
@SubclassMapping( source = HatchBack.class, target = CarDto.class )
|
||||
@SubclassMapping( source = Car.class, target = CarDto.class )
|
||||
@SubclassMapping( source = Bike.class, target = BikeDto.class )
|
||||
@Mapping( source = "vehicleManufacturingCompany", target = "maker")
|
||||
VehicleDto map(Vehicle vehicle);
|
||||
|
||||
VehicleCollection mapInverse(VehicleCollectionDto vehicles);
|
||||
|
||||
@SubclassMapping( source = CarDto.class, target = Car.class )
|
||||
@InheritInverseConfiguration
|
||||
Vehicle mapInverse(VehicleDto dto);
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
package org.mapstruct.ap.test.subclassmapping;
|
||||
|
||||
import org.mapstruct.InheritInverseConfiguration;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.SubclassMapping;
|
||||
@ -28,4 +29,9 @@ public interface SimpleSubclassMapper {
|
||||
@SubclassMapping( source = Bike.class, target = BikeDto.class )
|
||||
@Mapping( source = "vehicleManufacturingCompany", target = "maker")
|
||||
VehicleDto map(Vehicle vehicle);
|
||||
|
||||
VehicleCollection mapInverse(VehicleCollectionDto vehicles);
|
||||
|
||||
@InheritInverseConfiguration
|
||||
Vehicle mapInverse(VehicleDto dto);
|
||||
}
|
||||
|
@ -34,12 +34,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
VehicleCollectionDto.class,
|
||||
Vehicle.class,
|
||||
VehicleDto.class,
|
||||
SimpleSubclassMapper.class,
|
||||
SubclassMapperUsingExistingMappings.class,
|
||||
})
|
||||
public class SubclassMappingTest {
|
||||
|
||||
@ProcessorTest
|
||||
@WithClasses( SimpleSubclassMapper.class )
|
||||
void mappingIsDoneUsingSubclassMapping() {
|
||||
VehicleCollection vehicles = new VehicleCollection();
|
||||
vehicles.getVehicles().add( new Car() );
|
||||
@ -54,6 +53,22 @@ public class SubclassMappingTest {
|
||||
}
|
||||
|
||||
@ProcessorTest
|
||||
@WithClasses( SimpleSubclassMapper.class )
|
||||
void inverseMappingIsDoneUsingSubclassMapping() {
|
||||
VehicleCollectionDto vehicles = new VehicleCollectionDto();
|
||||
vehicles.getVehicles().add( new CarDto() );
|
||||
vehicles.getVehicles().add( new BikeDto() );
|
||||
|
||||
VehicleCollection result = SimpleSubclassMapper.INSTANCE.mapInverse( vehicles );
|
||||
|
||||
assertThat( result.getVehicles() ).doesNotContainNull();
|
||||
assertThat( result.getVehicles() ) // remove generic so that test works.
|
||||
.extracting( vehicle -> (Class) vehicle.getClass() )
|
||||
.containsExactly( Car.class, Bike.class );
|
||||
}
|
||||
|
||||
@ProcessorTest
|
||||
@WithClasses( SubclassMapperUsingExistingMappings.class )
|
||||
void existingMappingsAreUsedWhenFound() {
|
||||
VehicleCollection vehicles = new VehicleCollection();
|
||||
vehicles.getVehicles().add( new Car() );
|
||||
@ -66,19 +81,38 @@ public class SubclassMappingTest {
|
||||
}
|
||||
|
||||
@ProcessorTest
|
||||
void subclassMappingInheritsMapping() {
|
||||
VehicleCollection vehicles = new VehicleCollection();
|
||||
Car car = new Car();
|
||||
car.setVehicleManufacturingCompany( "BenZ" );
|
||||
vehicles.getVehicles().add( car );
|
||||
@WithClasses( SimpleSubclassMapper.class )
|
||||
void subclassMappingInheritsInverseMapping() {
|
||||
VehicleCollectionDto vehiclesDto = new VehicleCollectionDto();
|
||||
CarDto carDto = new CarDto();
|
||||
carDto.setMaker( "BenZ" );
|
||||
vehiclesDto.getVehicles().add( carDto );
|
||||
|
||||
VehicleCollectionDto result = SimpleSubclassMapper.INSTANCE.map( vehicles );
|
||||
VehicleCollection result = SimpleSubclassMapper.INSTANCE.mapInverse( vehiclesDto );
|
||||
|
||||
assertThat( result.getVehicles() )
|
||||
.extracting( VehicleDto::getMaker )
|
||||
.extracting( Vehicle::getVehicleManufacturingCompany )
|
||||
.containsExactly( "BenZ" );
|
||||
}
|
||||
|
||||
@ProcessorTest
|
||||
@WithClasses( {
|
||||
HatchBack.class,
|
||||
InverseOrderSubclassMapper.class
|
||||
} )
|
||||
void subclassMappingOverridesInverseInheritsMapping() {
|
||||
VehicleCollectionDto vehicleDtos = new VehicleCollectionDto();
|
||||
CarDto carDto = new CarDto();
|
||||
carDto.setMaker( "BenZ" );
|
||||
vehicleDtos.getVehicles().add( carDto );
|
||||
|
||||
VehicleCollection result = InverseOrderSubclassMapper.INSTANCE.mapInverse( vehicleDtos );
|
||||
|
||||
assertThat( result.getVehicles() ) // remove generic so that test works.
|
||||
.extracting( vehicle -> (Class) vehicle.getClass() )
|
||||
.containsExactly( Car.class );
|
||||
}
|
||||
|
||||
@ProcessorTest
|
||||
@WithClasses({
|
||||
HatchBack.class,
|
||||
@ -91,11 +125,11 @@ public class SubclassMappingTest {
|
||||
line = 28,
|
||||
alternativeLine = 30,
|
||||
message = "SubclassMapping annotation for "
|
||||
+ "'org.mapstruct.ap.test.subclassmapping.mappables.HatchBackDto' found after "
|
||||
+ "'org.mapstruct.ap.test.subclassmapping.mappables.CarDto', but all "
|
||||
+ "'org.mapstruct.ap.test.subclassmapping.mappables.HatchBackDto' "
|
||||
+ "'org.mapstruct.ap.test.subclassmapping.mappables.HatchBack' found after "
|
||||
+ "'org.mapstruct.ap.test.subclassmapping.mappables.Car', but all "
|
||||
+ "'org.mapstruct.ap.test.subclassmapping.mappables.HatchBack' "
|
||||
+ "objects are also instances of "
|
||||
+ "'org.mapstruct.ap.test.subclassmapping.mappables.CarDto'.")
|
||||
+ "'org.mapstruct.ap.test.subclassmapping.mappables.Car'.")
|
||||
})
|
||||
void subclassOrderWarning() {
|
||||
}
|
||||
@ -136,4 +170,18 @@ public class SubclassMappingTest {
|
||||
})
|
||||
void erroneousMethodWithSourceTargetType() {
|
||||
}
|
||||
|
||||
@ProcessorTest
|
||||
@WithClasses({ ErroneousInverseSubclassMapper.class })
|
||||
@ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = {
|
||||
@Diagnostic(type = ErroneousInverseSubclassMapper.class,
|
||||
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||
line = 28,
|
||||
message = "Subclass "
|
||||
+ "'org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto'"
|
||||
+ " is already defined as a source."
|
||||
)
|
||||
})
|
||||
void inverseSubclassMappingNotPossible() {
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.test.subclassmapping.fixture;
|
||||
|
||||
public class SubSourceOverride extends ImplementedParentSource implements InterfaceParentSource {
|
||||
private final String finalValue;
|
||||
|
||||
public SubSourceOverride(String finalValue) {
|
||||
this.finalValue = finalValue;
|
||||
}
|
||||
|
||||
public String getFinalValue() {
|
||||
return finalValue;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.test.subclassmapping.fixture;
|
||||
|
||||
public class SubSourceSeparate extends ImplementedParentSource implements InterfaceParentSource {
|
||||
private final String separateValue;
|
||||
|
||||
public SubSourceSeparate(String separateValue) {
|
||||
this.separateValue = separateValue;
|
||||
}
|
||||
|
||||
public String getSeparateValue() {
|
||||
return separateValue;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.test.subclassmapping.fixture;
|
||||
|
||||
public class SubTargetSeparate extends ImplementedParentTarget implements InterfaceParentTarget {
|
||||
private final String separateValue;
|
||||
|
||||
public SubTargetSeparate(String separateValue) {
|
||||
this.separateValue = separateValue;
|
||||
}
|
||||
|
||||
public String getSeparateValue() {
|
||||
return separateValue;
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
package org.mapstruct.ap.test.subclassmapping.fixture;
|
||||
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.InheritInverseConfiguration;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.SubclassMapping;
|
||||
|
||||
@ -18,4 +19,9 @@ public interface SubclassAbstractMapper {
|
||||
@SubclassMapping( source = SubSource.class, target = SubTarget.class )
|
||||
@SubclassMapping( source = SubSourceOther.class, target = SubTargetOther.class )
|
||||
AbstractParentTarget map(AbstractParentSource item);
|
||||
|
||||
@SubclassMapping( source = SubTargetSeparate.class, target = SubSourceSeparate.class )
|
||||
@InheritInverseConfiguration
|
||||
@SubclassMapping( source = SubTargetOther.class, target = SubSourceOverride.class )
|
||||
AbstractParentSource map(AbstractParentTarget item);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
package org.mapstruct.ap.test.subclassmapping.fixture;
|
||||
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import org.mapstruct.ap.testutil.ProcessorTest;
|
||||
import org.mapstruct.ap.testutil.WithClasses;
|
||||
import org.mapstruct.ap.testutil.runner.GeneratedSource;
|
||||
@ -37,6 +38,9 @@ public class SubclassFixtureTest {
|
||||
|
||||
@ProcessorTest
|
||||
@WithClasses( {
|
||||
SubSourceOverride.class,
|
||||
SubSourceSeparate.class,
|
||||
SubTargetSeparate.class,
|
||||
SubclassAbstractMapper.class,
|
||||
} )
|
||||
void subclassAbstractParentFixture() {
|
||||
|
@ -9,7 +9,7 @@ import javax.annotation.processing.Generated;
|
||||
|
||||
@Generated(
|
||||
value = "org.mapstruct.ap.MappingProcessor",
|
||||
date = "2021-09-12T14:37:10+0200",
|
||||
date = "2022-01-31T19:35:15+0100",
|
||||
comments = "version: , compiler: Eclipse JDT (Batch) 3.20.0.v20191203-2131, environment: Java 11.0.12 (Azul Systems, Inc.)"
|
||||
)
|
||||
public class SubclassAbstractMapperImpl implements SubclassAbstractMapper {
|
||||
@ -31,6 +31,26 @@ public class SubclassAbstractMapperImpl implements SubclassAbstractMapper {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractParentSource map(AbstractParentTarget item) {
|
||||
if ( item == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (item instanceof SubTargetSeparate) {
|
||||
return subTargetSeparateToSubSourceSeparate( (SubTargetSeparate) item );
|
||||
}
|
||||
else if (item instanceof SubTargetOther) {
|
||||
return subTargetOtherToSubSourceOverride( (SubTargetOther) item );
|
||||
}
|
||||
else if (item instanceof SubTarget) {
|
||||
return subTargetToSubSource( (SubTarget) item );
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for " + item.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
protected SubTarget subSourceToSubTarget(SubSource subSource) {
|
||||
if ( subSource == null ) {
|
||||
return null;
|
||||
@ -56,4 +76,44 @@ public class SubclassAbstractMapperImpl implements SubclassAbstractMapper {
|
||||
|
||||
return subTargetOther;
|
||||
}
|
||||
|
||||
protected SubSourceSeparate subTargetSeparateToSubSourceSeparate(SubTargetSeparate subTargetSeparate) {
|
||||
if ( subTargetSeparate == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String separateValue = null;
|
||||
|
||||
separateValue = subTargetSeparate.getSeparateValue();
|
||||
|
||||
SubSourceSeparate subSourceSeparate = new SubSourceSeparate( separateValue );
|
||||
|
||||
return subSourceSeparate;
|
||||
}
|
||||
|
||||
protected SubSourceOverride subTargetOtherToSubSourceOverride(SubTargetOther subTargetOther) {
|
||||
if ( subTargetOther == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String finalValue = null;
|
||||
|
||||
finalValue = subTargetOther.getFinalValue();
|
||||
|
||||
SubSourceOverride subSourceOverride = new SubSourceOverride( finalValue );
|
||||
|
||||
return subSourceOverride;
|
||||
}
|
||||
|
||||
protected SubSource subTargetToSubSource(SubTarget subTarget) {
|
||||
if ( subTarget == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SubSource subSource = new SubSource();
|
||||
|
||||
subSource.setValue( subTarget.getValue() );
|
||||
|
||||
return subSource;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user