Use MappingEntry for defaultTarget and nullTarget in ValueMappingMethod to simplify certain things
This commit is contained in:
Filip Hrisafov 2021-03-15 00:21:24 +01:00
parent c4135e68ed
commit 197dd4327a
8 changed files with 125 additions and 53 deletions

View File

@ -42,10 +42,8 @@ import static org.mapstruct.ap.internal.util.Collections.first;
public class ValueMappingMethod extends MappingMethod { public class ValueMappingMethod extends MappingMethod {
private final List<MappingEntry> valueMappings; private final List<MappingEntry> valueMappings;
private final String defaultTarget; private final MappingEntry defaultTarget;
private final String nullTarget; private final MappingEntry nullTarget;
private boolean nullAsException;
private boolean defaultAsException;
private final Type unexpectedValueMappingException; private final Type unexpectedValueMappingException;
@ -122,12 +120,10 @@ public class ValueMappingMethod extends MappingMethod {
return new ValueMappingMethod( method, return new ValueMappingMethod( method,
mappingEntries, mappingEntries,
valueMappings.nullValueTarget, valueMappings.nullValueTarget,
valueMappings.hasNullValueAsException,
valueMappings.defaultTargetValue, valueMappings.defaultTargetValue,
determineUnexpectedValueMappingException(), determineUnexpectedValueMappingException(),
beforeMappingMethods, beforeMappingMethods,
afterMappingMethods, afterMappingMethods
determineExceptionMappingForDefaultCase()
); );
} }
@ -417,6 +413,7 @@ public class ValueMappingMethod extends MappingMethod {
foundIncorrectMapping = true; foundIncorrectMapping = true;
} }
else if ( valueMappings.nullTarget == null && valueMappings.nullValueTarget != null else if ( valueMappings.nullTarget == null && valueMappings.nullValueTarget != null
&& !valueMappings.nullValueTarget.equals( THROW_EXCEPTION )
&& !targetEnumConstants.contains( valueMappings.nullValueTarget ) ) { && !targetEnumConstants.contains( valueMappings.nullValueTarget ) ) {
// if there is no nullTarget, but nullValueTarget has a value it means that there was an SPI // if there is no nullTarget, but nullValueTarget has a value it means that there was an SPI
// which returned an enum for the target enum // which returned an enum for the target enum
@ -433,28 +430,13 @@ public class ValueMappingMethod extends MappingMethod {
} }
private Type determineUnexpectedValueMappingException() { private Type determineUnexpectedValueMappingException() {
boolean noDefaultValueForSwitchCase = !valueMappings.hasDefaultValue; TypeMirror unexpectedValueMappingException = enumMapping.getUnexpectedValueMappingException();
if ( noDefaultValueForSwitchCase || valueMappings.hasAtLeastOneExceptionValue if ( unexpectedValueMappingException != null ) {
|| valueMappings.hasNullValueAsException ) { return ctx.getTypeFactory().getType( unexpectedValueMappingException );
TypeMirror unexpectedValueMappingException = enumMapping.getUnexpectedValueMappingException();
if ( unexpectedValueMappingException != null ) {
return ctx.getTypeFactory().getType( unexpectedValueMappingException );
}
return ctx.getTypeFactory()
.getType( ctx.getEnumMappingStrategy().getUnexpectedValueMappingExceptionType() );
} }
return null; return ctx.getTypeFactory()
} .getType( ctx.getEnumMappingStrategy().getUnexpectedValueMappingExceptionType() );
private boolean determineExceptionMappingForDefaultCase() {
if ( valueMappings.hasDefaultValue ) {
return THROW_EXCEPTION.equals( valueMappings.defaultTargetValue );
}
else {
return true;
}
} }
} }
@ -493,7 +475,6 @@ public class ValueMappingMethod extends MappingMethod {
boolean hasMapAnyUnmapped = false; boolean hasMapAnyUnmapped = false;
boolean hasMapAnyRemaining = false; boolean hasMapAnyRemaining = false;
boolean hasDefaultValue = false; boolean hasDefaultValue = false;
boolean hasNullValueAsException = false;
boolean hasAtLeastOneExceptionValue = false; boolean hasAtLeastOneExceptionValue = false;
ValueMappings(List<ValueMappingOptions> valueMappings) { ValueMappings(List<ValueMappingOptions> valueMappings) {
@ -501,22 +482,19 @@ public class ValueMappingMethod extends MappingMethod {
for ( ValueMappingOptions valueMapping : valueMappings ) { for ( ValueMappingOptions valueMapping : valueMappings ) {
if ( ANY_REMAINING.equals( valueMapping.getSource() ) ) { if ( ANY_REMAINING.equals( valueMapping.getSource() ) ) {
defaultTarget = valueMapping; defaultTarget = valueMapping;
defaultTargetValue = getValue( defaultTarget ); defaultTargetValue = defaultTarget.getTarget();
hasDefaultValue = true; hasDefaultValue = true;
hasMapAnyRemaining = true; hasMapAnyRemaining = true;
} }
else if ( ANY_UNMAPPED.equals( valueMapping.getSource() ) ) { else if ( ANY_UNMAPPED.equals( valueMapping.getSource() ) ) {
defaultTarget = valueMapping; defaultTarget = valueMapping;
defaultTargetValue = getValue( defaultTarget ); defaultTargetValue = defaultTarget.getTarget();
hasDefaultValue = true; hasDefaultValue = true;
hasMapAnyUnmapped = true; hasMapAnyUnmapped = true;
} }
else if ( NULL.equals( valueMapping.getSource() ) ) { else if ( NULL.equals( valueMapping.getSource() ) ) {
nullTarget = valueMapping; nullTarget = valueMapping;
nullValueTarget = getValue( nullTarget ); nullValueTarget = getValue( nullTarget );
if ( THROW_EXCEPTION.equals( nullValueTarget ) ) {
hasNullValueAsException = true;
}
} }
else { else {
regularValueMappings.add( valueMapping ); regularValueMappings.add( valueMapping );
@ -536,17 +514,14 @@ public class ValueMappingMethod extends MappingMethod {
private ValueMappingMethod(Method method, private ValueMappingMethod(Method method,
List<MappingEntry> enumMappings, List<MappingEntry> enumMappings,
String nullTarget, String nullTarget,
boolean hasNullTargetAsException,
String defaultTarget, String defaultTarget,
Type unexpectedValueMappingException, Type unexpectedValueMappingException,
List<LifecycleCallbackMethodReference> beforeMappingMethods, List<LifecycleCallbackMethodReference> beforeMappingMethods,
List<LifecycleCallbackMethodReference> afterMappingMethods, boolean defaultAsException) { List<LifecycleCallbackMethodReference> afterMappingMethods) {
super( method, beforeMappingMethods, afterMappingMethods ); super( method, beforeMappingMethods, afterMappingMethods );
this.valueMappings = enumMappings; this.valueMappings = enumMappings;
this.nullTarget = nullTarget; this.nullTarget = new MappingEntry( null, nullTarget );
this.nullAsException = hasNullTargetAsException; this.defaultTarget = new MappingEntry( null, defaultTarget != null ? defaultTarget : THROW_EXCEPTION);
this.defaultTarget = defaultTarget;
this.defaultAsException = defaultAsException;
this.unexpectedValueMappingException = unexpectedValueMappingException; this.unexpectedValueMappingException = unexpectedValueMappingException;
this.overridden = method.overridesMethod(); this.overridden = method.overridesMethod();
} }
@ -556,36 +531,37 @@ public class ValueMappingMethod extends MappingMethod {
Set<Type> importTypes = super.getImportTypes(); Set<Type> importTypes = super.getImportTypes();
if ( unexpectedValueMappingException != null && !unexpectedValueMappingException.isJavaLangType() ) { if ( unexpectedValueMappingException != null && !unexpectedValueMappingException.isJavaLangType() ) {
importTypes.addAll( unexpectedValueMappingException.getImportTypes() ); if ( defaultTarget.isTargetAsException() || nullTarget.isTargetAsException() ||
hasMappingWithTargetAsException() ) {
importTypes.addAll( unexpectedValueMappingException.getImportTypes() );
}
} }
return importTypes; return importTypes;
} }
protected boolean hasMappingWithTargetAsException() {
return getValueMappings()
.stream()
.anyMatch( MappingEntry::isTargetAsException );
}
public List<MappingEntry> getValueMappings() { public List<MappingEntry> getValueMappings() {
return valueMappings; return valueMappings;
} }
public String getDefaultTarget() { public MappingEntry getDefaultTarget() {
return defaultTarget; return defaultTarget;
} }
public String getNullTarget() { public MappingEntry getNullTarget() {
return nullTarget; return nullTarget;
} }
public boolean isNullAsException() {
return nullAsException;
}
public Type getUnexpectedValueMappingException() { public Type getUnexpectedValueMappingException() {
return unexpectedValueMappingException; return unexpectedValueMappingException;
} }
public boolean isDefaultAsException() {
return defaultAsException;
}
public Parameter getSourceParameter() { public Parameter getSourceParameter() {
return first( getSourceParameters() ); return first( getSourceParameters() );
} }

View File

@ -15,7 +15,7 @@
</#if> </#if>
</#list> </#list>
if ( ${sourceParameter.name} == null ) { if ( ${sourceParameter.name} == null ) {
<#if nullAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>return <@writeTarget target=nullTarget/>;</#if> <#if nullTarget.targetAsException>throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>return <@writeTarget target=nullTarget.target/>;</#if>
} }
<@includeModel object=resultType/> ${resultName}; <@includeModel object=resultType/> ${resultName};
@ -25,7 +25,7 @@
case <@writeSource source=valueMapping.source/>: <#if valueMapping.targetAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>${resultName} = <@writeTarget target=valueMapping.target/>; case <@writeSource source=valueMapping.source/>: <#if valueMapping.targetAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>${resultName} = <@writeTarget target=valueMapping.target/>;
break;</#if> break;</#if>
</#list> </#list>
default: <#if defaultAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} )<#else>${resultName} = <@writeTarget target=defaultTarget/></#if>; default: <#if defaultTarget.targetAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} )<#else>${resultName} = <@writeTarget target=defaultTarget.target/></#if>;
} }
<#list beforeMappingReferencesWithMappingTarget as callback> <#list beforeMappingReferencesWithMappingTarget as callback>
<#if callback_index = 0> <#if callback_index = 0>
@ -40,7 +40,7 @@
<@includeModel object=callback targetBeanName=resultName targetType=resultType/> <@includeModel object=callback targetBeanName=resultName targetType=resultType/>
</#list> </#list>
<#if !(valueMappings.empty && unexpectedValueMappingException??)> <#if !(valueMappings.empty && defaultTarget.targetAsException)>
return ${resultName}; return ${resultName};
</#if> </#if>
} }

View File

@ -23,6 +23,7 @@ public class ConstantTest {
assertThat( MappingConstants.ANY_REMAINING ).isEqualTo( MappingConstantsGem.ANY_REMAINING ); assertThat( MappingConstants.ANY_REMAINING ).isEqualTo( MappingConstantsGem.ANY_REMAINING );
assertThat( MappingConstants.ANY_UNMAPPED ).isEqualTo( MappingConstantsGem.ANY_UNMAPPED ); assertThat( MappingConstants.ANY_UNMAPPED ).isEqualTo( MappingConstantsGem.ANY_UNMAPPED );
assertThat( MappingConstants.NULL ).isEqualTo( MappingConstantsGem.NULL ); assertThat( MappingConstants.NULL ).isEqualTo( MappingConstantsGem.NULL );
assertThat( MappingConstants.THROW_EXCEPTION ).isEqualTo( MappingConstantsGem.THROW_EXCEPTION );
assertThat( MappingConstants.SUFFIX_TRANSFORMATION ).isEqualTo( MappingConstantsGem.SUFFIX_TRANSFORMATION ); assertThat( MappingConstants.SUFFIX_TRANSFORMATION ).isEqualTo( MappingConstantsGem.SUFFIX_TRANSFORMATION );
assertThat( MappingConstants.STRIP_SUFFIX_TRANSFORMATION ) assertThat( MappingConstants.STRIP_SUFFIX_TRANSFORMATION )
.isEqualTo( MappingConstantsGem.STRIP_SUFFIX_TRANSFORMATION ); .isEqualTo( MappingConstantsGem.STRIP_SUFFIX_TRANSFORMATION );

View File

@ -8,6 +8,7 @@ package org.mapstruct.ap.test.value.spi;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import org.mapstruct.MappingConstants;
import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.spi.DefaultEnumMappingStrategy; import org.mapstruct.ap.spi.DefaultEnumMappingStrategy;
import org.mapstruct.ap.spi.EnumMappingStrategy; import org.mapstruct.ap.spi.EnumMappingStrategy;
@ -20,6 +21,10 @@ public class CustomEnumMappingStrategy extends DefaultEnumMappingStrategy implem
@Override @Override
public String getDefaultNullEnumConstant(TypeElement enumType) { public String getDefaultNullEnumConstant(TypeElement enumType) {
if ( isCustomThrowingEnum( enumType ) ) {
return MappingConstants.THROW_EXCEPTION;
}
if ( isCustomEnum( enumType ) ) { if ( isCustomEnum( enumType ) ) {
return "UNSPECIFIED"; return "UNSPECIFIED";
} }
@ -29,6 +34,10 @@ public class CustomEnumMappingStrategy extends DefaultEnumMappingStrategy implem
@Override @Override
public String getEnumConstant(TypeElement enumType, String enumConstant) { public String getEnumConstant(TypeElement enumType, String enumConstant) {
if ( isCustomThrowingEnum( enumType ) ) {
return getCustomEnumConstant( enumConstant );
}
if ( isCustomEnum( enumType ) ) { if ( isCustomEnum( enumType ) ) {
return getCustomEnumConstant( enumConstant ); return getCustomEnumConstant( enumConstant );
} }
@ -53,6 +62,18 @@ public class CustomEnumMappingStrategy extends DefaultEnumMappingStrategy implem
return false; return false;
} }
protected boolean isCustomThrowingEnum(TypeElement enumType) {
for ( TypeMirror enumTypeInterface : enumType.getInterfaces() ) {
if ( typeUtils.asElement( enumTypeInterface )
.getSimpleName()
.contentEquals( "CustomThrowingEnumMarker" ) ) {
return true;
}
}
return false;
}
@Override @Override
protected Class<? extends Exception> getUnexpectedValueMappingExceptionClass() { protected Class<? extends Exception> getUnexpectedValueMappingExceptionClass() {
return CustomIllegalArgumentException.class; return CustomIllegalArgumentException.class;

View File

@ -9,12 +9,14 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mapstruct.ap.test.value.CustomIllegalArgumentException; import org.mapstruct.ap.test.value.CustomIllegalArgumentException;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithServiceImplementation; import org.mapstruct.ap.testutil.WithServiceImplementation;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.mapstruct.ap.testutil.runner.GeneratedSource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/** /**
* @author Filip Hrisafov * @author Filip Hrisafov
@ -24,6 +26,8 @@ import static org.assertj.core.api.Assertions.assertThat;
CheeseType.class, CheeseType.class,
CustomCheeseType.class, CustomCheeseType.class,
CustomEnumMarker.class, CustomEnumMarker.class,
CustomThrowingCheeseType.class,
CustomThrowingEnumMarker.class,
CustomIllegalArgumentException.class, CustomIllegalArgumentException.class,
}) })
@WithServiceImplementation(CustomEnumMappingStrategy.class) @WithServiceImplementation(CustomEnumMappingStrategy.class)
@ -122,4 +126,23 @@ public class CustomEnumMappingStrategyTest {
assertThat( mapper.mapStringToCustom( "ROQUEFORT" ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT ); assertThat( mapper.mapStringToCustom( "ROQUEFORT" ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.mapStringToCustom( "UNKNOWN" ) ).isEqualTo( CustomCheeseType.CUSTOM_BRIE ); assertThat( mapper.mapStringToCustom( "UNKNOWN" ) ).isEqualTo( CustomCheeseType.CUSTOM_BRIE );
} }
@Test
@IssueKey("2339")
@WithClasses({
CustomThrowingCheeseMapper.class
})
public void shouldApplyCustomEnumMappingStrategyWithThrowingException() {
CustomThrowingCheeseMapper mapper = CustomThrowingCheeseMapper.INSTANCE;
// CustomCheeseType -> CustomThrowingCheeseType
assertThatThrownBy( () -> mapper.map( (CheeseType) null ) )
.isInstanceOf( CustomIllegalArgumentException.class )
.hasMessage( "Unexpected enum constant: null" );
assertThat( mapper.map( CheeseType.BRIE ) ).isEqualTo( CustomThrowingCheeseType.CUSTOM_BRIE );
// CustomThrowingCheeseType -> CustomCheeseType
assertThat( mapper.map( (CustomThrowingCheeseType) null ) ).isNull();
assertThat( mapper.map( CustomThrowingCheeseType.CUSTOM_BRIE ) ).isEqualTo( CheeseType.BRIE );
}
} }

View File

@ -0,0 +1,22 @@
/*
* 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.value.spi;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface CustomThrowingCheeseMapper {
CustomThrowingCheeseMapper INSTANCE = Mappers.getMapper( CustomThrowingCheeseMapper.class );
CustomThrowingCheeseType map(CheeseType cheese);
CheeseType map(CustomThrowingCheeseType cheese);
}

View File

@ -0,0 +1,17 @@
/*
* 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.value.spi;
/**
* @author Filip Hrisafov
*/
public enum CustomThrowingCheeseType implements CustomThrowingEnumMarker {
UNSPECIFIED,
CUSTOM_BRIE,
CUSTOM_ROQUEFORT,
UNRECOGNIZED,
}

View File

@ -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.value.spi;
/**
* @author Filip Hrisafov
*/
public interface CustomThrowingEnumMarker {
}