#1 Adding support for implicit mapping of enums

This commit is contained in:
Gunnar Morling 2013-03-03 13:06:06 +01:00
parent 192cfc8937
commit 7121f22ff0
14 changed files with 254 additions and 71 deletions

View File

@ -52,6 +52,7 @@ import org.mapstruct.ap.model.source.MappedProperty;
import org.mapstruct.ap.model.source.Mapping;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.Parameter;
import org.mapstruct.ap.util.TypeUtil;
import org.mapstruct.ap.writer.ModelWriter;
import static javax.lang.model.util.ElementFilter.methodsIn;
@ -66,11 +67,13 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
private final ProcessingEnvironment processingEnvironment;
private final Types typeUtils;
private final Elements elementUtils;
private final TypeUtil typeUtil;
public MapperGenerationVisitor(ProcessingEnvironment processingEnvironment) {
this.processingEnvironment = processingEnvironment;
this.typeUtils = processingEnvironment.getTypeUtils();
this.elementUtils = processingEnvironment.getElementUtils();
this.typeUtil = new TypeUtil( elementUtils, typeUtils );
}
@Override
@ -101,6 +104,8 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
Set<Method> processedMethods = new HashSet<Method>();
List<BeanMapping> mappings = new ArrayList<BeanMapping>();
Conversions conversions = new Conversions( elementUtils, typeUtils, typeUtil );
for ( Method method : methods ) {
if ( processedMethods.contains( method ) ) {
continue;
@ -116,7 +121,6 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
Method rawReverseMappingMethod = getReverseMappingMethod( methods, method );
if ( rawReverseMappingMethod != null ) {
processedMethods.add( rawReverseMappingMethod );
// MappingMethod reverseElementMappingMethod = rawReverseElementMappingMethod == null ? null : new MappingMethod(rawReverseElementMappingMethod.getName(), rawReverseElementMappingMethod.getParameterName() );
reverseMappingMethod = new MappingMethod(
rawReverseMappingMethod.getName(),
@ -125,14 +129,13 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
);
}
List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
for ( MappedProperty property : method.getMappedProperties() ) {
Method propertyMappingMethod = getPropertyMappingMethod( methods, property );
Method reversePropertyMappingMethod = getReversePropertyMappingMethod( methods, property );
Conversion conversion = Conversions.getConversion( property.getSourceType(), property.getTargetType() );
Conversion conversion = conversions.getConversion( property.getSourceType(), property.getTargetType() );
propertyMappings.add(
new PropertyMapping(
@ -152,12 +155,14 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
conversion != null ? conversion.to(
mappingMethod.getParameterName() + "." + getAccessor(
property.getSourceName()
)
),
property.getTargetType()
) : null,
conversion != null ? conversion.from(
reverseMappingMethod.getParameterName() + "." + getAccessor(
property.getTargetName()
)
),
property.getSourceType()
) : null
)
);
@ -287,20 +292,12 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
for ( ExecutableElement getterMethod : getterMethodsIn( parameterElement.getEnclosedElements() ) ) {
String sourcePropertyName = Introspector.decapitalize(
getterMethod.getSimpleName()
.toString()
.substring( 3 )
);
String sourcePropertyName = getPropertyName( getterMethod );
Mapping mapping = mappings.get( sourcePropertyName );
for ( ExecutableElement setterMethod : setterMethodsIn( returnTypeElement.getEnclosedElements() ) ) {
String targetPropertyName = Introspector.decapitalize(
setterMethod.getSimpleName()
.toString()
.substring( 3 )
);
String targetPropertyName = getPropertyName( setterMethod );
if ( targetPropertyName.equals( mapping != null ? mapping.getTargetName() : sourcePropertyName ) ) {
properties.add(
@ -319,6 +316,13 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
return properties;
}
private String getPropertyName(ExecutableElement getterOrSetterMethod) {
//TODO consider is/has
return Introspector.decapitalize(
getterOrSetterMethod.getSimpleName().toString().substring( 3 )
);
}
private Map<String, Mapping> getMappings(AnnotationMirror annotationMirror) {
Map<String, Mapping> mappings = new HashMap<String, Mapping>();
@ -344,25 +348,12 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
Type converterType = null;
if ( converterTypeMirror != null ) {
converterType = getType( (DeclaredType) converterTypeMirror );
converterType = typeUtil.getType( (DeclaredType) converterTypeMirror );
}
return new Mapping( sourcePropertyName, targetPropertyName, converterType );
}
private Type getType(DeclaredType type) {
Type elementType = null;
if ( !type.getTypeArguments().isEmpty() ) {
elementType = retrieveType( type.getTypeArguments().iterator().next() );
}
return new Type(
elementUtils.getPackageOf( type.asElement() ).toString(),
type.asElement().getSimpleName().toString(),
elementType
);
}
private Parameter retrieveParameter(ExecutableElement method) {
List<? extends VariableElement> parameters = method.getParameters();
@ -375,26 +366,15 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
return new Parameter(
parameter.getSimpleName().toString(),
retrieveType( parameter.asType() )
typeUtil.retrieveType( parameter.asType() )
);
}
private Type retrieveReturnType(ExecutableElement method) {
return retrieveType( method.getReturnType() );
}
private Type retrieveType(TypeMirror mirror) {
if ( mirror.getKind() == TypeKind.DECLARED ) {
return getType( ( (DeclaredType) mirror ) );
}
else {
return new Type( null, mirror.toString() );
}
return typeUtil.retrieveType( method.getReturnType() );
}
private String getStringValue(AnnotationMirror annotationMirror, String attributeName) {
for ( Entry<? extends ExecutableElement, ? extends AnnotationValue> oneAttribute : annotationMirror.getElementValues()
.entrySet() ) {
@ -407,7 +387,6 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
}
private TypeMirror getTypeMirrorValue(AnnotationMirror annotationMirror, String attributeName) {
for ( Entry<? extends ExecutableElement, ? extends AnnotationValue> oneAttribute : annotationMirror.getElementValues()
.entrySet() ) {
@ -420,7 +399,6 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
}
private List<? extends AnnotationValue> getAnnotationValueListValue(AnnotationMirror annotationMirror, String attributeName) {
for ( Entry<? extends ExecutableElement, ? extends AnnotationValue> oneAttribute : annotationMirror.getElementValues()
.entrySet() ) {
@ -464,7 +442,6 @@ public class MapperGenerationVisitor extends ElementKindVisitor6<Void, Void> {
return setterMethods;
}
private static class NameDeterminationVisitor extends ElementKindVisitor6<String, Void> {
@Override

View File

@ -15,9 +15,11 @@
*/
package org.mapstruct.ap.conversion;
import org.mapstruct.ap.model.Type;
public interface Conversion {
String to(String sourcePropertyAccessor);
String to(String sourcePropertyAccessor, Type type);
String from(String targetPropertyAccessor);
String from(String targetPropertyAccessor, Type type);
}

View File

@ -15,28 +15,45 @@
*/
package org.mapstruct.ap.conversion;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.HashMap;
import java.util.Map;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.mapstruct.ap.model.Type;
import org.mapstruct.ap.util.TypeUtil;
import static org.mapstruct.ap.conversion.ReverseConversion.reverse;
public class Conversions {
private static ConcurrentMap<Key, Conversion> conversions = new ConcurrentHashMap<Conversions.Key, Conversion>();
private TypeUtil typeUtil;
private final Map<Key, Conversion> conversions = new HashMap<Conversions.Key, Conversion>();
private final DeclaredType enumType;
private final DeclaredType stringType;
public Conversions(Elements elementUtils, Types typeUtils, TypeUtil typeUtil) {
this.typeUtil = typeUtil;
this.enumType = typeUtils.getDeclaredType( elementUtils.getTypeElement( Enum.class.getCanonicalName() ) );
this.stringType = typeUtils.getDeclaredType( elementUtils.getTypeElement( String.class.getCanonicalName() ) );
static {
register( int.class, Long.class, new IntLongConversion() );
register( int.class, String.class, new IntStringConversion() );
register( Enum.class, String.class, new EnumStringConversion() );
}
private static void register(Class<?> sourceType, Class<?> targetType, Conversion conversion) {
private void register(Class<?> sourceType, Class<?> targetType, Conversion conversion) {
conversions.put( Key.forClasses( sourceType, targetType ), conversion );
conversions.put( Key.forClasses( targetType, sourceType ), reverse( conversion ) );
}
public static Conversion getConversion(Type sourceType, Type targetType) {
public Conversion getConversion(Type sourceType, Type targetType) {
if ( sourceType.isEnumType() && targetType.equals( typeUtil.getType( stringType ) ) ) {
sourceType = typeUtil.getType( enumType );
}
return conversions.get( new Key( sourceType, targetType ) );
}
@ -53,6 +70,12 @@ public class Conversions {
this.targetType = targetType;
}
@Override
public String toString() {
return "Key [sourceType=" + sourceType + ", targetType="
+ targetType + "]";
}
@Override
public int hashCode() {
final int prime = 31;

View File

@ -0,0 +1,31 @@
/**
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.conversion;
import org.mapstruct.ap.model.Type;
public class EnumStringConversion implements Conversion {
@Override
public String to(String sourcePropertyAccessor, Type type) {
return sourcePropertyAccessor + " != null ? " + sourcePropertyAccessor + ".toString() : null";
}
@Override
public String from(String targetPropertyAccessor, Type type) {
return targetPropertyAccessor + " != null ? Enum.valueOf( " + type.getName() + ".class, " + targetPropertyAccessor + " ) : null";
}
}

View File

@ -15,15 +15,17 @@
*/
package org.mapstruct.ap.conversion;
import org.mapstruct.ap.model.Type;
public class IntLongConversion implements Conversion {
@Override
public String to(String sourcePropertyAccessor) {
public String to(String sourcePropertyAccessor, Type type) {
return "Long.valueOf( " + sourcePropertyAccessor + " )";
}
@Override
public String from(String targetPropertyAccessor) {
public String from(String targetPropertyAccessor, Type type) {
return targetPropertyAccessor + ".intValue()";
}
}

View File

@ -15,15 +15,17 @@
*/
package org.mapstruct.ap.conversion;
import org.mapstruct.ap.model.Type;
public class IntStringConversion implements Conversion {
@Override
public String to(String sourcePropertyAccessor) {
public String to(String sourcePropertyAccessor, Type type) {
return "String.valueOf( " + sourcePropertyAccessor + " )";
}
@Override
public String from(String targetPropertyAccessor) {
public String from(String targetPropertyAccessor, Type type) {
return "Integer.parseInt( " + targetPropertyAccessor + " )";
}
}

View File

@ -15,6 +15,8 @@
*/
package org.mapstruct.ap.conversion;
import org.mapstruct.ap.model.Type;
public class ReverseConversion implements Conversion {
private Conversion conversion;
@ -28,12 +30,12 @@ public class ReverseConversion implements Conversion {
}
@Override
public String to(String sourcePropertyAccessor) {
return conversion.from( sourcePropertyAccessor );
public String to(String sourcePropertyAccessor, Type type) {
return conversion.from( sourcePropertyAccessor, type );
}
@Override
public String from(String targetPropertyAccessor) {
return conversion.to( targetPropertyAccessor );
public String from(String targetPropertyAccessor, Type type) {
return conversion.to( targetPropertyAccessor, type );
}
}

View File

@ -33,13 +33,13 @@ public class Type {
private final String packageName;
private final String name;
private final Type elementType;
private final boolean isEnumType;
public static Type forClass(Class<?> clazz) {
Package pakkage = clazz.getPackage();
if ( pakkage != null ) {
return new Type( pakkage.getName(), clazz.getSimpleName() );
return new Type( pakkage.getName(), clazz.getSimpleName(), null, clazz.isEnum() );
}
else {
return new Type( clazz.getSimpleName() );
@ -47,21 +47,18 @@ public class Type {
}
public Type(String name) {
this.packageName = null;
this.name = name;
this.elementType = null;
this( null, name, null, false );
}
public Type(String packageName, String name) {
this.packageName = packageName;
this.name = name;
this.elementType = null;
this( packageName, name, null, false );
}
public Type(String packageName, String name, Type elementType) {
public Type(String packageName, String name, Type elementType, boolean isEnumType) {
this.packageName = packageName;
this.name = name;
this.elementType = elementType;
this.isEnumType = isEnumType;
}
public String getPackageName() {
@ -80,6 +77,10 @@ public class Type {
return packageName == null && primitiveTypeNames.contains( name );
}
public boolean isEnumType() {
return isEnumType;
}
@Override
public String toString() {
if ( packageName == null ) {

View File

@ -0,0 +1,61 @@
/**
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.util;
import javax.lang.model.element.ElementKind;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.mapstruct.ap.model.Type;
public class TypeUtil {
private final Elements elementUtils;
private final Types typeUtils;
public TypeUtil(Elements elementUtils, Types typeUtils) {
this.elementUtils = elementUtils;
this.typeUtils = typeUtils;
}
public Type getType(DeclaredType type) {
Type elementType = isIterableType( type ) ? retrieveType( type.getTypeArguments().iterator().next() ) : null;
return new Type(
elementUtils.getPackageOf( type.asElement() ).toString(),
type.asElement().getSimpleName().toString(),
elementType,
type.asElement().getKind() == ElementKind.ENUM
);
}
private boolean isIterableType(DeclaredType type) {
TypeMirror iterableType = typeUtils.getDeclaredType( elementUtils.getTypeElement( Iterable.class.getCanonicalName() ) );
return typeUtils.isSubtype( type, iterableType );
}
public Type retrieveType(TypeMirror mirror) {
if ( mirror.getKind() == TypeKind.DECLARED ) {
return getType( ( (DeclaredType) mirror ) );
}
else {
return new Type( mirror.toString() );
}
}
}

View File

@ -26,6 +26,10 @@ public class ${implementationName} implements ${interfaceName} {
<#if beanMapping.iterableMapping == true>
@Override
public ${beanMapping.targetType.name}<${beanMapping.targetType.elementType.name}> ${beanMapping.mappingMethod.name}(${beanMapping.sourceType.name}<${beanMapping.sourceType.elementType.name}> ${beanMapping.mappingMethod.parameterName}) {
if( ${beanMapping.mappingMethod.parameterName} == null ) {
return new ${beanMapping.targetType.name}<${beanMapping.targetType.elementType.name}>();
}
${beanMapping.targetType.name}<${beanMapping.targetType.elementType.name}> ${beanMapping.targetType.name?uncap_first} = new ${beanMapping.targetType.name}<${beanMapping.targetType.elementType.name}>();
for ( ${beanMapping.sourceType.elementType.name} ${beanMapping.sourceType.elementType.name?uncap_first} : ${beanMapping.mappingMethod.parameterName} ) {
@ -37,6 +41,10 @@ public class ${implementationName} implements ${interfaceName} {
<#else>
@Override
public ${beanMapping.targetType.name} ${beanMapping.mappingMethod.name}(${beanMapping.sourceType.name} ${beanMapping.mappingMethod.parameterName}) {
if( ${beanMapping.mappingMethod.parameterName} == null ) {
return null;
}
${beanMapping.targetType.name} ${beanMapping.targetType.name?uncap_first} = new ${beanMapping.targetType.name}();
<#list beanMapping.propertyMappings as propertyMapping>
@ -65,6 +73,10 @@ public class ${implementationName} implements ${interfaceName} {
<#if beanMapping.iterableMapping == true>
@Override
public ${beanMapping.sourceType.name}<${beanMapping.sourceType.elementType.name}> ${beanMapping.reverseMappingMethod.name}(${beanMapping.targetType.name}<${beanMapping.targetType.elementType.name}> ${beanMapping.reverseMappingMethod.parameterName}) {
if( ${beanMapping.reverseMappingMethod.parameterName} == null ) {
return new ${beanMapping.sourceType.name}<${beanMapping.sourceType.elementType.name}>();
}
${beanMapping.sourceType.name}<${beanMapping.sourceType.elementType.name}> ${beanMapping.sourceType.name?uncap_first} = new ${beanMapping.sourceType.name}<${beanMapping.sourceType.elementType.name}>();
for ( ${beanMapping.targetType.elementType.name} ${beanMapping.targetType.elementType.name?uncap_first} : ${beanMapping.reverseMappingMethod.parameterName} ) {
@ -76,6 +88,10 @@ public class ${implementationName} implements ${interfaceName} {
<#else>
@Override
public ${beanMapping.sourceType.name} ${beanMapping.reverseMappingMethod.name}(${beanMapping.targetType.name} ${beanMapping.reverseMappingMethod.parameterName}) {
if( ${beanMapping.reverseMappingMethod.parameterName} == null ) {
return null;
}
${beanMapping.sourceType.name} ${beanMapping.sourceType.name?uncap_first} = new ${beanMapping.sourceType.name}();
<#list beanMapping.propertyMappings as propertyMapping>

View File

@ -25,6 +25,7 @@ import javax.tools.JavaFileObject;
import org.mapstruct.ap.test.model.Car;
import org.mapstruct.ap.test.model.CarDto;
import org.mapstruct.ap.test.model.CarMapper;
import org.mapstruct.ap.test.model.Category;
import org.mapstruct.ap.test.model.IntToStringConverter;
import org.mapstruct.ap.test.model.Person;
import org.mapstruct.ap.test.model.PersonDto;
@ -50,7 +51,8 @@ public class CarMapperTest extends MapperTestBase {
Person.class,
PersonDto.class,
CarMapper.class,
IntToStringConverter.class
IntToStringConverter.class,
Category.class
);
boolean compilationSuccessful = compile( diagnostics, sourceFiles );
@ -248,4 +250,30 @@ public class CarMapperTest extends MapperTestBase {
assertThat( car.getPassengers().get( 0 ).getName() ).isEqualTo( "Alice" );
assertThat( car.getPassengers().get( 1 ).getName() ).isEqualTo( "Bill" );
}
@Test
public void shouldMapEnumToString() {
//given
Car car = new Car();
car.setCategory( Category.CONVERTIBLE );
//when
CarDto carDto = CarMapper.INSTANCE.carToCarDto( car );
//then
assertThat( carDto ).isNotNull();
assertThat( carDto.getCategory() ).isEqualTo( "CONVERTIBLE" );
}
@Test
public void shouldMapStringToEnum() {
//given
CarDto carDto = new CarDto();
carDto.setCategory( "CONVERTIBLE" );
//when
Car car = CarMapper.INSTANCE.carDtoToCar( carDto );
//then
assertThat( car ).isNotNull();
assertThat( car.getCategory() ).isEqualTo( Category.CONVERTIBLE );
}
}

View File

@ -25,6 +25,7 @@ public class Car {
private Person driver;
private ArrayList<Person> passengers;
private int price;
private Category category;
public Car() {
}
@ -84,4 +85,12 @@ public class Car {
public void setPrice(int price) {
this.price = price;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
}

View File

@ -25,6 +25,7 @@ public class CarDto {
private PersonDto driver;
private ArrayList<PersonDto> passengers;
private Long price;
private String category;
public CarDto() {
}
@ -84,4 +85,12 @@ public class CarDto {
public void setPrice(Long price) {
this.price = price;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}

View File

@ -0,0 +1,20 @@
/**
* Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.model;
public enum Category {
SEDAN, CONVERTIBLE, TRUCK;
}