mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#3821: Add support for custom exception for subclass exhaustive strategy for @SubclassMapping
--------- Signed-off-by: TangYang <tangyang9464@163.com>
This commit is contained in:
parent
fce73aee6a
commit
6e6fd01a2e
@ -132,6 +132,18 @@ public @interface BeanMapping {
|
||||
*/
|
||||
SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
|
||||
|
||||
/**
|
||||
* Specifies the exception type to be thrown when a missing subclass implementation is detected
|
||||
* in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}.
|
||||
* <p>
|
||||
* This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to
|
||||
* {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}.
|
||||
*
|
||||
* @return the exception class to throw when missing implementations are found.
|
||||
* Defaults to {@link IllegalArgumentException}.
|
||||
*/
|
||||
Class<? extends Exception> subclassExhaustiveException() default IllegalArgumentException.class;
|
||||
|
||||
/**
|
||||
* Default ignore all mappings. All mappings have to be defined manually. No automatic mapping will take place. No
|
||||
* warning will be issued on missing source or target properties.
|
||||
|
@ -281,6 +281,18 @@ public @interface Mapper {
|
||||
*/
|
||||
SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
|
||||
|
||||
/**
|
||||
* Specifies the exception type to be thrown when a missing subclass implementation is detected
|
||||
* in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}.
|
||||
* <p>
|
||||
* This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to
|
||||
* {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}.
|
||||
*
|
||||
* @return the exception class to throw when missing implementations are found.
|
||||
* Defaults to {@link IllegalArgumentException}.
|
||||
*/
|
||||
Class<? extends Exception> subclassExhaustiveException() default IllegalArgumentException.class;
|
||||
|
||||
/**
|
||||
* Determines whether to use field or constructor injection. This is only used on annotated based component models
|
||||
* such as CDI, Spring and JSR 330.
|
||||
|
@ -249,6 +249,18 @@ public @interface MapperConfig {
|
||||
*/
|
||||
SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
|
||||
|
||||
/**
|
||||
* Specifies the exception type to be thrown when a missing subclass implementation is detected
|
||||
* in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}.
|
||||
* <p>
|
||||
* This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to
|
||||
* {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}.
|
||||
*
|
||||
* @return the exception class to throw when missing implementations are found.
|
||||
* Defaults to {@link IllegalArgumentException}.
|
||||
*/
|
||||
Class<? extends Exception> subclassExhaustiveException() default IllegalArgumentException.class;
|
||||
|
||||
/**
|
||||
* Determines whether to use field or constructor injection. This is only used on annotated based component models
|
||||
* such as CDI, Spring and JSR 330.
|
||||
|
@ -100,6 +100,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
private final String finalizedResultName;
|
||||
private final List<LifecycleCallbackMethodReference> beforeMappingReferencesWithFinalizedReturnType;
|
||||
private final List<LifecycleCallbackMethodReference> afterMappingReferencesWithFinalizedReturnType;
|
||||
private final Type subclassExhaustiveException;
|
||||
|
||||
private final MappingReferences mappingReferences;
|
||||
|
||||
@ -378,6 +379,11 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
|
||||
}
|
||||
|
||||
TypeMirror subclassExhaustiveException = method.getOptions()
|
||||
.getBeanMapping()
|
||||
.getSubclassExhaustiveException();
|
||||
Type subclassExhaustiveExceptionType = ctx.getTypeFactory().getType( subclassExhaustiveException );
|
||||
|
||||
List<SubclassMapping> subclasses = new ArrayList<>();
|
||||
for ( SubclassMappingOptions subclassMappingOptions : method.getOptions().getSubclassMappings() ) {
|
||||
subclasses.add( createSubclassMapping( subclassMappingOptions ) );
|
||||
@ -451,7 +457,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
finalizeMethod,
|
||||
mappingReferences,
|
||||
subclasses,
|
||||
presenceChecksByParameter
|
||||
presenceChecksByParameter,
|
||||
subclassExhaustiveExceptionType
|
||||
);
|
||||
}
|
||||
|
||||
@ -1954,7 +1961,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
MethodReference finalizerMethod,
|
||||
MappingReferences mappingReferences,
|
||||
List<SubclassMapping> subclassMappings,
|
||||
Map<String, PresenceCheck> presenceChecksByParameter) {
|
||||
Map<String, PresenceCheck> presenceChecksByParameter,
|
||||
Type subclassExhaustiveException) {
|
||||
super(
|
||||
method,
|
||||
annotations,
|
||||
@ -1969,6 +1977,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
this.propertyMappings = propertyMappings;
|
||||
this.returnTypeBuilder = returnTypeBuilder;
|
||||
this.finalizerMethod = finalizerMethod;
|
||||
this.subclassExhaustiveException = subclassExhaustiveException;
|
||||
if ( this.finalizerMethod != null ) {
|
||||
this.finalizedResultName =
|
||||
Strings.getSafeVariableName( getResultName() + "Result", existingVariableNames );
|
||||
@ -2017,6 +2026,10 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
this.subclassMappings = subclassMappings;
|
||||
}
|
||||
|
||||
public Type getSubclassExhaustiveException() {
|
||||
return subclassExhaustiveException;
|
||||
}
|
||||
|
||||
public List<PropertyMapping> getConstantMappings() {
|
||||
return constantMappings;
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import org.mapstruct.ap.internal.gem.BeanMappingGem;
|
||||
import org.mapstruct.ap.internal.gem.BuilderGem;
|
||||
@ -182,6 +183,14 @@ public class BeanMappingOptions extends DelegatingOptions {
|
||||
.orElse( next().getSubclassExhaustiveStrategy() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeMirror getSubclassExhaustiveException() {
|
||||
return Optional.ofNullable( beanMapping ).map( BeanMappingGem::subclassExhaustiveException )
|
||||
.filter( GemValue::hasValue )
|
||||
.map( GemValue::getValue )
|
||||
.orElse( next().getSubclassExhaustiveException() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReportingPolicyGem unmappedTargetPolicy() {
|
||||
return Optional.ofNullable( beanMapping ).map( BeanMappingGem::unmappedTargetPolicy )
|
||||
|
@ -131,6 +131,10 @@ public class DefaultOptions extends DelegatingOptions {
|
||||
return SubclassExhaustiveStrategyGem.valueOf( mapper.subclassExhaustiveStrategy().getDefaultValue() );
|
||||
}
|
||||
|
||||
public TypeMirror getSubclassExhaustiveException() {
|
||||
return mapper.subclassExhaustiveException().getDefaultValue();
|
||||
}
|
||||
|
||||
public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() {
|
||||
NullValueMappingStrategyGem nullValueIterableMappingStrategy = options.getNullValueIterableMappingStrategy();
|
||||
if ( nullValueIterableMappingStrategy != null ) {
|
||||
|
@ -106,6 +106,10 @@ public abstract class DelegatingOptions {
|
||||
return next.getSubclassExhaustiveStrategy();
|
||||
}
|
||||
|
||||
public TypeMirror getSubclassExhaustiveException() {
|
||||
return next.getSubclassExhaustiveException();
|
||||
}
|
||||
|
||||
public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() {
|
||||
return next.getNullValueIterableMappingStrategy();
|
||||
}
|
||||
|
@ -141,6 +141,12 @@ public class MapperConfigOptions extends DelegatingOptions {
|
||||
next().getSubclassExhaustiveStrategy();
|
||||
}
|
||||
|
||||
public TypeMirror getSubclassExhaustiveException() {
|
||||
return mapperConfig.subclassExhaustiveException().hasValue() ?
|
||||
mapperConfig.subclassExhaustiveException().get() :
|
||||
next().getSubclassExhaustiveException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() {
|
||||
if ( mapperConfig.nullValueIterableMappingStrategy().hasValue() ) {
|
||||
|
@ -170,6 +170,13 @@ public class MapperOptions extends DelegatingOptions {
|
||||
next().getSubclassExhaustiveStrategy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeMirror getSubclassExhaustiveException() {
|
||||
return mapper.subclassExhaustiveException().hasValue() ?
|
||||
mapper.subclassExhaustiveException().get() :
|
||||
next().getSubclassExhaustiveException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() {
|
||||
if ( mapper.nullValueIterableMappingStrategy().hasValue() ) {
|
||||
|
@ -42,7 +42,7 @@
|
||||
else {
|
||||
</#if>
|
||||
<#if isAbstractReturnType()>
|
||||
throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for " + ${subclassMappings[0].sourceArgument}.getClass());
|
||||
throw new <@includeModel object=subclassExhaustiveException />("Not all subclasses are supported for this mapping. Missing for " + ${subclassMappings[0].sourceArgument}.getClass());
|
||||
<#else>
|
||||
<#if !existingInstanceMapping>
|
||||
<#if hasConstructorMappings()>
|
||||
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.abstractsuperclass;
|
||||
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.SubclassExhaustiveStrategy;
|
||||
import org.mapstruct.SubclassMapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface CustomExceptionSubclassMapper {
|
||||
CustomExceptionSubclassMapper INSTANCE = Mappers.getMapper( CustomExceptionSubclassMapper.class );
|
||||
|
||||
@BeanMapping(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION,
|
||||
subclassExhaustiveException = CustomSubclassMappingException.class)
|
||||
@SubclassMapping(source = Car.class, target = CarDto.class)
|
||||
@SubclassMapping(source = Bike.class, target = BikeDto.class)
|
||||
VehicleDto map(AbstractVehicle vehicle);
|
||||
|
||||
VehicleCollectionDto mapInverse(VehicleCollection vehicles);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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.abstractsuperclass;
|
||||
|
||||
public class CustomSubclassMappingException extends RuntimeException {
|
||||
public CustomSubclassMappingException(String message) {
|
||||
super( message );
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.abstractsuperclass;
|
||||
|
||||
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.assertThatThrownBy;
|
||||
|
||||
@IssueKey("3821")
|
||||
@WithClasses({
|
||||
Bike.class,
|
||||
BikeDto.class,
|
||||
Car.class,
|
||||
CarDto.class,
|
||||
Motorcycle.class,
|
||||
VehicleCollection.class,
|
||||
VehicleCollectionDto.class,
|
||||
AbstractVehicle.class,
|
||||
VehicleDto.class,
|
||||
CustomSubclassMappingException.class,
|
||||
CustomExceptionSubclassMapper.class
|
||||
})
|
||||
public class CustomSubclassMappingExceptionTest {
|
||||
|
||||
private static final String EXPECTED_ERROR_MESSAGE = "Not all subclasses are supported for this mapping. "
|
||||
+ "Missing for class org.mapstruct.ap.test.subclassmapping.abstractsuperclass.Motorcycle";
|
||||
|
||||
@ProcessorTest
|
||||
void customExceptionIsThrownForUnknownSubclass() {
|
||||
VehicleCollection vehicles = new VehicleCollection();
|
||||
vehicles.getVehicles().add( new Car() );
|
||||
vehicles.getVehicles().add( new Motorcycle() ); // undefine subclass
|
||||
|
||||
assertThatThrownBy( () -> CustomExceptionSubclassMapper.INSTANCE.mapInverse( vehicles ) )
|
||||
.isInstanceOf( CustomSubclassMappingException.class )
|
||||
.hasMessage( EXPECTED_ERROR_MESSAGE );
|
||||
}
|
||||
|
||||
@ProcessorTest
|
||||
void customExceptionIsThrownForSingleVehicle() {
|
||||
AbstractVehicle vehicle = new Motorcycle(); // undefine subclass
|
||||
|
||||
assertThatThrownBy( () -> CustomExceptionSubclassMapper.INSTANCE.map( vehicle ) )
|
||||
.isInstanceOf( CustomSubclassMappingException.class )
|
||||
.hasMessage( EXPECTED_ERROR_MESSAGE );
|
||||
}
|
||||
|
||||
@ProcessorTest
|
||||
@WithClasses({ MapperConfigSubclassMapper.class })
|
||||
void customExceptionIsThrownForMapperConfig() {
|
||||
VehicleCollection vehicles = new VehicleCollection();
|
||||
vehicles.getVehicles().add( new Car() );
|
||||
vehicles.getVehicles().add( new Motorcycle() ); // undefined subclass
|
||||
|
||||
assertThatThrownBy( () -> MapperConfigSubclassMapper.INSTANCE.mapInverse( vehicles ) )
|
||||
.isInstanceOf( CustomSubclassMappingException.class )
|
||||
.hasMessage( EXPECTED_ERROR_MESSAGE );
|
||||
}
|
||||
|
||||
@ProcessorTest
|
||||
@WithClasses({ MapperSubclassMapper.class })
|
||||
void customExceptionIsThrownForMapper() {
|
||||
VehicleCollection vehicles = new VehicleCollection();
|
||||
vehicles.getVehicles().add( new Car() );
|
||||
vehicles.getVehicles().add( new Motorcycle() ); // undefined subclass
|
||||
|
||||
assertThatThrownBy( () -> MapperSubclassMapper.INSTANCE.mapInverse( vehicles ) )
|
||||
.isInstanceOf( CustomSubclassMappingException.class )
|
||||
.hasMessage( EXPECTED_ERROR_MESSAGE );
|
||||
}
|
||||
}
|
@ -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.abstractsuperclass;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MapperConfig;
|
||||
import org.mapstruct.SubclassExhaustiveStrategy;
|
||||
import org.mapstruct.SubclassMapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper(config = MapperConfigSubclassMapper.Config.class)
|
||||
public interface MapperConfigSubclassMapper {
|
||||
|
||||
MapperConfigSubclassMapper INSTANCE = Mappers.getMapper( MapperConfigSubclassMapper.class );
|
||||
|
||||
@MapperConfig(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION,
|
||||
subclassExhaustiveException = CustomSubclassMappingException.class)
|
||||
interface Config {
|
||||
}
|
||||
|
||||
@SubclassMapping(source = Car.class, target = CarDto.class)
|
||||
@SubclassMapping(source = Bike.class, target = BikeDto.class)
|
||||
VehicleDto map(AbstractVehicle vehicle);
|
||||
|
||||
VehicleCollectionDto mapInverse(VehicleCollection vehicles);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.abstractsuperclass;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.SubclassExhaustiveStrategy;
|
||||
import org.mapstruct.SubclassMapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION,
|
||||
subclassExhaustiveException = CustomSubclassMappingException.class)
|
||||
public interface MapperSubclassMapper {
|
||||
|
||||
MapperSubclassMapper INSTANCE = Mappers.getMapper( MapperSubclassMapper.class );
|
||||
|
||||
@SubclassMapping(source = Car.class, target = CarDto.class)
|
||||
@SubclassMapping(source = Bike.class, target = BikeDto.class)
|
||||
VehicleDto map(AbstractVehicle vehicle);
|
||||
|
||||
VehicleCollectionDto mapInverse(VehicleCollection vehicles);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user