diff --git a/processor/src/main/java/org/mapstruct/ap/MapperGenerationVisitor.java b/processor/src/main/java/org/mapstruct/ap/MapperGenerationVisitor.java index de640049a..7c7683bf6 100644 --- a/processor/src/main/java/org/mapstruct/ap/MapperGenerationVisitor.java +++ b/processor/src/main/java/org/mapstruct/ap/MapperGenerationVisitor.java @@ -52,8 +52,8 @@ 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.Executables; import org.mapstruct.ap.util.Filters; -import org.mapstruct.ap.util.Strings; import org.mapstruct.ap.util.TypeUtil; import org.mapstruct.ap.writer.ModelWriter; @@ -158,9 +158,11 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { propertyMappings.add( new PropertyMapping( - property.getSourceName(), + property.getSourceReadAccessorName(), + property.getSourceWriteAccessorName(), property.getSourceType(), - property.getTargetName(), + property.getTargetReadAccessorName(), + property.getTargetWriteAccessorName(), property.getTargetType(), propertyMappingMethod != null ? new MappingMethod( propertyMappingMethod.getDeclaringMapper(), @@ -173,15 +175,11 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { reversePropertyMappingMethod.getParameterName() ) : null, conversion != null ? conversion.to( - mappingMethod.getParameterName() + "." + getAccessor( - property.getSourceName() - ), + mappingMethod.getParameterName() + "." + property.getSourceReadAccessorName() + "()", property.getTargetType() ) : null, conversion != null ? conversion.from( - reverseMappingMethod.getParameterName() + "." + getAccessor( - property.getTargetName() - ), + reverseMappingMethod.getParameterName() + "." + property.getTargetReadAccessorName() + "()", property.getSourceType() ) : null ) @@ -246,10 +244,6 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { return usedMapperTypes; } - private String getAccessor(String name) { - return "get" + Strings.capitalize( name ) + "()"; - } - private MappingMethod getElementMappingMethod(Iterable methods, Method method) { Method elementMappingMethod = null; for ( Method oneMethod : methods ) { @@ -376,19 +370,27 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { for ( ExecutableElement getterMethod : Filters.getterMethodsIn( parameterElement.getEnclosedElements() ) ) { - String sourcePropertyName = getPropertyName( getterMethod ); + String sourcePropertyName = Executables.getPropertyName( getterMethod ); Mapping mapping = mappings.get( sourcePropertyName ); for ( ExecutableElement setterMethod : Filters.setterMethodsIn( returnTypeElement.getEnclosedElements() ) ) { - String targetPropertyName = getPropertyName( setterMethod ); + String targetPropertyName = Executables.getPropertyName( setterMethod ); if ( targetPropertyName.equals( mapping != null ? mapping.getTargetName() : sourcePropertyName ) ) { properties.add( new MappedProperty( sourcePropertyName, + getterMethod.getSimpleName().toString(), + Executables.getCorrespondingSetterMethod( parameterElement, getterMethod ) + .getSimpleName() + .toString(), retrieveReturnType( getterMethod ), mapping != null ? mapping.getTargetName() : targetPropertyName, + Executables.getCorrespondingGetterMethod( returnTypeElement, setterMethod ) + .getSimpleName() + .toString(), + setterMethod.getSimpleName().toString(), retrieveParameter( setterMethod ).getType() ) ); @@ -399,13 +401,6 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { return properties; } - private String getPropertyName(ExecutableElement getterOrSetterMethod) { - //TODO consider is/has - return Introspector.decapitalize( - getterOrSetterMethod.getSimpleName().toString().substring( 3 ) - ); - } - private Map getMappings(MappingsPrism mappingsAnnotation) { Map mappings = new HashMap(); diff --git a/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java index 1dc9519f3..cdf0d47c7 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java @@ -18,21 +18,34 @@ */ package org.mapstruct.ap.model; +/** + * Represents the mapping between a source and target property, e.g. from + * {@code String Source#foo} to {@code int Target#bar}. Name and type of source + * and target property can differ. If they have different types, the mapping + * must either refer to a mapping method or a conversion. + * + * @author Gunnar Morling + */ public class PropertyMapping { - private final String sourceName; + private final String sourceReadAccessorName; + private final String sourceWriteAccessorName; private final Type sourceType; - private final String targetName; + private final String targetReadAccessorName; + private final String targetWriteAccessorName; private final Type targetType; + private final MappingMethod mappingMethod; private final MappingMethod reverseMappingMethod; private final String toConversion; private final String fromConversion; - public PropertyMapping(String sourceName, Type sourceType, String targetName, Type targetType, MappingMethod mappingMethod, MappingMethod reverseMappingMethod, String toConversion, String fromConversion) { - this.sourceName = sourceName; + public PropertyMapping(String sourceReadAccessorName, String sourceWriteAccessorName, Type sourceType, String targetReadAccessorName, String targetWriteAccessorName, Type targetType, MappingMethod mappingMethod, MappingMethod reverseMappingMethod, String toConversion, String fromConversion) { + this.sourceReadAccessorName = sourceReadAccessorName; + this.sourceWriteAccessorName = sourceWriteAccessorName; this.sourceType = sourceType; - this.targetName = targetName; + this.targetReadAccessorName = targetReadAccessorName; + this.targetWriteAccessorName = targetWriteAccessorName; this.targetType = targetType; this.mappingMethod = mappingMethod; this.reverseMappingMethod = reverseMappingMethod; @@ -40,16 +53,24 @@ public class PropertyMapping { this.fromConversion = fromConversion; } - public String getSourceName() { - return sourceName; + public String getSourceReadAccessorName() { + return sourceReadAccessorName; + } + + public String getSourceWriteAccessorName() { + return sourceWriteAccessorName; } public Type getSourceType() { return sourceType; } - public String getTargetName() { - return targetName; + public String getTargetReadAccessorName() { + return targetReadAccessorName; + } + + public String getTargetWriteAccessorName() { + return targetWriteAccessorName; } public Type getTargetType() { @@ -75,9 +96,9 @@ public class PropertyMapping { @Override public String toString() { return "PropertyMapping {" + - "\n sourceName='" + sourceName + "\'," + + "\n sourceName='" + sourceReadAccessorName + "/" + sourceWriteAccessorName + "\'," + "\n sourceType=" + sourceType + "," + - "\n targetName='" + targetName + "\'," + + "\n targetName='" + targetReadAccessorName + "/" + targetWriteAccessorName + "\'," + "\n targetType=" + targetType + "," + "\n mappingMethod=" + mappingMethod + "," + "\n reverseMappingMethod=" + reverseMappingMethod + "," + diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/MappedProperty.java b/processor/src/main/java/org/mapstruct/ap/model/source/MappedProperty.java index a5a0fe92f..215921893 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/MappedProperty.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/MappedProperty.java @@ -23,14 +23,24 @@ import org.mapstruct.ap.model.Type; public class MappedProperty { private final String sourceName; + private final String sourceReadAccessorName; + private final String sourceWriteAccessorName; private final Type sourceType; private final String targetName; + private final String targetReadAccessorName; + private final String targetWriteAccessorName; private final Type targetType; - public MappedProperty(String sourceName, Type sourceType, String targetName, Type targetType) { + public MappedProperty(String sourceName, String sourceReadAccessorName, String sourceWriteAccessorName, + Type sourceType, String targetName, String targetReadAccessorName, String targetWriteAccessorName, + Type targetType) { this.sourceName = sourceName; + this.sourceReadAccessorName = sourceReadAccessorName; + this.sourceWriteAccessorName = sourceWriteAccessorName; this.sourceType = sourceType; this.targetName = targetName; + this.targetReadAccessorName = targetReadAccessorName; + this.targetWriteAccessorName = targetWriteAccessorName; this.targetType = targetType; } @@ -38,6 +48,14 @@ public class MappedProperty { return sourceName; } + public String getSourceReadAccessorName() { + return sourceReadAccessorName; + } + + public String getSourceWriteAccessorName() { + return sourceWriteAccessorName; + } + public Type getSourceType() { return sourceType; } @@ -46,6 +64,14 @@ public class MappedProperty { return targetName; } + public String getTargetReadAccessorName() { + return targetReadAccessorName; + } + + public String getTargetWriteAccessorName() { + return targetWriteAccessorName; + } + public Type getTargetType() { return targetType; } diff --git a/processor/src/main/java/org/mapstruct/ap/util/Executables.java b/processor/src/main/java/org/mapstruct/ap/util/Executables.java new file mode 100644 index 000000000..0beb2399d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/util/Executables.java @@ -0,0 +1,109 @@ +/** + * Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * 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 java.beans.Introspector; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; + +/** + * Provides functionality around {@link ExecutableElement}s. + * + * @author Gunnar Morling + */ +public class Executables { + + public static boolean isGetterMethod(ExecutableElement method) { + return isNonBooleanGetterMethod( method ) || isBooleanGetterMethod( method ); + } + + private static boolean isNonBooleanGetterMethod(ExecutableElement method) { + String name = method.getSimpleName().toString(); + + return method.getParameters().isEmpty() && + name.startsWith( "get" ) && + name.length() > 3 && + method.getReturnType().getKind() != TypeKind.VOID; + } + + private static boolean isBooleanGetterMethod(ExecutableElement method) { + String name = method.getSimpleName().toString(); + + return method.getParameters().isEmpty() && + name.startsWith( "is" ) && + name.length() > 2 && + method.getReturnType().getKind() == TypeKind.BOOLEAN; + } + + public static boolean isSetterMethod(ExecutableElement method) { + String name = method.getSimpleName().toString(); + + if ( name.startsWith( "set" ) && name.length() > 3 && method.getParameters() + .size() == 1 && method.getReturnType().getKind() == TypeKind.VOID ) { + return true; + } + + return false; + } + + public static String getPropertyName(ExecutableElement getterOrSetterMethod) { + if ( isNonBooleanGetterMethod( getterOrSetterMethod ) ) { + return Introspector.decapitalize( + getterOrSetterMethod.getSimpleName().toString().substring( 3 ) + ); + } + else if ( isBooleanGetterMethod( getterOrSetterMethod ) ) { + return Introspector.decapitalize( + getterOrSetterMethod.getSimpleName().toString().substring( 2 ) + ); + } + else if ( isSetterMethod( getterOrSetterMethod ) ) { + return Introspector.decapitalize( + getterOrSetterMethod.getSimpleName().toString().substring( 3 ) + ); + } + + throw new IllegalArgumentException( "Executable " + getterOrSetterMethod + " is not getter or setter method." ); + } + + public static ExecutableElement getCorrespondingSetterMethod(Element element, ExecutableElement getterMethod) { + String propertyName = getPropertyName( getterMethod ); + + for ( ExecutableElement setterMethod : Filters.setterMethodsIn( element.getEnclosedElements() ) ) { + if ( getPropertyName( setterMethod ).equals( propertyName ) ) { + return setterMethod; + } + } + + return null; + } + + public static ExecutableElement getCorrespondingGetterMethod(Element element, ExecutableElement setterMethod) { + String propertyName = getPropertyName( setterMethod ); + + for ( ExecutableElement getterMethod : Filters.getterMethodsIn( element.getEnclosedElements() ) ) { + if ( getPropertyName( getterMethod ).equals( propertyName ) ) { + return getterMethod; + } + } + + return null; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/util/Filters.java b/processor/src/main/java/org/mapstruct/ap/util/Filters.java index 1e932efb2..e12aebd5b 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/Filters.java +++ b/processor/src/main/java/org/mapstruct/ap/util/Filters.java @@ -22,7 +22,6 @@ import java.util.LinkedList; import java.util.List; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; import static javax.lang.model.util.ElementFilter.methodsIn; @@ -37,11 +36,7 @@ public class Filters { List getterMethods = new LinkedList(); for ( ExecutableElement method : methodsIn( elements ) ) { - //TODO: consider is/has - String name = method.getSimpleName().toString(); - - if ( name.startsWith( "get" ) && name.length() > 3 && method.getParameters() - .isEmpty() && method.getReturnType().getKind() != TypeKind.VOID ) { + if ( Executables.isGetterMethod( method ) ) { getterMethods.add( method ); } } @@ -53,11 +48,7 @@ public class Filters { List setterMethods = new LinkedList(); for ( ExecutableElement method : methodsIn( elements ) ) { - //TODO: consider is/has - String name = method.getSimpleName().toString(); - - if ( name.startsWith( "set" ) && name.length() > 3 && method.getParameters() - .size() == 1 && method.getReturnType().getKind() == TypeKind.VOID ) { + if ( Executables.isSetterMethod( method ) ) { setterMethods.add( method ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/util/NativeTypes.java index 92a4e8956..6329bbf47 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/NativeTypes.java +++ b/processor/src/main/java/org/mapstruct/ap/util/NativeTypes.java @@ -22,6 +22,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +/** + * Provides functionality around the Java primitive data types and their wrapper + * types. + * + * @author Gunnar Morling + */ public class NativeTypes { private final static Map, Class> wrapperToPrimitiveTypes; diff --git a/processor/src/main/resources/mapper-implementation.ftl b/processor/src/main/resources/mapper-implementation.ftl index 0ab979341..e523bcd7c 100644 --- a/processor/src/main/resources/mapper-implementation.ftl +++ b/processor/src/main/resources/mapper-implementation.ftl @@ -69,10 +69,10 @@ public class ${implementationName} implements ${interfaceName} { <@simpleMap sourceBeanName=beanMapping.mappingMethod.parameterName sourceType=propertyMapping.sourceType - sourcePropertyName=propertyMapping.sourceName + sourceAccessorName=propertyMapping.sourceReadAccessorName targetBeanName=beanMapping.targetType.name?uncap_first targetType=propertyMapping.targetType - targetPropertyName=propertyMapping.targetName?cap_first + targetAccessorName=propertyMapping.targetWriteAccessorName conversion=propertyMapping.toConversion mappingMethod=propertyMapping.mappingMethod /> @@ -119,10 +119,10 @@ public class ${implementationName} implements ${interfaceName} { <@simpleMap sourceBeanName=beanMapping.reverseMappingMethod.parameterName sourceType=propertyMapping.targetType - sourcePropertyName=propertyMapping.targetName + sourceAccessorName=propertyMapping.targetReadAccessorName targetBeanName=beanMapping.sourceType.name?uncap_first targetType=propertyMapping.sourceType - targetPropertyName=propertyMapping.sourceName?cap_first + targetAccessorName=propertyMapping.sourceWriteAccessorName conversion=propertyMapping.fromConversion mappingMethod=propertyMapping.reverseMappingMethod /> @@ -137,27 +137,27 @@ public class ${implementationName} implements ${interfaceName} { } <#-- Generates the mapping of one bean property --> -<#macro simpleMap sourceBeanName sourceType sourcePropertyName targetBeanName targetType targetPropertyName conversion="" mappingMethod=""> +<#macro simpleMap sourceBeanName sourceType sourceAccessorName targetBeanName targetType targetAccessorName conversion="" mappingMethod=""> <#-- a) simple conversion --> <#if conversion != ""> <#if sourceType.primitive == false> - if ( ${sourceBeanName}.get${sourcePropertyName?cap_first}() != null ) { - ${targetBeanName}.set${targetPropertyName}( ${conversion} ); + if ( ${sourceBeanName}.${sourceAccessorName}() != null ) { + ${targetBeanName}.${targetAccessorName}( ${conversion} ); } <#else> - ${targetBeanName}.set${targetPropertyName}( ${conversion} ); + ${targetBeanName}.${targetAccessorName}( ${conversion} ); <#-- b) invoke mapping method --> <#elseif mappingMethod != ""> - ${targetBeanName}.set${targetPropertyName}( <#if mappingMethod.declaringMapper??>${mappingMethod.declaringMapper.name?uncap_first}.${mappingMethod.name}( ${sourceBeanName}.get${sourcePropertyName?cap_first}() ) ); + ${targetBeanName}.${targetAccessorName}( <#if mappingMethod.declaringMapper??>${mappingMethod.declaringMapper.name?uncap_first}.${mappingMethod.name}( ${sourceBeanName}.${sourceAccessorName}() ) ); <#else> <#if targetType.collectionType == true> - if ( ${sourceBeanName}.get${sourcePropertyName?cap_first}() != null ) { - ${targetBeanName}.set${targetPropertyName}( new <#if targetType.collectionImplementationType??>${targetType.collectionImplementationType.name}<#else>${targetType.name}<#if targetType.elementType??><${targetType.elementType.name}>( ${sourceBeanName}.get${sourcePropertyName?cap_first}() ) ); + if ( ${sourceBeanName}.${sourceAccessorName}() != null ) { + ${targetBeanName}.${targetAccessorName}( new <#if targetType.collectionImplementationType??>${targetType.collectionImplementationType.name}<#else>${targetType.name}<#if targetType.elementType??><${targetType.elementType.name}>( ${sourceBeanName}.${sourceAccessorName}() ) ); } <#-- c) simply set --> <#else> - ${targetBeanName}.set${targetPropertyName}( ${sourceBeanName}.get${sourcePropertyName?cap_first}() ); + ${targetBeanName}.${targetAccessorName}( ${sourceBeanName}.${sourceAccessorName}() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanConversionTest.java index 298a8e32f..80025c64b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanConversionTest.java @@ -34,22 +34,26 @@ public class BooleanConversionTest extends MapperTestBase { @Test public void shouldApplyBooleanConversion() { BooleanSource source = new BooleanSource(); + source.setB( true ); source.setBool( true ); BooleanTarget target = BooleanMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( Boolean.TRUE ); assertThat( target.getBool() ).isEqualTo( Boolean.TRUE ); } @Test public void shouldApplyReverseBooleanConversion() { BooleanTarget target = new BooleanTarget(); + target.setB( Boolean.TRUE ); target.setBool( Boolean.TRUE ); BooleanSource source = BooleanMapper.INSTANCE.targetToSource( target ); assertThat( source ).isNotNull(); + assertThat( source.isB() ).isEqualTo( true ); assertThat( source.getBool() ).isEqualTo( true ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanSource.java index daa6c692f..980ce4801 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanSource.java @@ -20,6 +20,16 @@ package org.mapstruct.ap.test.conversion.nativetypes; public class BooleanSource { + private boolean b; + + public boolean isB() { + return b; + } + + public void setB(boolean b) { + this.b = b; + } + private boolean bool; public boolean getBool() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanTarget.java index fa3e7496d..830ea7f8e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanTarget.java @@ -20,8 +20,18 @@ package org.mapstruct.ap.test.conversion.nativetypes; public class BooleanTarget { + private Boolean b; + private Boolean bool; + public Boolean getB() { + return b; + } + + public void setB(Boolean b) { + this.b = b; + } + public Boolean getBool() { return bool; }