#1557 & #1936 String-to-enum and enum-to-String (#1938)

* #1557 making enum-to-string and string-to-enum

* #1936 <ANY_REMAINING> and <ANY_UNMAPPED> not ignored when inverting
This commit is contained in:
Sjaak Derksen 2019-10-07 20:30:20 +02:00 committed by GitHub
parent 44ae27d7ca
commit 507ec1b384
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 572 additions and 153 deletions

View File

@ -23,11 +23,16 @@ public final class MappingConstants {
/** /**
* In an {@link ValueMapping} this represents any source that is not already mapped by either a defined mapping or * In an {@link ValueMapping} this represents any source that is not already mapped by either a defined mapping or
* by means of name based mapping. * by means of name based mapping.
*
* NOTE: The value is only applicable to {@link ValueMapping#source()} and not to {@link ValueMapping#target()}.
*/ */
public static final String ANY_REMAINING = "<ANY_REMAINING>"; public static final String ANY_REMAINING = "<ANY_REMAINING>";
/** /**
* In an {@link ValueMapping} this represents any source that is not already mapped by a defined mapping. * In an {@link ValueMapping} this represents any source that is not already mapped by a defined mapping.
*
* NOTE: The value is only applicable to {@link ValueMapping#source()} and not to {@link ValueMapping#target()}.
*
*/ */
public static final String ANY_UNMAPPED = "<ANY_UNMAPPED>"; public static final String ANY_UNMAPPED = "<ANY_UNMAPPED>";

View File

@ -1,7 +1,7 @@
[[mapping-enum-types]] [[mapping-enum-types]]
== Mapping Values == Mapping Values
=== Mapping enum types === Mapping enum to enum types
MapStruct supports the generation of methods which map one Java enum type into another. MapStruct supports the generation of methods which map one Java enum type into another.
@ -67,7 +67,7 @@ public class OrderMapperImpl implements OrderMapper {
By default an error will be raised by MapStruct in case a constant of the source enum type does not have a corresponding constant with the same name in the target type and also is not mapped to another constant via `@ValueMapping`. This ensures that all constants are mapped in a safe and predictable manner. The generated By default an error will be raised by MapStruct in case a constant of the source enum type does not have a corresponding constant with the same name in the target type and also is not mapped to another constant via `@ValueMapping`. This ensures that all constants are mapped in a safe and predictable manner. The generated
mapping method will throw an IllegalStateException if for some reason an unrecognized source value occurs. mapping method will throw an IllegalStateException if for some reason an unrecognized source value occurs.
MapStruct also has a mechanism for mapping any remaining (unspecified) mappings to a default. This can be used only once in a set of value mappings. It comes in two flavors: `<ANY_REMAINING>` and `<ANY_UNMAPPED>`. MapStruct also has a mechanism for mapping any remaining (unspecified) mappings to a default. This can be used only once in a set of value mappings and only applies to the source. It comes in two flavors: `<ANY_REMAINING>` and `<ANY_UNMAPPED>`. They cannot be used at the same time.
In case of source `<ANY_REMAINING>` MapStruct will continue to map a source enum constant to a target enum constant with the same name. The remainder of the source enum constants will be mapped to the target specified in the `@ValueMapping` with `<ANY_REMAINING>` source. In case of source `<ANY_REMAINING>` MapStruct will continue to map a source enum constant to a target enum constant with the same name. The remainder of the source enum constants will be mapped to the target specified in the `@ValueMapping` with `<ANY_REMAINING>` source.
@ -80,7 +80,7 @@ MapStruct is able to handle `null` sources and `null` targets by means of the `<
Constants for `<ANY_REMAINING>`, `<ANY_UNMAPPED>` and `<NULL>` are available in the `MappingConstants` class. Constants for `<ANY_REMAINING>`, `<ANY_UNMAPPED>` and `<NULL>` are available in the `MappingConstants` class.
==== ====
Finally `@InheritInverseConfiguration` and `@InheritConfiguration` can be used in combination with `@ValueMappings`. Finally `@InheritInverseConfiguration` and `@InheritConfiguration` can be used in combination with `@ValueMappings`. `<ANY_REMAINING>` and `<ANY_UNMAPPED>` will be ignored in that case.
.Enum mapping method, <NULL> and <ANY_REMAINING> .Enum mapping method, <NULL> and <ANY_REMAINING>
==== ====
@ -141,3 +141,21 @@ public class SpecialOrderMapperImpl implements SpecialOrderMapper {
==== ====
The mapping of enum to enum via the `@Mapping` annotation is *DEPRECATED*. It will be removed from future versions of MapStruct. Please adapt existing enum mapping methods to make use of `@ValueMapping` instead. The mapping of enum to enum via the `@Mapping` annotation is *DEPRECATED*. It will be removed from future versions of MapStruct. Please adapt existing enum mapping methods to make use of `@ValueMapping` instead.
==== ====
=== Mapping enum-to-String or String-to-enum
MapStruct supports enum to a String mapping along the same lines as is described in <<Mapping enum to enum types, enum-to-enum types>>. There are similarities and differences:
*enum to `String`*
1. Similarity: All not explicit defined mappings will result in each source enum constant value being mapped a `String` value with the same constant value.
2. Similarity: `<ANY_UNMAPPED`> stops after handling defined mapping and proceeds to the switch/default clause value.
3. Difference: `<ANY_REMAINING>` will result in an error. It acts on the premise that there is name similarity between enum constants in source and target which does not make sense for a String type.
4. Difference: Given 1. and 3. there will never be unmapped values.
*`String` to enum*
1. Similarity: All not explicit defined mappings will result in the target enum constant mapped from the `String` value when that maches the target enum constant name.
2. Similarity: `<ANY_UNMAPPED`> stops after handling defined mapping and proceeds to the switch/default clause value.
3. Similarity: `<ANY_REMAINING>` will create a mapping for each target enum constant and proceed to the switch/default clause value.
4. Difference: A switch/default value needs to be provided to have a determined outcome (enum has a limited set of values, `String` has unlimited options). Failing to specify `<ANY_REMAINING>` or `<ANY_UNMAPPED`> will result in a warning.

View File

@ -5,6 +5,9 @@
*/ */
package org.mapstruct.ap.internal.model; package org.mapstruct.ap.internal.model;
import static org.mapstruct.ap.internal.prism.MappingConstantsPrism.ANY_REMAINING;
import static org.mapstruct.ap.internal.prism.MappingConstantsPrism.ANY_UNMAPPED;
import static org.mapstruct.ap.internal.prism.MappingConstantsPrism.NULL;
import static org.mapstruct.ap.internal.util.Collections.first; import static org.mapstruct.ap.internal.util.Collections.first;
import java.util.ArrayList; import java.util.ArrayList;
@ -16,11 +19,11 @@ import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types; import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.ValueMapping; import org.mapstruct.ap.internal.model.source.ValueMapping;
import org.mapstruct.ap.internal.prism.BeanMappingPrism; import org.mapstruct.ap.internal.prism.BeanMappingPrism;
import org.mapstruct.ap.internal.prism.MappingConstantsPrism;
import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
@ -42,10 +45,7 @@ public class ValueMappingMethod extends MappingMethod {
private Method method; private Method method;
private MappingBuilderContext ctx; private MappingBuilderContext ctx;
private final List<ValueMapping> trueValueMappings = new ArrayList<>(); private ValueMappings valueMappings;
private ValueMapping defaultTargetValue = null;
private ValueMapping nullTargetValue = null;
private boolean applyNamebasedMappings = true;
public Builder mappingContext(MappingBuilderContext mappingContext) { public Builder mappingContext(MappingBuilderContext mappingContext) {
this.ctx = mappingContext; this.ctx = mappingContext;
@ -58,21 +58,7 @@ public class ValueMappingMethod extends MappingMethod {
} }
public Builder valueMappings(List<ValueMapping> valueMappings) { public Builder valueMappings(List<ValueMapping> valueMappings) {
for ( ValueMapping valueMapping : valueMappings ) { this.valueMappings = new ValueMappings( valueMappings );
if ( MappingConstantsPrism.ANY_REMAINING.equals( valueMapping.getSource() ) ) {
defaultTargetValue = valueMapping;
}
else if ( MappingConstantsPrism.ANY_UNMAPPED.equals( valueMapping.getSource() ) ) {
defaultTargetValue = valueMapping;
applyNamebasedMappings = false;
}
else if ( MappingConstantsPrism.NULL.equals( valueMapping.getSource() ) ) {
nullTargetValue = valueMapping;
}
else {
trueValueMappings.add( valueMapping );
}
}
return this; return this;
} }
@ -80,27 +66,19 @@ public class ValueMappingMethod extends MappingMethod {
// initialize all relevant parameters // initialize all relevant parameters
List<MappingEntry> mappingEntries = new ArrayList<>(); List<MappingEntry> mappingEntries = new ArrayList<>();
String nullTarget = null;
String defaultTarget = null;
boolean throwIllegalArgumentException = false;
// for now, we're only dealing with enum mappings, populate relevant parameters based on enum-2-enum Type sourceType = first( method.getSourceParameters() ).getType();
if ( first( method.getSourceParameters() ).getType().isEnumType() && method.getResultType().isEnumType() ) { Type targetType = method.getResultType();
mappingEntries.addAll( enumToEnumMapping( method ) );
if ( (nullTargetValue != null) && !MappingConstantsPrism.NULL.equals( nullTargetValue.getTarget() ) ) {
// absense nulltargetvalue reverts to null. Or it could be a deliberate choice to return null
nullTarget = nullTargetValue.getTarget();
}
if ( defaultTargetValue != null ) {
// If the default target value is NULL then we should map it to null
defaultTarget = MappingConstantsPrism.NULL.equals( defaultTargetValue.getTarget() ) ? null :
defaultTargetValue.getTarget();
}
else {
throwIllegalArgumentException = true;
}
// enum-to-enum
if ( sourceType.isEnumType() && targetType.isEnumType() ) {
mappingEntries.addAll( enumToEnumMapping( method, sourceType, targetType ) );
}
else if ( sourceType.isEnumType() && targetType.isString() ) {
mappingEntries.addAll( enumToStringMapping( method, sourceType ) );
}
else if ( sourceType.isString() && targetType.isEnumType() ) {
mappingEntries.addAll( stringToEnumMapping( method, targetType ) );
} }
// do before / after lifecycle mappings // do before / after lifecycle mappings
@ -112,32 +90,36 @@ public class ValueMappingMethod extends MappingMethod {
LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables ); LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables );
// finally return a mapping // finally return a mapping
return new ValueMappingMethod( method, mappingEntries, nullTarget, defaultTarget, return new ValueMappingMethod( method,
throwIllegalArgumentException, beforeMappingMethods, afterMappingMethods ); mappingEntries,
valueMappings.nullValueTarget,
valueMappings.defaultTargetValue,
!valueMappings.hasDefaultValue,
beforeMappingMethods,
afterMappingMethods
);
} }
private List<MappingEntry> enumToEnumMapping(Method method) { private List<MappingEntry> enumToEnumMapping(Method method, Type sourceType, Type targetType ) {
List<MappingEntry> mappings = new ArrayList<>(); List<MappingEntry> mappings = new ArrayList<>();
List<String> unmappedSourceConstants List<String> unmappedSourceConstants = new ArrayList<>( sourceType.getEnumConstants() );
= new ArrayList<>( first( method.getSourceParameters() ).getType().getEnumConstants() ); boolean sourceErrorOccurred = !reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType );
boolean targetErrorOccurred = !reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType );
if ( sourceErrorOccurred || targetErrorOccurred ) {
if ( !reportErrorIfMappedEnumConstantsDontExist( method ) ) {
return mappings; return mappings;
} }
// Start to fill the mappings with the defined valuemappings // Start to fill the mappings with the defined value mappings
for ( ValueMapping valueMapping : trueValueMappings ) { for ( ValueMapping valueMapping : valueMappings.regularValueMappings ) {
String target = String target =
MappingConstantsPrism.NULL.equals( valueMapping.getTarget() ) ? null : valueMapping.getTarget(); NULL.equals( valueMapping.getTarget() ) ? null : valueMapping.getTarget();
mappings.add( new MappingEntry( valueMapping.getSource(), target ) ); mappings.add( new MappingEntry( valueMapping.getSource(), target ) );
unmappedSourceConstants.remove( valueMapping.getSource() ); unmappedSourceConstants.remove( valueMapping.getSource() );
} }
// add mappings based on name // add mappings based on name
if ( applyNamebasedMappings ) { if ( !valueMappings.hasMapAnyUnmapped ) {
// get all target constants // get all target constants
List<String> targetConstants = method.getReturnType().getEnumConstants(); List<String> targetConstants = method.getReturnType().getEnumConstants();
@ -148,7 +130,7 @@ public class ValueMappingMethod extends MappingMethod {
} }
} }
if ( defaultTargetValue == null && !unmappedSourceConstants.isEmpty() ) { if ( valueMappings.defaultTarget == null && !unmappedSourceConstants.isEmpty() ) {
String sourceErrorMessage = "source"; String sourceErrorMessage = "source";
String targetErrorMessage = "target"; String targetErrorMessage = "target";
if ( method instanceof ForgedMethod && ( (ForgedMethod) method ).getHistory() != null ) { if ( method instanceof ForgedMethod && ( (ForgedMethod) method ).getHistory() != null ) {
@ -160,7 +142,7 @@ public class ValueMappingMethod extends MappingMethod {
// all sources should now be matched, there's no default to fall back to, so if sources remain, // all sources should now be matched, there's no default to fall back to, so if sources remain,
// we have an issue. // we have an issue.
ctx.getMessager().printMessage( method.getExecutable(), ctx.getMessager().printMessage( method.getExecutable(),
Message.VALUE_MAPPING_UNMAPPED_SOURCES, Message.VALUEMAPPING_UNMAPPED_SOURCES,
sourceErrorMessage, sourceErrorMessage,
targetErrorMessage, targetErrorMessage,
Strings.join( unmappedSourceConstants, ", " ) Strings.join( unmappedSourceConstants, ", " )
@ -171,6 +153,64 @@ public class ValueMappingMethod extends MappingMethod {
return mappings; return mappings;
} }
private List<MappingEntry> enumToStringMapping(Method method, Type sourceType ) {
List<MappingEntry> mappings = new ArrayList<>();
List<String> unmappedSourceConstants = new ArrayList<>( sourceType.getEnumConstants() );
boolean sourceErrorOccurred = !reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType );
boolean anyRemainingUsedError = !reportErrorIfSourceEnumConstantsContainsAnyRemaining( method );
if ( sourceErrorOccurred || anyRemainingUsedError ) {
return mappings;
}
// Start to fill the mappings with the defined valuemappings
for ( ValueMapping valueMapping : valueMappings.regularValueMappings ) {
String target =
NULL.equals( valueMapping.getTarget() ) ? null : valueMapping.getTarget();
mappings.add( new MappingEntry( valueMapping.getSource(), target ) );
unmappedSourceConstants.remove( valueMapping.getSource() );
}
// add mappings based on name
if ( !valueMappings.hasMapAnyUnmapped ) {
// all remaining constants are mapped
for ( String sourceConstant : unmappedSourceConstants ) {
mappings.add( new MappingEntry( sourceConstant, sourceConstant ) );
}
}
return mappings;
}
private List<MappingEntry> stringToEnumMapping(Method method, Type targetType ) {
List<MappingEntry> mappings = new ArrayList<>();
List<String> unmappedSourceConstants = new ArrayList<>( targetType.getEnumConstants() );
boolean sourceErrorOccurred = !reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType );
boolean mandatoryMissing = !reportErrorIfAnyRemainingOrAnyUnMappedMissing( method );
if ( sourceErrorOccurred || mandatoryMissing ) {
return mappings;
}
// Start to fill the mappings with the defined valuemappings
for ( ValueMapping valueMapping : valueMappings.regularValueMappings ) {
String target =
NULL.equals( valueMapping.getTarget() ) ? null : valueMapping.getTarget();
mappings.add( new MappingEntry( valueMapping.getSource(), target ) );
unmappedSourceConstants.remove( valueMapping.getSource() );
}
// add mappings based on name
if ( !valueMappings.hasMapAnyUnmapped ) {
// all remaining constants are mapped
for ( String sourceConstant : unmappedSourceConstants ) {
mappings.add( new MappingEntry( sourceConstant, sourceConstant ) );
}
}
return mappings;
}
private SelectionParameters getSelectionParameters(Method method, Types typeUtils) { private SelectionParameters getSelectionParameters(Method method, Types typeUtils) {
BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() ); BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() );
if ( beanMappingPrism != null ) { if ( beanMappingPrism != null ) {
@ -182,13 +222,12 @@ public class ValueMappingMethod extends MappingMethod {
return null; return null;
} }
private boolean reportErrorIfMappedEnumConstantsDontExist(Method method) { private boolean reportErrorIfMappedSourceEnumConstantsDontExist(Method method, Type sourceType) {
List<String> sourceEnumConstants = first( method.getSourceParameters() ).getType().getEnumConstants(); List<String> sourceEnumConstants = sourceType.getEnumConstants();
List<String> targetEnumConstants = method.getReturnType().getEnumConstants();
boolean foundIncorrectMapping = false; boolean foundIncorrectMapping = false;
for ( ValueMapping mappedConstant : trueValueMappings ) { for ( ValueMapping mappedConstant : valueMappings.regularValueMappings ) {
if ( !sourceEnumConstants.contains( mappedConstant.getSource() ) ) { if ( !sourceEnumConstants.contains( mappedConstant.getSource() ) ) {
ctx.getMessager().printMessage( method.getExecutable(), ctx.getMessager().printMessage( method.getExecutable(),
@ -196,11 +235,50 @@ public class ValueMappingMethod extends MappingMethod {
mappedConstant.getSourceAnnotationValue(), mappedConstant.getSourceAnnotationValue(),
Message.VALUEMAPPING_NON_EXISTING_CONSTANT, Message.VALUEMAPPING_NON_EXISTING_CONSTANT,
mappedConstant.getSource(), mappedConstant.getSource(),
first( method.getSourceParameters() ).getType() sourceType
); );
foundIncorrectMapping = true; foundIncorrectMapping = true;
} }
if ( !MappingConstantsPrism.NULL.equals( mappedConstant.getTarget() ) }
return !foundIncorrectMapping;
}
private boolean reportErrorIfSourceEnumConstantsContainsAnyRemaining(Method method) {
boolean foundIncorrectMapping = false;
if ( valueMappings.hasMapAnyRemaining ) {
ctx.getMessager().printMessage(
method.getExecutable(),
valueMappings.defaultTarget.getMirror(),
valueMappings.defaultTarget.getSourceAnnotationValue(),
Message.VALUEMAPPING_ANY_REMAINING_FOR_NON_ENUM,
method.getResultType()
);
foundIncorrectMapping = true;
}
return !foundIncorrectMapping;
}
private boolean reportErrorIfAnyRemainingOrAnyUnMappedMissing(Method method) {
boolean foundIncorrectMapping = false;
if ( !( valueMappings.hasMapAnyUnmapped || valueMappings.hasMapAnyRemaining ) ) {
ctx.getMessager().printMessage(
method.getExecutable(),
Message.VALUEMAPPING_ANY_REMAINING_OR_UNMAPPED_MISSING
);
foundIncorrectMapping = true;
}
return !foundIncorrectMapping;
}
private boolean reportErrorIfMappedTargetEnumConstantsDontExist(Method method, Type targetType) {
List<String> targetEnumConstants = targetType.getEnumConstants();
boolean foundIncorrectMapping = false;
for ( ValueMapping mappedConstant : valueMappings.regularValueMappings ) {
if ( !NULL.equals( mappedConstant.getTarget() )
&& !targetEnumConstants.contains( mappedConstant.getTarget() ) ) { && !targetEnumConstants.contains( mappedConstant.getTarget() ) ) {
ctx.getMessager().printMessage( method.getExecutable(), ctx.getMessager().printMessage( method.getExecutable(),
mappedConstant.getMirror(), mappedConstant.getMirror(),
@ -213,25 +291,25 @@ public class ValueMappingMethod extends MappingMethod {
} }
} }
if ( defaultTargetValue != null && !MappingConstantsPrism.NULL.equals( defaultTargetValue.getTarget() ) if ( valueMappings.defaultTarget != null && !NULL.equals( valueMappings.defaultTarget.getTarget() )
&& !targetEnumConstants.contains( defaultTargetValue.getTarget() ) ) { && !targetEnumConstants.contains( valueMappings.defaultTarget.getTarget() ) ) {
ctx.getMessager().printMessage( method.getExecutable(), ctx.getMessager().printMessage( method.getExecutable(),
defaultTargetValue.getMirror(), valueMappings.defaultTarget.getMirror(),
defaultTargetValue.getTargetAnnotationValue(), valueMappings.defaultTarget.getTargetAnnotationValue(),
Message.VALUEMAPPING_NON_EXISTING_CONSTANT, Message.VALUEMAPPING_NON_EXISTING_CONSTANT,
defaultTargetValue.getTarget(), valueMappings.defaultTarget.getTarget(),
method.getReturnType() method.getReturnType()
); );
foundIncorrectMapping = true; foundIncorrectMapping = true;
} }
if ( nullTargetValue != null && MappingConstantsPrism.NULL.equals( nullTargetValue.getTarget() ) if ( valueMappings.nullTarget != null && NULL.equals( valueMappings.nullTarget.getTarget() )
&& !targetEnumConstants.contains( nullTargetValue.getTarget() ) ) { && !targetEnumConstants.contains( valueMappings.nullTarget.getTarget() ) ) {
ctx.getMessager().printMessage( method.getExecutable(), ctx.getMessager().printMessage( method.getExecutable(),
nullTargetValue.getMirror(), valueMappings.nullTarget.getMirror(),
nullTargetValue.getTargetAnnotationValue(), valueMappings.nullTarget.getTargetAnnotationValue(),
Message.VALUEMAPPING_NON_EXISTING_CONSTANT, Message.VALUEMAPPING_NON_EXISTING_CONSTANT,
nullTargetValue.getTarget(), valueMappings.nullTarget.getTarget(),
method.getReturnType() method.getReturnType()
); );
foundIncorrectMapping = true; foundIncorrectMapping = true;
@ -241,6 +319,47 @@ public class ValueMappingMethod extends MappingMethod {
} }
} }
private static class ValueMappings {
List<ValueMapping> regularValueMappings = new ArrayList<>();
ValueMapping defaultTarget = null;
String defaultTargetValue = null;
ValueMapping nullTarget = null;
String nullValueTarget = null;
boolean hasMapAnyUnmapped = false;
boolean hasMapAnyRemaining = false;
boolean hasDefaultValue = false;
ValueMappings(List<ValueMapping> valueMappings) {
for ( ValueMapping valueMapping : valueMappings ) {
if ( ANY_REMAINING.equals( valueMapping.getSource() ) ) {
defaultTarget = valueMapping;
defaultTargetValue = getValue( defaultTarget );
hasDefaultValue = true;
hasMapAnyRemaining = true;
}
else if ( ANY_UNMAPPED.equals( valueMapping.getSource() ) ) {
defaultTarget = valueMapping;
defaultTargetValue = getValue( defaultTarget );
hasDefaultValue = true;
hasMapAnyUnmapped = true;
}
else if ( NULL.equals( valueMapping.getSource() ) ) {
nullTarget = valueMapping;
nullValueTarget = getValue( nullTarget );
}
else {
regularValueMappings.add( valueMapping );
}
}
}
String getValue(ValueMapping valueMapping) {
return NULL.equals( valueMapping.getTarget() ) ? null : valueMapping.getTarget();
}
}
private ValueMappingMethod(Method method, List<MappingEntry> enumMappings, String nullTarget, String defaultTarget, private ValueMappingMethod(Method method, List<MappingEntry> enumMappings, String nullTarget, String defaultTarget,
boolean throwIllegalArgumentException, List<LifecycleCallbackMethodReference> beforeMappingMethods, boolean throwIllegalArgumentException, List<LifecycleCallbackMethodReference> beforeMappingMethods,
List<LifecycleCallbackMethodReference> afterMappingMethods) { List<LifecycleCallbackMethodReference> afterMappingMethods) {

View File

@ -220,6 +220,10 @@ public class Type extends ModelElement implements Comparable<Type> {
return typeElement != null && typeElement.getModifiers().contains( Modifier.ABSTRACT ); return typeElement != null && typeElement.getModifiers().contains( Modifier.ABSTRACT );
} }
public boolean isString() {
return String.class.getName().equals( getFullyQualifiedName() );
}
/** /**
* @return this type's enum constants in case it is an enum, an empty list otherwise. * @return this type's enum constants in case it is an enum, an empty list otherwise.
*/ */

View File

@ -5,6 +5,8 @@
*/ */
package org.mapstruct.ap.internal.model.source; package org.mapstruct.ap.internal.model.source;
import org.mapstruct.ap.internal.model.common.Type;
import static org.mapstruct.ap.internal.util.Collections.first; import static org.mapstruct.ap.internal.util.Collections.first;
/** /**
@ -18,18 +20,34 @@ public final class MappingMethodUtils {
private MappingMethodUtils() { private MappingMethodUtils() {
} }
/** /**
* Checks if the provided {@code method} is for enum mapping. A Method is an Enum Mapping method when the * Checks if the provided {@code method} is for enum mapping. A Method is an Enum Mapping method when the
* source parameter and result type are enum types. * <ol>
* <li>source parameter type and result type are enum types</li>
* <li>source parameter type is a String and result type is an enum type</li>
* <li>source parameter type is a enum type and result type is a String</li>
* </ol>
* *
* @param method to check * @param method to check
* *
* @return {@code true} if the method is for enum mapping, {@code false} otherwise * @return {@code true} if the method is for enum mapping, {@code false} otherwise
*/ */
public static boolean isEnumMapping(Method method) { public static boolean isEnumMapping(Method method) {
return method.getSourceParameters().size() == 1 if ( method.getSourceParameters().size() != 1 ) {
&& first( method.getSourceParameters() ).getType().isEnumType() return false;
&& method.getResultType().isEnumType(); }
Type source = first( method.getSourceParameters() ).getType();
Type result = method.getResultType();
if ( source.isEnumType() && result.isEnumType() ) {
return true;
}
if ( source.isString() && result.isEnumType() ) {
return true;
}
if ( source.isEnumType() && result.isString() ) {
return true;
}
return false;
} }
} }

View File

@ -7,17 +7,18 @@ package org.mapstruct.ap.internal.model.source;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import org.mapstruct.ap.internal.prism.MappingConstantsPrism;
import org.mapstruct.ap.internal.prism.ValueMappingPrism; import org.mapstruct.ap.internal.prism.ValueMappingPrism;
import org.mapstruct.ap.internal.prism.ValueMappingsPrism; import org.mapstruct.ap.internal.prism.ValueMappingsPrism;
import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Message;
import static org.mapstruct.ap.internal.prism.MappingConstantsPrism.ANY_REMAINING;
import static org.mapstruct.ap.internal.prism.MappingConstantsPrism.ANY_UNMAPPED;
/** /**
* Represents the mapping between one value constant and another. * Represents the mapping between one value constant and another.
* *
@ -51,8 +52,8 @@ public class ValueMapping {
mappingPrism.source() mappingPrism.source()
); );
} }
if ( MappingConstantsPrism.ANY_REMAINING.equals( mapping.source ) if ( ANY_REMAINING.equals( mapping.source )
|| MappingConstantsPrism.ANY_UNMAPPED.equals( mapping.source ) ) { || ANY_UNMAPPED.equals( mapping.source ) ) {
if ( anyFound ) { if ( anyFound ) {
messager.printMessage( messager.printMessage(
method, method,
@ -111,8 +112,7 @@ public class ValueMapping {
public ValueMapping inverse() { public ValueMapping inverse() {
ValueMapping result; ValueMapping result;
if ( !MappingConstantsPrism.ANY_REMAINING.equals( source ) if ( !(ANY_REMAINING.equals( source ) || ANY_UNMAPPED.equals( source ) ) ) {
|| !MappingConstantsPrism.ANY_UNMAPPED.equals( source ) ) {
result = new ValueMapping( result = new ValueMapping(
target, target,
source, source,

View File

@ -461,12 +461,12 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
return false; return false;
} }
if ( parameterType.isEnumType() && !resultType.isEnumType() ) { if ( parameterType.isEnumType() && !( resultType.isEnumType() || resultType.isString() ) ) {
messager.printMessage( method, Message.RETRIEVAL_ENUM_TO_NON_ENUM ); messager.printMessage( method, Message.RETRIEVAL_ENUM_TO_NON_ENUM );
return false; return false;
} }
if ( !parameterType.isEnumType() && resultType.isEnumType() ) { if ( !( parameterType.isEnumType() || parameterType.isString() ) && resultType.isEnumType() ) {
messager.printMessage( method, Message.RETRIEVAL_NON_ENUM_TO_ENUM ); messager.printMessage( method, Message.RETRIEVAL_NON_ENUM_TO_ENUM );
return false; return false;
} }

View File

@ -152,7 +152,9 @@ public enum Message {
VALUEMAPPING_CREATE_NOTE( "creating value mapping method implementation for %s.", Diagnostic.Kind.NOTE ), VALUEMAPPING_CREATE_NOTE( "creating value mapping method implementation for %s.", Diagnostic.Kind.NOTE ),
VALUEMAPPING_DUPLICATE_SOURCE( "Source value mapping: \"%s\" cannot be mapped more than once." ), VALUEMAPPING_DUPLICATE_SOURCE( "Source value mapping: \"%s\" cannot be mapped more than once." ),
VALUEMAPPING_ANY_AREADY_DEFINED( "Source = \"<ANY_REMAINING>\" or \"<ANY_UNMAPPED>\" can only be used once." ), VALUEMAPPING_ANY_AREADY_DEFINED( "Source = \"<ANY_REMAINING>\" or \"<ANY_UNMAPPED>\" can only be used once." ),
VALUE_MAPPING_UNMAPPED_SOURCES( "The following constants from the %s enum have no corresponding constant in the %s enum and must be be mapped via adding additional mappings: %s." ), VALUEMAPPING_UNMAPPED_SOURCES( "The following constants from the %s enum have no corresponding constant in the %s enum and must be be mapped via adding additional mappings: %s." ),
VALUEMAPPING_ANY_REMAINING_FOR_NON_ENUM( "Source = \"<ANY_REMAINING>\" can only be used on targets of type enum and not for %s." ),
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 ),
VALUEMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s." ); VALUEMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s." );
// CHECKSTYLE:ON // CHECKSTYLE:ON

View File

@ -1,44 +0,0 @@
<#--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.EnumMappingMethod" -->
@Override
public <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>) {
<#list beforeMappingReferencesWithoutMappingTarget as callback>
<@includeModel object=callback targetBeanName=resultName targetType=resultType/>
<#if !callback_has_next>
</#if>
</#list>
if ( ${sourceParameter.name} == null ) {
return null;
}
<@includeModel object=resultType/> ${resultName};
switch ( ${sourceParameter.name} ) {
<#list enumMappings as enumMapping>
case ${enumMapping.source}: ${resultName} = <@includeModel object=returnType/>.${enumMapping.target};
break;
</#list>
default: throw new IllegalArgumentException( "Unexpected enum constant: " + ${sourceParameter.name} );
}
<#list beforeMappingReferencesWithMappingTarget as callback>
<#if callback_index = 0>
</#if>
<@includeModel object=callback targetBeanName=resultName targetType=resultType/>
</#list>
<#list afterMappingReferences as callback>
<#if callback_index = 0>
</#if>
<@includeModel object=callback targetBeanName=resultName targetType=resultType/>
</#list>
return ${resultName};
}

View File

@ -15,17 +15,17 @@
</#if> </#if>
</#list> </#list>
if ( ${sourceParameter.name} == null ) { if ( ${sourceParameter.name} == null ) {
return <#if nullTarget??><@includeModel object=returnType/>.${nullTarget}<#else>null</#if>; return <@writeTarget target=nullTarget/>;
} }
<@includeModel object=resultType/> ${resultName}; <@includeModel object=resultType/> ${resultName};
switch ( ${sourceParameter.name} ) { switch ( ${sourceParameter.name} ) {
<#list valueMappings as valueMapping> <#list valueMappings as valueMapping>
case ${valueMapping.source}: ${resultName} = <#if valueMapping.target??><@includeModel object=returnType/>.${valueMapping.target}<#else>null</#if>; case <@writeSource source=valueMapping.source/>: ${resultName} = <@writeTarget target=valueMapping.target/>;
break; break;
</#list> </#list>
default: <#if throwIllegalArgumentException>throw new IllegalArgumentException( "Unexpected enum constant: " + ${sourceParameter.name} )<#else>${resultName} = <#if defaultTarget??><@includeModel object=returnType/>.${defaultTarget}<#else>null</#if></#if>; default: <#if throwIllegalArgumentException>throw new IllegalArgumentException( "Unexpected enum constant: " + ${sourceParameter.name} )<#else>${resultName} = <@writeTarget target=defaultTarget/></#if>;
} }
<#list beforeMappingReferencesWithMappingTarget as callback> <#list beforeMappingReferencesWithMappingTarget as callback>
<#if callback_index = 0> <#if callback_index = 0>
@ -40,5 +40,29 @@
<@includeModel object=callback targetBeanName=resultName targetType=resultType/> <@includeModel object=callback targetBeanName=resultName targetType=resultType/>
</#list> </#list>
<#if !(valueMappings.empty && throwIllegalArgumentException)>
return ${resultName}; return ${resultName};
</#if>
} }
<#macro writeSource source="">
<@compress single_line=true>
<#if sourceParameter.type.enumType>
${source}
<#elseif sourceParameter.type.string>
"${source}"
</#if>
</@compress>
</#macro>
<#macro writeTarget target="">
<@compress single_line=true>
<#if target?has_content>
<#if returnType.enumType>
<@includeModel object=returnType/>.${target}
<#elseif returnType.string>
"${target}"
</#if>
<#else>
null
</#if>
</@compress>
</#macro>

View File

@ -3,11 +3,13 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants; import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping; import org.mapstruct.ValueMapping;
import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
/** /**

View File

@ -3,7 +3,7 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -12,6 +12,8 @@ import javax.tools.Diagnostic.Kind;
import org.junit.Rule; 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.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
@ -214,7 +216,7 @@ public class EnumToEnumMappingTest {
diagnostics = { diagnostics = {
@Diagnostic(type = ErroneousOrderMapperMappingSameConstantTwice.class, @Diagnostic(type = ErroneousOrderMapperMappingSameConstantTwice.class,
kind = Kind.ERROR, kind = Kind.ERROR,
line = 25, line = 27,
messageRegExp = "Source value mapping: \"EXTRA\" cannot be mapped more than once\\.") messageRegExp = "Source value mapping: \"EXTRA\" cannot be mapped more than once\\.")
} }
) )
@ -228,11 +230,11 @@ public class EnumToEnumMappingTest {
diagnostics = { diagnostics = {
@Diagnostic(type = ErroneousOrderMapperUsingUnknownEnumConstants.class, @Diagnostic(type = ErroneousOrderMapperUsingUnknownEnumConstants.class,
kind = Kind.ERROR, kind = Kind.ERROR,
line = 24, line = 26,
messageRegExp = "Constant FOO doesn't exist in enum type org.mapstruct.ap.test.value.OrderType\\."), messageRegExp = "Constant FOO doesn't exist in enum type org.mapstruct.ap.test.value.OrderType\\."),
@Diagnostic(type = ErroneousOrderMapperUsingUnknownEnumConstants.class, @Diagnostic(type = ErroneousOrderMapperUsingUnknownEnumConstants.class,
kind = Kind.ERROR, kind = Kind.ERROR,
line = 25, line = 27,
messageRegExp = "Constant BAR doesn't exist in enum type org.mapstruct.ap.test.value." + messageRegExp = "Constant BAR doesn't exist in enum type org.mapstruct.ap.test.value." +
"ExternalOrderType\\.") "ExternalOrderType\\.")
} }
@ -247,7 +249,7 @@ public class EnumToEnumMappingTest {
diagnostics = { diagnostics = {
@Diagnostic(type = ErroneousOrderMapperNotMappingConstantWithoutMatchInTargetType.class, @Diagnostic(type = ErroneousOrderMapperNotMappingConstantWithoutMatchInTargetType.class,
kind = Kind.ERROR, kind = Kind.ERROR,
line = 21, line = 23,
messageRegExp = "The following constants from the source enum have no corresponding constant in the " + messageRegExp = "The following constants from the source enum have no corresponding constant in the " +
"target enum and must be be mapped via adding additional mappings: EXTRA, STANDARD, NORMAL") "target enum and must be be mapped via adding additional mappings: EXTRA, STANDARD, NORMAL")
} }
@ -262,7 +264,7 @@ public class EnumToEnumMappingTest {
diagnostics = { diagnostics = {
@Diagnostic(type = ErroneousOrderMapperDuplicateANY.class, @Diagnostic(type = ErroneousOrderMapperDuplicateANY.class,
kind = Kind.ERROR, kind = Kind.ERROR,
line = 26, line = 28,
messageRegExp = "Source = \"<ANY_REMAINING>\" or \"<ANY_UNMAPPED>\" can only be used once\\." ) messageRegExp = "Source = \"<ANY_REMAINING>\" or \"<ANY_UNMAPPED>\" can only be used once\\." )
} }
) )

View File

@ -3,11 +3,13 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping; import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings; import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
/** /**

View File

@ -3,11 +3,13 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping; import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings; import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
/** /**

View File

@ -3,9 +3,11 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
/** /**

View File

@ -3,11 +3,13 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping; import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings; import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
/** /**

View File

@ -3,8 +3,9 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import org.mapstruct.ap.test.value.ExternalOrderType;
/** /**
* @author Gunnar Morling * @author Gunnar Morling

View File

@ -3,8 +3,9 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import org.mapstruct.ap.test.value.OrderType;
/** /**
* @author Gunnar Morling * @author Gunnar Morling

View File

@ -3,12 +3,14 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping; import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings; import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
/** /**

View File

@ -3,7 +3,7 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
@ -12,6 +12,8 @@ import org.mapstruct.MappingConstants;
import org.mapstruct.Named; import org.mapstruct.Named;
import org.mapstruct.ValueMapping; import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings; import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
/** /**

View File

@ -0,0 +1,60 @@
/*
* 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.enum2string;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
@IssueKey( "1557" )
@WithClasses({ OrderType.class, OrderMapper.class })
@RunWith(AnnotationProcessorTestRunner.class)
public class EnumToStringMappingTest {
@Test
public void testTheNormalStuff() {
assertThat( OrderMapper.INSTANCE.mapNormal( null ) ).isNull();
assertThat( OrderMapper.INSTANCE.mapNormal( OrderType.EXTRA ) ).isEqualTo( "SPECIAL" );
assertThat( OrderMapper.INSTANCE.mapNormal( OrderType.STANDARD ) ).isEqualTo( "DEFAULT" );
assertThat( OrderMapper.INSTANCE.mapNormal( OrderType.NORMAL ) ).isEqualTo( "DEFAULT" );
assertThat( OrderMapper.INSTANCE.mapNormal( OrderType.B2B ) ).isEqualTo( "B2B" );
assertThat( OrderMapper.INSTANCE.mapNormal( OrderType.RETAIL ) ).isEqualTo( "RETAIL" );
}
@Test
public void testRemainingAndNull() {
assertThat( OrderMapper.INSTANCE.withAnyUnmappedAndNull( null ) ).isEqualTo( "DEFAULT" );
assertThat( OrderMapper.INSTANCE.withAnyUnmappedAndNull( OrderType.STANDARD ) ).isNull();
assertThat( OrderMapper.INSTANCE.withAnyUnmappedAndNull( OrderType.NORMAL ) ).isEqualTo( "SPECIAL" );
assertThat( OrderMapper.INSTANCE.withAnyUnmappedAndNull( OrderType.B2B ) ).isEqualTo( "SPECIAL" );
assertThat( OrderMapper.INSTANCE.withAnyUnmappedAndNull( OrderType.RETAIL ) ).isEqualTo( "SPECIAL" );
assertThat( OrderMapper.INSTANCE.withAnyUnmappedAndNull( OrderType.EXTRA ) ).isEqualTo( "SPECIAL" );
}
@Test
@WithClasses(ErroneousOrderMapperUsingAnyRemaining.class)
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = ErroneousOrderMapperUsingAnyRemaining.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 26,
messageRegExp = "\"<ANY_REMAINING>\" can only be used on targets of type enum and not for " +
"java\\.lang\\.String\\.")
}
)
public void shouldRaiseErrorWhenUsingAnyRemaining() {
}
}

View File

@ -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.value.enum2string;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.factory.Mappers;
/**
* @author Sjaak Derksen
*/
@Mapper
public interface ErroneousOrderMapperUsingAnyRemaining {
ErroneousOrderMapperUsingAnyRemaining INSTANCE = Mappers.getMapper( ErroneousOrderMapperUsingAnyRemaining.class );
@ValueMappings({
@ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
@ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),
@ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" )
})
String mapWithRemainingAndNull(OrderType orderType);
}

View File

@ -0,0 +1,36 @@
/*
* 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.enum2string;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.factory.Mappers;
/**
* @author Sjaak Derksen
*/
@Mapper
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class );
@ValueMappings({
@ValueMapping(source = "EXTRA", target = "SPECIAL"),
@ValueMapping(source = "STANDARD", target = "DEFAULT"),
@ValueMapping(source = "NORMAL", target = "DEFAULT")
})
String mapNormal(OrderType orderType);
@ValueMappings({
@ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
@ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),
@ValueMapping( source = MappingConstants.ANY_UNMAPPED, target = "SPECIAL" )
})
String withAnyUnmappedAndNull(OrderType orderType);
}

View File

@ -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.value.string2enum;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.factory.Mappers;
/**
* @author Sjaak Derksen
*/
@Mapper
public interface ErroneousOrderMapperUsingNoAnyRemainingAndNoAnyUnmapped {
ErroneousOrderMapperUsingNoAnyRemainingAndNoAnyUnmapped INSTANCE =
Mappers.getMapper( ErroneousOrderMapperUsingNoAnyRemainingAndNoAnyUnmapped.class );
@ValueMappings({
@ValueMapping( source = MappingConstants.NULL, target = "STANDARD" ),
@ValueMapping( source = "STANDARD", target = MappingConstants.NULL )
})
OrderType map(String orderType);
}

View File

@ -0,0 +1,36 @@
/*
* 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.string2enum;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.factory.Mappers;
/**
* @author Sjaak Derksen
*/
@Mapper
public interface OrderMapper {
OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class );
@ValueMappings({
@ValueMapping(source = "SPECIAL", target = "EXTRA" ),
@ValueMapping(source = "DEFAULT", target = "STANDARD"),
@ValueMapping( source = MappingConstants.ANY_REMAINING, target = "RETAIL" )
})
OrderType mapNormal(String orderType);
@ValueMappings({
@ValueMapping( source = MappingConstants.NULL, target = "STANDARD" ),
@ValueMapping( source = "DEFAULT", target = MappingConstants.NULL ),
@ValueMapping( source = MappingConstants.ANY_UNMAPPED, target = "RETAIL" )
})
OrderType mapWithAnyUnmapped(String orderType);
}

View File

@ -0,0 +1,59 @@
/*
* 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.string2enum;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
@IssueKey( "1557" )
@WithClasses({ OrderType.class, OrderMapper.class })
@RunWith(AnnotationProcessorTestRunner.class)
public class StringToEnumMappingTest {
@Test
public void testTheNormalStuff() {
assertThat( OrderMapper.INSTANCE.mapNormal( null ) ).isNull();
assertThat( OrderMapper.INSTANCE.mapNormal( "SPECIAL" ) ).isEqualTo( OrderType.EXTRA );
assertThat( OrderMapper.INSTANCE.mapNormal( "DEFAULT" ) ).isEqualTo( OrderType.STANDARD );
assertThat( OrderMapper.INSTANCE.mapNormal( "RETAIL" ) ).isEqualTo( OrderType.RETAIL );
assertThat( OrderMapper.INSTANCE.mapNormal( "B2B" ) ).isEqualTo( OrderType.B2B );
assertThat( OrderMapper.INSTANCE.mapNormal( "STANDARD" ) ).isEqualTo( OrderType.STANDARD );
assertThat( OrderMapper.INSTANCE.mapNormal( "NORMAL" ) ).isEqualTo( OrderType.NORMAL );
assertThat( OrderMapper.INSTANCE.mapNormal( "blah" ) ).isEqualTo( OrderType.RETAIL );
}
@Test
public void testRemainingAndNull() {
assertThat( OrderMapper.INSTANCE.mapWithAnyUnmapped( null ) ).isEqualTo( OrderType.STANDARD );
assertThat( OrderMapper.INSTANCE.mapWithAnyUnmapped( "DEFAULT" ) ).isNull();
assertThat( OrderMapper.INSTANCE.mapWithAnyUnmapped( "BLAH" ) ).isEqualTo( OrderType.RETAIL );
}
@Test
@WithClasses(ErroneousOrderMapperUsingNoAnyRemainingAndNoAnyUnmapped.class)
@ExpectedCompilationOutcome(
value = CompilationResult.SUCCEEDED,
diagnostics = {
@Diagnostic(type = ErroneousOrderMapperUsingNoAnyRemainingAndNoAnyUnmapped.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 28,
messageRegExp = "Source = \"<ANY_REMAINING>\" or \"<ANY_UNMAPPED>\" is advisable for mapping of " +
"type String to an enum type" )
}
)
public void shouldRaiseErrorWhenUsingAnyRemaining() {
}
}

View File

@ -3,9 +3,11 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import javax.annotation.Generated; import javax.annotation.Generated;
import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
@Generated( @Generated(
value = "org.mapstruct.ap.MappingProcessor", value = "org.mapstruct.ap.MappingProcessor",

View File

@ -3,9 +3,11 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import javax.annotation.Generated; import javax.annotation.Generated;
import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
@Generated( @Generated(
value = "org.mapstruct.ap.MappingProcessor", value = "org.mapstruct.ap.MappingProcessor",

View File

@ -3,9 +3,11 @@
* *
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/ */
package org.mapstruct.ap.test.value; package org.mapstruct.ap.test.value.enum2enum;
import javax.annotation.Generated; import javax.annotation.Generated;
import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
@Generated( @Generated(
value = "org.mapstruct.ap.MappingProcessor", value = "org.mapstruct.ap.MappingProcessor",