#1398 allowing @ObjectFactory methods on context

This commit is contained in:
sjaakd 2018-05-22 22:41:53 +02:00
parent db851701ef
commit e67c849c17
22 changed files with 489 additions and 140 deletions

View File

@ -32,8 +32,9 @@ import java.lang.annotation.Target;
* {@code @}{@link BeforeMapping}/{@code @}{@link AfterMapping} methods, which are called on the provided context * {@code @}{@link BeforeMapping}/{@code @}{@link AfterMapping} methods, which are called on the provided context
* parameter value if applicable. * parameter value if applicable.
* <p> * <p>
* <strong>Note:</strong> no {@code null} checks are performed before calling before/after mapping methods on context * <strong>Note:</strong> no {@code null} checks are performed before calling before/after mapping methods or object
* parameters. The caller needs to make sure that no {@code null} are passed in that case. * factory methods on {@code @}{@link Context} annotated parameters. The caller needs to make sure that no {@code null}
* are passed in that case.
* <p> * <p>
* For generated code to call a method that is declared with {@code @Context} parameters, the declaration of the mapping * For generated code to call a method that is declared with {@code @Context} parameters, the declaration of the mapping
* method being generated needs to contain at least those (or assignable) {@code @Context} parameters as well. MapStruct * method being generated needs to contain at least those (or assignable) {@code @Context} parameters as well. MapStruct
@ -135,7 +136,55 @@ import java.lang.annotation.Target;
* } * }
* </code> * </code>
* </pre> * </pre>
* <p>
* <strong>Example 3:</strong> Using {@code @Context} parameters for creating an entity object by calling an
* {@code @}{@link ObjectFactory} methods:
* *
* <pre>
* <code>
* // type of the context parameter
* public class ContextObjectFactory {
* &#64;PersistenceContext(unitName = "my-unit")
* private EntityManager em;
*
* &#64;ObjectFactory
* public Valve create( String id ) {
* Query query = em.createNamedQuery("Valve.findById");
* query.setParameter("id", id);
* Valve result = query.getSingleResult();
* if ( result != null ) {
* result = new Valve( id );
* }
* return result;
* }
*
* }
*
* &#64;Mapper
* public interface ContextWithObjectFactoryMapper {
* Valve map(ValveDto dto, &#64;Context ContextObjectFactory factory);
* }
*
*
* // generates:
*
* public class ContextWithObjectFactoryMapperImpl implements ContextWithObjectFactoryMapper {
*
* &#64;Override
* public Valve map(ValveDto dto, ContextObjectFactory factory) {
* if ( dto == null ) {
* return null;
* }
*
* Valve valve = factory.create();
*
* valve.setOneWay( dto.isOneWay() );
*
* return valve;
* }
* }
* </code>
* </pre>
* @author Andreas Gudian * @author Andreas Gudian
* @since 1.2 * @since 1.2
*/ */

View File

@ -29,14 +29,15 @@ import java.lang.annotation.Target;
* By default beans are created during the mapping process with the default constructor. If a factory method with a * By default beans are created during the mapping process with the default constructor. If a factory method with a
* return type that is assignable to the required object type is present, then the factory method is used instead. * return type that is assignable to the required object type is present, then the factory method is used instead.
* <p> * <p>
* Factory methods can be defined without parameters, with an {@code @}{@link TargetType} parameter, a {@code @} * Factory methods can be defined without parameters, with an {@code @}{@link TargetType} parameter, a
* {@link Context} parameter, or with the mapping source parameter. If any of those parameters are defined, then * {@code @}{@link Context} parameter, or with the mapping source parameter. If any of those parameters are defined,
* the mapping method that is supposed to use the factory method needs to be declared with an assignable result type, * then the mapping method that is supposed to use the factory method needs to be declared with an assignable result
* assignable context parameter, and/or assignable source types. * type, assignable context parameter, and/or assignable source types.
* <p> * <p>
* <strong>Note:</strong> the usage of this annotation is <em>optional</em> if no source parameters are part of the * <strong>Note:</strong> the usage of this annotation is <em>optional</em> when used in the {@link Mapper#uses()}
* signature, i.e. it is declared without parameters or only with {@code @}{@link TargetType} and/or {@code @} * if no source parameters are part of the signature, i.e. it is declared without parameters or only with
* {@link Context}. * {@code @}{@link TargetType} and/or {@code @}{@link Context}. It is however <em>mandatory</em> when used inside
* an {@code @}{@link Context} annotated class.
* <p> * <p>
* <strong>Example:</strong> Using a factory method for entities to check whether the entity already exists in the * <strong>Example:</strong> Using a factory method for entities to check whether the entity already exists in the
* EntityManager and then returns the managed instance: * EntityManager and then returns the managed instance:

View File

@ -1119,6 +1119,8 @@ public class CarMapperImpl implements CarMapper {
Additional _context_ or _state_ information can be passed through generated mapping methods to custom methods with `@Context` parameters. Such parameters are passed to other mapping methods, `@ObjectFactory` methods (see <<object-factories>>) or `@BeforeMapping` / `@AfterMapping` methods (see <<customizing-mappings-with-before-and-after>>) when applicable and can thus be used in custom code. Additional _context_ or _state_ information can be passed through generated mapping methods to custom methods with `@Context` parameters. Such parameters are passed to other mapping methods, `@ObjectFactory` methods (see <<object-factories>>) or `@BeforeMapping` / `@AfterMapping` methods (see <<customizing-mappings-with-before-and-after>>) when applicable and can thus be used in custom code.
`@Context` parameters are searched for `@ObjectFactory` methods, which are called on the provided context parameter value if applicable.
`@Context` parameters are also searched for `@BeforeMapping` / `@AfterMapping` methods, which are called on the provided context parameter value if applicable. `@Context` parameters are also searched for `@BeforeMapping` / `@AfterMapping` methods, which are called on the provided context parameter value if applicable.
*Note:* no `null` checks are performed before calling before/after mapping methods on context parameters. The caller needs to make sure that `null` is not passed in that case. *Note:* no `null` checks are performed before calling before/after mapping methods on context parameters. The caller needs to make sure that `null` is not passed in that case.

View File

@ -190,10 +190,11 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
MethodReference factoryMethod = null; MethodReference factoryMethod = null;
if ( !method.isUpdateMethod() ) { if ( !method.isUpdateMethod() ) {
factoryMethod = ctx.getMappingResolver().getFactoryMethod( factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod(
method, method,
method.getResultType(), method.getResultType(),
selectionParameters selectionParameters,
ctx
); );
} }
@ -246,14 +247,14 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
sortPropertyMappingsByDependencies(); sortPropertyMappingsByDependencies();
List<LifecycleCallbackMethodReference> beforeMappingMethods = LifecycleCallbackFactory.beforeMappingMethods( List<LifecycleCallbackMethodReference> beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods(
method, method,
selectionParameters, selectionParameters,
ctx, ctx,
existingVariableNames existingVariableNames
); );
List<LifecycleCallbackMethodReference> afterMappingMethods = List<LifecycleCallbackMethodReference> afterMappingMethods =
LifecycleCallbackFactory.afterMappingMethods( method, selectionParameters, ctx, existingVariableNames ); LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariableNames );
if (factoryMethod != null && method instanceof ForgedMethod ) { if (factoryMethod != null && method instanceof ForgedMethod ) {
( (ForgedMethod) method ).addThrownTypes( factoryMethod.getThrownTypes() ); ( (ForgedMethod) method ).addThrownTypes( factoryMethod.getThrownTypes() );

View File

@ -141,8 +141,8 @@ public class CollectionAssignmentBuilder {
); );
} }
Assignment factoryMethod = ctx.getMappingResolver() Assignment factoryMethod = ObjectFactoryMethodResolver
.getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( sourceRHS ) ); .getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( sourceRHS ), ctx );
result = new UpdateWrapper( result = new UpdateWrapper(
result, result,
method.getThrownTypes(), method.getThrownTypes(),

View File

@ -138,19 +138,19 @@ public abstract class ContainerMappingMethodBuilder<B extends ContainerMappingMe
MethodReference factoryMethod = null; MethodReference factoryMethod = null;
if ( !method.isUpdateMethod() ) { if ( !method.isUpdateMethod() ) {
factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType(), null ); factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod( method, method.getResultType(), null, ctx );
} }
Set<String> existingVariables = new HashSet<String>( method.getParameterNames() ); Set<String> existingVariables = new HashSet<String>( method.getParameterNames() );
existingVariables.add( loopVariableName ); existingVariables.add( loopVariableName );
List<LifecycleCallbackMethodReference> beforeMappingMethods = LifecycleCallbackFactory.beforeMappingMethods( List<LifecycleCallbackMethodReference> beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods(
method, method,
selectionParameters, selectionParameters,
ctx, ctx,
existingVariables existingVariables
); );
List<LifecycleCallbackMethodReference> afterMappingMethods = LifecycleCallbackFactory.afterMappingMethods( List<LifecycleCallbackMethodReference> afterMappingMethods = LifecycleMethodResolver.afterMappingMethods(
method, method,
selectionParameters, selectionParameters,
ctx, ctx,

View File

@ -104,9 +104,9 @@ public class EnumMappingMethod extends MappingMethod {
Set<String> existingVariables = new HashSet<String>( method.getParameterNames() ); Set<String> existingVariables = new HashSet<String>( method.getParameterNames() );
List<LifecycleCallbackMethodReference> beforeMappingMethods = List<LifecycleCallbackMethodReference> beforeMappingMethods =
LifecycleCallbackFactory.beforeMappingMethods( method, selectionParameters, ctx, existingVariables ); LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables );
List<LifecycleCallbackMethodReference> afterMappingMethods = List<LifecycleCallbackMethodReference> afterMappingMethods =
LifecycleCallbackFactory.afterMappingMethods( method, selectionParameters, ctx, existingVariables ); LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables );
return new EnumMappingMethod( method, enumMappings, beforeMappingMethods, afterMappingMethods ); return new EnumMappingMethod( method, enumMappings, beforeMappingMethods, afterMappingMethods );
} }

