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:
Zegveld 2022-02-06 20:03:23 +01:00 committed by GitHub
parent 2a2c11e871
commit 7bb85d05c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 417 additions and 73 deletions

View File

@ -157,6 +157,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return this;
}
@Override
public BeanMappingMethod build() {
BeanMappingOptions beanMapping = method.getOptions().getBeanMapping();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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." ),

View File

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

View File

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

View File

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

View File

@ -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() {
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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