Add EnumNamingStrategy SPI (#2100)

Add a new EnumNamingStrategy SPI which can be used for customising the way enums are matched by name.
It is similar to the AccessorNamingStrategy such that it allows implementors to provide a custom way of defining a property.

Related to #796, #1220, #1789 and #1667
This commit is contained in:
Filip Hrisafov 2020-05-25 21:31:29 +02:00 committed by GitHub
parent 7b5a54971f
commit c23592a7fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 918 additions and 18 deletions

View File

@ -199,6 +199,152 @@ include::{processor-ap-main}/spi/NoOpBuilderProvider.java[tag=documentation]
----
====
[[custom-enum-naming-strategy]]
=== Custom Enum Naming Strategy
MapStruct offers the possibility to override the `EnumNamingStrategy` via the Service Provider Interface (SPI).
This can be used when you have certain enums that follow some conventions within your organization.
For example all enums which implement an interface named `CustomEnumMarker` are prefixed with `CUSTOM_`
and the default value for them when mapping from `null` is `UNSPECIFIED`
.Normal Enum
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public enum CheeseType {
BRIE,
ROQUEFORT;
}
----
====
.Custom marker enum
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public enum CustomCheeseType implements CustomEnumMarker {
UNSPECIFIED,
CUSTOM_BRIE,
CUSTOM_ROQUEFORT;
}
----
====
We want `CheeseType` and `CustomCheeseType` to be mapped without the need to manually define the value mappings:
.Custom enum mapping
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CheeseTypeMapper {
CheeseType map(CustomCheeseType cheese);
CustomCheeseType map(CheeseType cheese);
}
----
====
This can be achieved with implementing the SPI `org.mapstruct.ap.spi.EnumNamingStrategy` as in the following example.
Heres an implemented `org.mapstruct.ap.spi.EnumNamingStrategy`:
.Custom enum naming strategy
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class CustomEnumNamingStrategy extends DefaultEnumNamingStrategy {
@Override
public String getDefaultNullEnumConstant(TypeElement enumType) {
if ( isCustomEnum( enumType ) ) {
return "UNSPECIFIED";
}
return super.getDefaultNullEnumConstant( enumType );
}
@Override
public String getEnumConstant(TypeElement enumType, String enumConstant) {
if ( isCustomEnum( enumType ) ) {
return getCustomEnumConstant( enumConstant );
}
return super.getEnumConstant( enumType, enumConstant );
}
protected String getCustomEnumConstant(String enumConstant) {
if ( "UNSPECIFIED".equals( enumConstant ) ) {
return MappingConstantsGem.NULL;
}
return enumConstant.replace( "CUSTOM_", "" );
}
protected boolean isCustomEnum(TypeElement enumType) {
for ( TypeMirror enumTypeInterface : enumType.getInterfaces() ) {
if ( typeUtils.asElement( enumTypeInterface ).getSimpleName().contentEquals( "CustomEnumMarker" ) ) {
return true;
}
}
return false;
}
}
----
====
The generated code then for the `CheeseMapper` looks like:
.Generated CheeseTypeMapper
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class CheeseTypeMapperImpl implements CheeseTypeMapper {
@Override
public CheeseType map(CustomCheeseType cheese) {
if ( cheese == null ) {
return null;
}
CheeseType cheeseType;
switch ( cheese ) {
case UNRECOGNIZED: cheeseType = null;
break;
case CUSTOM_BRIE: cheeseType = CheeseType.BRIE;
break;
case CUSTOM_ROQUEFORT: cheeseType = CheeseType.ROQUEFORT;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return cheeseType;
}
@Override
public CustomCheeseType map(CheeseType cheese) {
if ( cheese == null ) {
return CustomCheeseType.UNSPECIFIED;
}
CustomCheeseType customCheeseType;
switch ( cheese ) {
case BRIE: customCheeseType = CustomCheeseType.CUSTOM_BRIE;
break;
case ROQUEFORT: customCheeseType = CustomCheeseType.CUSTOM_ROQUEFORT;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return customCheeseType;
}
}
----
====
[[custom-enum-transformation-strategy]]
=== Custom Enum Transformation Strategy

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.EnumNamingStrategy;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
import org.mapstruct.ap.spi.MappingExclusionProvider;
@ -105,6 +106,7 @@ public class MappingBuilderContext {
private final Types typeUtils;
private final FormattingMessager messager;
private final AccessorNamingUtils accessorNaming;
private final EnumNamingStrategy enumNamingStrategy;
private final Map<String, EnumTransformationStrategy> enumTransformationStrategies;
private final Options options;
private final TypeElement mapperTypeElement;
@ -121,6 +123,7 @@ public class MappingBuilderContext {
Types typeUtils,
FormattingMessager messager,
AccessorNamingUtils accessorNaming,
EnumNamingStrategy enumNamingStrategy,
Map<String, EnumTransformationStrategy> enumTransformationStrategies,
Options options,
MappingResolver mappingResolver,
@ -132,6 +135,7 @@ public class MappingBuilderContext {
this.typeUtils = typeUtils;
this.messager = messager;
this.accessorNaming = accessorNaming;
this.enumNamingStrategy = enumNamingStrategy;
this.enumTransformationStrategies = enumTransformationStrategies;
this.options = options;
this.mappingResolver = mappingResolver;
@ -186,6 +190,10 @@ public class MappingBuilderContext {
return accessorNaming;
}
public EnumNamingStrategy getEnumNamingStrategy() {
return enumNamingStrategy;
}
public Map<String, EnumTransformationStrategy> getEnumTransformationStrategies() {
return enumTransformationStrategies;
}

View File

@ -8,9 +8,11 @@ package org.mapstruct.ap.internal.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
@ -86,6 +88,12 @@ public class ValueMappingMethod extends MappingMethod {
Type sourceType = first( method.getSourceParameters() ).getType();
Type targetType = method.getResultType();
if ( targetType.isEnumType() && valueMappings.nullTarget == null ) {
// If null target is not set it means that the user has not explicitly defined a mapping for null
valueMappings.nullValueTarget = ctx.getEnumNamingStrategy()
.getDefaultNullEnumConstant( targetType.getTypeElement() );
}
// enum-to-enum
if ( sourceType.isEnumType() && targetType.isEnumType() ) {
mappingEntries.addAll( enumToEnumMapping( method, sourceType, targetType ) );
@ -145,9 +153,7 @@ public class ValueMappingMethod extends MappingMethod {
// Start to fill the mappings with the defined value mappings
for ( ValueMappingOptions valueMapping : valueMappings.regularValueMappings ) {
String target =
NULL.equals( valueMapping.getTarget() ) ? null : valueMapping.getTarget();
mappings.add( new MappingEntry( valueMapping.getSource(), target ) );
mappings.add( new MappingEntry( valueMapping.getSource(), valueMapping.getTarget() ) );
unmappedSourceConstants.remove( valueMapping.getSource() );
}
@ -160,32 +166,40 @@ public class ValueMappingMethod extends MappingMethod {
Map<String, String> targetConstants = new LinkedHashMap<>();
boolean enumMappingInverse = enumMapping.isInverse();
TypeElement targetTypeElement = method.getReturnType().getTypeElement();
for ( String targetEnumConstant : method.getReturnType().getEnumConstants() ) {
String targetNameEnum = getEnumConstant( targetTypeElement, targetEnumConstant );
if ( enumMappingInverse ) {
// If the mapping is inverse we have to change the target enum constant
targetConstants.put(
enumTransformationInvoker.transform( targetEnumConstant ),
enumTransformationInvoker.transform( targetNameEnum ),
targetEnumConstant
);
}
else {
targetConstants.put( targetEnumConstant, targetEnumConstant );
targetConstants.put( targetNameEnum, targetEnumConstant );
}
}
TypeElement sourceTypeElement = sourceType.getTypeElement();
for ( String sourceConstant : new ArrayList<>( unmappedSourceConstants ) ) {
String sourceNameConstant = getEnumConstant( sourceTypeElement, sourceConstant );
String targetConstant;
if ( !enumMappingInverse ) {
targetConstant = enumTransformationInvoker.transform( sourceConstant );
targetConstant = enumTransformationInvoker.transform( sourceNameConstant );
}
else {
targetConstant = sourceConstant;
targetConstant = sourceNameConstant;
}
if ( targetConstants.containsKey( targetConstant ) ) {
mappings.add( new MappingEntry( sourceConstant, targetConstants.get( targetConstant ) ) );
unmappedSourceConstants.remove( sourceConstant );
}
else if ( NULL.equals( targetConstant ) ) {
mappings.add( new MappingEntry( sourceConstant, null ) );
unmappedSourceConstants.remove( sourceConstant );
}
}
if ( valueMappings.defaultTarget == null && !unmappedSourceConstants.isEmpty() ) {
@ -223,18 +237,18 @@ public class ValueMappingMethod extends MappingMethod {
// Start to fill the mappings with the defined valuemappings
for ( ValueMappingOptions valueMapping : valueMappings.regularValueMappings ) {
String target =
NULL.equals( valueMapping.getTarget() ) ? null : valueMapping.getTarget();
mappings.add( new MappingEntry( valueMapping.getSource(), target ) );
mappings.add( new MappingEntry( valueMapping.getSource(), valueMapping.getTarget() ) );
unmappedSourceConstants.remove( valueMapping.getSource() );
}
// add mappings based on name
if ( !valueMappings.hasMapAnyUnmapped ) {
TypeElement sourceTypeElement = sourceType.getTypeElement();
// all remaining constants are mapped
for ( String sourceConstant : unmappedSourceConstants ) {
String targetConstant = enumTransformationInvoker.transform( sourceConstant );
String sourceNameConstant = getEnumConstant( sourceTypeElement, sourceConstant );
String targetConstant = enumTransformationInvoker.transform( sourceNameConstant );
mappings.add( new MappingEntry( sourceConstant, targetConstant ) );
}
}
@ -250,27 +264,35 @@ public class ValueMappingMethod extends MappingMethod {
if ( sourceErrorOccurred || mandatoryMissing ) {
return mappings;
}
Set<String> mappedSources = new LinkedHashSet<>();
// Start to fill the mappings with the defined valuemappings
for ( ValueMappingOptions valueMapping : valueMappings.regularValueMappings ) {
String target =
NULL.equals( valueMapping.getTarget() ) ? null : valueMapping.getTarget();
mappings.add( new MappingEntry( valueMapping.getSource(), target ) );
mappedSources.add( valueMapping.getSource() );
mappings.add( new MappingEntry( valueMapping.getSource(), valueMapping.getTarget() ) );
unmappedSourceConstants.remove( valueMapping.getSource() );
}
// add mappings based on name
if ( !valueMappings.hasMapAnyUnmapped ) {
mappedSources.add( NULL );
TypeElement targetTypeElement = targetType.getTypeElement();
// all remaining constants are mapped
for ( String sourceConstant : unmappedSourceConstants ) {
String stringConstant = enumTransformationInvoker.transform( sourceConstant );
mappings.add( new MappingEntry( stringConstant, sourceConstant ) );
String sourceNameConstant = getEnumConstant( targetTypeElement, sourceConstant );
String stringConstant = enumTransformationInvoker.transform( sourceNameConstant );
if ( !mappedSources.contains( stringConstant ) ) {
mappings.add( new MappingEntry( stringConstant, sourceConstant ) );
}
}
}
return mappings;
}
private String getEnumConstant(TypeElement typeElement, String enumConstant) {
return ctx.getEnumNamingStrategy().getEnumConstant( typeElement, enumConstant );
}
private SelectionParameters getSelectionParameters(Method method, Types typeUtils) {
BeanMappingGem beanMapping = BeanMappingGem.instanceOn( method.getExecutable() );
if ( beanMapping != null ) {
@ -377,6 +399,18 @@ public class ValueMappingMethod extends MappingMethod {
);
foundIncorrectMapping = true;
}
else if ( valueMappings.nullTarget == null && valueMappings.nullValueTarget != null
&& !targetEnumConstants.contains( valueMappings.nullValueTarget ) ) {
// if there is no nullTarget, but nullValueTarget has a value it means that there was an SPI
// which returned an enum for the target enum
ctx.getMessager().printMessage(
method.getExecutable(),
Message.VALUEMAPPING_NON_EXISTING_CONSTANT_FROM_SPI,
valueMappings.nullValueTarget,
method.getReturnType(),
ctx.getEnumNamingStrategy()
);
}
return !foundIncorrectMapping;
}
@ -417,6 +451,7 @@ public class ValueMappingMethod extends MappingMethod {
boolean hasMapAnyUnmapped = false;
boolean hasMapAnyRemaining = false;
boolean hasDefaultValue = false;
boolean hasNullValue = false;
ValueMappings(List<ValueMappingOptions> valueMappings) {
@ -436,6 +471,7 @@ public class ValueMappingMethod extends MappingMethod {
else if ( NULL.equals( valueMapping.getSource() ) ) {
nullTarget = valueMapping;
nullValueTarget = getValue( nullTarget );
hasNullValue = true;
}
else {
regularValueMappings.add( valueMapping );
@ -489,7 +525,12 @@ public class ValueMappingMethod extends MappingMethod {
MappingEntry( String source, String target ) {
this.source = source;
this.target = target;
if ( !NULL.equals( target ) ) {
this.target = target;
}
else {
this.target = null;
}
}
public String getSource() {

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.EnumNamingStrategy;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
/**
@ -98,6 +99,11 @@ public class DefaultModelElementProcessorContext implements ProcessorContext {
return roundContext.getAnnotationProcessorContext().getEnumTransformationStrategies();
}
@Override
public EnumNamingStrategy getEnumNamingStrategy() {
return roundContext.getAnnotationProcessorContext().getEnumNamingStrategy();
}
@Override
public Options getOptions() {
return options;

View File

@ -101,6 +101,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
typeUtils,
messager,
accessorNaming,
context.getEnumNamingStrategy(),
context.getEnumTransformationStrategies(),
options,
new MappingResolverImpl(

View File

@ -17,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.EnumNamingStrategy;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
/**
@ -55,6 +56,8 @@ public interface ModelElementProcessor<P, R> {
Map<String, EnumTransformationStrategy> getEnumTransformationStrategies();
EnumNamingStrategy getEnumNamingStrategy();
Options getOptions();
VersionInformation getVersionInformation();

View File

@ -24,6 +24,8 @@ 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.DefaultEnumNamingStrategy;
import org.mapstruct.ap.spi.EnumNamingStrategy;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
import org.mapstruct.ap.spi.FreeBuilderAccessorNamingStrategy;
import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy;
@ -41,6 +43,7 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
private BuilderProvider builderProvider;
private AccessorNamingStrategy accessorNamingStrategy;
private EnumNamingStrategy enumNamingStrategy;
private boolean initialized;
private Map<String, EnumTransformationStrategy> enumTransformationStrategies;
@ -110,6 +113,15 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
}
this.accessorNaming = new AccessorNamingUtils( this.accessorNamingStrategy );
this.enumNamingStrategy = Services.get( EnumNamingStrategy.class, new DefaultEnumNamingStrategy() );
this.enumNamingStrategy.init( this );
if ( verbose ) {
messager.printMessage(
Diagnostic.Kind.NOTE,
"MapStruct: Using enum naming strategy: "
+ this.enumNamingStrategy.getClass().getCanonicalName()
);
}
this.enumTransformationStrategies = new LinkedHashMap<>();
ServiceLoader<EnumTransformationStrategy> transformationStrategiesLoader = ServiceLoader.load(
@ -238,6 +250,11 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen
return accessorNamingStrategy;
}
public EnumNamingStrategy getEnumNamingStrategy() {
initialize();
return enumNamingStrategy;
}
public BuilderProvider getBuilderProvider() {
initialize();
return builderProvider;

View File

@ -158,6 +158,7 @@ public enum Message {
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_FROM_SPI( "Constant %s doesn't exist in enum type %s. Constant was returned from EnumNamingStrategy: %s"),
VALUEMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s." );
// CHECKSTYLE:ON

View File

@ -0,0 +1,35 @@
/*
* 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 javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
/**
* @author Filip Hrisafov
*/
public class DefaultEnumNamingStrategy implements EnumNamingStrategy {
protected Elements elementUtils;
protected Types typeUtils;
@Override
public void init(MapStructProcessingEnvironment processingEnvironment) {
this.elementUtils = processingEnvironment.getElementUtils();
this.typeUtils = processingEnvironment.getTypeUtils();
}
@Override
public String getDefaultNullEnumConstant(TypeElement enumType) {
return null;
}
@Override
public String getEnumConstant(TypeElement enumType, String enumConstant) {
return enumConstant;
}
}

View File

@ -0,0 +1,48 @@
/*
* 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 javax.lang.model.element.TypeElement;
import org.mapstruct.util.Experimental;
/**
* A service provider interface for the mapping between different enum constants
*
* @author Arne Seime
*/
@Experimental("This SPI can have it's signature changed in subsequent releases")
public interface EnumNamingStrategy {
/**
* Initializes the enum value mapping strategy
*
* @param processingEnvironment environment for facilities
*/
default void init(MapStructProcessingEnvironment processingEnvironment) {
}
/**
* Return the default enum constant to use if the source is null.
*
* @param enumType the enum
* @return enum value or null if there is no designated enum constant
*/
String getDefaultNullEnumConstant(TypeElement enumType);
/**
* Map the enum constant to the value use for matching.
* In case you want this enum constant to match to null return {@link org.mapstruct.MappingConstants#NULL}
*
* @param enumType the enum this constant belongs to
* @param enumConstant constant to transform
*
* @return the transformed constant - or the original value from the parameter if no transformation is needed.
* never return null
*/
String getEnumConstant(TypeElement enumType, String enumConstant);
}

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

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.spi;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface CustomCheeseMapper {
CustomCheeseMapper INSTANCE = Mappers.getMapper( CustomCheeseMapper.class );
CheeseType map(CustomCheeseType cheese);
CustomCheeseType map(CheeseType cheese);
String mapToString(CustomCheeseType cheeseType);
String mapToString(CheeseType cheeseType);
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "CUSTOM_BRIE")
CustomCheeseType mapStringToCustom(String cheese);
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "BRIE")
CheeseType mapStringToCheese(String cheese);
}

View File

@ -0,0 +1,17 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.value.spi;
/**
* @author Filip Hrisafov
*/
public enum CustomCheeseType implements CustomEnumMarker {
UNSPECIFIED,
CUSTOM_BRIE,
CUSTOM_ROQUEFORT,
UNRECOGNIZED,
}

View File

@ -0,0 +1,12 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.value.spi;
/**
* @author Filip Hrisafov
*/
public interface CustomEnumMarker {
}

View File

@ -0,0 +1,54 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.value.spi;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.spi.DefaultEnumNamingStrategy;
import org.mapstruct.ap.spi.EnumNamingStrategy;
/**
* @author Filip Hrisafov
*/
public class CustomEnumNamingStrategy extends DefaultEnumNamingStrategy implements EnumNamingStrategy {
@Override
public String getDefaultNullEnumConstant(TypeElement enumType) {
if ( isCustomEnum( enumType ) ) {
return "UNSPECIFIED";
}
return super.getDefaultNullEnumConstant( enumType );
}
@Override
public String getEnumConstant(TypeElement enumType, String enumConstant) {
if ( isCustomEnum( enumType ) ) {
return getCustomEnumConstant( enumConstant );
}
return super.getEnumConstant( enumType, enumConstant );
}
protected String getCustomEnumConstant(String enumConstant) {
if ( "UNRECOGNIZED".equals( enumConstant ) || "UNSPECIFIED".equals( enumConstant ) ) {
return MappingConstantsGem.NULL;
}
return enumConstant.replace( "CUSTOM_", "" );
}
protected boolean isCustomEnum(TypeElement enumType) {
for ( TypeMirror enumTypeInterface : enumType.getInterfaces() ) {
if ( typeUtils.asElement( enumTypeInterface ).getSimpleName().contentEquals( "CustomEnumMarker" ) ) {
return true;
}
}
return false;
}
}

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.ap.test.value.spi;
import org.junit.Rule;
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.runner.AnnotationProcessorTestRunner;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses({
CheeseType.class,
CustomCheeseType.class,
CustomEnumMarker.class,
})
@WithServiceImplementation(CustomEnumNamingStrategy.class)
public class CustomEnumNamingStrategyTest {
@Rule
public final GeneratedSource generatedSource = new GeneratedSource();
@Test
@WithClasses({
CustomCheeseMapper.class
})
public void shouldApplyCustomEnumNamingStrategy() {
generatedSource.addComparisonToFixtureFor( CustomCheeseMapper.class );
CustomCheeseMapper mapper = CustomCheeseMapper.INSTANCE;
// CheeseType -> CustomCheeseType
assertThat( mapper.map( (CheeseType) null ) ).isEqualTo( CustomCheeseType.UNSPECIFIED );
assertThat( mapper.map( CheeseType.BRIE ) ).isEqualTo( CustomCheeseType.CUSTOM_BRIE );
assertThat( mapper.map( CheeseType.ROQUEFORT ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
// CustomCheeseType -> CheeseType
assertThat( mapper.map( (CustomCheeseType) null ) ).isNull();
assertThat( mapper.map( CustomCheeseType.UNSPECIFIED ) ).isNull();
assertThat( mapper.map( CustomCheeseType.CUSTOM_BRIE ) ).isEqualTo( CheeseType.BRIE );
assertThat( mapper.map( CustomCheeseType.CUSTOM_ROQUEFORT ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.map( CustomCheeseType.UNRECOGNIZED ) ).isNull();
// CheeseType -> String
assertThat( mapper.mapToString( (CheeseType) null ) ).isNull();
assertThat( mapper.mapToString( CheeseType.BRIE ) ).isEqualTo( "BRIE" );
assertThat( mapper.mapToString( CheeseType.ROQUEFORT ) ).isEqualTo( "ROQUEFORT" );
// CustomCheeseType -> String
assertThat( mapper.mapToString( (CustomCheeseType) null ) ).isNull();
assertThat( mapper.mapToString( CustomCheeseType.UNSPECIFIED ) ).isNull();
assertThat( mapper.mapToString( CustomCheeseType.CUSTOM_BRIE ) ).isEqualTo( "BRIE" );
assertThat( mapper.mapToString( CustomCheeseType.CUSTOM_ROQUEFORT ) ).isEqualTo( "ROQUEFORT" );
assertThat( mapper.mapToString( CustomCheeseType.UNRECOGNIZED ) ).isNull();
// String - > CheeseType
assertThat( mapper.mapStringToCheese( null ) ).isNull();
assertThat( mapper.mapStringToCheese( "BRIE" ) ).isEqualTo( CheeseType.BRIE );
assertThat( mapper.mapStringToCheese( "ROQUEFORT" ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.mapStringToCheese( "UNKNOWN" ) ).isEqualTo( CheeseType.BRIE );
// CustomCheeseType -> String
assertThat( mapper.mapStringToCustom( null ) ).isEqualTo( CustomCheeseType.UNSPECIFIED );
assertThat( mapper.mapStringToCustom( "UNRECOGNIZED" ) ).isEqualTo( CustomCheeseType.CUSTOM_BRIE );
assertThat( mapper.mapStringToCustom( "BRIE" ) ).isEqualTo( CustomCheeseType.CUSTOM_BRIE );
assertThat( mapper.mapStringToCustom( "ROQUEFORT" ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.mapStringToCustom( "UNKNOWN" ) ).isEqualTo( CustomCheeseType.CUSTOM_BRIE );
}
@Test
@WithClasses({
OverridesCustomCheeseMapper.class
})
public void shouldApplyDefinedMappingsInsteadOfCustomEnumNamingStrategy() {
OverridesCustomCheeseMapper mapper = OverridesCustomCheeseMapper.INSTANCE;
// CheeseType -> CustomCheeseType
assertThat( mapper.map( (CheeseType) null ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.map( CheeseType.BRIE ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.map( CheeseType.ROQUEFORT ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
// CustomCheeseType -> CheeseType
assertThat( mapper.map( (CustomCheeseType) null ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.map( CustomCheeseType.UNSPECIFIED ) ).isNull();
assertThat( mapper.map( CustomCheeseType.CUSTOM_BRIE ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.map( CustomCheeseType.CUSTOM_ROQUEFORT ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.map( CustomCheeseType.UNRECOGNIZED ) ).isNull();
// CheeseType -> String
assertThat( mapper.mapToString( (CheeseType) null ) ).isNull();
assertThat( mapper.mapToString( CheeseType.BRIE ) ).isEqualTo( "BRIE" );
assertThat( mapper.mapToString( CheeseType.ROQUEFORT ) ).isEqualTo( "BRIE" );
// CustomCheeseType -> String
assertThat( mapper.mapToString( (CustomCheeseType) null ) ).isEqualTo( "ROQUEFORT" );
assertThat( mapper.mapToString( CustomCheeseType.UNSPECIFIED ) ).isNull();
assertThat( mapper.mapToString( CustomCheeseType.CUSTOM_BRIE ) ).isEqualTo( "BRIE" );
assertThat( mapper.mapToString( CustomCheeseType.CUSTOM_ROQUEFORT ) ).isEqualTo( "BRIE" );
assertThat( mapper.mapToString( CustomCheeseType.UNRECOGNIZED ) ).isNull();
// String - > CheeseType
assertThat( mapper.mapStringToCheese( null ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.mapStringToCheese( "BRIE" ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.mapStringToCheese( "ROQUEFORT" ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.mapStringToCheese( "UNKNOWN" ) ).isEqualTo( CheeseType.BRIE );
// CustomCheeseType -> String
assertThat( mapper.mapStringToCustom( null ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.mapStringToCustom( "UNRECOGNIZED" ) ).isEqualTo( CustomCheeseType.CUSTOM_BRIE );
assertThat( mapper.mapStringToCustom( "BRIE" ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.mapStringToCustom( "ROQUEFORT" ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.mapStringToCustom( "UNKNOWN" ) ).isEqualTo( CustomCheeseType.CUSTOM_BRIE );
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.value.spi;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.internal.gem.MappingConstantsGem;
import org.mapstruct.ap.spi.DefaultEnumNamingStrategy;
import org.mapstruct.ap.spi.EnumNamingStrategy;
/**
* @author Filip Hrisafov
*/
public class CustomErroneousEnumNamingStrategy extends DefaultEnumNamingStrategy implements EnumNamingStrategy {
@Override
public String getDefaultNullEnumConstant(TypeElement enumType) {
if ( isCustomEnum( enumType ) ) {
return "INCORRECT";
}
return super.getDefaultNullEnumConstant( enumType );
}
@Override
public String getEnumConstant(TypeElement enumType, String enumConstant) {
if ( isCustomEnum( enumType ) ) {
return getCustomEnumConstant( enumConstant );
}
return super.getEnumConstant( enumType, enumConstant );
}
protected String getCustomEnumConstant(String enumConstant) {
if ( "UNRECOGNIZED".equals( enumConstant ) || "UNSPECIFIED".equals( enumConstant ) ) {
return MappingConstantsGem.NULL;
}
return enumConstant.replace( "CUSTOM_", "" );
}
protected boolean isCustomEnum(TypeElement enumType) {
for ( TypeMirror enumTypeInterface : enumType.getInterfaces() ) {
if ( typeUtils.asElement( enumTypeInterface ).getSimpleName().contentEquals( "CustomEnumMarker" ) ) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.value.spi;
import org.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,
CustomCheeseType.class,
CustomEnumMarker.class,
})
@WithServiceImplementation(CustomErroneousEnumNamingStrategy.class)
public class CustomErroneousEnumNamingStrategyTest {
@Test
@WithClasses({
CustomCheeseMapper.class
})
@ExpectedCompilationOutcome(value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(
type = CustomCheeseMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 23,
messageRegExp = "Constant INCORRECT doesn't exist in enum type " +
"org\\.mapstruct\\.ap\\.test\\.value\\.spi\\.CustomCheeseType." +
" Constant was returned from EnumNamingStrategy: .*CustomErroneousEnumNamingStrategy@.*"
),
@Diagnostic(
type = CustomCheeseMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 30,
messageRegExp = "Constant INCORRECT doesn't exist in enum type " +
"org\\.mapstruct\\.ap\\.test\\.value\\.spi\\.CustomCheeseType." +
" Constant was returned from EnumNamingStrategy: .*CustomErroneousEnumNamingStrategy@.*"
)
}
)
public void shouldThrowCompileErrorWhenDefaultEnumDoesNotExist() {
}
@Test
@WithClasses({
OverridesCustomCheeseMapper.class
})
public void shouldApplyDefinedMappingsInsteadOfCustomEnumNamingStrategy() {
OverridesCustomCheeseMapper mapper = OverridesCustomCheeseMapper.INSTANCE;
// CheeseType -> CustomCheeseType
assertThat( mapper.map( (CheeseType) null ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.map( CheeseType.BRIE ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.map( CheeseType.ROQUEFORT ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
// CustomCheeseType -> CheeseType
assertThat( mapper.map( (CustomCheeseType) null ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.map( CustomCheeseType.UNSPECIFIED ) ).isNull();
assertThat( mapper.map( CustomCheeseType.CUSTOM_BRIE ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.map( CustomCheeseType.CUSTOM_ROQUEFORT ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.map( CustomCheeseType.UNRECOGNIZED ) ).isNull();
// CheeseType -> String
assertThat( mapper.mapToString( (CheeseType) null ) ).isNull();
assertThat( mapper.mapToString( CheeseType.BRIE ) ).isEqualTo( "BRIE" );
assertThat( mapper.mapToString( CheeseType.ROQUEFORT ) ).isEqualTo( "BRIE" );
// CustomCheeseType -> String
assertThat( mapper.mapToString( (CustomCheeseType) null ) ).isEqualTo( "ROQUEFORT" );
assertThat( mapper.mapToString( CustomCheeseType.UNSPECIFIED ) ).isNull();
assertThat( mapper.mapToString( CustomCheeseType.CUSTOM_BRIE ) ).isEqualTo( "BRIE" );
assertThat( mapper.mapToString( CustomCheeseType.CUSTOM_ROQUEFORT ) ).isEqualTo( "BRIE" );
assertThat( mapper.mapToString( CustomCheeseType.UNRECOGNIZED ) ).isNull();
// String - > CheeseType
assertThat( mapper.mapStringToCheese( null ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.mapStringToCheese( "BRIE" ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.mapStringToCheese( "ROQUEFORT" ) ).isEqualTo( CheeseType.ROQUEFORT );
assertThat( mapper.mapStringToCheese( "UNKNOWN" ) ).isEqualTo( CheeseType.BRIE );
// CustomCheeseType -> String
assertThat( mapper.mapStringToCustom( null ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.mapStringToCustom( "UNRECOGNIZED" ) ).isEqualTo( CustomCheeseType.CUSTOM_BRIE );
assertThat( mapper.mapStringToCustom( "BRIE" ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.mapStringToCustom( "ROQUEFORT" ) ).isEqualTo( CustomCheeseType.CUSTOM_ROQUEFORT );
assertThat( mapper.mapStringToCustom( "UNKNOWN" ) ).isEqualTo( CustomCheeseType.CUSTOM_BRIE );
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.value.spi;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface OverridesCustomCheeseMapper {
OverridesCustomCheeseMapper INSTANCE = Mappers.getMapper( OverridesCustomCheeseMapper.class );
@ValueMapping(source = "CUSTOM_BRIE", target = "ROQUEFORT")
@ValueMapping(source = MappingConstants.NULL, target = "ROQUEFORT")
CheeseType map(CustomCheeseType cheese);
@ValueMapping(source = "BRIE", target = "CUSTOM_ROQUEFORT")
@ValueMapping(source = MappingConstants.NULL, target = "CUSTOM_ROQUEFORT")
CustomCheeseType map(CheeseType cheese);
@ValueMapping(source = "CUSTOM_ROQUEFORT", target = "BRIE")
@ValueMapping(source = MappingConstants.NULL, target = "ROQUEFORT")
String mapToString(CustomCheeseType cheeseType);
@ValueMapping(source = "ROQUEFORT", target = "BRIE")
String mapToString(CheeseType cheeseType);
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "CUSTOM_BRIE")
@ValueMapping(source = "BRIE", target = "CUSTOM_ROQUEFORT")
@ValueMapping(source = MappingConstants.NULL, target = "CUSTOM_ROQUEFORT")
CustomCheeseType mapStringToCustom(String cheese);
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "BRIE")
@ValueMapping(source = "BRIE", target = "ROQUEFORT")
@ValueMapping(source = MappingConstants.NULL, target = "ROQUEFORT")
CheeseType mapStringToCheese(String cheese);
}

View File

@ -0,0 +1,138 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.value.spi;
import javax.annotation.processing.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2020-05-16T12:53:12+0200",
comments = "version: , compiler: javac, environment: Java 14.0.1 (Oracle Corporation)"
)
public class CustomCheeseMapperImpl implements CustomCheeseMapper {
@Override
public CheeseType map(CustomCheeseType cheese) {
if ( cheese == null ) {
return null;
}
CheeseType cheeseType;
switch ( cheese ) {
case UNSPECIFIED: cheeseType = null;
break;
case CUSTOM_BRIE: cheeseType = CheeseType.BRIE;
break;
case CUSTOM_ROQUEFORT: cheeseType = CheeseType.ROQUEFORT;
break;
case UNRECOGNIZED: cheeseType = null;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return cheeseType;
}
@Override
public CustomCheeseType map(CheeseType cheese) {
if ( cheese == null ) {
return CustomCheeseType.UNSPECIFIED;
}
CustomCheeseType customCheeseType;
switch ( cheese ) {
case BRIE: customCheeseType = CustomCheeseType.CUSTOM_BRIE;
break;
case ROQUEFORT: customCheeseType = CustomCheeseType.CUSTOM_ROQUEFORT;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return customCheeseType;
}
@Override
public String mapToString(CustomCheeseType cheeseType) {
if ( cheeseType == null ) {
return null;
}
String string;
switch ( cheeseType ) {
case UNSPECIFIED: string = null;
break;
case CUSTOM_BRIE: string = "BRIE";
break;
case CUSTOM_ROQUEFORT: string = "ROQUEFORT";
break;
case UNRECOGNIZED: string = null;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheeseType );
}
return string;
}
@Override
public String mapToString(CheeseType cheeseType) {
if ( cheeseType == null ) {
return null;
}
String string;
switch ( cheeseType ) {
case BRIE: string = "BRIE";
break;
case ROQUEFORT: string = "ROQUEFORT";
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheeseType );
}
return string;
}
@Override
public CustomCheeseType mapStringToCustom(String cheese) {
if ( cheese == null ) {
return CustomCheeseType.UNSPECIFIED;
}
CustomCheeseType customCheeseType;
switch ( cheese ) {
case "BRIE": customCheeseType = CustomCheeseType.CUSTOM_BRIE;
break;
case "ROQUEFORT": customCheeseType = CustomCheeseType.CUSTOM_ROQUEFORT;
break;
default: customCheeseType = CustomCheeseType.CUSTOM_BRIE;
}
return customCheeseType;
}
@Override
public CheeseType mapStringToCheese(String cheese) {
if ( cheese == null ) {
return null;
}
CheeseType cheeseType;
switch ( cheese ) {
case "BRIE": cheeseType = CheeseType.BRIE;
break;
case "ROQUEFORT": cheeseType = CheeseType.ROQUEFORT;
break;
default: cheeseType = CheeseType.BRIE;
}
return cheeseType;
}
}