View File

@ -38,9 +38,9 @@ import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
* *
* @author Andreas Gudian * @author Andreas Gudian
*/ */
public final class LifecycleCallbackFactory { public final class LifecycleMethodResolver {
private LifecycleCallbackFactory() { private LifecycleMethodResolver() {
} }
/** /**
@ -54,8 +54,7 @@ public final class LifecycleCallbackFactory {
SelectionParameters selectionParameters, SelectionParameters selectionParameters,
MappingBuilderContext ctx, MappingBuilderContext ctx,
Set<String> existingVariableNames) { Set<String> existingVariableNames) {
return collectLifecycleCallbackMethods( return collectLifecycleCallbackMethods( method,
method,
selectionParameters, selectionParameters,
filterBeforeMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ), filterBeforeMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ),
ctx, ctx,
@ -73,8 +72,7 @@ public final class LifecycleCallbackFactory {
SelectionParameters selectionParameters, SelectionParameters selectionParameters,
MappingBuilderContext ctx, MappingBuilderContext ctx,
Set<String> existingVariableNames) { Set<String> existingVariableNames) {
return collectLifecycleCallbackMethods( return collectLifecycleCallbackMethods( method,
method,
selectionParameters, selectionParameters,
filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ), filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ),
ctx, ctx,
@ -93,7 +91,9 @@ public final class LifecycleCallbackFactory {
List<SourceMethod> availableMethods = List<SourceMethod> availableMethods =
new ArrayList<SourceMethod>( methodsProvidedByParams.size() + sourceModelMethods.size() ); new ArrayList<SourceMethod>( methodsProvidedByParams.size() + sourceModelMethods.size() );
availableMethods.addAll( methodsProvidedByParams ); for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) {
availableMethods.add( methodProvidedByParams );
}
availableMethods.addAll( sourceModelMethods ); availableMethods.addAll( sourceModelMethods );
return availableMethods; return availableMethods;
@ -144,7 +144,7 @@ public final class LifecycleCallbackFactory {
existingVariableNames ) ); existingVariableNames ) );
} }
else { else {
MapperReference mapperReference = findMapperReference( MapperReference mapperReference = MapperReference.findMapperReference(
ctx.getMapperReferences(), ctx.getMapperReferences(),
candidate.getMethod() ); candidate.getMethod() );
@ -158,17 +158,6 @@ public final class LifecycleCallbackFactory {
return result; return result;
} }
private static MapperReference findMapperReference(List<MapperReference> mapperReferences, SourceMethod method) {
for ( MapperReference ref : mapperReferences ) {
if ( ref.getType().equals( method.getDeclaringMapper() ) ) {
ref.setUsed( ref.isUsed() || !method.isStatic() );
ref.setTypeRequiresImport( true );
return ref;
}
}
return null;
}
private static List<SourceMethod> filterBeforeMappingMethods(List<SourceMethod> methods) { private static List<SourceMethod> filterBeforeMappingMethods(List<SourceMethod> methods) {
List<SourceMethod> result = new ArrayList<SourceMethod>(); List<SourceMethod> result = new ArrayList<SourceMethod>();
for ( SourceMethod method : methods ) { for ( SourceMethod method : methods ) {

View File

@ -18,6 +18,8 @@
*/ */
package org.mapstruct.ap.internal.model; package org.mapstruct.ap.internal.model;
import static org.mapstruct.ap.internal.util.Collections.first;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -36,8 +38,6 @@ import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
import static org.mapstruct.ap.internal.util.Collections.first;
/** /**
* A {@link MappingMethod} implemented by a {@link Mapper} class which maps one {@code Map} type to another. Keys and * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one {@code Map} type to another. Keys and
* values are mapped either by a {@link TypeConversion} or another mapping method if required. * values are mapped either by a {@link TypeConversion} or another mapping method if required.
@ -191,7 +191,8 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
MethodReference factoryMethod = null; MethodReference factoryMethod = null;
if ( !method.isUpdateMethod() ) { if ( !method.isUpdateMethod() ) {
factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType(), null ); factoryMethod = ObjectFactoryMethodResolver
.getFactoryMethod( method, method.getResultType(), null, ctx );
} }
keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes(), keyTargetType, false ); keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes(), keyTargetType, false );
@ -199,9 +200,9 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
Set<String> existingVariables = new HashSet<String>( method.getParameterNames() ); Set<String> existingVariables = new HashSet<String>( method.getParameterNames() );
List<LifecycleCallbackMethodReference> beforeMappingMethods = List<LifecycleCallbackMethodReference> beforeMappingMethods =
LifecycleCallbackFactory.beforeMappingMethods( method, null, ctx, existingVariables ); LifecycleMethodResolver.beforeMappingMethods( method, null, ctx, existingVariables );
List<LifecycleCallbackMethodReference> afterMappingMethods = List<LifecycleCallbackMethodReference> afterMappingMethods =
LifecycleCallbackFactory.afterMappingMethods( method, null, ctx, existingVariables ); LifecycleMethodResolver.afterMappingMethods( method, null, ctx, existingVariables );
return new MapMappingMethod( return new MapMappingMethod(
method, method,

View File

@ -18,7 +18,9 @@
*/ */
package org.mapstruct.ap.internal.model; package org.mapstruct.ap.internal.model;
import java.util.List;
import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.SourceMethod;
/** /**
* A reference to another mapper class, which itself may be generated or hand-written. * A reference to another mapper class, which itself may be generated or hand-written.
@ -34,4 +36,15 @@ public abstract class MapperReference extends Field {
public MapperReference(Type type, String variableName, boolean isUsed) { public MapperReference(Type type, String variableName, boolean isUsed) {
super( type, variableName, isUsed ); super( type, variableName, isUsed );
} }
public static MapperReference findMapperReference(List<MapperReference> mapperReferences, SourceMethod method) {
for ( MapperReference ref : mapperReferences ) {
if ( ref.getType().equals( method.getDeclaringMapper() ) ) {
ref.setUsed( ref.isUsed() || !method.isStatic() );
ref.setTypeRequiresImport( true );
return ref;
}
}
return null;
}
} }

View File

@ -107,18 +107,6 @@ public class MappingBuilderContext {
SelectionParameters selectionParameters, SourceRHS sourceRHS, SelectionParameters selectionParameters, SourceRHS sourceRHS,
boolean preferUpdateMethods); boolean preferUpdateMethods);
/**
* returns a no arg factory method
*
* @param mappingMethod target mapping method
* @param target return type to match
* @param selectionParameters parameters used in the selection process
*
* @return a method reference to the factory method, or null if no suitable, or ambiguous method found
*
*/
MethodReference getFactoryMethod(Method mappingMethod, Type target, SelectionParameters selectionParameters);
Set<VirtualMappingMethod> getUsedVirtualMappings(); Set<VirtualMappingMethod> getUsedVirtualMappings();
} }

