#782 Add BuilderInfo to SPI

The implementors of the SPI should return all the required information
This commit is contained in:
Filip Hrisafov 2018-02-10 15:33:14 +01:00
parent d99a4cc217
commit 45abe9e35b
13 changed files with 304 additions and 98 deletions

View File

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

View File

@ -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.<Parameter>emptyList(),
null
),
Collections.<ParameterBinding>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.
*/

View File

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

View File

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

View File

@ -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<Type> {
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<Type> {
public Type(Types typeUtils, Elements elementUtils, TypeFactory typeFactory,
TypeMirror typeMirror, TypeElement typeElement,
List<Type> 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<Type> {
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<Type> {
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<Type> {
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<Type> {
typeParameters,
implementationType,
componentType,
builderType,
builderType == null ? null : builderType.asBuilderInfo(),
packageName,
name,
qualifiedName,

View File

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

View File

@ -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,39 +165,26 @@ 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<ExecutableElement> builderCreators = new ArrayList<ExecutableElement>();
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
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) {
for ( MapperReference ref : mapperReferences ) {
if ( ref.getType().equals( method.getDeclaringMapper() ) ) {

View File

@ -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.

View File

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

View File

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

View File

@ -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<DeclaredType, Void>() {
@Override
@ -57,7 +69,7 @@ public class DefaultBuilderProvider implements BuilderProvider {
return null;
}
TypeElement typeElement = declaredType.asElement().accept(
return declaredType.asElement().accept(
new SimpleElementVisitor6<TypeElement, Void>() {
@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<ExecutableElement> 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 );
}
return findBuilder( typeElement.getSuperclass(), elements, types );
}
protected boolean isBuilderMethod(ExecutableElement method) {
protected boolean isPossibleBuilderCreationMethod(ExecutableElement method, TypeElement typeElement, Types types) {
return method.getParameters().isEmpty()
&& method.getSimpleName().toString().equals( "builder" )
&& method.getModifiers().contains( Modifier.PUBLIC )
&& method.getModifiers().contains( Modifier.STATIC );
&& 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<ExecutableElement> builderMethods = ElementFilter.methodsIn( builderElement.getEnclosedElements() );
for ( ExecutableElement buildMethod : builderMethods ) {
if ( isBuildMethod( buildMethod, typeElement, types ) ) {
return buildMethod;
}
}
return findBuildMethod(
getTypeElement( builderElement.getSuperclass() ),
typeElement,
types
);
}
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();
}
}

View File

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

View File

@ -78,9 +78,7 @@
</#list>
<#if returnType.name != "void">
<#if resultType.builderType??>
return ${resultName}.build();
<#elseif finalizeMethod??>
<#if finalizeMethod??>
return ${resultName}.<@includeModel object=finalizeMethod />;
<#else>
return ${resultName};