diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index bdfd8cf54..267702826 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -157,6 +157,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { return this; } + @Override public BeanMappingMethod build() { BeanMappingOptions beanMapping = method.getOptions().getBeanMapping(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java index 45b1175f8..4a140c4fb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -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 valueMappings; private boolean fullyInitialized; - private Set subclassMapping; + private Set subclassMappings; + + private SubclassValidator subclassValidator; public MappingMethodOptions(MapperOptions mapper, Set mappings, IterableMappingOptions iterableMapping, MapMappingOptions mapMapping, BeanMappingOptions beanMapping, EnumMappingOptions enumMappingOptions, List valueMappings, - Set subclassMapping) { + Set 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 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 inheritedMappings = SubclassMappingOptions.copyForInverseInheritance( + templateOptions.getSubclassMappings(), + getBeanMapping() ); + addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings ); + } Set newMappings = new LinkedHashSet<>(); for ( MappingOptions mapping : templateOptions.getMappings() ) { @@ -218,6 +232,21 @@ public class MappingMethodOptions { } } + private void addAllNonRedefined(SourceMethod sourceMethod, AnnotationMirror annotationMirror, + List inheritedMappings) { + Set 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 inheritedMappings) { Set redefinedSources = new HashSet<>(); Set 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 ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java index 5895ecba2..92180bca8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java @@ -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 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() diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java index 54a0ba456..08df8083b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java @@ -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 mappings, - List sourceParameters, Type resultType) { - SubclassValidator subclassValidator = new SubclassValidator( messager, typeUtils ); + List 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 mappings, - List 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 mappings, - List sourceParameters, Type resultType, - SubclassValidator subclassValidator) { + List 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 copyForInverseInheritance(Set subclassMappings, + BeanMappingOptions beanMappingOptions) { + // we are not interested in keeping it unique at this point. + List 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. } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java index dd79ba0c0..601b588e4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java @@ -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 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; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index bae0cda1a..d8acb1039 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -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() ); + mergeInheritedOptions( method, mapperAnnotation, methods, new ArrayList<>(), null ); MappingMethodOptions mappingOptions = method.getOptions(); @@ -462,7 +463,8 @@ public class MapperCreationProcessor implements ModelElementProcessor availableMethods, List initializingMethods) { + List availableMethods, List initializingMethods, + AnnotationMirror annotationMirror) { if ( initializingMethods.contains( method ) ) { // cycle detected @@ -497,10 +499,10 @@ public class MapperCreationProcessor implements ModelElementProcessor 1 ) { messager.printMessage( @@ -523,7 +526,8 @@ public class MapperCreationProcessor implements ModelElementProcessor 1 ) { messager.printMessage( @@ -607,16 +611,23 @@ public class MapperCreationProcessor implements ModelElementProcessor rawMethods, MapperOptions mapperConfig, - List initializingMethods) { + List 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 candidates, SourceMethod method, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index a31fb41a7..201f1a1f2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -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 subclassMappingOptions = getSubclassMappings( sourceParameters, targetParameter != null ? null : resultType, method, - beanMappingOptions + beanMappingOptions, + subclassValidator ); return new SourceMethod.Builder() @@ -327,6 +330,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor getSubclassMappings(List 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 { @@ -637,11 +643,13 @@ public class MethodRetrievalProcessor implements ModelElementProcessor { private final List sourceParameters; private final Type resultType; + private SubclassValidator validator; - RepeatableSubclassMappings(List sourceParameters, Type resultType) { + RepeatableSubclassMappings(List 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 (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() { + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceOverride.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceOverride.java new file mode 100644 index 000000000..97b9079e8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceOverride.java @@ -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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceSeparate.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceSeparate.java new file mode 100644 index 000000000..ccae04eff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceSeparate.java @@ -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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTargetSeparate.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTargetSeparate.java new file mode 100644 index 000000000..6b98c2410 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTargetSeparate.java @@ -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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java index 8ef7908c3..c50afb9e5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java @@ -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); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java index 14dc16d4a..9bc72942e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java @@ -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() { diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java index 6deed338c..a66b646f1 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java @@ -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; + } }