View File

@ -0,0 +1,161 @@
/**
* 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;
import static org.mapstruct.ap.internal.util.Collections.first;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.model.source.SourceMethod;
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.Message;
import org.mapstruct.ap.internal.util.Strings;
/**
*
* @author Sjaak Derksen
*/
public class ObjectFactoryMethodResolver {
private ObjectFactoryMethodResolver() {
}
/**
* returns a no arg factory method
*
* @param method target mapping method
* @param targetType return type to match
* @param selectionParameters parameters used in the selection process
* @param ctx
*
* @return a method reference to the factory method, or null if no suitable, or ambiguous method found
*
*/
public static MethodReference getFactoryMethod( Method method,
Type targetType,
SelectionParameters selectionParameters,
MappingBuilderContext ctx) {
MethodSelectors selectors =
new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory() );
List<SelectedMethod<SourceMethod>> matchingFactoryMethods =
selectors.getMatchingMethods(
method,
getAllAvailableMethods( method, ctx.getSourceModel() ),
java.util.Collections.<Type> emptyList(),
targetType.getEffectiveType(),
SelectionCriteria.forFactoryMethods( selectionParameters ) );
if (matchingFactoryMethods.isEmpty()) {
return findBuilderFactoryMethod( targetType );
}
if ( matchingFactoryMethods.size() > 1 ) {
ctx.getMessager().printMessage(
method.getExecutable(),
Message.GENERAL_AMBIGIOUS_FACTORY_METHOD,
targetType.getEffectiveType(),
Strings.join( matchingFactoryMethods, ", " ) );
return null;
}
SelectedMethod<SourceMethod> matchingFactoryMethod = first( matchingFactoryMethods );
Parameter providingParameter =
method.getContextProvidedMethods().getParameterForProvidedMethod( matchingFactoryMethod.getMethod() );
if ( providingParameter != null ) {
return MethodReference.forParameterProvidedMethod(
matchingFactoryMethod.getMethod(),
providingParameter,
matchingFactoryMethod.getParameterBindings() );
}
else {
MapperReference ref = MapperReference.findMapperReference(
ctx.getMapperReferences(),
matchingFactoryMethod.getMethod() );
return MethodReference.forMapperReference(
matchingFactoryMethod.getMethod(),
ref,
matchingFactoryMethod.getParameterBindings() );
}
}
private static MethodReference findBuilderFactoryMethod(Type targetType) {
BuilderType builder = targetType.getBuilderType();
if ( builder == null ) {
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.getOwningType()
);
}
private static List<SourceMethod> getAllAvailableMethods(Method method, List<SourceMethod> sourceModelMethods) {
ParameterProvidedMethods contextProvidedMethods = method.getContextProvidedMethods();
if ( contextProvidedMethods.isEmpty() ) {
return sourceModelMethods;
}
List<SourceMethod> methodsProvidedByParams = contextProvidedMethods
.getAllProvidedMethodsInParameterOrder( method.getContextParameters() );
List<SourceMethod> availableMethods =
new ArrayList<SourceMethod>( methodsProvidedByParams.size() + sourceModelMethods.size() );
for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) {
// add only methods from context that do have the @ObjectFactory annotation
if ( methodProvidedByParams.hasObjectFactoryAnnotation() ) {
availableMethods.add( methodProvidedByParams );
}
}
availableMethods.addAll( sourceModelMethods );
return availableMethods;
}
}

