#3331 Do not handle defined mappings if the result type is abstract due to runtime exception subclass exhaustive strategy (#3487)

This commit is contained in:
Filip Hrisafov 2024-02-11 12:51:19 +01:00 committed by GitHub
parent 8191c850e0
commit ca1fd0d85d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 229 additions and 4 deletions

View File

@ -284,10 +284,15 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
initializeMappingReferencesIfNeeded( resultTypeToMap );
// map properties with mapping
boolean mappingErrorOccurred = handleDefinedMappings( resultTypeToMap );
if ( mappingErrorOccurred ) {
return null;
boolean shouldHandledDefinedMappings = shouldHandledDefinedMappings( resultTypeToMap );
if ( shouldHandledDefinedMappings ) {
// map properties with mapping
boolean mappingErrorOccurred = handleDefinedMappings( resultTypeToMap );
if ( mappingErrorOccurred ) {
return null;
}
}
boolean applyImplicitMappings = !mappingReferences.isRestrictToDefinedMappings();
@ -1045,6 +1050,36 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return (List<AnnotationValue>) av.getValue();
}
/**
* Determine whether defined mappings should be handled on the result type.
* They should be, if any of the following is true:
* <ul>
* <li>The {@code resultTypeToMap} is not abstract</li>
* <li>There is a factory method</li>
* <li>The method is an update method</li>
* </ul>
* Otherwise, it means that we have reached this because subclass mappings are being used
* and the chosen strategy is runtime exception.
*
* @param resultTypeToMap the type in which the defined target properties are defined
* @return {@code true} if defined mappings should be handled for the result type, {@code false} otherwise
*/
private boolean shouldHandledDefinedMappings(Type resultTypeToMap) {
if ( !resultTypeToMap.isAbstract() ) {
return true;
}
if ( hasFactoryMethod ) {
return true;
}
if ( method.isUpdateMethod() ) {
return true;
}
return false;
}
/**
* Iterates over all defined mapping methods ({@code @Mapping(s)}), either directly given or inherited from the
* inverse mapping method.

View File

@ -0,0 +1,27 @@
/*
* 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.bugs._3331;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.SubclassExhaustiveStrategy;
import org.mapstruct.SubclassMapping;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION)
public interface Issue3331Mapper {
Issue3331Mapper INSTANCE = Mappers.getMapper( Issue3331Mapper.class );
@SubclassMapping(source = Vehicle.Car.class, target = VehicleDto.Car.class)
@SubclassMapping(source = Vehicle.Motorbike.class, target = VehicleDto.Motorbike.class)
@Mapping(target = "name", constant = "noname")
VehicleDto map(Vehicle vehicle);
}

View File

@ -0,0 +1,48 @@
/*
* 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.bugs._3331;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* @author Filip Hrisafov
*/
@IssueKey("3331")
@WithClasses({
Issue3331Mapper.class,
Vehicle.class,
VehicleDto.class,
})
class Issue3331Test {
@ProcessorTest
void shouldCorrectCompileAndThrowExceptionOnRuntime() {
VehicleDto target = Issue3331Mapper.INSTANCE.map( new Vehicle.Car( "Test car", 4 ) );
assertThat( target.getName() ).isEqualTo( "noname" );
assertThat( target )
.isInstanceOfSatisfying( VehicleDto.Car.class, car -> {
assertThat( car.getNumOfDoors() ).isEqualTo( 4 );
} );
target = Issue3331Mapper.INSTANCE.map( new Vehicle.Motorbike( "Test bike", true ) );
assertThat( target.getName() ).isEqualTo( "noname" );
assertThat( target )
.isInstanceOfSatisfying( VehicleDto.Motorbike.class, bike -> {
assertThat( bike.isAllowedForMinor() ).isTrue();
} );
assertThatThrownBy( () -> Issue3331Mapper.INSTANCE.map( new Vehicle.Truck( "Test truck", 3 ) ) )
.isInstanceOf( IllegalArgumentException.class )
.hasMessage( "Not all subclasses are supported for this mapping. Missing for " + Vehicle.Truck.class );
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.bugs._3331;
/**
* @author Filip Hrisafov
*/
public abstract class Vehicle {
private final String name;
protected Vehicle(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static class Car extends Vehicle {
private final int numOfDoors;
public Car(String name, int numOfDoors) {
super( name );
this.numOfDoors = numOfDoors;
}
public int getNumOfDoors() {
return numOfDoors;
}
}
public static class Motorbike extends Vehicle {
private final boolean allowedForMinor;
public Motorbike(String name, boolean allowedForMinor) {
super( name );
this.allowedForMinor = allowedForMinor;
}
public boolean isAllowedForMinor() {
return allowedForMinor;
}
}
public static class Truck extends Vehicle {
private final int numOfAxis;
public Truck(String name, int numOfAxis) {
super( name );
this.numOfAxis = numOfAxis;
}
public int getNumOfAxis() {
return numOfAxis;
}
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.bugs._3331;
/**
* @author Filip Hrisafov
*/
public abstract class VehicleDto {
private final String name;
protected VehicleDto(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static class Car extends VehicleDto {
private final int numOfDoors;
public Car(String name, int numOfDoors) {
super( name );
this.numOfDoors = numOfDoors;
}
public int getNumOfDoors() {
return numOfDoors;
}
}
public static class Motorbike extends VehicleDto {
private final boolean allowedForMinor;
public Motorbike(String name, boolean allowedForMinor) {
super( name );
this.allowedForMinor = allowedForMinor;
}
public boolean isAllowedForMinor() {
return allowedForMinor;
}
}
}