From 9992801f28e976658227d0157704e4564613825b Mon Sep 17 00:00:00 2001 From: Gunnar Morling Date: Tue, 4 Nov 2014 23:58:26 +0100 Subject: [PATCH] #212 Adding support for isXy()-style getters for Boolean --- .../mapstruct/ap/model/BeanMappingMethod.java | 83 ++++++++++++++----- .../org/mapstruct/ap/util/Executables.java | 42 +++++++++- .../ap/test/bool/BooleanMappingTest.java | 61 ++++++++++++++ .../org/mapstruct/ap/test/bool/Person.java | 45 ++++++++++ .../org/mapstruct/ap/test/bool/PersonDto.java | 41 +++++++++ .../mapstruct/ap/test/bool/PersonMapper.java | 30 +++++++ 6 files changed, 278 insertions(+), 24 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bool/BooleanMappingTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bool/Person.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bool/PersonDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bool/PersonMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java index c9f3594cc..97923229c 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java @@ -267,40 +267,53 @@ public class BeanMappingMethod extends MappingMethod { Iterator> targetProperties = unprocessedTargetProperties.entrySet().iterator(); + // usually there should be only one getter; only for Boolean there may be two: isFoo() and getFoo() + List candidates = new ArrayList( 2 ); + while ( targetProperties.hasNext() ) { Entry targetProperty = targetProperties.next(); PropertyMapping propertyMapping = null; + if ( propertyMapping == null ) { + for ( Parameter sourceParameter : method.getSourceParameters() ) { - PropertyMapping newPropertyMapping = null; for ( ExecutableElement sourceAccessor : sourceParameter.getType().getGetters() ) { String sourcePropertyName = Executables.getPropertyName( sourceAccessor ); + if ( sourcePropertyName.equals( targetProperty.getKey() ) ) { - - Mapping mapping = method.getSingleMappingByTargetPropertyName( sourcePropertyName ); - - SourceReference sourceRef = new SourceReference.BuilderFromProperty() - .sourceParameter( sourceParameter ) - .type( ctx.getTypeFactory().getReturnType( sourceAccessor ) ) - .accessor( sourceAccessor ) - .name( sourcePropertyName ) - .build(); - - newPropertyMapping = new PropertyMappingBuilder() - .mappingContext( ctx ) - .souceMethod( method ) - .targetAccessor( targetProperty.getValue() ) - .targetPropertyName( targetProperty.getKey() ) - .sourceReference( sourceRef ) - .qualifiers( mapping != null ? mapping.getQualifiers() : null ) - .dateFormat( mapping != null ? mapping.getDateFormat() : null ) - .build(); - break; + candidates.add( sourceAccessor ); } } + PropertyMapping newPropertyMapping = null; + ExecutableElement sourceAccessor = getSourceAccessor( targetProperty.getKey(), candidates ); + if ( sourceAccessor != null ) { + Mapping mapping = method.getSingleMappingByTargetPropertyName( targetProperty.getKey() ); + + SourceReference sourceRef = new SourceReference.BuilderFromProperty() + .sourceParameter( sourceParameter ) + .type( ctx.getTypeFactory().getReturnType( sourceAccessor ) ) + .accessor( sourceAccessor ) + .name( targetProperty.getKey() ) + .build(); + + newPropertyMapping = new PropertyMappingBuilder() + .mappingContext( ctx ) + .souceMethod( method ) + .targetAccessor( targetProperty.getValue() ) + .targetPropertyName( targetProperty.getKey() ) + .sourceReference( sourceRef ) + .qualifiers( mapping != null ? mapping.getQualifiers() : null ) + .dateFormat( mapping != null ? mapping.getDateFormat() : null ) + .build(); + + // candidates are handled + candidates.clear(); + } + + if ( propertyMapping != null && newPropertyMapping != null ) { // TODO improve error message ctx.getMessager().printMessage( @@ -325,6 +338,34 @@ public class BeanMappingMethod extends MappingMethod { } } + private ExecutableElement getSourceAccessor(String sourcePropertyName, List candidates) { + if ( candidates.isEmpty() ) { + return null; + } + else if ( candidates.size() == 1 ) { + return candidates.get( 0 ); + } + // can only be the case for Booleans: isFoo() and getFoo(); The latter is preferred then + else if ( candidates.size() == 2 ) { + if ( candidates.get( 0 ).getSimpleName().toString().startsWith( "get" ) ) { + return candidates.get( 0 ); + } + else { + return candidates.get( 1 ); + } + } + // Should never really happen + else { + ctx.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( "Found several matching getters for property \"%s\"", sourcePropertyName ), + method.getExecutable() + ); + + return null; + } + } + /** * Returns the effective policy for reporting unmapped getReturnType properties. If explicitly set via * {@code Mapper}, this value will be returned. Otherwise the value from the corresponding processor option will diff --git a/processor/src/main/java/org/mapstruct/ap/util/Executables.java b/processor/src/main/java/org/mapstruct/ap/util/Executables.java index e5b31e20c..10395196b 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/Executables.java +++ b/processor/src/main/java/org/mapstruct/ap/util/Executables.java @@ -22,9 +22,15 @@ import java.beans.Introspector; import java.util.HashSet; import java.util.List; import java.util.Set; + import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleElementVisitor6; +import javax.lang.model.util.SimpleTypeVisitor6; /** * Provides functionality around {@link ExecutableElement}s. @@ -51,11 +57,13 @@ public class Executables { private static boolean isBooleanGetterMethod(ExecutableElement method) { String name = method.getSimpleName().toString(); + boolean returnTypeIsBoolean = method.getReturnType().getKind() == TypeKind.BOOLEAN || + "java.lang.Boolean".equals( getQualifiedName( method.getReturnType() ) ); return method.getParameters().isEmpty() && name.startsWith( "is" ) && name.length() > 2 && - method.getReturnType().getKind() == TypeKind.BOOLEAN; + returnTypeIsBoolean; } public static boolean isSetterMethod(ExecutableElement method) { @@ -73,8 +81,8 @@ public class Executables { String name = method.getSimpleName().toString(); return isPublic( method ) && - name.startsWith( "add" ) && name.length() > 3 && - method.getParameters().size() == 1; + name.startsWith( "add" ) && name.length() > 3 && + method.getParameters().size() == 1; } @@ -125,4 +133,32 @@ public class Executables { return propertyNames; } + + private static String getQualifiedName(TypeMirror type) { + DeclaredType declaredType = type.accept( + new SimpleTypeVisitor6() { + @Override + public DeclaredType visitDeclared(DeclaredType t, Void p) { + return t; + } + }, + null + ); + + if ( declaredType == null ) { + return null; + } + + TypeElement typeElement = declaredType.asElement().accept( + new SimpleElementVisitor6() { + @Override + public TypeElement visitType(TypeElement e, Void p) { + return e; + } + }, + null + ); + + return typeElement != null ? typeElement.getQualifiedName().toString() : null; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bool/BooleanMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/bool/BooleanMappingTest.java new file mode 100644 index 000000000..c87054d18 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bool/BooleanMappingTest.java @@ -0,0 +1,61 @@ +/** + * Copyright 2012-2014 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.test.bool; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.fest.assertions.Assertions.assertThat; + +@WithClasses({ + Person.class, + PersonDto.class, + PersonMapper.class +}) +@RunWith(AnnotationProcessorTestRunner.class) +public class BooleanMappingTest { + + @Test + public void shouldMapBooleanPropertyWithIsPrefixedGetter() { + //given + Person person = new Person(); + person.setMarried( Boolean.TRUE ); + + //when + PersonDto personDto = PersonMapper.INSTANCE.personToDto( person ); + + //then + assertThat( personDto.getMarried() ).isEqualTo( "true" ); + } + + @Test + public void shouldMapBooleanPropertyPreferringGetPrefixedGetterOverIsPrefixedGetter() { + //given + Person person = new Person(); + person.setEngaged( Boolean.TRUE ); + + //when + PersonDto personDto = PersonMapper.INSTANCE.personToDto( person ); + + //then + assertThat( personDto.getEngaged() ).isEqualTo( "true" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bool/Person.java b/processor/src/test/java/org/mapstruct/ap/test/bool/Person.java new file mode 100644 index 000000000..ae35f5af9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bool/Person.java @@ -0,0 +1,45 @@ +/** + * Copyright 2012-2014 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.test.bool; + +public class Person { + + private Boolean married; + private Boolean engaged; + + public Boolean isMarried() { + return married; + } + + public void setMarried(Boolean married) { + this.married = married; + } + + public Boolean isEngaged() { + return engaged != null && !engaged; + } + + public Boolean getEngaged() { + return engaged; + } + + public void setEngaged(Boolean engaged) { + this.engaged = engaged; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bool/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/bool/PersonDto.java new file mode 100644 index 000000000..3f8bf77e0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bool/PersonDto.java @@ -0,0 +1,41 @@ +/** + * Copyright 2012-2014 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.test.bool; + +public class PersonDto { + + private String married; + private String engaged; + + public String getMarried() { + return married; + } + + public void setMarried(String married) { + this.married = married; + } + + public String getEngaged() { + return engaged; + } + + public void setEngaged(String engaged) { + this.engaged = engaged; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bool/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bool/PersonMapper.java new file mode 100644 index 000000000..cee7f3d7b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bool/PersonMapper.java @@ -0,0 +1,30 @@ +/** + * Copyright 2012-2014 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.test.bool; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface PersonMapper { + + PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class ); + + PersonDto personToDto(Person person); +}