View File

@ -18,13 +18,18 @@
*/ */
package org.mapstruct.ap.internal.model; package org.mapstruct.ap.internal.model;
import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.DIRECT;
import static org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism.ALWAYS;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.last;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType; import javax.lang.model.type.DeclaredType;
@ -54,16 +59,11 @@ import org.mapstruct.ap.internal.util.AccessorNamingUtils;
import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.Executables;
import org.mapstruct.ap.internal.util.MapperConfiguration; import org.mapstruct.ap.internal.util.MapperConfiguration;
import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.NativeTypes;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.ValueProvider; import org.mapstruct.ap.internal.util.ValueProvider;
import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.Accessor;
import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.DIRECT;
import static org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism.ALWAYS;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.last;
import org.mapstruct.ap.internal.util.NativeTypes;
/** /**
* Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to * Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to
* {@code int Target#bar}. Name and type of source and target property can differ. If they have different types, the * {@code int Target#bar}. Name and type of source and target property can differ. If they have different types, the
@ -433,8 +433,8 @@ public class PropertyMapping extends ModelElement {
boolean mapNullToDefault = method.getMapperConfiguration(). boolean mapNullToDefault = method.getMapperConfiguration().
getNullValueMappingStrategy() == NullValueMappingStrategyPrism.RETURN_DEFAULT; getNullValueMappingStrategy() == NullValueMappingStrategyPrism.RETURN_DEFAULT;
Assignment factory = ctx.getMappingResolver() Assignment factory = ObjectFactoryMethodResolver
.getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( rightHandSide ) ); .getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( rightHandSide ), ctx );
return new UpdateWrapper( rhs, method.getThrownTypes(), factory, isFieldAssignment(), targetType, return new UpdateWrapper( rhs, method.getThrownTypes(), factory, isFieldAssignment(), targetType,
!rhs.isSourceReferenceParameter(), mapNullToDefault ); !rhs.isSourceReferenceParameter(), mapNullToDefault );
} }
@ -816,7 +816,7 @@ public class PropertyMapping extends ModelElement {
getNullValueMappingStrategy() == NullValueMappingStrategyPrism.RETURN_DEFAULT; getNullValueMappingStrategy() == NullValueMappingStrategyPrism.RETURN_DEFAULT;
Assignment factoryMethod = Assignment factoryMethod =
ctx.getMappingResolver().getFactoryMethod( method, targetType, null ); ObjectFactoryMethodResolver.getFactoryMethod( method, targetType, null, ctx );
assignment = new UpdateWrapper( assignment, method.getThrownTypes(), factoryMethod, assignment = new UpdateWrapper( assignment, method.getThrownTypes(), factoryMethod,
isFieldAssignment(), targetType, false, mapNullToDefault ); isFieldAssignment(), targetType, false, mapNullToDefault );

View File

@ -122,9 +122,9 @@ public class ValueMappingMethod extends MappingMethod {
SelectionParameters selectionParameters = getSelectionParameters( method, ctx.getTypeUtils() ); SelectionParameters selectionParameters = getSelectionParameters( method, ctx.getTypeUtils() );
Set<String> existingVariables = new HashSet<String>( method.getParameterNames() ); Set<String> existingVariables = new HashSet<String>( method.getParameterNames() );
List<LifecycleCallbackMethodReference> beforeMappingMethods = List<LifecycleCallbackMethodReference> beforeMappingMethods =
LifecycleCallbackFactory.beforeMappingMethods( method, selectionParameters, ctx, existingVariables ); LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables );
List<LifecycleCallbackMethodReference> afterMappingMethods = List<LifecycleCallbackMethodReference> afterMappingMethods =
LifecycleCallbackFactory.afterMappingMethods( method, selectionParameters, ctx, existingVariables ); LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables );
// finally return a mapping // finally return a mapping
return new ValueMappingMethod( method, mappingEntries, nullTarget, defaultTarget, return new ValueMappingMethod( method, mappingEntries, nullTarget, defaultTarget,

View File

@ -79,12 +79,12 @@ public class SourceMethod implements Method {
private List<SourceMethod> applicablePrototypeMethods; private List<SourceMethod> applicablePrototypeMethods;
private List<SourceMethod> applicableReversePrototypeMethods; private List<SourceMethod> applicableReversePrototypeMethods;
private Boolean isBeanMapping;
private Boolean isEnumMapping; private Boolean isEnumMapping;
private Boolean isValueMapping; private Boolean isValueMapping;
private Boolean isIterableMapping; private Boolean isIterableMapping;
private Boolean isMapMapping; private Boolean isMapMapping;
private Boolean isStreamMapping; private Boolean isStreamMapping;
private final boolean hasObjectFactoryAnnotation;
public static class Builder { public static class Builder {
@ -231,7 +231,8 @@ public class SourceMethod implements Method {
this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters );
this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters ); this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters );
this.isObjectFactory = determineIfIsObjectFactory( executable ); this.hasObjectFactoryAnnotation = ObjectFactoryPrism.getInstanceOn( executable ) != null;
this.isObjectFactory = determineIfIsObjectFactory();
this.typeUtils = builder.typeUtils; this.typeUtils = builder.typeUtils;
this.typeFactory = builder.typeFactory; this.typeFactory = builder.typeFactory;
@ -240,13 +241,12 @@ public class SourceMethod implements Method {
this.mapperToImplement = builder.definingType; this.mapperToImplement = builder.definingType;
} }
private boolean determineIfIsObjectFactory(ExecutableElement executable) { private boolean determineIfIsObjectFactory() {
boolean hasFactoryAnnotation = ObjectFactoryPrism.getInstanceOn( executable ) != null;
boolean hasNoSourceParameters = getSourceParameters().isEmpty(); boolean hasNoSourceParameters = getSourceParameters().isEmpty();
boolean hasNoMappingTargetParam = getMappingTargetParameter() == null; boolean hasNoMappingTargetParam = getMappingTargetParameter() == null;
return !isLifecycleCallbackMethod() && !returnType.isVoid() return !isLifecycleCallbackMethod() && !returnType.isVoid()
&& hasNoMappingTargetParam && hasNoMappingTargetParam
&& ( hasFactoryAnnotation || hasNoSourceParameters ); && ( hasObjectFactoryAnnotation || hasNoSourceParameters );
} }
@Override @Override
@ -606,4 +606,8 @@ public class SourceMethod implements Method {
public boolean isUpdateMethod() { public boolean isUpdateMethod() {
return getMappingTargetParameter() != null; return getMappingTargetParameter() != null;
} }
public boolean hasObjectFactoryAnnotation() {
return hasObjectFactoryAnnotation;
}
} }

View File

@ -245,7 +245,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
} }
ParameterProvidedMethods contextProvidedMethods = ParameterProvidedMethods contextProvidedMethods =
retrieveLifecycleMethodsFromContext( contextParameters, mapperToImplement, mapperConfig ); retrieveContextProvidedMethods( contextParameters, mapperToImplement, mapperConfig );
return new SourceMethod.Builder() return new SourceMethod.Builder()
.setExecutable( method ) .setExecutable( method )
@ -275,7 +275,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
.build(); .build();
} }
private ParameterProvidedMethods retrieveLifecycleMethodsFromContext( private ParameterProvidedMethods retrieveContextProvidedMethods(
List<Parameter> contextParameters, TypeElement mapperToImplement, MapperConfiguration mapperConfig) { List<Parameter> contextParameters, TypeElement mapperToImplement, MapperConfiguration mapperConfig) {
ParameterProvidedMethods.Builder builder = ParameterProvidedMethods.builder(); ParameterProvidedMethods.Builder builder = ParameterProvidedMethods.builder();
@ -289,14 +289,14 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
mapperConfig, mapperConfig,
Collections.<SourceMethod> emptyList() ); Collections.<SourceMethod> emptyList() );
List<SourceMethod> lifecycleMethods = new ArrayList<SourceMethod>( contextParamMethods.size() ); List<SourceMethod> contextProvidedMethods = new ArrayList<SourceMethod>( contextParamMethods.size() );
for ( SourceMethod sourceMethod : contextParamMethods ) { for ( SourceMethod sourceMethod : contextParamMethods ) {
if ( sourceMethod.isLifecycleCallbackMethod() ) { if ( sourceMethod.isLifecycleCallbackMethod() || sourceMethod.isObjectFactory() ) {
lifecycleMethods.add( sourceMethod ); contextProvidedMethods.add( sourceMethod );
} }
} }
builder.addMethodsForParameter( contextParam, lifecycleMethods ); builder.addMethodsForParameter( contextParam, contextProvidedMethods );
} }
return builder.build(); return builder.build();

View File

@ -26,7 +26,6 @@ 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.ElementKind;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType; import javax.lang.model.type.DeclaredType;
@ -45,7 +44,6 @@ import org.mapstruct.ap.internal.model.MappingBuilderContext.MappingResolver;
import org.mapstruct.ap.internal.model.MethodReference; import org.mapstruct.ap.internal.model.MethodReference;
import org.mapstruct.ap.internal.model.VirtualMappingMethod; import org.mapstruct.ap.internal.model.VirtualMappingMethod;
import org.mapstruct.ap.internal.model.common.Assignment; 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.ConversionContext;
import org.mapstruct.ap.internal.model.common.DefaultConversionContext; import org.mapstruct.ap.internal.model.common.DefaultConversionContext;
import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.model.common.FormattingParameters;
@ -129,65 +127,6 @@ public class MappingResolverImpl implements MappingResolver {
return usedVirtualMappings; return usedVirtualMappings;
} }
@Override
public MethodReference getFactoryMethod(final Method mappingMethod, Type targetType,
SelectionParameters selectionParameters) {
List<SelectedMethod<Method>> matchingFactoryMethods =
methodSelectors.getMatchingMethods(
mappingMethod,
sourceModel,
java.util.Collections.<Type> emptyList(),
targetType.getEffectiveType(),
SelectionCriteria.forFactoryMethods( selectionParameters ) );
if (matchingFactoryMethods.isEmpty()) {
return findBuilderFactoryMethod( targetType );
}
if ( matchingFactoryMethods.size() > 1 ) {
messager.printMessage(
mappingMethod.getExecutable(),
Message.GENERAL_AMBIGIOUS_FACTORY_METHOD,
targetType.getEffectiveType(),
Strings.join( matchingFactoryMethods, ", " ) );
return null;
}
SelectedMethod<Method> matchingFactoryMethod = first( matchingFactoryMethods );
MapperReference ref = findMapperReference( matchingFactoryMethod.getMethod() );
return MethodReference.forMapperReference(
matchingFactoryMethod.getMethod(),
ref,
matchingFactoryMethod.getParameterBindings() );
}
private MethodReference findBuilderFactoryMethod(Type targetType) {
BuilderType builder = targetType.getBuilderType();
if ( builder == null ) {
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.getOwningType()
);
}
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.test.context.objectfactory;
import org.mapstruct.ObjectFactory;
/**
* @author Andreas Gudian
*/
public class ContextObjectFactory {
@ObjectFactory
public Valve create() {
return new Valve("123id");
}
}

