#782 Add support for mapping immutable classes with builders

This commit is contained in:
Filip Hrisafov 2017-12-13 18:43:11 +01:00
parent 70419f91b0
commit d99a4cc217
15 changed files with 297 additions and 15 deletions

View File

@ -41,6 +41,7 @@ import org.mapstruct.ap.internal.model.PropertyMapping.ConstantMappingBuilder;
import org.mapstruct.ap.internal.model.PropertyMapping.JavaExpressionMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.JavaExpressionMappingBuilder;
import org.mapstruct.ap.internal.model.PropertyMapping.PropertyMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.PropertyMappingBuilder;
import org.mapstruct.ap.internal.model.common.Parameter; 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.common.Type;
import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer;
import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder;
@ -76,6 +77,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private final Map<String, List<PropertyMapping>> mappingsByParameter; private final Map<String, List<PropertyMapping>> mappingsByParameter;
private final List<PropertyMapping> constantMappings; private final List<PropertyMapping> constantMappings;
private final Type resultType; private final Type resultType;
private final MethodReference finalizeMethod;
public static class Builder { public static class Builder {
@ -112,7 +114,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
this.method = sourceMethod; this.method = sourceMethod;
this.methodMappings = sourceMethod.getMappingOptions().getMappings(); this.methodMappings = sourceMethod.getMappingOptions().getMappings();
CollectionMappingStrategyPrism cms = sourceMethod.getMapperConfiguration().getCollectionMappingStrategy(); CollectionMappingStrategyPrism cms = sourceMethod.getMapperConfiguration().getCollectionMappingStrategy();
Map<String, Accessor> accessors = method.getResultType().getPropertyWriteAccessors( cms ); Map<String, Accessor> accessors = method.getResultType().getMappingType().getPropertyWriteAccessors( cms );
this.targetProperties = accessors.keySet(); this.targetProperties = accessors.keySet();
this.unprocessedTargetProperties = new LinkedHashMap<String, Accessor>( accessors ); this.unprocessedTargetProperties = new LinkedHashMap<String, Accessor>( accessors );
@ -184,7 +186,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
Type resultType = null; Type resultType = null;
if ( factoryMethod == null ) { if ( factoryMethod == null ) {
if ( selectionParameters != null && selectionParameters.getResultType() != null ) { if ( selectionParameters != null && selectionParameters.getResultType() != null ) {
resultType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ); resultType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ).getMappingType();
if ( resultType.isAbstract() ) { if ( resultType.isAbstract() ) {
ctx.getMessager().printMessage( ctx.getMessager().printMessage(
method.getExecutable(), method.getExecutable(),
@ -210,18 +212,19 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
); );
} }
} }
else if ( !method.isUpdateMethod() && method.getReturnType().isAbstract() ) { else if ( !method.isUpdateMethod() && method.getReturnType().getMappingType().isAbstract() ) {
ctx.getMessager().printMessage( ctx.getMessager().printMessage(
method.getExecutable(), method.getExecutable(),
Message.GENERAL_ABSTRACT_RETURN_TYPE, Message.GENERAL_ABSTRACT_RETURN_TYPE,
method.getReturnType() method.getReturnType().getMappingType()
); );
} }
else if ( !method.isUpdateMethod() && !method.getReturnType().hasEmptyAccessibleContructor() ) { else if ( !method.isUpdateMethod() &&
!method.getReturnType().getMappingType().hasEmptyAccessibleContructor() ) {
ctx.getMessager().printMessage( ctx.getMessager().printMessage(
method.getExecutable(), method.getExecutable(),
Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, Message.GENERAL_NO_SUITABLE_CONSTRUCTOR,
method.getReturnType() method.getReturnType().getMappingType()
); );
} }
} }
@ -241,6 +244,34 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
( (ForgedMethod) method ).addThrownTypes( factoryMethod.getThrownTypes() ); ( (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()
);
}
return new BeanMappingMethod( return new BeanMappingMethod(
method, method,
existingVariableNames, existingVariableNames,
@ -249,7 +280,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
mapNullToDefault, mapNullToDefault,
resultType, resultType,
beforeMappingMethods, beforeMappingMethods,
afterMappingMethods afterMappingMethods,
finalizeMethod
); );
} }
@ -786,7 +818,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
boolean mapNullToDefault, boolean mapNullToDefault,
Type resultType, Type resultType,
List<LifecycleCallbackMethodReference> beforeMappingReferences, List<LifecycleCallbackMethodReference> beforeMappingReferences,
List<LifecycleCallbackMethodReference> afterMappingReferences) { List<LifecycleCallbackMethodReference> afterMappingReferences,
MethodReference finalizeMethod) {
super( super(
method, method,
existingVariableNames, existingVariableNames,
@ -797,6 +830,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
); );
this.propertyMappings = propertyMappings; this.propertyMappings = propertyMappings;
this.finalizeMethod = finalizeMethod;
// intialize constant mappings as all mappings, but take out the ones that can be contributed to a // intialize constant mappings as all mappings, but take out the ones that can be contributed to a
// parameter mapping. // parameter mapping.
@ -838,6 +872,10 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
} }
} }
public MethodReference getFinalizeMethod() {
return finalizeMethod;
}
@Override @Override
public Set<Type> getImportTypes() { public Set<Type> getImportTypes() {
Set<Type> types = super.getImportTypes(); Set<Type> types = super.getImportTypes();
@ -846,6 +884,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
types.addAll( propertyMapping.getImportTypes() ); types.addAll( propertyMapping.getImportTypes() );
} }
types.add( getResultType().getMappingType() );
return types; return types;
} }

