From 45abe9e35b46a53a7aad20dfd8dbf2a4086a9bb0 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 10 Feb 2018 15:33:14 +0100 Subject: [PATCH] #782 Add BuilderInfo to SPI The implementors of the SPI should return all the required information --- .../org/mapstruct/ap/MappingProcessor.java | 2 +- .../ap/internal/model/BeanMappingMethod.java | 45 +++---- .../ap/internal/model/MethodReference.java | 10 +- .../ap/internal/model/common/BuilderType.java | 113 ++++++++++++++++++ .../ap/internal/model/common/Type.java | 14 ++- .../ap/internal/model/common/TypeFactory.java | 12 +- .../creation/MappingResolverImpl.java | 50 +++----- .../ap/internal/util/Executables.java | 1 + .../org/mapstruct/ap/spi/BuilderInfo.java | 68 +++++++++++ .../org/mapstruct/ap/spi/BuilderProvider.java | 2 +- .../ap/spi/DefaultBuilderProvider.java | 79 +++++++++--- .../TypeHierarchyErroneousException.java | 2 +- .../ap/internal/model/BeanMappingMethod.ftl | 4 +- 13 files changed, 304 insertions(+), 98 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java create mode 100644 processor/src/main/java/org/mapstruct/ap/spi/BuilderInfo.java rename processor/src/main/java/org/mapstruct/ap/{internal/util => spi}/TypeHierarchyErroneousException.java (97%) diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 76c4f6a83..a5bcc6613 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -52,7 +52,7 @@ import org.mapstruct.ap.internal.processor.ModelElementProcessor.ProcessorContex import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.AnnotationProcessorContext; import org.mapstruct.ap.internal.util.RoundContext; -import org.mapstruct.ap.internal.util.TypeHierarchyErroneousException; +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; /** * A JSR 269 annotation {@link Processor} which generates the implementations for mapper interfaces (interfaces diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 6a5b1d09a..29cba8dbb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -40,8 +40,8 @@ import javax.tools.Diagnostic; import org.mapstruct.ap.internal.model.PropertyMapping.ConstantMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.JavaExpressionMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.PropertyMappingBuilder; +import org.mapstruct.ap.internal.model.common.BuilderType; import org.mapstruct.ap.internal.model.common.Parameter; -import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder; @@ -244,33 +244,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { ( (ForgedMethod) method ).addThrownTypes( factoryMethod.getThrownTypes() ); } - MethodReference finalizeMethod = null; - if ( - !method.getReturnType().isVoid() && - ( resultType != null - && !ctx.getTypeUtils().isAssignable( - resultType.getMappingType().getTypeMirror(), - resultType.getTypeMirror() - ) || - !ctx.getTypeUtils().isSameType( - method.getReturnType().getMappingType().getTypeMirror(), - method.getReturnType().getTypeMirror() - ) - ) - ) { - finalizeMethod = MethodReference.forForgedMethod( - new ForgedMethod( - "build", - method.getReturnType(), - method.getReturnType(), - null, - null, - Collections.emptyList(), - null - ), - Collections.emptyList() - ); - } + MethodReference finalizeMethod = getFinalizeMethod( + resultType == null ? method.getReturnType() : resultType ); return new BeanMappingMethod( method, @@ -285,6 +260,20 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { ); } + private MethodReference getFinalizeMethod(Type resultType) { + if ( method.getReturnType().isVoid() || + resultType.getMappingType().isAssignableTo( resultType ) ) { + return null; + } + BuilderType builderType = resultType.getBuilderType(); + if ( builderType == null ) { + // If the mapping type is assignable to the result type this should never happen + return null; + } + + return MethodReference.forMethodCall( builderType.getBuildMethod() ); + } + /** * If there were nested defined targets that have not been handled. Then we need to process them at the end. */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java index db21246ce..003c196ab 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java @@ -118,7 +118,7 @@ public class MethodReference extends ModelElement implements Assignment { this.name = method.getName(); } - private MethodReference(String name, Type definingType) { + private MethodReference(String name, Type definingType, boolean isStatic) { this.name = name; this.definingType = definingType; this.sourceParameters = Collections.emptyList(); @@ -130,7 +130,7 @@ public class MethodReference extends ModelElement implements Assignment { this.contextParam = null; this.parameterBindings = Collections.emptyList(); this.providingParameter = null; - this.isStatic = true; + this.isStatic = isStatic; } public MapperReference getDeclaringMapper() { @@ -335,6 +335,10 @@ public class MethodReference extends ModelElement implements Assignment { } public static MethodReference forStaticBuilder(String builderCreationMethod, Type definingType) { - return new MethodReference( builderCreationMethod, definingType ); + return new MethodReference( builderCreationMethod, definingType, true ); + } + + public static MethodReference forMethodCall(String methodName) { + return new MethodReference( methodName, null, false ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java new file mode 100644 index 000000000..36e4c5f8e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java @@ -0,0 +1,113 @@ +/** + * Copyright 2012-2017 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.internal.model.common; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + +import org.mapstruct.ap.spi.BuilderInfo; + +/** + * @author Filip Hrisafov + */ +public class BuilderType { + + private final Type builder; + private final Type owner; + private final Type buildingType; + private final ExecutableElement builderCreationMethod; + private final ExecutableElement buildMethod; + + private BuilderType( + Type builder, + Type owner, + Type buildingType, + ExecutableElement builderCreationMethod, + ExecutableElement buildMethod + ) { + this.builder = builder; + this.owner = owner; + this.buildingType = buildingType; + this.builderCreationMethod = builderCreationMethod; + this.buildMethod = buildMethod; + } + + public Type getBuilder() { + return builder; + } + + public Type getOwner() { + return owner; + } + + public Type getBuildingType() { + return buildingType; + } + + public ExecutableElement getBuilderCreationMethod() { + return builderCreationMethod; + } + + public String getBuildMethod() { + return buildMethod.getSimpleName().toString(); + } + + public BuilderInfo asBuilderInfo() { + return new BuilderInfo.Builder() + .builderCreationMethod( this.builderCreationMethod ) + .buildMethod( this.buildMethod ) + .build(); + } + + public static BuilderType create(BuilderInfo builderInfo, Type typeToBuild, TypeFactory typeFactory, + Types typeUtils) { + if ( builderInfo == null ) { + return null; + } + ExecutableElement buildMethod = builderInfo.getBuildMethod(); + if ( !typeUtils.isAssignable( buildMethod.getReturnType(), typeToBuild.getTypeMirror() ) ) { + //TODO throw error + throw new IllegalArgumentException( "Build return type is not assignable" ); + } + + Type builder = typeFactory.getType( builderInfo.getBuilderCreationMethod().getReturnType() ); + ExecutableElement builderCreationMethod = builderInfo.getBuilderCreationMethod(); + Type owner; + TypeMirror builderCreationOwner = builderCreationMethod.getEnclosingElement().asType(); + if ( typeUtils.isSameType( builderCreationOwner, typeToBuild.getTypeMirror() ) ) { + owner = typeToBuild; + } + else if ( typeUtils.isSameType( builder.getTypeMirror(), builderCreationOwner ) ) { + owner = builder; + } + else { + owner = typeFactory.getType( builderCreationOwner ); + } + + + return new BuilderType( + builder, + owner, + typeToBuild, + builderCreationMethod, + buildMethod + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index cfcd29d38..ee7b7b445 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -49,6 +49,7 @@ import org.mapstruct.ap.internal.util.Filters; import org.mapstruct.ap.internal.util.Nouns; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; +import org.mapstruct.ap.spi.BuilderInfo; /** * Represents (a reference to) the type of a bean property, parameter etc. Types are managed per generated source file. @@ -72,7 +73,7 @@ public class Type extends ModelElement implements Comparable { private final ImplementationType implementationType; private final Type componentType; - private final Type builderType; + private final BuilderType builderType; private final String packageName; private final String name; @@ -105,7 +106,7 @@ public class Type extends ModelElement implements Comparable { public Type(Types typeUtils, Elements elementUtils, TypeFactory typeFactory, TypeMirror typeMirror, TypeElement typeElement, List typeParameters, ImplementationType implementationType, Type componentType, - Type builderType, + BuilderInfo builderInfo, String packageName, String name, String qualifiedName, boolean isInterface, boolean isEnumType, boolean isIterableType, boolean isCollectionType, boolean isMapType, boolean isStreamType, boolean isImported) { @@ -119,7 +120,6 @@ public class Type extends ModelElement implements Comparable { this.typeParameters = typeParameters; this.componentType = componentType; this.implementationType = implementationType; - this.builderType = builderType; this.packageName = packageName; this.name = name; @@ -149,6 +149,8 @@ public class Type extends ModelElement implements Comparable { else { enumConstants = Collections.emptyList(); } + + this.builderType = BuilderType.create( builderInfo, this, this.typeFactory, this.typeUtils ); } //CHECKSTYLE:ON @@ -176,12 +178,12 @@ public class Type extends ModelElement implements Comparable { return componentType; } - public Type getBuilderType() { + public BuilderType getBuilderType() { return builderType; } public Type getMappingType() { - return builderType != null ? builderType : this; + return builderType != null ? builderType.getBuilder() : this; } public boolean isPrimitive() { @@ -366,7 +368,7 @@ public class Type extends ModelElement implements Comparable { typeParameters, implementationType, componentType, - builderType, + builderType == null ? null : builderType.asBuilderInfo(), packageName, name, qualifiedName, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index 377b9648f..878059682 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -58,11 +58,12 @@ import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.internal.util.JavaStreamConstants; import org.mapstruct.ap.internal.util.RoundContext; import org.mapstruct.ap.internal.util.Services; -import org.mapstruct.ap.internal.util.TypeHierarchyErroneousException; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; +import org.mapstruct.ap.spi.BuilderInfo; import org.mapstruct.ap.spi.BuilderProvider; import org.mapstruct.ap.spi.DefaultBuilderProvider; +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; import static org.mapstruct.ap.internal.model.common.ImplementationType.withDefaultConstructor; import static org.mapstruct.ap.internal.model.common.ImplementationType.withInitialCapacity; @@ -170,7 +171,7 @@ public class TypeFactory { } ImplementationType implementationType = getImplementationType( mirror ); - Type builderType = findBuilder( mirror ); + BuilderInfo builderInfo = findBuilder( mirror ); boolean isIterableType = typeUtils.isSubtype( mirror, iterableType ); boolean isCollectionType = typeUtils.isSubtype( mirror, collectionType ); @@ -256,7 +257,7 @@ public class TypeFactory { getTypeParameters( mirror, false ), implementationType, componentType, - builderType, + builderInfo, packageName, name, qualifiedName, @@ -486,9 +487,8 @@ public class TypeFactory { return null; } - private Type findBuilder(TypeMirror type) { - TypeMirror builder = BUILDER_PROVIDER.findBuilder( type, elementUtils, typeUtils ); - return builder == null ? null : getType( builder ); + private BuilderInfo findBuilder(TypeMirror type) { + return BUILDER_PROVIDER.findBuilderInfo( type, elementUtils, typeUtils ); } private TypeMirror getComponentType(TypeMirror mirror) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index 9c3cccb52..01f26e04c 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -22,13 +22,12 @@ import static java.util.Collections.singletonList; import static org.mapstruct.ap.internal.util.Collections.first; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import javax.lang.model.element.ElementKind; 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; @@ -46,6 +45,7 @@ import org.mapstruct.ap.internal.model.MappingBuilderContext.MappingResolver; import org.mapstruct.ap.internal.model.MethodReference; import org.mapstruct.ap.internal.model.VirtualMappingMethod; import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.BuilderType; import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.DefaultConversionContext; import org.mapstruct.ap.internal.model.common.FormattingParameters; @@ -60,7 +60,6 @@ import org.mapstruct.ap.internal.model.source.selector.MethodSelectors; import org.mapstruct.ap.internal.model.source.selector.SelectedMethod; import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.util.Collections; -import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; @@ -143,7 +142,7 @@ public class MappingResolverImpl implements MappingResolver { SelectionCriteria.forFactoryMethods( selectionParameters ) ); if (matchingFactoryMethods.isEmpty()) { - return findBuilderFactoryMethod( mappingMethod, targetType ); + return findBuilderFactoryMethod( targetType ); } if ( matchingFactoryMethods.size() > 1 ) { @@ -166,37 +165,24 @@ public class MappingResolverImpl implements MappingResolver { matchingFactoryMethod.getParameterBindings() ); } - private MethodReference findBuilderFactoryMethod(Method mappingMethod, Type targetType) { - if ( targetType.getBuilderType() == null ) { - return null; - } - Type builderType = targetType.getBuilderType(); - - Type returnType = mappingMethod.getReturnType(); - List builderCreators = new ArrayList(); - for ( ExecutableElement executableElement : Executables.getAllEnclosedExecutableElements( - elementsUtils, - returnType.getTypeElement() - ) ) { - if ( !executableElement.getModifiers().containsAll( Arrays.asList( Modifier.PUBLIC, Modifier.STATIC ) ) - || !executableElement.getParameters().isEmpty() - || !typeUtils.isSameType( executableElement.getReturnType(), builderType.getTypeMirror() )) { - continue; - } - builderCreators.add( executableElement ); - } - - if ( builderCreators.size() == 1 ) { - return MethodReference.forStaticBuilder( first( builderCreators ).getSimpleName().toString(), targetType ); - } - else if ( builderCreators.size() > 1 ) { - //error + private MethodReference findBuilderFactoryMethod(Type targetType) { + BuilderType builder = targetType.getBuilderType(); + if ( builder == null ) { return null; } - // Find the default constructor, if it exists, and construct the FactoryMethod - // We could also live with assuming it exists - return null; + ExecutableElement builderCreationMethod = builder.getBuilderCreationMethod(); + if ( builderCreationMethod.getKind() == ElementKind.CONSTRUCTOR ) { + // If the builder creation method is a constructor it would be handled properly down the line + return null; + } + + if ( !builder.getBuildingType().isAssignableTo( targetType ) ) { + //TODO print error message + return null; + } + + return MethodReference.forStaticBuilder( builderCreationMethod.getSimpleName().toString(), builder.getOwner() ); } private MapperReference findMapperReference(Method method) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java index 3b087b59c..f70b5fcff 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java @@ -47,6 +47,7 @@ import org.mapstruct.ap.internal.util.accessor.VariableElementAccessor; import org.mapstruct.ap.spi.AccessorNamingStrategy; import org.mapstruct.ap.spi.DefaultAccessorNamingStrategy; import org.mapstruct.ap.spi.MethodType; +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; /** * Provides functionality around {@link ExecutableElement}s. diff --git a/processor/src/main/java/org/mapstruct/ap/spi/BuilderInfo.java b/processor/src/main/java/org/mapstruct/ap/spi/BuilderInfo.java new file mode 100644 index 000000000..24cbe36ea --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/BuilderInfo.java @@ -0,0 +1,68 @@ +/** + * Copyright 2012-2017 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.spi; + +import javax.lang.model.element.ExecutableElement; + +/** + * @author Filip Hrisafov + */ +public class BuilderInfo { + + private final ExecutableElement builderCreationMethod; + private final ExecutableElement buildMethod; + + private BuilderInfo(ExecutableElement builderCreationMethod, ExecutableElement buildMethod) { + this.builderCreationMethod = builderCreationMethod; + this.buildMethod = buildMethod; + } + + public ExecutableElement getBuilderCreationMethod() { + return builderCreationMethod; + } + + public ExecutableElement getBuildMethod() { + return buildMethod; + } + + public static class Builder { + private ExecutableElement builderCreationMethod; + private ExecutableElement buildMethod; + + public Builder builderCreationMethod(ExecutableElement method) { + this.builderCreationMethod = method; + return this; + } + + public Builder buildMethod(ExecutableElement method) { + this.buildMethod = method; + return this; + } + + public BuilderInfo build() { + if ( builderCreationMethod == null ) { + throw new IllegalArgumentException( "Builder creation method is mandatory" ); + } + else if ( buildMethod == null ) { + throw new IllegalArgumentException( "Build method is mandatory" ); + } + return new BuilderInfo( builderCreationMethod, buildMethod ); + } + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/BuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/BuilderProvider.java index 2eb50dcc9..038578b69 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/BuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/BuilderProvider.java @@ -29,5 +29,5 @@ import javax.lang.model.util.Types; */ public interface BuilderProvider { - TypeMirror findBuilder(TypeMirror type, Elements elements, Types types); + BuilderInfo findBuilderInfo(TypeMirror type, Elements elements, Types types); } diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index 4efcd8e3b..c1fb980d5 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -22,9 +22,9 @@ import java.util.List; import java.util.regex.Pattern; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import javax.lang.model.element.Name; 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.ElementFilter; import javax.lang.model.util.Elements; @@ -42,7 +42,19 @@ public class DefaultBuilderProvider implements BuilderProvider { private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" ); @Override - public TypeMirror findBuilder(TypeMirror type, Elements elements, Types types) { + public BuilderInfo findBuilderInfo(TypeMirror type, Elements elements, Types types) { + TypeElement typeElement = getTypeElement( type ); + if ( typeElement == null ) { + return null; + } + + return findBuilderInfo( typeElement, elements, types ); + } + + protected TypeElement getTypeElement(TypeMirror type) { + if ( type.getKind() == TypeKind.ERROR ) { + throw new TypeHierarchyErroneousException( type ); + } DeclaredType declaredType = type.accept( new SimpleTypeVisitor6() { @Override @@ -57,7 +69,7 @@ public class DefaultBuilderProvider implements BuilderProvider { return null; } - TypeElement typeElement = declaredType.asElement().accept( + return declaredType.asElement().accept( new SimpleElementVisitor6() { @Override public TypeElement visitType(TypeElement e, Void p) { @@ -66,30 +78,63 @@ public class DefaultBuilderProvider implements BuilderProvider { }, null ); - - return findBuilder( typeElement, elements, types ); } - protected TypeMirror findBuilder(TypeElement typeElement, Elements elements, Types types) { - Name name = typeElement.getQualifiedName(); - if ( name.length() == 0 || JAVA_JAVAX_PACKAGE.matcher( name ).matches() ) { + protected BuilderInfo findBuilderInfo(TypeElement typeElement, Elements elements, Types types) { + if ( shouldIgnore( typeElement ) ) { return null; } - List methods = ElementFilter.methodsIn( typeElement.getEnclosedElements() ); + List methods = ElementFilter.methodsIn( typeElement.getEnclosedElements() ); for ( ExecutableElement method : methods ) { - if ( isBuilderMethod( method ) ) { - return method.getReturnType(); + if ( isPossibleBuilderCreationMethod( method, typeElement, types ) ) { + TypeElement builderElement = getTypeElement( method.getReturnType() ); + ExecutableElement buildMethod = findBuildMethod( builderElement, typeElement, types ); + if ( buildMethod != null ) { + return new BuilderInfo.Builder() + .builderCreationMethod( method ) + .buildMethod( buildMethod ) + .build(); + } + } + } + return findBuilderInfo( typeElement.getSuperclass(), elements, types ); + } + + protected boolean isPossibleBuilderCreationMethod(ExecutableElement method, TypeElement typeElement, Types types) { + return method.getParameters().isEmpty() + && method.getModifiers().contains( Modifier.PUBLIC ) + && method.getModifiers().contains( Modifier.STATIC ) + && !types.isSameType( method.getReturnType(), typeElement.asType() ); + } + + protected ExecutableElement findBuildMethod(TypeElement builderElement, TypeElement typeElement, Types types) { + if ( shouldIgnore( builderElement ) ) { + return null; + } + + List builderMethods = ElementFilter.methodsIn( builderElement.getEnclosedElements() ); + for ( ExecutableElement buildMethod : builderMethods ) { + if ( isBuildMethod( buildMethod, typeElement, types ) ) { + return buildMethod; } } - return findBuilder( typeElement.getSuperclass(), elements, types ); + return findBuildMethod( + getTypeElement( builderElement.getSuperclass() ), + typeElement, + types + ); } - protected boolean isBuilderMethod(ExecutableElement method) { - return method.getParameters().isEmpty() - && method.getSimpleName().toString().equals( "builder" ) - && method.getModifiers().contains( Modifier.PUBLIC ) - && method.getModifiers().contains( Modifier.STATIC ); + protected boolean isBuildMethod(ExecutableElement buildMethod, TypeElement typeElement, + Types types) { + return buildMethod.getParameters().isEmpty() && + buildMethod.getModifiers().contains( Modifier.PUBLIC ) + && types.isAssignable( buildMethod.getReturnType(), typeElement.asType() ); + } + + protected boolean shouldIgnore(TypeElement typeElement) { + return typeElement == null || JAVA_JAVAX_PACKAGE.matcher( typeElement.getQualifiedName() ).matches(); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/TypeHierarchyErroneousException.java b/processor/src/main/java/org/mapstruct/ap/spi/TypeHierarchyErroneousException.java similarity index 97% rename from processor/src/main/java/org/mapstruct/ap/internal/util/TypeHierarchyErroneousException.java rename to processor/src/main/java/org/mapstruct/ap/spi/TypeHierarchyErroneousException.java index 18194c222..e4c975b17 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/TypeHierarchyErroneousException.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/TypeHierarchyErroneousException.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.internal.util; +package org.mapstruct.ap.spi; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 168f93d21..d36a9fc23 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -78,9 +78,7 @@ <#if returnType.name != "void"> - <#if resultType.builderType??> - return ${resultName}.build(); - <#elseif finalizeMethod??> + <#if finalizeMethod??> return ${resultName}.<@includeModel object=finalizeMethod />; <#else> return ${resultName};