mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#3125: Allow subclassmapping inheritance for methods with identical signature
Co-authored-by: Ben Zegveld <Ben.Zegveld@gmail.com>
This commit is contained in:
parent
1ab5db6a27
commit
2f78d3f4e2
@ -5,12 +5,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.mapstruct.ap.internal.model.source;
|
package org.mapstruct.ap.internal.model.source;
|
||||||
|
|
||||||
|
import static org.mapstruct.ap.internal.model.source.MappingOptions.getMappingTargetNamesBy;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.lang.model.element.AnnotationMirror;
|
import javax.lang.model.element.AnnotationMirror;
|
||||||
|
|
||||||
import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem;
|
import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem;
|
||||||
@ -20,8 +23,6 @@ import org.mapstruct.ap.internal.util.FormattingMessager;
|
|||||||
import org.mapstruct.ap.internal.util.Message;
|
import org.mapstruct.ap.internal.util.Message;
|
||||||
import org.mapstruct.ap.internal.util.accessor.Accessor;
|
import org.mapstruct.ap.internal.util.accessor.Accessor;
|
||||||
|
|
||||||
import static org.mapstruct.ap.internal.model.source.MappingOptions.getMappingTargetNamesBy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates all options specifiable on a mapping method
|
* Encapsulates all options specifiable on a mapping method
|
||||||
*
|
*
|
||||||
@ -205,12 +206,19 @@ public class MappingMethodOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( isInverse ) {
|
if ( isInverse ) {
|
||||||
// normal inheritence of subclass mappings will result runtime in infinite loops.
|
|
||||||
List<SubclassMappingOptions> inheritedMappings = SubclassMappingOptions.copyForInverseInheritance(
|
List<SubclassMappingOptions> inheritedMappings = SubclassMappingOptions.copyForInverseInheritance(
|
||||||
templateOptions.getSubclassMappings(),
|
templateOptions.getSubclassMappings(),
|
||||||
getBeanMapping() );
|
getBeanMapping() );
|
||||||
addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings );
|
addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings );
|
||||||
}
|
}
|
||||||
|
else if ( methodsHaveIdenticalSignature( templateMethod, sourceMethod ) ) {
|
||||||
|
List<SubclassMappingOptions> inheritedMappings =
|
||||||
|
SubclassMappingOptions
|
||||||
|
.copyForInheritance(
|
||||||
|
templateOptions.getSubclassMappings(),
|
||||||
|
getBeanMapping() );
|
||||||
|
addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings );
|
||||||
|
}
|
||||||
|
|
||||||
Set<MappingOptions> newMappings = new LinkedHashSet<>();
|
Set<MappingOptions> newMappings = new LinkedHashSet<>();
|
||||||
for ( MappingOptions mapping : templateOptions.getMappings() ) {
|
for ( MappingOptions mapping : templateOptions.getMappings() ) {
|
||||||
@ -232,6 +240,28 @@ public class MappingMethodOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean methodsHaveIdenticalSignature(SourceMethod templateMethod, SourceMethod sourceMethod) {
|
||||||
|
return parametersAreOfIdenticalTypeAndOrder( templateMethod, sourceMethod )
|
||||||
|
&& resultTypeIsTheSame( templateMethod, sourceMethod );
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean parametersAreOfIdenticalTypeAndOrder(SourceMethod templateMethod, SourceMethod sourceMethod) {
|
||||||
|
if (templateMethod.getParameters().size() != sourceMethod.getParameters().size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for ( int i = 0; i < templateMethod.getParameters().size(); i++ ) {
|
||||||
|
if (!templateMethod.getParameters().get( i ).getType().equals(
|
||||||
|
sourceMethod.getParameters().get( i ).getType() ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean resultTypeIsTheSame(SourceMethod templateMethod, SourceMethod sourceMethod) {
|
||||||
|
return templateMethod.getResultType().equals( sourceMethod.getResultType() );
|
||||||
|
}
|
||||||
|
|
||||||
private void addAllNonRedefined(SourceMethod sourceMethod, AnnotationMirror annotationMirror,
|
private void addAllNonRedefined(SourceMethod sourceMethod, AnnotationMirror annotationMirror,
|
||||||
List<SubclassMappingOptions> inheritedMappings) {
|
List<SubclassMappingOptions> inheritedMappings) {
|
||||||
Set<SubclassMappingOptions> redefinedSubclassMappings = new HashSet<>( subclassMappings );
|
Set<SubclassMappingOptions> redefinedSubclassMappings = new HashSet<>( subclassMappings );
|
||||||
|
@ -202,6 +202,23 @@ public class SubclassMappingOptions extends DelegatingOptions {
|
|||||||
) ).collect( Collectors.toCollection( ArrayList::new ) );
|
) ).collect( Collectors.toCollection( ArrayList::new ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<SubclassMappingOptions> copyForInheritance(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.source,
|
||||||
|
subclassMapping.target,
|
||||||
|
subclassMapping.typeUtils,
|
||||||
|
beanMappingOptions,
|
||||||
|
subclassMapping.selectionParameters,
|
||||||
|
subclassMapping.subclassMapping ) );
|
||||||
|
}
|
||||||
|
return mappings;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if ( obj == null || !( obj instanceof SubclassMappingOptions ) ) {
|
if ( obj == null || !( obj instanceof SubclassMappingOptions ) ) {
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.InheritConfiguration;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.ReportingPolicy;
|
||||||
|
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.VehicleDto;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Mapper( unmappedTargetPolicy = ReportingPolicy.IGNORE )
|
||||||
|
public interface InheritedSubclassMapper {
|
||||||
|
InheritedSubclassMapper INSTANCE = Mappers.getMapper( InheritedSubclassMapper.class );
|
||||||
|
|
||||||
|
@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);
|
||||||
|
|
||||||
|
@InheritConfiguration( name = "map" )
|
||||||
|
VehicleDto mapInherited(Vehicle dto);
|
||||||
|
|
||||||
|
@SubclassMapping( source = Bike.class, target = CarDto.class )
|
||||||
|
@InheritConfiguration( name = "map" )
|
||||||
|
VehicleDto mapInheritedOverride(Vehicle dto);
|
||||||
|
}
|
@ -130,7 +130,7 @@ public class SubclassMappingTest {
|
|||||||
HatchBack.class,
|
HatchBack.class,
|
||||||
InverseOrderSubclassMapper.class
|
InverseOrderSubclassMapper.class
|
||||||
} )
|
} )
|
||||||
void subclassMappingOverridesInverseInheritsMapping() {
|
void subclassMappingOverridesInverseInheritedMapping() {
|
||||||
VehicleCollectionDto vehicleDtos = new VehicleCollectionDto();
|
VehicleCollectionDto vehicleDtos = new VehicleCollectionDto();
|
||||||
CarDto carDto = new CarDto();
|
CarDto carDto = new CarDto();
|
||||||
carDto.setMaker( "BenZ" );
|
carDto.setMaker( "BenZ" );
|
||||||
@ -143,6 +143,24 @@ public class SubclassMappingTest {
|
|||||||
.containsExactly( Car.class );
|
.containsExactly( Car.class );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IssueKey( "3125" )
|
||||||
|
@ProcessorTest
|
||||||
|
@WithClasses( {
|
||||||
|
HatchBack.class,
|
||||||
|
InheritedSubclassMapper.class
|
||||||
|
} )
|
||||||
|
void subclassMappingOverridesInheritedMapping() {
|
||||||
|
Vehicle bike = new Bike();
|
||||||
|
|
||||||
|
VehicleDto result = InheritedSubclassMapper.INSTANCE.map( bike );
|
||||||
|
VehicleDto resultInherited = InheritedSubclassMapper.INSTANCE.mapInherited( bike );
|
||||||
|
VehicleDto resultOverride = InheritedSubclassMapper.INSTANCE.mapInheritedOverride( bike );
|
||||||
|
|
||||||
|
assertThat( result ).isInstanceOf( BikeDto.class );
|
||||||
|
assertThat( resultInherited ).isInstanceOf( BikeDto.class );
|
||||||
|
assertThat( resultOverride ).isInstanceOf( CarDto.class );
|
||||||
|
}
|
||||||
|
|
||||||
@ProcessorTest
|
@ProcessorTest
|
||||||
@WithClasses( { SubclassCompositeMapper.class, CompositeSubclassMappingAnnotation.class })
|
@WithClasses( { SubclassCompositeMapper.class, CompositeSubclassMappingAnnotation.class })
|
||||||
void mappingIsDoneUsingSubclassMappingWithCompositeMapping() {
|
void mappingIsDoneUsingSubclassMappingWithCompositeMapping() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user