View File

@ -118,6 +118,21 @@ public class MethodReference extends ModelElement implements Assignment {
this.name = method.getName(); this.name = method.getName();
} }
private MethodReference(String name, Type definingType) {
this.name = name;
this.definingType = definingType;
this.sourceParameters = Collections.emptyList();
this.returnType = null;
this.declaringMapper = null;
this.importTypes = Collections.emptySet();
this.thrownTypes = Collections.emptyList();
this.isUpdateMethod = false;
this.contextParam = null;
this.parameterBindings = Collections.emptyList();
this.providingParameter = null;
this.isStatic = true;
}
public MapperReference getDeclaringMapper() { public MapperReference getDeclaringMapper() {
return declaringMapper; return declaringMapper;
} }
@ -244,7 +259,6 @@ public class MethodReference extends ModelElement implements Assignment {
} }
} }
@Override
public Type getReturnType() { public Type getReturnType() {
return returnType; return returnType;
} }
@ -319,4 +333,8 @@ public class MethodReference extends ModelElement implements Assignment {
List<ParameterBinding> parameterBindings) { List<ParameterBinding> parameterBindings) {
return new MethodReference( method, declaringMapper, null, parameterBindings ); return new MethodReference( method, declaringMapper, null, parameterBindings );
} }
public static MethodReference forStaticBuilder(String builderCreationMethod, Type definingType) {
return new MethodReference( builderCreationMethod, definingType );
}
} }

View File

