mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#782 Add support for mapping immutable classes with builders
This commit is contained in:
parent
70419f91b0
commit
d99a4cc217
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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() ) ) {
|
||||||
|
@ -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);
|
||||||
|
}
|
@ -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 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -175,6 +175,7 @@ public class DateFormatValidatorFactoryTest {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
fullQualifiedName,
|
fullQualifiedName,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
@ -126,6 +126,7 @@ public class DefaultConversionContextTest {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
fullQualifiedName,
|
fullQualifiedName,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user