mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#3463 DefaultBuilderProvider should be able to handle methods in parent interfaces
This commit is contained in:
parent
6cb126cd7c
commit
60f162ca88
@ -14,6 +14,7 @@ import javax.lang.model.element.ExecutableElement;
|
|||||||
import javax.lang.model.element.Modifier;
|
import javax.lang.model.element.Modifier;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
import javax.lang.model.type.DeclaredType;
|
import javax.lang.model.type.DeclaredType;
|
||||||
|
import javax.lang.model.type.ExecutableType;
|
||||||
import javax.lang.model.type.TypeKind;
|
import javax.lang.model.type.TypeKind;
|
||||||
import javax.lang.model.type.TypeMirror;
|
import javax.lang.model.type.TypeMirror;
|
||||||
import javax.lang.model.util.ElementFilter;
|
import javax.lang.model.util.ElementFilter;
|
||||||
@ -107,19 +108,17 @@ public class DefaultBuilderProvider implements BuilderProvider {
|
|||||||
* @throws TypeHierarchyErroneousException if the {@link TypeMirror} is of kind {@link TypeKind#ERROR}
|
* @throws TypeHierarchyErroneousException if the {@link TypeMirror} is of kind {@link TypeKind#ERROR}
|
||||||
*/
|
*/
|
||||||
protected TypeElement getTypeElement(TypeMirror type) {
|
protected TypeElement getTypeElement(TypeMirror type) {
|
||||||
if ( type.getKind() == TypeKind.ERROR ) {
|
DeclaredType declaredType = getDeclaredType( type );
|
||||||
throw new TypeHierarchyErroneousException( type );
|
return getTypeElement( declaredType );
|
||||||
}
|
}
|
||||||
DeclaredType declaredType = type.accept(
|
|
||||||
new SimpleTypeVisitor6<DeclaredType, Void>() {
|
|
||||||
@Override
|
|
||||||
public DeclaredType visitDeclared(DeclaredType t, Void p) {
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the {@link TypeElement} for the given {@link DeclaredType}.
|
||||||
|
*
|
||||||
|
* @param declaredType for which the {@link TypeElement} needs to be found.
|
||||||
|
* @return the type element or {@code null} if the declared type element is not {@link TypeElement}
|
||||||
|
*/
|
||||||
|
private TypeElement getTypeElement(DeclaredType declaredType) {
|
||||||
if ( declaredType == null ) {
|
if ( declaredType == null ) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -135,6 +134,28 @@ public class DefaultBuilderProvider implements BuilderProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the {@link DeclaredType} for the given {@link TypeMirror}.
|
||||||
|
*
|
||||||
|
* @param type for which the {@link DeclaredType} needs to be found.
|
||||||
|
* @return the declared or {@code null} if the {@link TypeMirror} is not a {@link DeclaredType}
|
||||||
|
* @throws TypeHierarchyErroneousException if the {@link TypeMirror} is of kind {@link TypeKind#ERROR}
|
||||||
|
*/
|
||||||
|
private DeclaredType getDeclaredType(TypeMirror type) {
|
||||||
|
if ( type.getKind() == TypeKind.ERROR ) {
|
||||||
|
throw new TypeHierarchyErroneousException( type );
|
||||||
|
}
|
||||||
|
return type.accept(
|
||||||
|
new SimpleTypeVisitor6<DeclaredType, Void>() {
|
||||||
|
@Override
|
||||||
|
public DeclaredType visitDeclared(DeclaredType t, Void p) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the {@link BuilderInfo} for the given {@code typeElement}.
|
* Find the {@link BuilderInfo} for the given {@code typeElement}.
|
||||||
* <p>
|
* <p>
|
||||||
@ -218,21 +239,32 @@ public class DefaultBuilderProvider implements BuilderProvider {
|
|||||||
* Searches for a build method for {@code typeElement} within the {@code builderElement}.
|
* Searches for a build method for {@code typeElement} within the {@code builderElement}.
|
||||||
* <p>
|
* <p>
|
||||||
* The default implementation iterates over each method in {@code builderElement} and uses
|
* The default implementation iterates over each method in {@code builderElement} and uses
|
||||||
* {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, TypeElement)} to check if the method is a
|
* {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, DeclaredType, TypeElement)}
|
||||||
* build method for {@code typeElement}.
|
* to check if the method is a build method for {@code typeElement}.
|
||||||
* <p>
|
* <p>
|
||||||
* The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the
|
* The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the
|
||||||
* {@code builderElement} should be ignored, i.e. not checked for build elements.
|
* {@code builderElement} should be ignored, i.e. not checked for build elements.
|
||||||
* <p>
|
* <p>
|
||||||
* If there are multiple methods that satisfy
|
|
||||||
* {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, TypeElement)} and one of those methods
|
|
||||||
* is names {@code build} that that method would be considered as a build method.
|
|
||||||
* @param builderElement the element for the builder
|
* @param builderElement the element for the builder
|
||||||
* @param typeElement the element for the type that is being built
|
* @param typeElement the element for the type that is being built
|
||||||
* @return the build method for the {@code typeElement} if it exists, or {@code null} if it does not
|
* @return the build method for the {@code typeElement} if it exists, or {@code null} if it does not
|
||||||
* {@code build}
|
* {@code build}
|
||||||
*/
|
*/
|
||||||
protected Collection<ExecutableElement> findBuildMethods(TypeElement builderElement, TypeElement typeElement) {
|
protected Collection<ExecutableElement> findBuildMethods(TypeElement builderElement, TypeElement typeElement) {
|
||||||
|
if ( shouldIgnore( builderElement ) || typeElement == null ) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
DeclaredType builderType = getDeclaredType( builderElement.asType() );
|
||||||
|
|
||||||
|
if ( builderType == null ) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return findBuildMethods( builderElement, builderType, typeElement );
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<ExecutableElement> findBuildMethods(TypeElement builderElement, DeclaredType builderType,
|
||||||
|
TypeElement typeElement) {
|
||||||
if ( shouldIgnore( builderElement ) ) {
|
if ( shouldIgnore( builderElement ) ) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@ -240,23 +272,57 @@ public class DefaultBuilderProvider implements BuilderProvider {
|
|||||||
List<ExecutableElement> builderMethods = ElementFilter.methodsIn( builderElement.getEnclosedElements() );
|
List<ExecutableElement> builderMethods = ElementFilter.methodsIn( builderElement.getEnclosedElements() );
|
||||||
List<ExecutableElement> buildMethods = new ArrayList<>();
|
List<ExecutableElement> buildMethods = new ArrayList<>();
|
||||||
for ( ExecutableElement buildMethod : builderMethods ) {
|
for ( ExecutableElement buildMethod : builderMethods ) {
|
||||||
if ( isBuildMethod( buildMethod, typeElement ) ) {
|
if ( isBuildMethod( buildMethod, builderType, typeElement ) ) {
|
||||||
buildMethods.add( buildMethod );
|
buildMethods.add( buildMethod );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( buildMethods.isEmpty() ) {
|
if ( !buildMethods.isEmpty() ) {
|
||||||
return findBuildMethods(
|
|
||||||
getTypeElement( builderElement.getSuperclass() ),
|
|
||||||
typeElement
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildMethods;
|
return buildMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Collection<ExecutableElement> parentClassBuildMethods = findBuildMethods(
|
||||||
|
getTypeElement( builderElement.getSuperclass() ),
|
||||||
|
builderType,
|
||||||
|
typeElement
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( !parentClassBuildMethods.isEmpty() ) {
|
||||||
|
return parentClassBuildMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<? extends TypeMirror> interfaces = builderElement.getInterfaces();
|
||||||
|
if ( interfaces.isEmpty() ) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<ExecutableElement> interfaceBuildMethods = new ArrayList<>();
|
||||||
|
|
||||||
|
for ( TypeMirror builderInterface : interfaces ) {
|
||||||
|
interfaceBuildMethods.addAll( findBuildMethods(
|
||||||
|
getTypeElement( builderInterface ),
|
||||||
|
builderType,
|
||||||
|
typeElement
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return interfaceBuildMethods;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the {@code buildMethod} is a method that creates {@code typeElement}.
|
* @see #isBuildMethod(ExecutableElement, DeclaredType, TypeElement)
|
||||||
|
* @deprecated use {@link #isBuildMethod(ExecutableElement, DeclaredType, TypeElement)} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
protected boolean isBuildMethod(ExecutableElement buildMethod, TypeElement typeElement) {
|
||||||
|
return buildMethod.getParameters().isEmpty() &&
|
||||||
|
buildMethod.getModifiers().contains( Modifier.PUBLIC )
|
||||||
|
&& typeUtils.isAssignable( buildMethod.getReturnType(), typeElement.asType() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the {@code buildMethod} is a method that creates the {@code typeElement}
|
||||||
|
* as a member of the {@code builderType}.
|
||||||
* <p>
|
* <p>
|
||||||
* The default implementation considers a method to be a build method if the following is satisfied:
|
* The default implementation considers a method to be a build method if the following is satisfied:
|
||||||
* <ul>
|
* <ul>
|
||||||
@ -266,14 +332,23 @@ public class DefaultBuilderProvider implements BuilderProvider {
|
|||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param buildMethod the method that should be checked
|
* @param buildMethod the method that should be checked
|
||||||
|
* @param builderType the type of the builder in which the {@code buildMethod} is located in
|
||||||
* @param typeElement the type element that needs to be built
|
* @param typeElement the type element that needs to be built
|
||||||
* @return {@code true} if the {@code buildMethod} is a build method for {@code typeElement}, {@code false}
|
* @return {@code true} if the {@code buildMethod} is a build method for {@code typeElement}, {@code false}
|
||||||
* otherwise
|
* otherwise
|
||||||
*/
|
*/
|
||||||
protected boolean isBuildMethod(ExecutableElement buildMethod, TypeElement typeElement) {
|
protected boolean isBuildMethod(ExecutableElement buildMethod, DeclaredType builderType, TypeElement typeElement) {
|
||||||
return buildMethod.getParameters().isEmpty() &&
|
if ( !buildMethod.getParameters().isEmpty() ) {
|
||||||
buildMethod.getModifiers().contains( Modifier.PUBLIC )
|
return false;
|
||||||
&& typeUtils.isAssignable( buildMethod.getReturnType(), typeElement.asType() );
|
}
|
||||||
|
if ( !buildMethod.getModifiers().contains( Modifier.PUBLIC ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TypeMirror buildMethodType = typeUtils.asMemberOf( builderType, buildMethod );
|
||||||
|
if ( buildMethodType instanceof ExecutableType ) {
|
||||||
|
return typeUtils.isAssignable( ( (ExecutableType) buildMethodType ).getReturnType(), typeElement.asType() );
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Copyright MapStruct Authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.bugs._3463;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Filip Hrisafov
|
||||||
|
*/
|
||||||
|
public interface EntityBuilder<T> {
|
||||||
|
|
||||||
|
T build();
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright MapStruct Authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.bugs._3463;
|
||||||
|
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Filip Hrisafov
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface Issue3463Mapper {
|
||||||
|
|
||||||
|
Issue3463Mapper INSTANCE = Mappers.getMapper( Issue3463Mapper.class );
|
||||||
|
|
||||||
|
Person map(PersonDto dto);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright MapStruct Authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.bugs._3463;
|
||||||
|
|
||||||
|
import org.mapstruct.ap.testutil.IssueKey;
|
||||||
|
import org.mapstruct.ap.testutil.ProcessorTest;
|
||||||
|
import org.mapstruct.ap.testutil.WithClasses;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Filip Hrisafov
|
||||||
|
*/
|
||||||
|
@IssueKey("3463")
|
||||||
|
@WithClasses({
|
||||||
|
EntityBuilder.class,
|
||||||
|
Issue3463Mapper.class,
|
||||||
|
Person.class,
|
||||||
|
PersonDto.class
|
||||||
|
})
|
||||||
|
class Issue3463Test {
|
||||||
|
|
||||||
|
@ProcessorTest
|
||||||
|
void shouldUseInterfaceBuildMethod() {
|
||||||
|
Person person = Issue3463Mapper.INSTANCE.map( new PersonDto( "Tester" ) );
|
||||||
|
|
||||||
|
assertThat( person ).isNotNull();
|
||||||
|
assertThat( person.getName() ).isEqualTo( "Tester" );
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright MapStruct Authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.bugs._3463;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Filip Hrisafov
|
||||||
|
*/
|
||||||
|
public class Person {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private Person(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new BuilderImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Builder extends EntityBuilder<Person> {
|
||||||
|
|
||||||
|
Builder name(String name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class BuilderImpl implements Builder {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private BuilderImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder name(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Person build() {
|
||||||
|
return new Person( this.name );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright MapStruct Authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
package org.mapstruct.ap.test.bugs._3463;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Filip Hrisafov
|
||||||
|
*/
|
||||||
|
public class PersonDto {
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public PersonDto(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user