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.TypeElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.ExecutableType;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
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}
|
||||
*/
|
||||
protected TypeElement getTypeElement(TypeMirror type) {
|
||||
if ( type.getKind() == TypeKind.ERROR ) {
|
||||
throw new TypeHierarchyErroneousException( type );
|
||||
DeclaredType declaredType = getDeclaredType( 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 ) {
|
||||
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}.
|
||||
* <p>
|
||||
@ -218,21 +239,32 @@ public class DefaultBuilderProvider implements BuilderProvider {
|
||||
* Searches for a build method for {@code typeElement} within the {@code builderElement}.
|
||||
* <p>
|
||||
* The default implementation iterates over each method in {@code builderElement} and uses
|
||||
* {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, TypeElement)} to check if the method is a
|
||||
* build method for {@code typeElement}.
|
||||
* {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, DeclaredType, TypeElement)}
|
||||
* to check if the method is a build method for {@code typeElement}.
|
||||
* <p>
|
||||
* The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the
|
||||
* {@code builderElement} should be ignored, i.e. not checked for build elements.
|
||||
* <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 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
|
||||
* {@code build}
|
||||
*/
|
||||
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 ) ) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@ -240,23 +272,57 @@ public class DefaultBuilderProvider implements BuilderProvider {
|
||||
List<ExecutableElement> builderMethods = ElementFilter.methodsIn( builderElement.getEnclosedElements() );
|
||||
List<ExecutableElement> buildMethods = new ArrayList<>();
|
||||
for ( ExecutableElement buildMethod : builderMethods ) {
|
||||
if ( isBuildMethod( buildMethod, typeElement ) ) {
|
||||
if ( isBuildMethod( buildMethod, builderType, typeElement ) ) {
|
||||
buildMethods.add( buildMethod );
|
||||
}
|
||||
}
|
||||
|
||||
if ( buildMethods.isEmpty() ) {
|
||||
return findBuildMethods(
|
||||
getTypeElement( builderElement.getSuperclass() ),
|
||||
typeElement
|
||||
);
|
||||
}
|
||||
|
||||
if ( !buildMethods.isEmpty() ) {
|
||||
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>
|
||||
* The default implementation considers a method to be a build method if the following is satisfied:
|
||||
* <ul>
|
||||
@ -266,14 +332,23 @@ public class DefaultBuilderProvider implements BuilderProvider {
|
||||
* </ul>
|
||||
*
|
||||
* @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
|
||||
* @return {@code true} if the {@code buildMethod} is a build method for {@code typeElement}, {@code false}
|
||||
* otherwise
|
||||
*/
|
||||
protected boolean isBuildMethod(ExecutableElement buildMethod, TypeElement typeElement) {
|
||||
return buildMethod.getParameters().isEmpty() &&
|
||||
buildMethod.getModifiers().contains( Modifier.PUBLIC )
|
||||
&& typeUtils.isAssignable( buildMethod.getReturnType(), typeElement.asType() );
|
||||
protected boolean isBuildMethod(ExecutableElement buildMethod, DeclaredType builderType, TypeElement typeElement) {
|
||||
if ( !buildMethod.getParameters().isEmpty() ) {
|
||||
return false;
|
||||
}
|
||||
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