View File

@ -0,0 +1,36 @@
/**
* 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.test.context.objectfactory;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
*
* @author Sjaak Derksen
*/
@Mapper
public interface ContextWithObjectFactoryMapper {
ContextWithObjectFactoryMapper INSTANCE = Mappers.getMapper( ContextWithObjectFactoryMapper.class );
Valve map(ValveDto dto, @Context ContextObjectFactory factory);
}

View File

@ -0,0 +1,53 @@
/**
* 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.test.context.objectfactory;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
/**
*
* @author Sjaak Derksen
*/
@IssueKey( "1398" )
@WithClasses({
Valve.class,
ValveDto.class,
ContextObjectFactory.class,
ContextWithObjectFactoryMapper.class})
@RunWith(AnnotationProcessorTestRunner.class)
public class ContextWithObjectFactoryTest {
@Test
public void testFactoryCalled( ) {
ValveDto dto = new ValveDto();
dto.setOneWay( true );
Valve result = ContextWithObjectFactoryMapper.INSTANCE.map( dto, new ContextObjectFactory() );
assertThat( result ).isNotNull();
assertThat( result.isOneWay() ).isTrue();
}
}

View File

@ -0,0 +1,42 @@
/**
* 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.test.context.objectfactory;
/**
*
* @author Sjaak Derksen
*/
public class Valve {
private boolean oneWay;
private final String id;
public Valve(String id) {
this.id = id;
}
public boolean isOneWay() {
return oneWay;
}
public void setOneWay(boolean oneWay) {
this.oneWay = oneWay;
}
}

View File

@ -0,0 +1,37 @@
/**
* 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.test.context.objectfactory;
/**
*
* @author Sjaak Derksen
*/
public class ValveDto {
private boolean oneWay;
public boolean isOneWay() {
return oneWay;
}
public void setOneWay(boolean oneWay) {
this.oneWay = oneWay;
}
}