Skip to content

Commit c4135e6

Browse files
JudeNiroshanfiliphr
authored andcommitted
mapstruct#2339 Support throwing an exception as an Enum Mapping option
1 parent 228660c commit c4135e6

15 files changed

Lines changed: 368 additions & 42 deletions

File tree

core/src/main/java/org/mapstruct/MappingConstants.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ private MappingConstants() {
3636
*/
3737
public static final String ANY_UNMAPPED = "<ANY_UNMAPPED>";
3838

39+
/**
40+
* In an {@link ValueMapping} this represents any target that will be mapped to an
41+
* {@link java.lang.IllegalArgumentException} which will be thrown at runtime.
42+
* <p>
43+
* NOTE: The value is only applicable to {@link ValueMapping#target()} and not to {@link ValueMapping#source()}.
44+
*/
45+
public static final String THROW_EXCEPTION = "<THROW_EXCEPTION>";
46+
3947
/**
4048
* In an {@link EnumMapping} this represent the enum transformation strategy that adds a suffix to the source enum.
4149
*

core/src/main/java/org/mapstruct/ValueMapping.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
*
2424
* <pre>
2525
* <code>
26-
* public enum OrderType { RETAIL, B2B, EXTRA, STANDARD, NORMAL }
26+
*
27+
* public enum OrderType { RETAIL, B2B, C2C, EXTRA, STANDARD, NORMAL }
2728
*
2829
* public enum ExternalOrderType { RETAIL, B2B, SPECIAL, DEFAULT }
2930
*
@@ -45,13 +46,12 @@
4546
* +---------------------+----------------------------+
4647
* </pre>
4748
*
48-
* MapStruct will <B>WARN</B> on incomplete mappings. However, if for some reason no match is found an
49-
* {@link java.lang.IllegalStateException} will be thrown.
5049
* <p>
5150
* <B>Example 2:</B>
5251
*
5352
* <pre>
5453
* <code>
54+
*
5555
* &#64;ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
5656
* &#64;ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),
5757
* &#64;ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" )
@@ -70,6 +70,26 @@
7070
* +---------------------+----------------------------+
7171
* </pre>
7272
*
73+
* <p>
74+
* <B>Example 3:</B>
75+
* </p>
76+
*
77+
* <p></p>MapStruct will <B>WARN</B> on incomplete mappings. However, if for some reason no match is found, an
78+
* {@link java.lang.IllegalStateException} will be thrown. This compile-time error can be avoided by
79+
* using {@link MappingConstants#THROW_EXCEPTION} for {@link ValueMapping#target()}. It will result an
80+
* {@link java.lang.IllegalArgumentException} at runtime.
81+
* <pre>
82+
* <code>
83+
*
84+
* &#64;ValueMapping( source = "STANDARD", target = "DEFAULT" ),
85+
* &#64;ValueMapping( source = "C2C", target = MappingConstants.THROW_EXCEPTION )
86+
* ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
87+
* </code>
88+
* Mapping result:
89+
* {@link java.lang.IllegalArgumentException} with the error message:
90+
* Unexpected enum constant: C2C
91+
* </pre>
92+
*
7393
* @author Sjaak Derksen
7494
*/
7595
@Repeatable(ValueMappings.class)
@@ -104,6 +124,7 @@
104124
* <ol>
105125
* <li>enum constant name</li>
106126
* <li>{@link MappingConstants#NULL}</li>
127+
* <li>{@link MappingConstants#THROW_EXCEPTION}</li>
107128
* </ol>
108129
*
109130
* @return The target value.

processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ private MappingConstantsGem() {
2121

2222
public static final String ANY_UNMAPPED = "<ANY_UNMAPPED>";
2323

24+
public static final String THROW_EXCEPTION = "<THROW_EXCEPTION>";
25+
2426
public static final String SUFFIX_TRANSFORMATION = "suffix";
2527

2628
public static final String STRIP_SUFFIX_TRANSFORMATION = "stripSuffix";

processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import java.util.Set;
1515
import javax.lang.model.element.TypeElement;
1616
import javax.lang.model.type.TypeMirror;
17-
import org.mapstruct.ap.internal.util.TypeUtils;
1817

1918
import org.mapstruct.ap.internal.gem.BeanMappingGem;
2019
import org.mapstruct.ap.internal.model.common.Parameter;
@@ -25,11 +24,13 @@
2524
import org.mapstruct.ap.internal.model.source.ValueMappingOptions;
2625
import org.mapstruct.ap.internal.util.Message;
2726
import org.mapstruct.ap.internal.util.Strings;
27+
import org.mapstruct.ap.internal.util.TypeUtils;
2828
import org.mapstruct.ap.spi.EnumTransformationStrategy;
2929

3030
import static org.mapstruct.ap.internal.gem.MappingConstantsGem.ANY_REMAINING;
3131
import static org.mapstruct.ap.internal.gem.MappingConstantsGem.ANY_UNMAPPED;
3232
import static org.mapstruct.ap.internal.gem.MappingConstantsGem.NULL;
33+
import static org.mapstruct.ap.internal.gem.MappingConstantsGem.THROW_EXCEPTION;
3334
import static org.mapstruct.ap.internal.util.Collections.first;
3435

3536
/**
@@ -43,6 +44,8 @@ public class ValueMappingMethod extends MappingMethod {
4344
private final List<MappingEntry> valueMappings;
4445
private final String defaultTarget;
4546
private final String nullTarget;
47+
private boolean nullAsException;
48+
private boolean defaultAsException;
4649

4750
private final Type unexpectedValueMappingException;
4851

@@ -119,10 +122,12 @@ else if ( sourceType.isString() && targetType.isEnumType() ) {
119122
return new ValueMappingMethod( method,
120123
mappingEntries,
121124
valueMappings.nullValueTarget,
125+
valueMappings.hasNullValueAsException,
122126
valueMappings.defaultTargetValue,
123127
determineUnexpectedValueMappingException(),
124128
beforeMappingMethods,
125-
afterMappingMethods
129+
afterMappingMethods,
130+
determineExceptionMappingForDefaultCase()
126131
);
127132
}
128133

@@ -313,7 +318,17 @@ private boolean reportErrorIfMappedSourceEnumConstantsDontExist(Method method, T
313318

314319
for ( ValueMappingOptions mappedConstant : valueMappings.regularValueMappings ) {
315320

316-
if ( !sourceEnumConstants.contains( mappedConstant.getSource() ) ) {
321+
if ( !enumMapping.isInverse() && THROW_EXCEPTION.equals( mappedConstant.getSource() ) ) {
322+
ctx.getMessager().printMessage(
323+
method.getExecutable(),
324+
mappedConstant.getMirror(),
325+
mappedConstant.getSourceAnnotationValue(),
326+
Message.VALUEMAPPING_THROW_EXCEPTION_SOURCE
327+
);
328+
foundIncorrectMapping = true;
329+
}
330+
else if ( !sourceEnumConstants.contains( mappedConstant.getSource() ) ) {
331+
317332
ctx.getMessager().printMessage(
318333
method.getExecutable(),
319334
mappedConstant.getMirror(),
@@ -361,6 +376,7 @@ private boolean reportErrorIfMappedTargetEnumConstantsDontExist(Method method, T
361376

362377
for ( ValueMappingOptions mappedConstant : valueMappings.regularValueMappings ) {
363378
if ( !NULL.equals( mappedConstant.getTarget() )
379+
&& !THROW_EXCEPTION.equals( mappedConstant.getTarget() )
364380
&& !targetEnumConstants.contains( mappedConstant.getTarget() ) ) {
365381
ctx.getMessager().printMessage(
366382
method.getExecutable(),
@@ -374,7 +390,9 @@ private boolean reportErrorIfMappedTargetEnumConstantsDontExist(Method method, T
374390
}
375391
}
376392

377-
if ( valueMappings.defaultTarget != null && !NULL.equals( valueMappings.defaultTarget.getTarget() )
393+
if ( valueMappings.defaultTarget != null
394+
&& !THROW_EXCEPTION.equals( valueMappings.defaultTarget.getTarget() )
395+
&& !NULL.equals( valueMappings.defaultTarget.getTarget() )
378396
&& !targetEnumConstants.contains( valueMappings.defaultTarget.getTarget() ) ) {
379397
ctx.getMessager().printMessage(
380398
method.getExecutable(),
@@ -415,7 +433,9 @@ else if ( valueMappings.nullTarget == null && valueMappings.nullValueTarget != n
415433
}
416434

417435
private Type determineUnexpectedValueMappingException() {
418-
if ( !valueMappings.hasDefaultValue ) {
436+
boolean noDefaultValueForSwitchCase = !valueMappings.hasDefaultValue;
437+
if ( noDefaultValueForSwitchCase || valueMappings.hasAtLeastOneExceptionValue
438+
|| valueMappings.hasNullValueAsException ) {
419439
TypeMirror unexpectedValueMappingException = enumMapping.getUnexpectedValueMappingException();
420440
if ( unexpectedValueMappingException != null ) {
421441
return ctx.getTypeFactory().getType( unexpectedValueMappingException );
@@ -427,6 +447,15 @@ private Type determineUnexpectedValueMappingException() {
427447

428448
return null;
429449
}
450+
451+
private boolean determineExceptionMappingForDefaultCase() {
452+
if ( valueMappings.hasDefaultValue ) {
453+
return THROW_EXCEPTION.equals( valueMappings.defaultTargetValue );
454+
}
455+
else {
456+
return true;
457+
}
458+
}
430459
}
431460

432461
private static class EnumTransformationStrategyInvoker {
@@ -464,7 +493,8 @@ private static class ValueMappings {
464493
boolean hasMapAnyUnmapped = false;
465494
boolean hasMapAnyRemaining = false;
466495
boolean hasDefaultValue = false;
467-
boolean hasNullValue = false;
496+
boolean hasNullValueAsException = false;
497+
boolean hasAtLeastOneExceptionValue = false;
468498

469499
ValueMappings(List<ValueMappingOptions> valueMappings) {
470500

@@ -484,11 +514,17 @@ else if ( ANY_UNMAPPED.equals( valueMapping.getSource() ) ) {
484514
else if ( NULL.equals( valueMapping.getSource() ) ) {
485515
nullTarget = valueMapping;
486516
nullValueTarget = getValue( nullTarget );
487-
hasNullValue = true;
517+
if ( THROW_EXCEPTION.equals( nullValueTarget ) ) {
518+
hasNullValueAsException = true;
519+
}
488520
}
489521
else {
490522
regularValueMappings.add( valueMapping );
491523
}
524+
525+
if ( THROW_EXCEPTION.equals( valueMapping.getTarget() ) ) {
526+
hasAtLeastOneExceptionValue = true;
527+
}
492528
}
493529
}
494530

@@ -497,14 +533,20 @@ String getValue(ValueMappingOptions valueMapping) {
497533
}
498534
}
499535

500-
private ValueMappingMethod(Method method, List<MappingEntry> enumMappings, String nullTarget, String defaultTarget,
501-
Type unexpectedValueMappingException,
502-
List<LifecycleCallbackMethodReference> beforeMappingMethods,
503-
List<LifecycleCallbackMethodReference> afterMappingMethods) {
536+
private ValueMappingMethod(Method method,
537+
List<MappingEntry> enumMappings,
538+
String nullTarget,
539+
boolean hasNullTargetAsException,
540+
String defaultTarget,
541+
Type unexpectedValueMappingException,
542+
List<LifecycleCallbackMethodReference> beforeMappingMethods,
543+
List<LifecycleCallbackMethodReference> afterMappingMethods, boolean defaultAsException) {
504544
super( method, beforeMappingMethods, afterMappingMethods );
505545
this.valueMappings = enumMappings;
506546
this.nullTarget = nullTarget;
547+
this.nullAsException = hasNullTargetAsException;
507548
this.defaultTarget = defaultTarget;
549+
this.defaultAsException = defaultAsException;
508550
this.unexpectedValueMappingException = unexpectedValueMappingException;
509551
this.overridden = method.overridesMethod();
510552
}
@@ -532,10 +574,18 @@ public String getNullTarget() {
532574
return nullTarget;
533575
}
534576

577+
public boolean isNullAsException() {
578+
return nullAsException;
579+
}
580+
535581
public Type getUnexpectedValueMappingException() {
536582
return unexpectedValueMappingException;
537583
}
538584

585+
public boolean isDefaultAsException() {
586+
return defaultAsException;
587+
}
588+
539589
public Parameter getSourceParameter() {
540590
return first( getSourceParameters() );
541591
}
@@ -547,17 +597,25 @@ public boolean isOverridden() {
547597
public static class MappingEntry {
548598
private final String source;
549599
private final String target;
600+
private boolean targetAsException = false;
550601

551-
MappingEntry( String source, String target ) {
602+
MappingEntry(String source, String target) {
552603
this.source = source;
553604
if ( !NULL.equals( target ) ) {
554605
this.target = target;
606+
if ( THROW_EXCEPTION.equals( target ) ) {
607+
this.targetAsException = true;
608+
}
555609
}
556610
else {
557611
this.target = null;
558612
}
559613
}
560614

615+
public boolean isTargetAsException() {
616+
return targetAsException;
617+
}
618+
561619
public String getSource() {
562620
return source;
563621
}

processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static org.mapstruct.ap.internal.gem.MappingConstantsGem.ANY_REMAINING;
2020
import static org.mapstruct.ap.internal.gem.MappingConstantsGem.ANY_UNMAPPED;
21+
import static org.mapstruct.ap.internal.gem.MappingConstantsGem.THROW_EXCEPTION;
2122

2223
/**
2324
* Represents the mapping between one value constant and another.
@@ -112,7 +113,7 @@ public AnnotationValue getTargetAnnotationValue() {
112113

113114
public ValueMappingOptions inverse() {
114115
ValueMappingOptions result;
115-
if ( !(ANY_REMAINING.equals( source ) || ANY_UNMAPPED.equals( source ) ) ) {
116+
if ( !(ANY_REMAINING.equals( source ) || ANY_UNMAPPED.equals( source ) || THROW_EXCEPTION.equals( target ) ) ) {
116117
result = new ValueMappingOptions(
117118
target,
118119
source,

processor/src/main/java/org/mapstruct/ap/internal/util/Message.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ public enum Message {
175175
VALUEMAPPING_ANY_REMAINING_FOR_NON_ENUM( "Source = \"<ANY_REMAINING>\" can only be used on targets of type enum and not for %s." ),
176176
VALUEMAPPING_ANY_REMAINING_OR_UNMAPPED_MISSING( "Source = \"<ANY_REMAINING>\" or \"<ANY_UNMAPPED>\" is advisable for mapping of type String to an enum type.", Diagnostic.Kind.WARNING ),
177177
VALUEMAPPING_NON_EXISTING_CONSTANT_FROM_SPI( "Constant %s doesn't exist in enum type %s. Constant was returned from EnumMappingStrategy: %s"),
178-
VALUEMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s." );
178+
VALUEMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s." ),
179+
VALUEMAPPING_THROW_EXCEPTION_SOURCE( "Source = \"<THROW_EXCEPTION>\" is not allowed. Target = \"<THROW_EXCEPTION>\" can only be used." );
179180
// CHECKSTYLE:ON
180181

181182

processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@
1515
</#if>
1616
</#list>
1717
if ( ${sourceParameter.name} == null ) {
18-
return <@writeTarget target=nullTarget/>;
18+
<#if nullAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>return <@writeTarget target=nullTarget/>;</#if>
1919
}
2020

2121
<@includeModel object=resultType/> ${resultName};
2222

2323
switch ( ${sourceParameter.name} ) {
2424
<#list valueMappings as valueMapping>
25-
case <@writeSource source=valueMapping.source/>: ${resultName} = <@writeTarget target=valueMapping.target/>;
26-
break;
25+
case <@writeSource source=valueMapping.source/>: <#if valueMapping.targetAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>${resultName} = <@writeTarget target=valueMapping.target/>;
26+
break;</#if>
2727
</#list>
28-
default: <#if unexpectedValueMappingException??>throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} )<#else>${resultName} = <@writeTarget target=defaultTarget/></#if>;
28+
default: <#if defaultAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} )<#else>${resultName} = <@writeTarget target=defaultTarget/></#if>;
2929
}
3030
<#list beforeMappingReferencesWithMappingTarget as callback>
3131
<#if callback_index = 0>
@@ -65,4 +65,4 @@
6565
null
6666
</#if>
6767
</@compress>
68-
</#macro>
68+
</#macro>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.value.enum2enum;
7+
8+
import org.mapstruct.Mapper;
9+
import org.mapstruct.MappingConstants;
10+
import org.mapstruct.Named;
11+
import org.mapstruct.ValueMapping;
12+
import org.mapstruct.ap.test.value.ExternalOrderType;
13+
import org.mapstruct.ap.test.value.OrderType;
14+
import org.mapstruct.factory.Mappers;
15+
16+
/**
17+
* @author Jude Niroshan
18+
*/
19+
@Mapper
20+
public interface DefaultOrderThrowExceptionMapper {
21+
DefaultOrderThrowExceptionMapper INSTANCE = Mappers.getMapper( DefaultOrderThrowExceptionMapper.class );
22+
23+
@Named("orderTypeToExternalOrderTypeAnyUnmappedToException")
24+
@ValueMapping(source = MappingConstants.ANY_UNMAPPED, target = MappingConstants.THROW_EXCEPTION)
25+
ExternalOrderType orderTypeToExternalOrderTypeAnyUnmappedToException(OrderType orderType);
26+
}

0 commit comments

Comments
 (0)