#327 Move MethodRetrievalProcessor#allEnclosingElementsIncludeSuper to Executables#getAllEnclosingExecutableElements and replace any Elements#getAllMembers calls with it (to create a fixed order of property mappings)

This commit is contained in:
Andreas Gudian 2014-11-18 17:58:54 +01:00
parent 9992801f28
commit 03535ecb18
10 changed files with 232 additions and 84 deletions

View File

@ -78,6 +78,7 @@ public class Type extends ModelElement implements Comparable<Type> {
private final List<String> enumConstants;
private List<ExecutableElement> allExecutables = null;
private List<ExecutableElement> getters = null;
private List<ExecutableElement> setters = null;
private List<ExecutableElement> adders = null;
@ -280,12 +281,19 @@ public class Type extends ModelElement implements Comparable<Type> {
*/
public List<ExecutableElement> getGetters() {
if ( getters == null ) {
List<? extends Element> members = elementUtils.getAllMembers( typeElement );
getters = Collections.unmodifiableList( Filters.getterMethodsIn( members ) );
getters = Collections.unmodifiableList( Filters.getterMethodsIn( getAllExecutables() ) );
}
return getters;
}
private List<ExecutableElement> 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<Type> {
*/
public List<ExecutableElement> getSetters() {
if ( setters == null ) {
List<? extends Element> 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<Type> {
*/
private List<ExecutableElement> getAdders() {
if ( adders == null ) {
List<? extends Element> members = elementUtils.getAllMembers( typeElement );
adders = Collections.unmodifiableList( Filters.adderMethodsIn( members ) );
adders = Collections.unmodifiableList( Filters.adderMethodsIn( getAllExecutables() ) );
}
return adders;
}

View File

@ -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<Void, Lis
private TypeFactory typeFactory;
private Types typeUtils;
private Elements elementUtils;
private TypeMirror javaLangObjectTypeMirror;
@Override
public List<SourceMethod> process(ProcessorContext context, TypeElement mapperTypeElement, Void sourceModel) {
@ -72,7 +71,6 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
this.typeFactory = context.getTypeFactory();
this.typeUtils = context.getTypeUtils();
this.elementUtils = context.getElementUtils();
this.javaLangObjectTypeMirror = typeFactory.getType( Object.class ).getTypeMirror();
return retrieveMethods( mapperTypeElement, mapperTypeElement );
}
@ -92,12 +90,10 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
private List<SourceMethod> retrieveMethods(TypeElement usedMapper, TypeElement mapperToImplement) {
List<SourceMethod> methods = new ArrayList<SourceMethod>();
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 ModelElementProcessor<Void, Lis
return methods;
}
/**
* @param executable the executable to check
*
* @return <code>true</code>, 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 <code>true</code>, iff the given executable was not yet overridden by a method in the given list.
*/
private boolean wasNotYetOverridden(List<SourceMethod> 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<Element> allEnclosingElementsIncludeSuper(TypeElement element) {
List<Element> enclosedElements = new ArrayList<Element>( 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 <code>true</code>, 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) {

View File

@ -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<ExecutableElement> getAllEnclosingExecutableElements(
Elements elementUtils, TypeElement element) {
List<ExecutableElement> enclosedElements = new ArrayList<ExecutableElement>();
addEnclosingElementsIncludingSuper( elementUtils, enclosedElements, element );
return enclosedElements;
}
private static void addEnclosingElementsIncludingSuper(Elements elementUtils,
List<ExecutableElement> 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<ExecutableElement> alreadyCollected,
List<ExecutableElement> methodsToAdd) {
List<ExecutableElement> safeToAdd = new ArrayList<ExecutableElement>( 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 <code>true</code>, 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 <code>true</code>, iff the given executable was not yet overridden by a method in the given list.
*/
private static boolean wasNotYetOverridden(Elements elementUtils, List<ExecutableElement> 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 <code>true</code>, 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;
}
}

View File

@ -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<ExecutableElement> getterMethodsIn(Iterable<? extends Element> elements) {
public static List<ExecutableElement> getterMethodsIn(Iterable<ExecutableElement> elements) {
List<ExecutableElement> getterMethods = new LinkedList<ExecutableElement>();
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<ExecutableElement> setterMethodsIn(Iterable<? extends Element> elements) {
public static List<ExecutableElement> setterMethodsIn(Iterable<ExecutableElement> elements) {
List<ExecutableElement> setterMethods = new LinkedList<ExecutableElement>();
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<ExecutableElement> adderMethodsIn(Iterable<? extends Element> elements) {
public static List<ExecutableElement> adderMethodsIn(Iterable<ExecutableElement> elements) {
List<ExecutableElement> adderMethods = new LinkedList<ExecutableElement>();
for ( ExecutableElement method : methodsIn( elements ) ) {
for ( ExecutableElement method : elements ) {
if ( Executables.isAdderMethod( method ) ) {
adderMethods.add( method );
}

View File

@ -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 );
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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() {

View File

@ -18,7 +18,7 @@
*/
package org.mapstruct.ap.test.abstractclass;
public class Target {
public class Target extends AbstractDto {
private Long size;
private String birthday;