diff --git a/processor/src/main/java/org/mapstruct/ap/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/model/common/Type.java index 35d430e94..cee88077a 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/model/common/Type.java @@ -78,6 +78,7 @@ public class Type extends ModelElement implements Comparable { private final List enumConstants; + private List allExecutables = null; private List getters = null; private List setters = null; private List adders = null; @@ -280,12 +281,19 @@ public class Type extends ModelElement implements Comparable { */ public List getGetters() { if ( getters == null ) { - List members = elementUtils.getAllMembers( typeElement ); - getters = Collections.unmodifiableList( Filters.getterMethodsIn( members ) ); + getters = Collections.unmodifiableList( Filters.getterMethodsIn( getAllExecutables() ) ); } return getters; } + private List getAllExecutables() { + if ( allExecutables == null ) { + allExecutables = Executables.getAllEnclosingExecutableElements( elementUtils, typeElement ); + } + + return allExecutables; + } + /** * Tries to find an addMethod in this type for given collection property in this type. * @@ -349,8 +357,7 @@ public class Type extends ModelElement implements Comparable { */ public List getSetters() { if ( setters == null ) { - List members = elementUtils.getAllMembers( typeElement ); - setters = Collections.unmodifiableList( Filters.setterMethodsIn( members ) ); + setters = Collections.unmodifiableList( Filters.setterMethodsIn( getAllExecutables() ) ); } return setters; } @@ -365,8 +372,7 @@ public class Type extends ModelElement implements Comparable { */ private List getAdders() { if ( adders == null ) { - List members = elementUtils.getAllMembers( typeElement ); - adders = Collections.unmodifiableList( Filters.adderMethodsIn( members ) ); + adders = Collections.unmodifiableList( Filters.adderMethodsIn( getAllExecutables() ) ); } return adders; } diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java index f023909f7..4cd68877f 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java @@ -22,8 +22,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.annotation.processing.Messager; -import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -48,7 +48,7 @@ import org.mapstruct.ap.prism.MappingsPrism; import org.mapstruct.ap.util.AnnotationProcessingException; import org.mapstruct.ap.util.MapperConfig; -import static javax.lang.model.util.ElementFilter.methodsIn; +import static org.mapstruct.ap.util.Executables.getAllEnclosingExecutableElements; /** * A {@link ModelElementProcessor} which retrieves a list of {@link SourceMethod}s @@ -64,7 +64,6 @@ public class MethodRetrievalProcessor implements ModelElementProcessor process(ProcessorContext context, TypeElement mapperTypeElement, Void sourceModel) { @@ -72,7 +71,6 @@ public class MethodRetrievalProcessor implements ModelElementProcessor retrieveMethods(TypeElement usedMapper, TypeElement mapperToImplement) { List methods = new ArrayList(); - for ( ExecutableElement executable : methodsIn( allEnclosingElementsIncludeSuper( usedMapper ) ) ) { - if ( isNotObjectEquals( executable ) && wasNotYetOverridden( methods, executable ) ) { - SourceMethod method = getMethod( usedMapper, executable, mapperToImplement ); - if ( method != null ) { - methods.add( method ); - } + for ( ExecutableElement executable : getAllEnclosingExecutableElements( elementUtils, usedMapper ) ) { + SourceMethod method = getMethod( usedMapper, executable, mapperToImplement ); + if ( method != null ) { + methods.add( method ); } } @@ -118,67 +114,10 @@ public class MethodRetrievalProcessor implements ModelElementProcessortrue, iff the executable does not represent {@link java.lang.Object#equals(Object)} or an - * overridden version of it - */ - private boolean isNotObjectEquals(ExecutableElement executable) { - if ( "equals".equals( executable.getSimpleName().toString() ) - && executable.getParameters().size() == 1 - && executable.getParameters().get( 0 ).asType().equals( javaLangObjectTypeMirror ) ) { - return false; - } - return true; - } - - /** - * @param methods the list of already collected methods of one type hierarchy (order is from sub-types to - * super-types) - * @param executable the method to check - * - * @return true, iff the given executable was not yet overridden by a method in the given list. - */ - private boolean wasNotYetOverridden(List methods, ExecutableElement executable) { - for ( SourceMethod alreadyAdded : methods ) { - ExecutableElement executableInSubtype = alreadyAdded.getExecutable(); - TypeElement declaringType = (TypeElement) executableInSubtype.getEnclosingElement(); - - if ( elementUtils.overrides( executableInSubtype, executable, declaringType ) ) { - return false; - } - } - return true; - } - private TypeElement asTypeElement(TypeMirror usedMapper) { return (TypeElement) ( (DeclaredType) usedMapper ).asElement(); } - private List allEnclosingElementsIncludeSuper(TypeElement element) { - List enclosedElements = new ArrayList( element.getEnclosedElements() ); - for ( TypeMirror interfaceType : element.getInterfaces() ) { - enclosedElements.addAll( allEnclosingElementsIncludeSuper( asTypeElement( interfaceType ) ) ); - } - - if ( hasNonObjectSuperclass( element ) ) { - enclosedElements.addAll( allEnclosingElementsIncludeSuper( asTypeElement( element.getSuperclass() ) ) ); - } - - return enclosedElements; - } - - /** - * @param element the type element to check - * - * @return true, iff the type has a super-class that is not java.lang.Object - */ - private boolean hasNonObjectSuperclass(TypeElement element) { - return element.getSuperclass().getKind() == TypeKind.DECLARED - && asTypeElement( element.getSuperclass() ).getSuperclass().getKind() == TypeKind.DECLARED; - } - private SourceMethod getMethod(TypeElement usedMapper, ExecutableElement method, TypeElement mapperToImplement) { 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 10395196b..a7b082057 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/Executables.java +++ b/processor/src/main/java/org/mapstruct/ap/util/Executables.java @@ -19,6 +19,7 @@ package org.mapstruct.ap.util; import java.beans.Introspector; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -29,9 +30,12 @@ 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.Elements; import javax.lang.model.util.SimpleElementVisitor6; import javax.lang.model.util.SimpleTypeVisitor6; +import static javax.lang.model.util.ElementFilter.methodsIn; + /** * Provides functionality around {@link ExecutableElement}s. * @@ -161,4 +165,107 @@ public class Executables { return typeElement != null ? typeElement.getQualifiedName().toString() : null; } + + /** + * @param mirror the type mirror + * @return the corresponding type element + */ + private static TypeElement asTypeElement(TypeMirror mirror) { + return (TypeElement) ( (DeclaredType) mirror ).asElement(); + } + + /** + * Finds all executable elements within the given type element, including executable elements defined in super + * classes and implemented interfaces. Methods defined in {@link java.lang.Object} are ignored, as well as + * implementations of {@link java.lang.Object#equals(Object)}. + * + * @param elementUtils element helper + * @param element the element to inspect + * @return the executable elements usable in the type + */ + public static List getAllEnclosingExecutableElements( + Elements elementUtils, TypeElement element) { + List enclosedElements = new ArrayList(); + + addEnclosingElementsIncludingSuper( elementUtils, enclosedElements, element ); + + return enclosedElements; + } + + private static void addEnclosingElementsIncludingSuper(Elements elementUtils, + List alreadyAdded, + TypeElement element) { + + addNotYetOverridden( elementUtils, alreadyAdded, methodsIn( element.getEnclosedElements() ) ); + + if ( hasNonObjectSuperclass( element ) ) { + addEnclosingElementsIncludingSuper( elementUtils, alreadyAdded, asTypeElement( element.getSuperclass() ) ); + } + + for ( TypeMirror interfaceType : element.getInterfaces() ) { + addEnclosingElementsIncludingSuper( elementUtils, alreadyAdded, asTypeElement( interfaceType ) ); + } + + } + + /** + * @param alreadyCollected methods that have already been collected and to which the not-yet-overridden methods will + * be added + * @param methodsToAdd methods to add to alreadyAdded, if they are not yet overridden by an element in the list + */ + private static void addNotYetOverridden(Elements elementUtils, List alreadyCollected, + List methodsToAdd) { + + List safeToAdd = new ArrayList( methodsToAdd.size() ); + for ( ExecutableElement toAdd : methodsToAdd ) { + if ( isNotObjectEquals( toAdd ) && wasNotYetOverridden( elementUtils, alreadyCollected, toAdd ) ) { + safeToAdd.add( toAdd ); + } + } + + alreadyCollected.addAll( safeToAdd ); + } + + /** + * @param executable the executable to check + * @return true, iff the executable does not represent {@link java.lang.Object#equals(Object)} or an + * overridden version of it + */ + private static boolean isNotObjectEquals(ExecutableElement executable) { + if ( executable.getSimpleName().contentEquals( "equals" ) && executable.getParameters().size() == 1 + && asTypeElement( executable.getParameters().get( 0 ).asType() ).getQualifiedName().contentEquals( + "java.lang.Object" ) ) { + return false; + } + return true; + } + + /** + * @param elementUtils the elementUtils + * @param methods the list of already collected methods of one type hierarchy (order is from sub-types to + * super-types) + * @param executable the method to check + * @return true, iff the given executable was not yet overridden by a method in the given list. + */ + private static boolean wasNotYetOverridden(Elements elementUtils, List alreadyAdded, + ExecutableElement executable) { + for ( ExecutableElement executableInSubtype : alreadyAdded ) { + TypeElement declaringType = (TypeElement) executableInSubtype.getEnclosingElement(); + + if ( elementUtils.overrides( executableInSubtype, executable, declaringType ) ) { + return false; + } + } + return true; + } + + /** + * @param element the type element to check + * @return true, iff the type has a super-class that is not java.lang.Object + */ + private static boolean hasNonObjectSuperclass(TypeElement element) { + return element.getSuperclass().getKind() == TypeKind.DECLARED + && asTypeElement( element.getSuperclass() ).getSuperclass().getKind() == TypeKind.DECLARED; + } + } 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 b47ae69dc..3e212b7fa 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/Filters.java +++ b/processor/src/main/java/org/mapstruct/ap/util/Filters.java @@ -20,11 +20,10 @@ package org.mapstruct.ap.util; import java.util.LinkedList; import java.util.List; + import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; -import static javax.lang.model.util.ElementFilter.methodsIn; - /** * Filter methods for working with {@link Element} collections. * @@ -35,10 +34,10 @@ public class Filters { private Filters() { } - public static List getterMethodsIn(Iterable elements) { + public static List getterMethodsIn(Iterable elements) { List getterMethods = new LinkedList(); - for ( ExecutableElement method : methodsIn( elements ) ) { + for ( ExecutableElement method : elements ) { if ( Executables.isGetterMethod( method ) ) { getterMethods.add( method ); } @@ -47,10 +46,10 @@ public class Filters { return getterMethods; } - public static List setterMethodsIn(Iterable elements) { + public static List setterMethodsIn(Iterable elements) { List setterMethods = new LinkedList(); - for ( ExecutableElement method : methodsIn( elements ) ) { + for ( ExecutableElement method : elements ) { if ( Executables.isSetterMethod( method ) ) { setterMethods.add( method ); } @@ -58,10 +57,10 @@ public class Filters { return setterMethods; } - public static List adderMethodsIn(Iterable elements) { + public static List adderMethodsIn(Iterable elements) { List adderMethods = new LinkedList(); - for ( ExecutableElement method : methodsIn( elements ) ) { + for ( ExecutableElement method : elements ) { if ( Executables.isAdderMethod( method ) ) { adderMethods.add( method ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java index fad6406f8..7cbfa87fd 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java @@ -35,7 +35,10 @@ import static org.fest.assertions.Assertions.assertThat; BaseMapperInterface.class, ReferencedMapper.class, AbstractReferencedMapper.class, - ReferencedMapperInterface.class } ) + ReferencedMapperInterface.class, + AbstractDto.class, + Identifiable.class, + Measurable.class } ) @RunWith( AnnotationProcessorTestRunner.class ) public class AbstractClassTest { @@ -69,5 +72,6 @@ public class AbstractClassTest { assertThat( target.getBirthday() ).isEqualTo( "Birthday: 26.04.1948" ); assertThat( target.getManuallyConverted() ).isEqualTo( 42 ); assertThat( target.isNotAttractingEqualsMethod() ).isTrue(); + assertThat( target.getId() ).isEqualTo( 42L ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractDto.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractDto.java new file mode 100644 index 000000000..05ebd2a6b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractDto.java @@ -0,0 +1,37 @@ +/** + * 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.abstractclass; + +/** + * @author Andreas Gudian + * + */ +public class AbstractDto implements Identifiable { + private Long id = 1L; + + @Override + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Identifiable.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Identifiable.java new file mode 100644 index 000000000..fd31b0de3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Identifiable.java @@ -0,0 +1,27 @@ +/** + * 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.abstractclass; + +/** + * @author Andreas Gudian + * + */ +public interface Identifiable { + Long getId(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Measurable.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Measurable.java new file mode 100644 index 000000000..89dc59214 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Measurable.java @@ -0,0 +1,27 @@ +/** + * 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.abstractclass; + +/** + * @author Andreas Gudian + * + */ +public interface Measurable { + int getSize(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java index 58fb8ae0d..1407e258c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java @@ -22,7 +22,7 @@ import java.util.Calendar; import javax.xml.ws.Holder; -public class Source { +public class Source extends AbstractDto { private final int size; private final Calendar birthday; @@ -33,6 +33,8 @@ public class Source { size = 181; birthday = Calendar.getInstance(); birthday.set( 1948, 3, 26 ); + + super.setId( 42L ); } public int getSize() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java index 6b687d753..9cc373d90 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java @@ -18,7 +18,7 @@ */ package org.mapstruct.ap.test.abstractclass; -public class Target { +public class Target extends AbstractDto { private Long size; private String birthday;