#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
* parameter value if applicable.
* <p>
* <strong>Note:</strong> no {@code null} checks are performed before calling before/after mapping methods on context
* parameters. The caller needs to make sure that no {@code null} are passed in that case.
* <strong>Note:</strong> no {@code null} checks are performed before calling before/after mapping methods or object
* factory methods on {@code @}{@link Context} annotated parameters. The caller needs to make sure that no {@code null}
* are passed in that case.
* <p>
* 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
@ -135,7 +136,55 @@ import java.lang.annotation.Target;
* }
* </code>
* </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
* @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
* return type that is assignable to the required object type is present, then the factory method is used instead.
* <p>
* Factory methods can be defined without parameters, with an {@code @}{@link TargetType} parameter, a {@code @}
* {@link Context} parameter, or with the mapping source parameter. If any of those parameters are defined, then
* the mapping method that is supposed to use the factory method needs to be declared with an assignable result type,
* assignable context parameter, and/or assignable source types.
* Factory methods can be defined without parameters, with an {@code @}{@link TargetType} parameter, a
* {@code @}{@link Context} parameter, or with the mapping source parameter. If any of those parameters are defined,
* then the mapping method that is supposed to use the factory method needs to be declared with an assignable result
* type, assignable context parameter, and/or assignable source types.
* <p>
* <strong>Note:</strong> the usage of this annotation is <em>optional</em> if no source parameters are part of the
* signature, i.e. it is declared without parameters or only with {@code @}{@link TargetType} and/or {@code @}
* {@link Context}.
* <strong>Note:</strong> the usage of this annotation is <em>optional</em> when used in the {@link Mapper#uses()}
* if no source parameters are part of the signature, i.e. it is declared without parameters or only with
* {@code @}{@link TargetType} and/or {@code @}{@link Context}. It is however <em>mandatory</em> when used inside
* an {@code @}{@link Context} annotated class.
* <p>
* <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:

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

View File

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

View File

@ -138,19 +138,19 @@ public abstract class ContainerMappingMethodBuilder<B extends ContainerMappingMe
MethodReference factoryMethod = null;
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() );
existingVariables.add( loopVariableName );
List<LifecycleCallbackMethodReference> beforeMappingMethods = LifecycleCallbackFactory.beforeMappingMethods(
List<LifecycleCallbackMethodReference> beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods(
method,
selectionParameters,
ctx,
existingVariables
);
List<LifecycleCallbackMethodReference> afterMappingMethods = LifecycleCallbackFactory.afterMappingMethods(
List<LifecycleCallbackMethodReference> afterMappingMethods = LifecycleMethodResolver.afterMappingMethods(
method,
selectionParameters,
ctx,

View File

@ -104,9 +104,9 @@ public class EnumMappingMethod extends MappingMethod {
Set<String> existingVariables = new HashSet<String>( method.getParameterNames() );
List<LifecycleCallbackMethodReference> beforeMappingMethods =
LifecycleCallbackFactory.beforeMappingMethods( method, selectionParameters, ctx, existingVariables );
LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables );
List<LifecycleCallbackMethodReference> afterMappingMethods =
LifecycleCallbackFactory.afterMappingMethods( method, selectionParameters, ctx, existingVariables );
LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables );
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
*/
public final class LifecycleCallbackFactory {
public final class LifecycleMethodResolver {
private LifecycleCallbackFactory() {
private LifecycleMethodResolver() {
}
/**
@ -54,8 +54,7 @@ public final class LifecycleCallbackFactory {
SelectionParameters selectionParameters,
MappingBuilderContext ctx,
Set<String> existingVariableNames) {
return collectLifecycleCallbackMethods(
method,
return collectLifecycleCallbackMethods( method,
selectionParameters,
filterBeforeMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ),
ctx,
@ -73,8 +72,7 @@ public final class LifecycleCallbackFactory {
SelectionParameters selectionParameters,
MappingBuilderContext ctx,
Set<String> existingVariableNames) {
return collectLifecycleCallbackMethods(
method,
return collectLifecycleCallbackMethods( method,
selectionParameters,
filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ),
ctx,
@ -93,7 +91,9 @@ public final class LifecycleCallbackFactory {
List<SourceMethod> availableMethods =
new ArrayList<SourceMethod>( methodsProvidedByParams.size() + sourceModelMethods.size() );
availableMethods.addAll( methodsProvidedByParams );
for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) {
availableMethods.add( methodProvidedByParams );
}
availableMethods.addAll( sourceModelMethods );
return availableMethods;
@ -144,7 +144,7 @@ public final class LifecycleCallbackFactory {
existingVariableNames ) );
}
else {
MapperReference mapperReference = findMapperReference(
MapperReference mapperReference = MapperReference.findMapperReference(
ctx.getMapperReferences(),
candidate.getMethod() );
@ -158,17 +158,6 @@ public final class LifecycleCallbackFactory {
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) {
List<SourceMethod> result = new ArrayList<SourceMethod>();
for ( SourceMethod method : methods ) {

View File

@ -18,6 +18,8 @@
*/
package org.mapstruct.ap.internal.model;
import static org.mapstruct.ap.internal.util.Collections.first;
import java.util.Collection;
import java.util.HashSet;
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.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
* 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;
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 );
@ -199,9 +200,9 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
Set<String> existingVariables = new HashSet<String>( method.getParameterNames() );
List<LifecycleCallbackMethodReference> beforeMappingMethods =
LifecycleCallbackFactory.beforeMappingMethods( method, null, ctx, existingVariables );
LifecycleMethodResolver.beforeMappingMethods( method, null, ctx, existingVariables );
List<LifecycleCallbackMethodReference> afterMappingMethods =
LifecycleCallbackFactory.afterMappingMethods( method, null, ctx, existingVariables );
LifecycleMethodResolver.afterMappingMethods( method, null, ctx, existingVariables );
return new MapMappingMethod(
method,

View File

@ -18,7 +18,9 @@
*/
package org.mapstruct.ap.internal.model;
import java.util.List;
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.
@ -34,4 +36,15 @@ public abstract class MapperReference extends Field {
public MapperReference(Type type, String variableName, boolean 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,
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();
}

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;
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.Arrays;
import java.util.Collections;
import java.util.List;
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.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.MapperConfiguration;
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.ValueProvider;
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
* {@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().
getNullValueMappingStrategy() == NullValueMappingStrategyPrism.RETURN_DEFAULT;
Assignment factory = ctx.getMappingResolver()
.getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( rightHandSide ) );
Assignment factory = ObjectFactoryMethodResolver
.getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( rightHandSide ), ctx );
return new UpdateWrapper( rhs, method.getThrownTypes(), factory, isFieldAssignment(), targetType,
!rhs.isSourceReferenceParameter(), mapNullToDefault );
}
@ -816,7 +816,7 @@ public class PropertyMapping extends ModelElement {
getNullValueMappingStrategy() == NullValueMappingStrategyPrism.RETURN_DEFAULT;
Assignment factoryMethod =
ctx.getMappingResolver().getFactoryMethod( method, targetType, null );
ObjectFactoryMethodResolver.getFactoryMethod( method, targetType, null, ctx );
assignment = new UpdateWrapper( assignment, method.getThrownTypes(), factoryMethod,
isFieldAssignment(), targetType, false, mapNullToDefault );

View File

@ -122,9 +122,9 @@ public class ValueMappingMethod extends MappingMethod {
SelectionParameters selectionParameters = getSelectionParameters( method, ctx.getTypeUtils() );
Set<String> existingVariables = new HashSet<String>( method.getParameterNames() );
List<LifecycleCallbackMethodReference> beforeMappingMethods =
LifecycleCallbackFactory.beforeMappingMethods( method, selectionParameters, ctx, existingVariables );
LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables );
List<LifecycleCallbackMethodReference> afterMappingMethods =
LifecycleCallbackFactory.afterMappingMethods( method, selectionParameters, ctx, existingVariables );
LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables );
// finally return a mapping
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> applicableReversePrototypeMethods;
private Boolean isBeanMapping;
private Boolean isEnumMapping;
private Boolean isValueMapping;
private Boolean isIterableMapping;
private Boolean isMapMapping;
private Boolean isStreamMapping;
private final boolean hasObjectFactoryAnnotation;
public static class Builder {
@ -231,7 +231,8 @@ public class SourceMethod implements Method {
this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters );
this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters );
this.isObjectFactory = determineIfIsObjectFactory( executable );
this.hasObjectFactoryAnnotation = ObjectFactoryPrism.getInstanceOn( executable ) != null;
this.isObjectFactory = determineIfIsObjectFactory();
this.typeUtils = builder.typeUtils;
this.typeFactory = builder.typeFactory;
@ -240,13 +241,12 @@ public class SourceMethod implements Method {
this.mapperToImplement = builder.definingType;
}
private boolean determineIfIsObjectFactory(ExecutableElement executable) {
boolean hasFactoryAnnotation = ObjectFactoryPrism.getInstanceOn( executable ) != null;
private boolean determineIfIsObjectFactory() {
boolean hasNoSourceParameters = getSourceParameters().isEmpty();
boolean hasNoMappingTargetParam = getMappingTargetParameter() == null;
return !isLifecycleCallbackMethod() && !returnType.isVoid()
&& hasNoMappingTargetParam
&& ( hasFactoryAnnotation || hasNoSourceParameters );
&& ( hasObjectFactoryAnnotation || hasNoSourceParameters );
}
@Override
@ -606,4 +606,8 @@ public class SourceMethod implements Method {
public boolean isUpdateMethod() {
return getMappingTargetParameter() != null;
}
public boolean hasObjectFactoryAnnotation() {
return hasObjectFactoryAnnotation;
}
}

View File

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

View File

@ -26,7 +26,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
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.VirtualMappingMethod;
import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.ConversionContext;
import org.mapstruct.ap.internal.model.common.DefaultConversionContext;
import org.mapstruct.ap.internal.model.common.FormattingParameters;
@ -129,65 +127,6 @@ public class MappingResolverImpl implements MappingResolver {
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) {
for ( MapperReference ref : mapperReferences ) {
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;
}
}