Add EnumTransformationStrategy SPI (#2089)

Add a new custom EnumTransformationStrategy SPI which can be used for providing custom way of name based mapping for enums.

Add 4 out of the box transformation strategies:

* prefix - add a prefix to the name based enum mapping
* stripPrefix - remove a prefix from the name based enum mapping
* suffix - add a suffix to the name based enum mapping
* stripSuffix - remove a suffix from the name based enum mapping

This can be achieved by using the new `EnumMapping`

e.g.

Add suffix `_TYPE` to all enums:

`@EnumMapping(nameTransformationStrategy = "suffix", configuration = "_TYPE")`

With this it would be possible to achieve what is needed in #796, #1220, #1789.
This commit is contained in:
Filip Hrisafov 2020-05-18 07:17:30 +02:00 committed by GitHub
parent b5fe96c9da
commit 7b5a54971f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1115 additions and 14 deletions

View File

@ -0,0 +1,123 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Configured the mapping between two value types.
* <p><strong>Example:</strong> Using a suffix for enums</p>
* <pre><code class='java'>
* public enum CheeseType {
* BRIE,
* ROQUEFORT
* }
*
* public enum CheeseTypeSuffixed {
* BRIE_TYPE,
* ROQUEFORT_TYPE
* }
*
* &#64;Mapper
* public interface CheeseMapper {
*
* &#64;EnumMapping(nameTransformationStrategy = "suffix", configuration = "_TYPE")
* CheeseTypeSuffixed map(Cheese cheese);
*
* &#64;InheritInverseConfiguration
* Cheese map(CheeseTypeSuffixed cheese);
*
* }
* </code></pre>
* <pre><code class='java'>
* // generates
* public class CheeseMapperImpl implements CheeseMapper {
*
* &#64;Override
* public CheeseTypeSuffixed map(Cheese cheese) {
* if ( cheese == null ) {
* return null;
* }
*
* CheeseTypeSuffixed cheeseTypeSuffixed;
*
* switch ( cheese ) {
* case BRIE:
* cheeseTypeSuffixed = CheeseTypeSuffixed.BRIE_TYPE;
* break;
* case ROQUEFORT:
* cheeseTypeSuffixed = CheeseTypeSuffixed.ROQUEFORT_TYPE;
* break;
* default:
* throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
* }
*
* return cheeseTypeSuffixed;
* }
*
* &#64;Override
* public Cheese map(CheeseTypeSuffixed cheese) {
* if ( cheese == null ) {
* return null;
* }
*
* CheeseType cheeseType;
*
* switch ( cheese ) {
* case BRIE_TYPE:
* cheeseType = CheeseType.BRIE;
* break;
* case ROQUEFORT_TYPE:
* cheeseType = CheeseType.ROQUEFORT;
* break;
* default:
* throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
* }
*
* return cheeseType;
* }
* }
* </code></pre>
*
* @author Filip Hrisafov
* @since 1.4
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface EnumMapping {
/**
* Specifies the name transformation strategy that should be used for implicit mapping between enums.
* Known strategies are:
* <ul>
* <li>{@link MappingConstants#SUFFIX_TRANSFORMATION} - applies the given {@link #configuration()} as a
* suffix to the source enum</li>
* <li>{@link MappingConstants#STRIP_SUFFIX_TRANSFORMATION} - strips the the given {@link #configuration()}
* from the end of the source enum</li>
* <li>{@link MappingConstants#PREFIX_TRANSFORMATION} - applies the given {@link #configuration()} as a
* prefix to the source enum</li>
* <li>{@link MappingConstants#STRIP_PREFIX_TRANSFORMATION} - strips the given {@link #configuration()} from
* the start of the source enum</li>
* </ul>
*
* It is possible to use custom name transformation strategies by implementing the {@code
* EnumTransformationStrategy} SPI.
*
* @return the name transformation strategy
*/
String nameTransformationStrategy();
/**
* The configuration that should be passed on the appropriate name transformation strategy.
* e.g. a suffix that should be applied to the source enum when doing name based mapping.
*
* @return the configuration to use
*/
String configuration();
}

View File

@ -36,4 +36,34 @@ public final class MappingConstants {
*/
public static final String ANY_UNMAPPED = "<ANY_UNMAPPED>";
/**
* In an {@link EnumMapping} this represent the enum transformation strategy that adds a suffix to the source enum.
*
* @since 1.4
*/
public static final String SUFFIX_TRANSFORMATION = "suffix";
/**
* In an {@link EnumMapping} this represent the enum transformation strategy that strips a suffix from the source
* enum.
*
* @since 1.4
*/
public static final String STRIP_SUFFIX_TRANSFORMATION = "stripSuffix";
/**
* In an {@link EnumMapping} this represent the enum transformation strategy that adds a prefix to the source enum.
*
* @since 1.4
*/
public static final String PREFIX_TRANSFORMATION = "prefix";
/**
* In an {@link EnumMapping} this represent the enum transformation strategy that strips a prefix from the source
* enum.
*
* @since 1.4
*/
public static final String STRIP_PREFIX_TRANSFORMATION = "stripPrefix";
}

View File

@ -198,3 +198,19 @@ A nice example is to provide support for a custom builder strategy.
include::{processor-ap-main}/spi/NoOpBuilderProvider.java[tag=documentation]
----
====
[[custom-enum-transformation-strategy]]
=== Custom Enum Transformation Strategy
MapStruct offers the possibility to other transformations strategies by implementing `EnumTransformationStrategy` via the Service Provider Interface (SPI).
A nice example is to provide support for a custom transformation strategy.
.Custom Enum Transformation Strategy which lower-cases the value and applies a suffix
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/value/nametransformation/CustomEnumTransformationStrategy.java[tag=documentation]
----
====

View File

@ -159,3 +159,106 @@ MapStruct supports enum to a String mapping along the same lines as is described
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.
=== Custom name transformation
When no `@ValueMapping`(s) are defined then each constant from the source enum is mapped to a constant with the same name in the target enum type.
However, there are cases where the source enum needs to be transformed before doing the mapping.
E.g. a suffix needs to be applied to map from the source into the target enum.
.Enum types
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public enum CheeseType {
BRIE,
ROQUEFORT
}
public enum CheeseTypeSuffixed {
BRIE_TYPE,
ROQUEFORT_TYPE
}
----
====
.Enum mapping method with custom name transformation strategy
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CheeseMapper {
CheeseMapper INSTANCE = Mappers.getMapper( CheeseMapper.class );
@EnumMapping(nameTransformationStrategy = "suffix", configuration = "_TYPE")
CheeseTypeSuffixed map(CheeseType cheese);
@InheritInverseConfiguration
CheeseType map(CheeseTypeSuffix cheese);
}
----
====
.Enum mapping method with custom name transformation strategy result
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class CheeseSuffixMapperImpl implements CheeseSuffixMapper {
@Override
public CheeseTypeSuffixed map(CheeseType cheese) {
if ( cheese == null ) {
return null;
}
CheeseTypeSuffixed cheeseTypeSuffixed;
switch ( cheese ) {
case BRIE: cheeseTypeSuffixed = CheeseTypeSuffixed.BRIE_TYPE;
break;
case ROQUEFORT: cheeseTypeSuffixed = CheeseTypeSuffixed.ROQUEFORT_TYPE;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return cheeseTypeSuffixed;
}
@Override
public CheeseType map(CheeseTypeSuffixed cheese) {
if ( cheese == null ) {
return null;
}
CheeseType cheeseType;
switch ( cheese ) {
case BRIE_TYPE: cheeseType = CheeseType.BRIE;
break;
case ROQUEFORT_TYPE: cheeseType = CheeseType.ROQUEFORT;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return cheeseType;
}
}
----
====
MapStruct provides the following out of the box enum name transformation strategies:
* _suffix_ - Applies a suffix on the source enum
* _stripSuffix_ - Strips a suffix from the source enum
* _prefix_ - Applies a prefix on the source enum
* _stripPrefix_ - Strips a prefix from the source enum
It is also possible to register custom strategies.
For more information on how to do that have a look at <<custom-enum-transformation-strategy>>

View File

@ -14,6 +14,7 @@ import org.mapstruct.BeforeMapping;
import org.mapstruct.Builder;
import org.mapstruct.Context;
import org.mapstruct.DecoratedWith;
import org.mapstruct.EnumMapping;
import org.mapstruct.InheritConfiguration;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.IterableMapping;
@ -43,6 +44,7 @@ import org.mapstruct.tools.gem.GemDefinition;
@GemDefinition(Mappings.class)
@GemDefinition(IterableMapping.class)
@GemDefinition(BeanMapping.class)
@GemDefinition(EnumMapping.class)
@GemDefinition(MapMapping.class)
@GemDefinition(TargetType.class)
@GemDefinition(MappingTarget.class)

View File

@ -20,4 +20,12 @@ public final class MappingConstantsGem {
public static final String ANY_REMAINING = "<ANY_REMAINING>";
public static final String ANY_UNMAPPED = "<ANY_UNMAPPED>";
public static final String SUFFIX_TRANSFORMATION = "suffix";
public static final String STRIP_SUFFIX_TRANSFORMATION = "stripSuffix";
public static final String PREFIX_TRANSFORMATION = "prefix";
public static final String STRIP_PREFIX_TRANSFORMATION = "stripPrefix";
}

View File

@ -86,6 +86,7 @@ class AbstractBaseBuilder<B extends AbstractBaseBuilder<B>> {
forgedMappingMethod = new ValueMappingMethod.Builder()
.method( forgedMethod )
.valueMappings( forgedMethod.getOptions().getValueMappings() )
.enumMapping( forgedMethod.getOptions().getEnumMappingOptions() )
.mappingContext( ctx )
.build();
}

View File

@ -30,6 +30,7 @@ import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.util.AccessorNamingUtils;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Services;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
import org.mapstruct.ap.spi.MappingExclusionProvider;
/**
@ -104,6 +105,7 @@ public class MappingBuilderContext {
private final Types typeUtils;
private final FormattingMessager messager;
private final AccessorNamingUtils accessorNaming;
private final Map<String, EnumTransformationStrategy> enumTransformationStrategies;
private final Options options;
private final TypeElement mapperTypeElement;
private final List<SourceMethod> sourceModel;
@ -113,11 +115,13 @@ public class MappingBuilderContext {
private final Map<ForgedMethod, ForgedMethod> forgedMethodsUnderCreation =
new HashMap<>();
//CHECKSTYLE:OFF
public MappingBuilderContext(TypeFactory typeFactory,
Elements elementUtils,
Types typeUtils,
FormattingMessager messager,
AccessorNamingUtils accessorNaming,
Map<String, EnumTransformationStrategy> enumTransformationStrategies,
Options options,
MappingResolver mappingResolver,
TypeElement mapper,
@ -128,12 +132,14 @@ public class MappingBuilderContext {
this.typeUtils = typeUtils;
this.messager = messager;
this.accessorNaming = accessorNaming;
this.enumTransformationStrategies = enumTransformationStrategies;
this.options = options;
this.mappingResolver = mappingResolver;
this.mapperTypeElement = mapper;
this.sourceModel = sourceModel;
this.mapperReferences = mapperReferences;
}
//CHECKSTYLE:ON
/**
* Returns a map which is used to track which forged methods are under creation.
@ -180,6 +186,10 @@ public class MappingBuilderContext {
return accessorNaming;
}
public Map<String, EnumTransformationStrategy> getEnumTransformationStrategies() {
return enumTransformationStrategies;
}
public Options getOptions() {
return options;
}

View File

@ -7,19 +7,23 @@ package org.mapstruct.ap.internal.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.gem.BeanMappingGem;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.EnumMappingOptions;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.ValueMappingOptions;
import org.mapstruct.ap.internal.gem.BeanMappingGem;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
import static org.mapstruct.ap.internal.gem.MappingConstantsGem.ANY_REMAINING;
import static org.mapstruct.ap.internal.gem.MappingConstantsGem.ANY_UNMAPPED;
@ -45,6 +49,8 @@ public class ValueMappingMethod extends MappingMethod {
private Method method;
private MappingBuilderContext ctx;
private ValueMappings valueMappings;
private EnumMappingOptions enumMapping;
private EnumTransformationStrategyInvoker enumTransformationInvoker;
public Builder mappingContext(MappingBuilderContext mappingContext) {
this.ctx = mappingContext;
@ -61,8 +67,19 @@ public class ValueMappingMethod extends MappingMethod {
return this;
}
public Builder enumMapping(EnumMappingOptions enumMapping) {
this.enumMapping = enumMapping;
return this;
}
public ValueMappingMethod build() {
if ( !enumMapping.isValid() ) {
return null;
}
initializeEnumTransformationStrategy();
// initialize all relevant parameters
List<MappingEntry> mappingEntries = new ArrayList<>();
@ -99,6 +116,23 @@ public class ValueMappingMethod extends MappingMethod {
);
}
private void initializeEnumTransformationStrategy() {
if ( !enumMapping.hasAnnotation() ) {
enumTransformationInvoker = EnumTransformationStrategyInvoker.DEFAULT;
}
else {
Map<String, EnumTransformationStrategy> enumTransformationStrategies =
ctx.getEnumTransformationStrategies();
String nameTransformationStrategy = enumMapping.getNameTransformationStrategy();
if ( enumTransformationStrategies.containsKey( nameTransformationStrategy ) ) {
enumTransformationInvoker = new EnumTransformationStrategyInvoker( enumTransformationStrategies.get(
nameTransformationStrategy ), enumMapping.getNameTransformationConfiguration() );
}
}
}
private List<MappingEntry> enumToEnumMapping(Method method, Type sourceType, Type targetType ) {
List<MappingEntry> mappings = new ArrayList<>();
@ -120,11 +154,36 @@ public class ValueMappingMethod extends MappingMethod {
// add mappings based on name
if ( !valueMappings.hasMapAnyUnmapped ) {
// get all target constants
List<String> targetConstants = method.getReturnType().getEnumConstants();
// We store the target constants in a map in order to support inherited inverse mapping
// When using a nameTransformationStrategy the transformation should be done on the target enum
// instead of the source enum
Map<String, String> targetConstants = new LinkedHashMap<>();
boolean enumMappingInverse = enumMapping.isInverse();
for ( String targetEnumConstant : method.getReturnType().getEnumConstants() ) {
if ( enumMappingInverse ) {
// If the mapping is inverse we have to change the target enum constant
targetConstants.put(
enumTransformationInvoker.transform( targetEnumConstant ),
targetEnumConstant
);
}
else {
targetConstants.put( targetEnumConstant, targetEnumConstant );
}
}
for ( String sourceConstant : new ArrayList<>( unmappedSourceConstants ) ) {
if ( targetConstants.contains( sourceConstant ) ) {
mappings.add( new MappingEntry( sourceConstant, sourceConstant ) );
String targetConstant;
if ( !enumMappingInverse ) {
targetConstant = enumTransformationInvoker.transform( sourceConstant );
}
else {
targetConstant = sourceConstant;
}
if ( targetConstants.containsKey( targetConstant ) ) {
mappings.add( new MappingEntry( sourceConstant, targetConstants.get( targetConstant ) ) );
unmappedSourceConstants.remove( sourceConstant );
}
}
@ -175,7 +234,8 @@ public class ValueMappingMethod extends MappingMethod {
// all remaining constants are mapped
for ( String sourceConstant : unmappedSourceConstants ) {
mappings.add( new MappingEntry( sourceConstant, sourceConstant ) );
String targetConstant = enumTransformationInvoker.transform( sourceConstant );
mappings.add( new MappingEntry( sourceConstant, targetConstant ) );
}
}
return mappings;
@ -204,7 +264,8 @@ public class ValueMappingMethod extends MappingMethod {
// all remaining constants are mapped
for ( String sourceConstant : unmappedSourceConstants ) {
mappings.add( new MappingEntry( sourceConstant, sourceConstant ) );
String stringConstant = enumTransformationInvoker.transform( sourceConstant );
mappings.add( new MappingEntry( stringConstant, sourceConstant ) );
}
}
return mappings;
@ -229,7 +290,8 @@ public class ValueMappingMethod extends MappingMethod {
for ( ValueMappingOptions mappedConstant : valueMappings.regularValueMappings ) {
if ( !sourceEnumConstants.contains( mappedConstant.getSource() ) ) {
ctx.getMessager().printMessage( method.getExecutable(),
ctx.getMessager().printMessage(
method.getExecutable(),
mappedConstant.getMirror(),
mappedConstant.getSourceAnnotationValue(),
Message.VALUEMAPPING_NON_EXISTING_CONSTANT,
@ -279,7 +341,8 @@ public class ValueMappingMethod extends MappingMethod {
for ( ValueMappingOptions mappedConstant : valueMappings.regularValueMappings ) {
if ( !NULL.equals( mappedConstant.getTarget() )
&& !targetEnumConstants.contains( mappedConstant.getTarget() ) ) {
ctx.getMessager().printMessage( method.getExecutable(),
ctx.getMessager().printMessage(
method.getExecutable(),
mappedConstant.getMirror(),
mappedConstant.getTargetAnnotationValue(),
Message.VALUEMAPPING_NON_EXISTING_CONSTANT,
@ -292,7 +355,8 @@ public class ValueMappingMethod extends MappingMethod {
if ( valueMappings.defaultTarget != null && !NULL.equals( valueMappings.defaultTarget.getTarget() )
&& !targetEnumConstants.contains( valueMappings.defaultTarget.getTarget() ) ) {
ctx.getMessager().printMessage( method.getExecutable(),
ctx.getMessager().printMessage(
method.getExecutable(),
valueMappings.defaultTarget.getMirror(),
valueMappings.defaultTarget.getTargetAnnotationValue(),
Message.VALUEMAPPING_NON_EXISTING_CONSTANT,
@ -318,6 +382,31 @@ public class ValueMappingMethod extends MappingMethod {
}
}
private static class EnumTransformationStrategyInvoker {
private static final EnumTransformationStrategyInvoker DEFAULT = new EnumTransformationStrategyInvoker(
null,
null
);
private final EnumTransformationStrategy transformationStrategy;
private final String configuration;
private EnumTransformationStrategyInvoker(
EnumTransformationStrategy transformationStrategy, String configuration) {
this.transformationStrategy = transformationStrategy;
this.configuration = configuration;
}
private String transform(String source) {
if ( transformationStrategy == null ) {
return source;
}
return transformationStrategy.transform( source, configuration );
}
}
private static class ValueMappings {
List<ValueMappingOptions> regularValueMappings = new ArrayList<>();

View File

@ -0,0 +1,99 @@
/*
* 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.internal.model.source;
import java.util.Map;
import javax.lang.model.element.ExecutableElement;
import org.mapstruct.ap.internal.gem.EnumMappingGem;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
import static org.mapstruct.ap.internal.util.Message.ENUMMAPPING_INCORRECT_TRANSFORMATION_STRATEGY;
/**
* @author Filip Hrisafov
*/
public class EnumMappingOptions extends DelegatingOptions {
private final EnumMappingGem enumMapping;
private final boolean inverse;
private final boolean valid;
private EnumMappingOptions(EnumMappingGem enumMapping, boolean inverse, boolean valid, DelegatingOptions next) {
super( next );
this.enumMapping = enumMapping;
this.inverse = inverse;
this.valid = valid;
}
@Override
public boolean hasAnnotation() {
return enumMapping != null;
}
public boolean isValid() {
return valid;
}
public String getNameTransformationStrategy() {
return enumMapping.nameTransformationStrategy().get();
}
public String getNameTransformationConfiguration() {
return enumMapping.configuration().get();
}
public boolean isInverse() {
return inverse;
}
public EnumMappingOptions inverse() {
return new EnumMappingOptions( enumMapping, true, valid, next() );
}
public static EnumMappingOptions getInstanceOn(ExecutableElement method, MapperOptions mapperOptions,
Map<String, EnumTransformationStrategy> enumTransformationStrategies, FormattingMessager messager) {
EnumMappingGem enumMapping = EnumMappingGem.instanceOn( method );
if ( enumMapping == null ) {
return new EnumMappingOptions( null, false, true, mapperOptions );
}
else if ( !isConsistent( enumMapping, method, enumTransformationStrategies, messager ) ) {
return new EnumMappingOptions( null, false, false, mapperOptions );
}
return new EnumMappingOptions(
enumMapping,
false,
true,
mapperOptions
);
}
private static boolean isConsistent(EnumMappingGem gem, ExecutableElement method,
Map<String, EnumTransformationStrategy> enumTransformationStrategies, FormattingMessager messager) {
String strategy = gem.nameTransformationStrategy().getValue();
if ( !enumTransformationStrategies.containsKey( strategy ) ) {
String registeredStrategies = Strings.join( enumTransformationStrategies.keySet(), ", " );
messager.printMessage(
method,
gem.mirror(),
gem.nameTransformationStrategy().getAnnotationValue(),
ENUMMAPPING_INCORRECT_TRANSFORMATION_STRATEGY,
strategy,
registeredStrategies
);
return false;
}
return true;
}
}

View File

@ -31,6 +31,7 @@ public class MappingMethodOptions {
null,
null,
null,
null,
Collections.emptyList()
);
@ -39,18 +40,21 @@ public class MappingMethodOptions {
private IterableMappingOptions iterableMapping;
private MapMappingOptions mapMapping;
private BeanMappingOptions beanMapping;
private EnumMappingOptions enumMappingOptions;
private List<ValueMappingOptions> valueMappings;
private boolean fullyInitialized;
public MappingMethodOptions(MapperOptions mapper, Set<MappingOptions> mappings,
IterableMappingOptions iterableMapping,
MapMappingOptions mapMapping, BeanMappingOptions beanMapping,
EnumMappingOptions enumMappingOptions,
List<ValueMappingOptions> valueMappings) {
this.mapper = mapper;
this.mappings = mappings;
this.iterableMapping = iterableMapping;
this.mapMapping = mapMapping;
this.beanMapping = beanMapping;
this.enumMappingOptions = enumMappingOptions;
this.valueMappings = valueMappings;
}
@ -83,6 +87,10 @@ public class MappingMethodOptions {
return beanMapping;
}
public EnumMappingOptions getEnumMappingOptions() {
return enumMappingOptions;
}
public List<ValueMappingOptions> getValueMappings() {
return valueMappings;
}
@ -99,6 +107,10 @@ public class MappingMethodOptions {
this.beanMapping = beanMapping;
}
public void setEnumMappingOptions(EnumMappingOptions enumMappingOptions) {
this.enumMappingOptions = enumMappingOptions;
}
public void setValueMappings(List<ValueMappingOptions> valueMappings) {
this.valueMappings = valueMappings;
}
@ -141,6 +153,17 @@ public class MappingMethodOptions {
setBeanMapping( BeanMappingOptions.forInheritance( templateOptions.getBeanMapping( ) ) );
}
if ( !getEnumMappingOptions().hasAnnotation() && templateOptions.getEnumMappingOptions().hasAnnotation() ) {
EnumMappingOptions newEnumMappingOptions;
if ( isInverse ) {
newEnumMappingOptions = templateOptions.getEnumMappingOptions().inverse();
}
else {
newEnumMappingOptions = templateOptions.getEnumMappingOptions();
}
setEnumMappingOptions( newEnumMappingOptions );
}
if ( getValueMappings() == null ) {
if ( templateOptions.getValueMappings() != null ) {
// there were no mappings, so the inherited mappings are the new ones

View File

@ -84,6 +84,7 @@ public class SourceMethod implements Method {
private MapperOptions mapper = null;
private List<SourceMethod> prototypeMethods = Collections.emptyList();
private List<ValueMappingOptions> valueMappings;
private EnumMappingOptions enumMappingOptions;
private ParameterProvidedMethods contextProvidedMethods;
public Builder setDeclaringMapper(Type declaringMapper) {
@ -136,6 +137,11 @@ public class SourceMethod implements Method {
return this;
}
public Builder setEnumMappingOptions(EnumMappingOptions enumMappingOptions) {
this.enumMappingOptions = enumMappingOptions;
return this;
}
public Builder setTypeUtils(Types typeUtils) {
this.typeUtils = typeUtils;
return this;
@ -172,8 +178,15 @@ public class SourceMethod implements Method {
mappings = Collections.emptySet();
}
MappingMethodOptions mappingMethodOptions =
new MappingMethodOptions( mapper, mappings, iterableMapping, mapMapping, beanMapping, valueMappings );
MappingMethodOptions mappingMethodOptions = new MappingMethodOptions(
mapper,
mappings,
iterableMapping,
mapMapping,
beanMapping,
enumMappingOptions,
valueMappings
);
return new SourceMethod( this, mappingMethodOptions );
}

View File

@ -26,6 +26,7 @@ import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.RoundContext;
import org.mapstruct.ap.internal.util.workarounds.TypesDecorator;
import org.mapstruct.ap.internal.version.VersionInformation;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
/**
* Default implementation of the processor context.
@ -41,6 +42,7 @@ public class DefaultModelElementProcessorContext implements ProcessorContext {
private final VersionInformation versionInformation;
private final Types delegatingTypes;
private final AccessorNamingUtils accessorNaming;
private final RoundContext roundContext;
public DefaultModelElementProcessorContext(ProcessingEnvironment processingEnvironment, Options options,
RoundContext roundContext, Map<String, String> notToBeImported) {
@ -50,6 +52,7 @@ public class DefaultModelElementProcessorContext implements ProcessorContext {
this.accessorNaming = roundContext.getAnnotationProcessorContext().getAccessorNaming();
this.versionInformation = DefaultVersionInformation.fromProcessingEnvironment( processingEnvironment );
this.delegatingTypes = new TypesDecorator( processingEnvironment, versionInformation );
this.roundContext = roundContext;
this.typeFactory = new TypeFactory(
processingEnvironment.getElementUtils(),
delegatingTypes,
@ -90,6 +93,11 @@ public class DefaultModelElementProcessorContext implements ProcessorContext {
return accessorNaming;
}
@Override
public Map<String, EnumTransformationStrategy> getEnumTransformationStrategies() {
return roundContext.getAnnotationProcessorContext().getEnumTransformationStrategies();
}
@Override
public Options getOptions() {
return options;

View File

@ -101,6 +101,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
typeUtils,
messager,
accessorNaming,
context.getEnumTransformationStrategies(),
options,
new MappingResolverImpl(
messager,
@ -336,9 +337,13 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
.mappingContext( mappingContext )
.method( method )
.valueMappings( mappingOptions.getValueMappings() )
.enumMapping( mappingOptions.getEnumMappingOptions() )
.build();
if ( valueMappingMethod != null ) {
mappingMethods.add( valueMappingMethod );
}
}
else if ( method.isRemovedEnumMapping() ) {
messager.printMessage(
method.getExecutable(),

View File

@ -10,6 +10,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
@ -27,6 +28,7 @@ import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.BeanMappingOptions;
import org.mapstruct.ap.internal.model.source.EnumMappingOptions;
import org.mapstruct.ap.internal.model.source.IterableMappingOptions;
import org.mapstruct.ap.internal.model.source.MapMappingOptions;
import org.mapstruct.ap.internal.model.source.MapperOptions;
@ -47,6 +49,7 @@ import org.mapstruct.ap.internal.util.AnnotationProcessingException;
import org.mapstruct.ap.internal.util.Executables;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
import static org.mapstruct.ap.internal.util.Executables.getAllEnclosedExecutableElements;
@ -68,6 +71,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
private FormattingMessager messager;
private TypeFactory typeFactory;
private AccessorNamingUtils accessorNaming;
private Map<String, EnumTransformationStrategy> enumTransformationStrategies;
private Types typeUtils;
private Elements elementUtils;
@ -78,6 +82,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
this.accessorNaming = context.getAccessorNaming();
this.typeUtils = context.getTypeUtils();
this.elementUtils = context.getElementUtils();
this.enumTransformationStrategies = context.getEnumTransformationStrategies();
this.messager.note( 0, Message.PROCESSING_NOTE, mapperTypeElement );
@ -273,6 +278,13 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
typeUtils
);
EnumMappingOptions enumMappingOptions = EnumMappingOptions.getInstanceOn(
method,
mapperOptions,
enumTransformationStrategies,
messager
);
return new SourceMethod.Builder()
.setExecutable( method )
.setParameters( parameters )
@ -284,6 +296,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
.setIterableMappingOptions( iterableMappingOptions )
.setMapMappingOptions( mapMappingOptions )
.setValueMappingOptionss( getValueMappings( method ) )
.setEnumMappingOptions( enumMappingOptions )
.setTypeUtils( typeUtils )
.setTypeFactory( typeFactory )
.setPrototypeMethods( prototypeMethods )

View File

@ -5,6 +5,7 @@
*/
package org.mapstruct.ap.internal.processor;
import java.util.Map;
import javax.annotation.processing.Filer;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
@ -16,6 +17,7 @@ import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.util.AccessorNamingUtils;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.version.VersionInformation;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
/**
* A processor which performs one task of the mapper generation, e.g. retrieving
@ -51,6 +53,8 @@ public interface ModelElementProcessor<P, R> {
AccessorNamingUtils getAccessorNaming();
Map<String, EnumTransformationStrategy> getEnumTransformationStrategies();
Options getOptions();
VersionInformation getVersionInformation();

View File

@ -9,7 +9,9 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import javax.annotation.processing.Messager;
@ -22,6 +24,7 @@ import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor;
import org.mapstruct.ap.spi.BuilderProvider;
import org.mapstruct.ap.spi.DefaultAccessorNamingStrategy;
import org.mapstruct.ap.spi.DefaultBuilderProvider;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
import org.mapstruct.ap.spi.FreeBuilderAccessorNamingStrategy;
import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy;
import org.mapstruct.ap.spi.ImmutablesBuilderProvider;
@ -39,6 +42,7 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
private BuilderProvider builderProvider;
private AccessorNamingStrategy accessorNamingStrategy;
private boolean initialized;
private Map<String, EnumTransformationStrategy> enumTransformationStrategies;
private AccessorNamingUtils accessorNaming;
private Elements elementUtils;
@ -105,6 +109,28 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
);
}
this.accessorNaming = new AccessorNamingUtils( this.accessorNamingStrategy );
this.enumTransformationStrategies = new LinkedHashMap<>();
ServiceLoader<EnumTransformationStrategy> transformationStrategiesLoader = ServiceLoader.load(
EnumTransformationStrategy.class,
AnnotationProcessorContext.class.getClassLoader()
);
for ( EnumTransformationStrategy transformationStrategy : transformationStrategiesLoader ) {
String transformationStrategyName = transformationStrategy.getStrategyName();
if ( enumTransformationStrategies.containsKey( transformationStrategyName ) ) {
throw new IllegalStateException(
"Multiple EnumTransformationStrategies are using the same ma,e. Found: " +
enumTransformationStrategies.get( transformationStrategyName ) + " and " +
transformationStrategy + " for name " + transformationStrategyName );
}
transformationStrategy.init( this );
enumTransformationStrategies.put( transformationStrategyName, transformationStrategy );
}
this.initialized = true;
}
@ -216,4 +242,9 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
initialize();
return builderProvider;
}
public Map<String, EnumTransformationStrategy> getEnumTransformationStrategies() {
initialize();
return enumTransformationStrategies;
}
}

View File

@ -100,6 +100,7 @@ public enum Message {
ENUMMAPPING_UNDEFINED_TARGET( "A target constant must be specified for mappings of an enum mapping method." ),
ENUMMAPPING_UNMAPPED_SOURCES( "The following constants from the source enum have no corresponding constant in the target enum and must be be mapped via adding additional mappings: %s." ),
ENUMMAPPING_REMOVED( "Mapping of Enums via @Mapping is removed. Please use @ValueMapping instead!" ),
ENUMMAPPING_INCORRECT_TRANSFORMATION_STRATEGY( "There is no registered EnumTransformationStrategy for '%s'. Registered strategies are: %s." ),
LIFECYCLEMETHOD_AMBIGUOUS_PARAMETERS( "Lifecycle method has multiple matching parameters (e. g. same type), in this case please ensure to name the parameters in the lifecycle and mapping method identical. This lifecycle method will not be used for the mapping method '%s'.", Diagnostic.Kind.WARNING),

View File

@ -0,0 +1,44 @@
/*
* 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.spi;
import org.mapstruct.util.Experimental;
/**
* A service provider interface for transforming name based value mappings.
*
* @author Filip Hrisafov
* @since 1.4
*/
@Experimental("This SPI can have it's signature changed in subsequent releases")
public interface EnumTransformationStrategy {
/**
* Initializes the enum transformation strategy with the MapStruct processing environment.
*
* @param processingEnvironment environment for facilities
*/
default void init(MapStructProcessingEnvironment processingEnvironment) {
}
/**
* The name of the strategy.
*
* @return the name of the strategy, never {@code null}
*/
String getStrategyName();
/**
* Transform the given value by using the given {@code configuration}.
*
* @param value the value that should be transformed
* @param configuration the configuration that should be used for the transformation
*
* @return the transformed value after applying the configuration
*/
String transform(String value, String configuration);
}

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.spi;
/**
* @author Filip Hrisafov
*/
public class PrefixEnumTransformationStrategy implements EnumTransformationStrategy {
@Override
public String getStrategyName() {
return "prefix";
}
@Override
public String transform(String value, String configuration) {
return configuration + value;
}
}

View File

@ -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.spi;
/**
* @author Filip Hrisafov
*/
public class StripPrefixEnumTransformationStrategy implements EnumTransformationStrategy {
@Override
public String getStrategyName() {
return "stripPrefix";
}
@Override
public String transform(String value, String configuration) {
if ( value.startsWith( configuration ) ) {
return value.substring( configuration.length() );
}
return value;
}
}

View File

@ -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.spi;
/**
* @author Filip Hrisafov
*/
public class StripSuffixEnumTransformationStrategy implements EnumTransformationStrategy {
@Override
public String getStrategyName() {
return "stripSuffix";
}
@Override
public String transform(String value, String configuration) {
if ( value.endsWith( configuration ) ) {
return value.substring( 0, value.length() - configuration.length() );
}
return value;
}
}

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.spi;
/**
* @author Filip Hrisafov
*/
public class SuffixEnumTransformationStrategy implements EnumTransformationStrategy {
@Override
public String getStrategyName() {
return "suffix";
}
@Override
public String transform(String value, String configuration) {
return value + configuration;
}
}

View File

@ -0,0 +1,8 @@
# Copyright MapStruct Authors.
#
# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
org.mapstruct.ap.spi.PrefixEnumTransformationStrategy
org.mapstruct.ap.spi.StripPrefixEnumTransformationStrategy
org.mapstruct.ap.spi.StripSuffixEnumTransformationStrategy
org.mapstruct.ap.spi.SuffixEnumTransformationStrategy

View File

@ -23,5 +23,11 @@ public class ConstantTest {
assertThat( MappingConstants.ANY_REMAINING ).isEqualTo( MappingConstantsGem.ANY_REMAINING );
assertThat( MappingConstants.ANY_UNMAPPED ).isEqualTo( MappingConstantsGem.ANY_UNMAPPED );
assertThat( MappingConstants.NULL ).isEqualTo( MappingConstantsGem.NULL );
assertThat( MappingConstants.SUFFIX_TRANSFORMATION ).isEqualTo( MappingConstantsGem.SUFFIX_TRANSFORMATION );
assertThat( MappingConstants.STRIP_SUFFIX_TRANSFORMATION )
.isEqualTo( MappingConstantsGem.STRIP_SUFFIX_TRANSFORMATION );
assertThat( MappingConstants.PREFIX_TRANSFORMATION ).isEqualTo( MappingConstantsGem.PREFIX_TRANSFORMATION );
assertThat( MappingConstants.STRIP_PREFIX_TRANSFORMATION )
.isEqualTo( MappingConstantsGem.STRIP_PREFIX_TRANSFORMATION );
}
}

View File

@ -5,6 +5,7 @@
*/
package org.mapstruct.ap.test.value.enum2string;
import org.mapstruct.EnumMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
@ -27,6 +28,7 @@ public interface OrderMapper {
})
String mapNormal(OrderType orderType);
@EnumMapping(nameTransformationStrategy = "prefix", configuration = "PREFIX_")
@ValueMappings({
@ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
@ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),

View File

@ -0,0 +1,34 @@
/*
* 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.nametransformation;
import org.mapstruct.EnumMapping;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface CheeseEnumToStringPrefixMapper {
CheeseEnumToStringPrefixMapper INSTANCE = Mappers.getMapper( CheeseEnumToStringPrefixMapper.class );
@EnumMapping(nameTransformationStrategy = MappingConstants.PREFIX_TRANSFORMATION, configuration = "SWISS_")
String map(CheeseType cheese);
@InheritInverseConfiguration
@EnumMapping(nameTransformationStrategy = MappingConstants.PREFIX_TRANSFORMATION, configuration = "FRENCH_")
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL)
CheeseType map(String cheese);
@EnumMapping(nameTransformationStrategy = MappingConstants.STRIP_PREFIX_TRANSFORMATION, configuration = "SWISS_")
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL)
CheeseTypePrefixed mapStripPrefix(String cheese);
}

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.nametransformation;
import org.mapstruct.EnumMapping;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface CheeseEnumToStringSuffixMapper {
CheeseEnumToStringSuffixMapper INSTANCE = Mappers.getMapper( CheeseEnumToStringSuffixMapper.class );
@EnumMapping(nameTransformationStrategy = MappingConstants.SUFFIX_TRANSFORMATION, configuration = "_CHEESE_TYPE")
String map(CheeseType cheese);
@InheritInverseConfiguration
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL)
CheeseType map(String cheese);
@EnumMapping(
nameTransformationStrategy = MappingConstants.STRIP_SUFFIX_TRANSFORMATION,
configuration = "_CHEESE_TYPE"
)
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL)
CheeseTypeSuffixed mapStripSuffix(String cheese);
}

View File

@ -0,0 +1,33 @@
/*
* 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.nametransformation;
import org.mapstruct.EnumMapping;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface CheesePrefixMapper {
CheesePrefixMapper INSTANCE = Mappers.getMapper( CheesePrefixMapper.class );
@EnumMapping(nameTransformationStrategy = MappingConstants.PREFIX_TRANSFORMATION, configuration = "SWISS_")
CheeseTypePrefixed map(CheeseType cheese);
@InheritInverseConfiguration
@ValueMapping(source = "DEFAULT", target = MappingConstants.NULL)
CheeseType mapInheritInverse(CheeseTypePrefixed cheese);
@ValueMapping(source = "DEFAULT", target = MappingConstants.NULL)
@EnumMapping(nameTransformationStrategy = MappingConstants.STRIP_PREFIX_TRANSFORMATION, configuration = "SWISS_")
CheeseType mapStripPrefix(CheeseTypePrefixed cheese);
}

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.nametransformation;
import org.mapstruct.EnumMapping;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface CheeseSuffixMapper {
CheeseSuffixMapper INSTANCE = Mappers.getMapper( CheeseSuffixMapper.class );
@EnumMapping(nameTransformationStrategy = MappingConstants.SUFFIX_TRANSFORMATION, configuration = "_CHEESE_TYPE")
CheeseTypeSuffixed map(CheeseType cheese);
@InheritInverseConfiguration
@ValueMapping(source = "DEFAULT", target = MappingConstants.NULL)
CheeseType mapInheritInverse(CheeseTypeSuffixed cheese);
@EnumMapping(
nameTransformationStrategy = MappingConstants.STRIP_SUFFIX_TRANSFORMATION,
configuration = "_CHEESE_TYPE"
)
@ValueMapping(source = "DEFAULT", target = MappingConstants.NULL)
CheeseType mapStripSuffix(CheeseTypeSuffixed cheese);
}

View File

@ -0,0 +1,15 @@
/*
* 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.nametransformation;
/**
* @author Filip Hrisafov
*/
public enum CheeseType {
BRIE,
ROQUEFORT
}

View File

@ -0,0 +1,15 @@
/*
* 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.nametransformation;
/**
* @author Filip Hrisafov
*/
public enum CheeseTypeCustomSuffix {
brie_TYPE,
roquefort_TYPE,
}

View File

@ -0,0 +1,16 @@
/*
* 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.nametransformation;
/**
* @author Filip Hrisafov
*/
public enum CheeseTypePrefixed {
DEFAULT,
SWISS_BRIE,
SWISS_ROQUEFORT,
}

View File

@ -0,0 +1,16 @@
/*
* 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.nametransformation;
/**
* @author Filip Hrisafov
*/
public enum CheeseTypeSuffixed {
DEFAULT,
BRIE_CHEESE_TYPE,
ROQUEFORT_CHEESE_TYPE,
}

View File

@ -0,0 +1,30 @@
/*
* 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.nametransformation;
// tag::documentation[]
import org.mapstruct.ap.spi.EnumTransformationStrategy;
// end::documentation[]
/**
* @author Filip Hrisafov
*/
// tag::documentation[]
public class CustomEnumTransformationStrategy implements EnumTransformationStrategy {
@Override
public String getStrategyName() {
return "custom";
}
@Override
public String transform(String value, String configuration) {
return value.toLowerCase() + configuration;
}
}
// end::documentation[]

View File

@ -0,0 +1,115 @@
/*
* 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.nametransformation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithServiceImplementation;
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;
/**
* @author Filip Hrisafov
*/
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses({
CheeseType.class,
CheeseTypeSuffixed.class,
CheeseTypePrefixed.class,
CheeseTypeCustomSuffix.class,
})
public class EnumNameTransformationStrategyTest {
@Test
@WithClasses({
CheeseSuffixMapper.class
})
public void shouldApplySuffixAndStripSuffixOnEnumToEnumMapping() {
CheeseSuffixMapper mapper = CheeseSuffixMapper.INSTANCE;
assertThat( mapper.map( CheeseType.BRIE ) )
.isEqualTo( CheeseTypeSuffixed.BRIE_CHEESE_TYPE );
assertThat( mapper.mapInheritInverse( CheeseTypeSuffixed.BRIE_CHEESE_TYPE ) )
.isEqualTo( CheeseType.BRIE );
assertThat( mapper.mapStripSuffix( CheeseTypeSuffixed.BRIE_CHEESE_TYPE ) )
.isEqualTo( CheeseType.BRIE );
}
@Test
@WithClasses({
CheesePrefixMapper.class
})
public void shouldApplyPrefixAndStripPrefixOnEnumToEnumMapping() {
CheesePrefixMapper mapper = CheesePrefixMapper.INSTANCE;
assertThat( mapper.map( CheeseType.BRIE ) )
.isEqualTo( CheeseTypePrefixed.SWISS_BRIE );
assertThat( mapper.mapInheritInverse( CheeseTypePrefixed.SWISS_BRIE ) )
.isEqualTo( CheeseType.BRIE );
assertThat( mapper.mapStripPrefix( CheeseTypePrefixed.SWISS_BRIE ) )
.isEqualTo( CheeseType.BRIE );
}
@Test
@WithClasses({
CheeseEnumToStringSuffixMapper.class
})
public void shouldApplySuffixAndStripSuffixOnEnumToStringMapping() {
CheeseEnumToStringSuffixMapper mapper = CheeseEnumToStringSuffixMapper.INSTANCE;
assertThat( mapper.map( CheeseType.BRIE ) ).isEqualTo( "BRIE_CHEESE_TYPE" );
assertThat( mapper.map( "BRIE_CHEESE_TYPE" ) ).isEqualTo( CheeseType.BRIE );
assertThat( mapper.mapStripSuffix( "BRIE" ) ).isEqualTo( CheeseTypeSuffixed.BRIE_CHEESE_TYPE );
assertThat( mapper.mapStripSuffix( "DEFAULT" ) ).isEqualTo( CheeseTypeSuffixed.DEFAULT );
}
@Test
@WithClasses({
CheeseEnumToStringPrefixMapper.class
})
public void shouldApplyPrefixAndStripPrefixOnEnumToStringMapping() {
CheeseEnumToStringPrefixMapper mapper = CheeseEnumToStringPrefixMapper.INSTANCE;
assertThat( mapper.map( CheeseType.BRIE ) ).isEqualTo( "SWISS_BRIE" );
assertThat( mapper.map( "FRENCH_BRIE" ) ).isEqualTo( CheeseType.BRIE );
assertThat( mapper.mapStripPrefix( "BRIE" ) ).isEqualTo( CheeseTypePrefixed.SWISS_BRIE );
assertThat( mapper.mapStripPrefix( "DEFAULT" ) ).isEqualTo( CheeseTypePrefixed.DEFAULT );
}
@Test
@WithClasses({
ErroneousNameTransformStrategyMapper.class
})
@ExpectedCompilationOutcome(value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
type = ErroneousNameTransformStrategyMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 20,
message = "There is no registered EnumTransformationStrategy for 'custom'. Registered strategies are:" +
" prefix, stripPrefix, stripSuffix, suffix."
)
}
)
public void shouldGiveCompileErrorWhenUsingUnknownNameTransformStrategy() {
}
@Test
@WithClasses({
ErroneousNameTransformStrategyMapper.class
})
@WithServiceImplementation(CustomEnumTransformationStrategy.class)
public void shouldUseCustomEnumTransformationStrategy() {
assertThat( ErroneousNameTransformStrategyMapper.INSTANCE.map( CheeseType.BRIE ) )
.isEqualTo( CheeseTypeCustomSuffix.brie_TYPE );
}
}

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.nametransformation;
import org.mapstruct.EnumMapping;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface ErroneousNameTransformStrategyMapper {
ErroneousNameTransformStrategyMapper INSTANCE = Mappers.getMapper( ErroneousNameTransformStrategyMapper.class );
@EnumMapping(nameTransformationStrategy = "custom", configuration = "_TYPE")
CheeseTypeCustomSuffix map(CheeseType cheese);
}