@ -148,7 +148,7 @@ public class PropertyMapping extends ModelElement {
private Type determineTargetType() { private Type determineTargetType() {
// This is a bean mapping method, so we know the result is a declared type // This is a bean mapping method, so we know the result is a declared type
DeclaredType resultType = (DeclaredType) method.getResultType().getTypeMirror(); DeclaredType resultType = (DeclaredType) method.getResultType().getMappingType().getTypeMirror();
switch ( targetWriteAccessorType ) { switch ( targetWriteAccessorType ) {
case ADDER: case ADDER:

View File

@ -72,6 +72,7 @@ public class Type extends ModelElement implements Comparable<Type> {
private final ImplementationType implementationType; private final ImplementationType implementationType;
private final Type componentType; private final Type componentType;
private final Type builderType;
private final String packageName; private final String packageName;
private final String name; private final String name;
@ -104,6 +105,7 @@ public class Type extends ModelElement implements Comparable<Type> {
public Type(Types typeUtils, Elements elementUtils, TypeFactory typeFactory, public Type(Types typeUtils, Elements elementUtils, TypeFactory typeFactory,
TypeMirror typeMirror, TypeElement typeElement, TypeMirror typeMirror, TypeElement typeElement,
List<Type> typeParameters, ImplementationType implementationType, Type componentType, List<Type> typeParameters, ImplementationType implementationType, Type componentType,
Type builderType,
String packageName, String name, String qualifiedName, String packageName, String name, String qualifiedName,
boolean isInterface, boolean isEnumType, boolean isIterableType, boolean isInterface, boolean isEnumType, boolean isIterableType,
boolean isCollectionType, boolean isMapType, boolean isStreamType, boolean isImported) { boolean isCollectionType, boolean isMapType, boolean isStreamType, boolean isImported) {
@ -117,6 +119,7 @@ public class Type extends ModelElement implements Comparable<Type> {
this.typeParameters = typeParameters; this.typeParameters = typeParameters;
this.componentType = componentType; this.componentType = componentType;
this.implementationType = implementationType; this.implementationType = implementationType;
this.builderType = builderType;
this.packageName = packageName; this.packageName = packageName;
this.name = name; this.name = name;
@ -173,6 +176,14 @@ public class Type extends ModelElement implements Comparable<Type> {
return componentType; return componentType;
} }
public Type getBuilderType() {
return builderType;
}
public Type getMappingType() {
return builderType != null ? builderType : this;
}
public boolean isPrimitive() { public boolean isPrimitive() {
return typeMirror.getKind().isPrimitive(); return typeMirror.getKind().isPrimitive();
} }
@ -355,6 +366,7 @@ public class Type extends ModelElement implements Comparable<Type> {
typeParameters, typeParameters,
implementationType, implementationType,
componentType, componentType,
builderType,
packageName, packageName,
name, name,
qualifiedName, qualifiedName,

View File

@ -57,9 +57,12 @@ import org.mapstruct.ap.internal.util.AnnotationProcessingException;
import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.internal.util.Collections;
import org.mapstruct.ap.internal.util.JavaStreamConstants; import org.mapstruct.ap.internal.util.JavaStreamConstants;
import org.mapstruct.ap.internal.util.RoundContext; 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.TypeHierarchyErroneousException;
import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.Accessor;
import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor;
import org.mapstruct.ap.spi.BuilderProvider;
import org.mapstruct.ap.spi.DefaultBuilderProvider;
import static org.mapstruct.ap.internal.model.common.ImplementationType.withDefaultConstructor; import static org.mapstruct.ap.internal.model.common.ImplementationType.withDefaultConstructor;
import static org.mapstruct.ap.internal.model.common.ImplementationType.withInitialCapacity; import static org.mapstruct.ap.internal.model.common.ImplementationType.withInitialCapacity;
@ -72,6 +75,11 @@ import static org.mapstruct.ap.internal.model.common.ImplementationType.withLoad
*/ */
public class TypeFactory { public class TypeFactory {
private static final BuilderProvider BUILDER_PROVIDER = Services.get(
BuilderProvider.class,
new DefaultBuilderProvider()
);
private final Elements elementUtils; private final Elements elementUtils;
private final Types typeUtils; private final Types typeUtils;
private final RoundContext roundContext; private final RoundContext roundContext;
@ -162,6 +170,7 @@ public class TypeFactory {
} }
ImplementationType implementationType = getImplementationType( mirror ); ImplementationType implementationType = getImplementationType( mirror );
Type builderType = findBuilder( mirror );
boolean isIterableType = typeUtils.isSubtype( mirror, iterableType ); boolean isIterableType = typeUtils.isSubtype( mirror, iterableType );
boolean isCollectionType = typeUtils.isSubtype( mirror, collectionType ); boolean isCollectionType = typeUtils.isSubtype( mirror, collectionType );
@ -247,6 +256,7 @@ public class TypeFactory {
getTypeParameters( mirror, false ), getTypeParameters( mirror, false ),
implementationType, implementationType,
componentType, componentType,
builderType,
packageName, packageName,
name, name,
qualifiedName, qualifiedName,
@ -458,6 +468,7 @@ public class TypeFactory {
getTypeParameters( mirror, true ), getTypeParameters( mirror, true ),
null, null,
null, null,
null,
implementationType.getPackageName(), implementationType.getPackageName(),
implementationType.getName(), implementationType.getName(),
implementationType.getFullyQualifiedName(), implementationType.getFullyQualifiedName(),
@ -475,6 +486,11 @@ public class TypeFactory {
return null; return null;
} }
private Type findBuilder(TypeMirror type) {
TypeMirror builder = BUILDER_PROVIDER.findBuilder( type, elementUtils, typeUtils );
return builder == null ? null : getType( builder );
}
private TypeMirror getComponentType(TypeMirror mirror) { private TypeMirror getComponentType(TypeMirror mirror) {
if ( mirror.getKind() != TypeKind.ARRAY ) { if ( mirror.getKind() != TypeKind.ARRAY ) {
return null; return null;

View File

@ -153,6 +153,7 @@ public class TargetReference {
boolean foundEntryMatch; boolean foundEntryMatch;
Type resultType = method.getResultType(); Type resultType = method.getResultType();
resultType = resultType.getMappingType();
// there can be 4 situations // there can be 4 situations
// 1. Return type // 1. Return type
@ -256,6 +257,10 @@ public class TargetReference {
else if ( targetWriteAccessor == null ) { else if ( targetWriteAccessor == null ) {
errorMessage = new NoWriteAccessorErrorMessage( mapping, method, messager ); errorMessage = new NoWriteAccessorErrorMessage( mapping, method, messager );
} }
else {
//TODO there is no read accessor. What should we do here?
errorMessage = new NoPropertyErrorMessage( mapping, method, messager, entryNames, index, nextType );
}
} }
/** /**

View File

@ -409,7 +409,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
} }
if ( returnType.getTypeMirror().getKind() != TypeKind.VOID && if ( returnType.getTypeMirror().getKind() != TypeKind.VOID &&
!resultType.isAssignableTo( returnType ) ) { !resultType.isAssignableTo( returnType ) &&
!resultType.isAssignableTo( returnType.getMappingType() )) {
messager.printMessage( method, Message.RETRIEVAL_NON_ASSIGNABLE_RESULTTYPE ); messager.printMessage( method, Message.RETRIEVAL_NON_ASSIGNABLE_RESULTTYPE );
return false; return false;
} }

View File

@ -22,11 +22,13 @@ import static java.util.Collections.singletonList;
import static org.mapstruct.ap.internal.util.Collections.first; import static org.mapstruct.ap.internal.util.Collections.first;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
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.ExecutableType;
@ -58,6 +60,7 @@ 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.SelectedMethod;
import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
import org.mapstruct.ap.internal.util.Collections; 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.FormattingMessager;
import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
@ -140,7 +143,7 @@ public class MappingResolverImpl implements MappingResolver {
SelectionCriteria.forFactoryMethods( selectionParameters ) ); SelectionCriteria.forFactoryMethods( selectionParameters ) );
if (matchingFactoryMethods.isEmpty()) { if (matchingFactoryMethods.isEmpty()) {
return null; return findBuilderFactoryMethod( mappingMethod, targetType );
} }
if ( matchingFactoryMethods.size() > 1 ) { if ( matchingFactoryMethods.size() > 1 ) {
@ -163,6 +166,39 @@ public class MappingResolverImpl implements MappingResolver {
matchingFactoryMethod.getParameterBindings() ); 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
return null;
}
// Find the default constructor, if it exists, and construct the FactoryMethod
// We could also live with assuming it exists
return null;
}
private MapperReference findMapperReference(Method method) { private MapperReference findMapperReference(Method method) {
for ( MapperReference ref : mapperReferences ) { for ( MapperReference ref : mapperReferences ) {
if ( ref.getType().equals( method.getDeclaringMapper() ) ) { if ( ref.getType().equals( method.getDeclaringMapper() ) ) {

View File

@ -0,0 +1,33 @@
/**
* 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.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
/**
* A service provider interface that is used to detect types that require a builder for mapping. This interface could
* support automatic detection of builders for projects like Lombok, Immutables, AutoValue, etc.
* @author Filip Hrisafov
*/
public interface BuilderProvider {
TypeMirror findBuilder(TypeMirror type, Elements elements, Types types);
}

View File

@ -19,6 +19,7 @@
package org.mapstruct.ap.spi; package org.mapstruct.ap.spi;
import java.beans.Introspector; import java.beans.Introspector;
import java.util.regex.Pattern;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
@ -36,6 +37,8 @@ import javax.lang.model.util.SimpleTypeVisitor6;
*/ */
public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" );
@Override @Override
public MethodType getMethodType(ExecutableElement method) { public MethodType getMethodType(ExecutableElement method) {
if ( isGetterMethod( method ) ) { if ( isGetterMethod( method ) ) {
@ -93,7 +96,14 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
public boolean isSetterMethod(ExecutableElement method) { public boolean isSetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString(); String methodName = method.getSimpleName().toString();
return methodName.startsWith( "set" ) && methodName.length() > 3; return methodName.startsWith( "set" ) && methodName.length() > 3 || isBuilderSetter( method );
}
protected boolean isBuilderSetter(ExecutableElement method) {
return method.getParameters().size() == 1 &&
!JAVA_JAVAX_PACKAGE.matcher( method.getEnclosingElement().asType().toString() ).matches() &&
//TODO The Types need to be compared with Types#isSameType(TypeMirror, TypeMirror)
method.getReturnType().toString().equals( method.getEnclosingElement().asType().toString() );
} }
/** /**
@ -145,6 +155,12 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {
@Override @Override
public String getPropertyName(ExecutableElement getterOrSetterMethod) { public String getPropertyName(ExecutableElement getterOrSetterMethod) {
String methodName = getterOrSetterMethod.getSimpleName().toString(); String methodName = getterOrSetterMethod.getSimpleName().toString();
if ( methodName.startsWith( "is" ) || methodName.startsWith( "get" ) || methodName.startsWith( "set" ) ) {
return Introspector.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) );
}
else if ( isBuilderSetter( getterOrSetterMethod ) ) {
return methodName;
}
return Introspector.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) ); return Introspector.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) );
} }

View File

@ -0,0 +1,95 @@
/**
* 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 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.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleElementVisitor6;
import javax.lang.model.util.SimpleTypeVisitor6;
import javax.lang.model.util.Types;
/**
* Default implementation of {@link BuilderProvider}
*
* @author Filip Hrisafov
*/
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) {
DeclaredType declaredType = type.accept(
new SimpleTypeVisitor6<DeclaredType, Void>() {
@Override
public DeclaredType visitDeclared(DeclaredType t, Void p) {
return t;
}
},
null
);
if ( declaredType == null ) {
return null;
}
TypeElement typeElement = declaredType.asElement().accept(
new SimpleElementVisitor6<TypeElement, Void>() {
@Override
public TypeElement visitType(TypeElement e, Void p) {
return e;
}
},
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() ) {
return null;
}
List<ExecutableElement> methods = ElementFilter.methodsIn( typeElement.getEnclosedElements() );
for ( ExecutableElement method : methods ) {
if ( isBuilderMethod( method ) ) {
return method.getReturnType();
}
}
return findBuilder( typeElement.getSuperclass(), elements, 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 );
}
}

View File

@ -34,7 +34,7 @@
</#if> </#if>
<#if !existingInstanceMapping> <#if !existingInstanceMapping>
<@includeModel object=resultType/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=resultType/><#else>new <@includeModel object=resultType/>()</#if>; <@includeModel object=resultType.mappingType/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=resultType.mappingType/><#else>new <@includeModel object=resultType.mappingType/>()</#if>;
</#if> </#if>
<#list beforeMappingReferencesWithMappingTarget as callback> <#list beforeMappingReferencesWithMappingTarget as callback>
@ -78,7 +78,13 @@
</#list> </#list>
<#if returnType.name != "void"> <#if returnType.name != "void">
return ${resultName}; <#if resultType.builderType??>
return ${resultName}.build();
<#elseif finalizeMethod??>
return ${resultName}.<@includeModel object=finalizeMethod />;
<#else>
return ${resultName};
</#if>
</#if> </#if>
} }
<#macro throws> <#macro throws>

View File

@ -175,6 +175,7 @@ public class DateFormatValidatorFactoryTest {
null, null,
null, null,
null, null,
null,
fullQualifiedName, fullQualifiedName,
false, false,
false, false,

View File

@ -126,6 +126,7 @@ public class DefaultConversionContextTest {
null, null,
null, null,
null, null,
null,
fullQualifiedName, fullQualifiedName,
false, false,
false, false,

View File

@ -18,6 +18,7 @@
*/ */
package org.mapstruct.ap.test.builder.nestedprop; package org.mapstruct.ap.test.builder.nestedprop;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithClasses;
@ -35,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
FlattenedMapper.class FlattenedMapper.class
}) })
@RunWith(AnnotationProcessorTestRunner.class) @RunWith(AnnotationProcessorTestRunner.class)
@Ignore("Nested target not working yet")
public class BuilderNestedPropertyTest { public class BuilderNestedPropertyTest {
@Test @Test