From e67c849c175d9efcc1fe32fe93075fadaf000cbe Mon Sep 17 00:00:00 2001 From: sjaakd Date: Tue, 22 May 2018 22:41:53 +0200 Subject: [PATCH] #1398 allowing `@ObjectFactory` methods on context --- .../src/main/java/org/mapstruct/Context.java | 53 +++++- .../java/org/mapstruct/ObjectFactory.java | 15 +- .../mapstruct-reference-guide.asciidoc | 2 + .../ap/internal/model/BeanMappingMethod.java | 9 +- .../model/CollectionAssignmentBuilder.java | 4 +- .../model/ContainerMappingMethodBuilder.java | 6 +- .../ap/internal/model/EnumMappingMethod.java | 4 +- ...tory.java => LifecycleMethodResolver.java} | 27 +-- .../ap/internal/model/MapMappingMethod.java | 11 +- .../ap/internal/model/MapperReference.java | 13 ++ .../internal/model/MappingBuilderContext.java | 12 -- .../model/ObjectFactoryMethodResolver.java | 161 ++++++++++++++++++ .../ap/internal/model/PropertyMapping.java | 20 +-- .../ap/internal/model/ValueMappingMethod.java | 4 +- .../internal/model/source/SourceMethod.java | 14 +- .../processor/MethodRetrievalProcessor.java | 12 +- .../creation/MappingResolverImpl.java | 61 ------- .../objectfactory/ContextObjectFactory.java | 33 ++++ .../ContextWithObjectFactoryMapper.java | 36 ++++ .../ContextWithObjectFactoryTest.java | 53 ++++++ .../ap/test/context/objectfactory/Valve.java | 42 +++++ .../test/context/objectfactory/ValveDto.java | 37 ++++ 22 files changed, 489 insertions(+), 140 deletions(-) rename processor/src/main/java/org/mapstruct/ap/internal/model/{LifecycleCallbackFactory.java => LifecycleMethodResolver.java} (90%) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextObjectFactory.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/Valve.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ValveDto.java diff --git a/core-common/src/main/java/org/mapstruct/Context.java b/core-common/src/main/java/org/mapstruct/Context.java index 7432e66ad..e1fc0c979 100644 --- a/core-common/src/main/java/org/mapstruct/Context.java +++ b/core-common/src/main/java/org/mapstruct/Context.java @@ -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. *

- * Note: 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. + * Note: 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. *

* 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; * } * * + *

+ * Example 3: Using {@code @Context} parameters for creating an entity object by calling an + * {@code @}{@link ObjectFactory} methods: * + *

+ * 
+ * // type of the context parameter
+ * public class ContextObjectFactory {
+ *     @PersistenceContext(unitName = "my-unit")
+ *     private EntityManager em;
+ *
+ *     @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;
+ *     }
+ *
+ * }
+ *
+ * @Mapper
+ * public interface ContextWithObjectFactoryMapper {
+ *     Valve map(ValveDto dto, @Context ContextObjectFactory factory);
+ * }
+ *
+ *
+ * // generates:
+ *
+ * public class ContextWithObjectFactoryMapperImpl implements ContextWithObjectFactoryMapper {
+ *
+ *   @Override
+ *   public Valve map(ValveDto dto, ContextObjectFactory factory) {
+ *       if ( dto == null ) {
+ *           return null;
+ *       }
+ *
+ *       Valve valve = factory.create();
+ *
+ *       valve.setOneWay( dto.isOneWay() );
+ *
+ *       return valve;
+ *   }
+ * }
+ * 
+ * 
* @author Andreas Gudian * @since 1.2 */ diff --git a/core-common/src/main/java/org/mapstruct/ObjectFactory.java b/core-common/src/main/java/org/mapstruct/ObjectFactory.java index 7319900b6..60c0acff0 100644 --- a/core-common/src/main/java/org/mapstruct/ObjectFactory.java +++ b/core-common/src/main/java/org/mapstruct/ObjectFactory.java @@ -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. *

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

- * Note: the usage of this annotation is optional 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}. + * Note: the usage of this annotation is optional 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 mandatory when used inside + * an {@code @}{@link Context} annotated class. *

* Example: Using a factory method for entities to check whether the entity already exists in the * EntityManager and then returns the managed instance: diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index 316600a83..58cf8ca3a 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -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 <>) or `@BeforeMapping` / `@AfterMapping` methods (see <>) 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. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 059a1c7ba..95a14e180 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -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 beforeMappingMethods = LifecycleCallbackFactory.beforeMappingMethods( + List beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariableNames ); List afterMappingMethods = - LifecycleCallbackFactory.afterMappingMethods( method, selectionParameters, ctx, existingVariableNames ); + LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariableNames ); if (factoryMethod != null && method instanceof ForgedMethod ) { ( (ForgedMethod) method ).addThrownTypes( factoryMethod.getThrownTypes() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java index 3443cfab7..7973b5c8d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java @@ -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(), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java index 722594720..5a3d5dde1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java @@ -138,19 +138,19 @@ public abstract class ContainerMappingMethodBuilder existingVariables = new HashSet( method.getParameterNames() ); existingVariables.add( loopVariableName ); - List beforeMappingMethods = LifecycleCallbackFactory.beforeMappingMethods( + List beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables ); - List afterMappingMethods = LifecycleCallbackFactory.afterMappingMethods( + List afterMappingMethods = LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/EnumMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/EnumMappingMethod.java index f91e6231c..ee4b663c2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/EnumMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/EnumMappingMethod.java @@ -104,9 +104,9 @@ public class EnumMappingMethod extends MappingMethod { Set existingVariables = new HashSet( method.getParameterNames() ); List beforeMappingMethods = - LifecycleCallbackFactory.beforeMappingMethods( method, selectionParameters, ctx, existingVariables ); + LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables ); List afterMappingMethods = - LifecycleCallbackFactory.afterMappingMethods( method, selectionParameters, ctx, existingVariables ); + LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables ); return new EnumMappingMethod( method, enumMappings, beforeMappingMethods, afterMappingMethods ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java similarity index 90% rename from processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackFactory.java rename to processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java index 6cad83b86..6c763e9c8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -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 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 existingVariableNames) { - return collectLifecycleCallbackMethods( - method, + return collectLifecycleCallbackMethods( method, selectionParameters, filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ), ctx, @@ -93,7 +91,9 @@ public final class LifecycleCallbackFactory { List availableMethods = new ArrayList( 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 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 filterBeforeMappingMethods(List methods) { List result = new ArrayList(); for ( SourceMethod method : methods ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java index 1f02b7756..2c7fb453f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java @@ -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 existingVariables = new HashSet( method.getParameterNames() ); List beforeMappingMethods = - LifecycleCallbackFactory.beforeMappingMethods( method, null, ctx, existingVariables ); + LifecycleMethodResolver.beforeMappingMethods( method, null, ctx, existingVariables ); List afterMappingMethods = - LifecycleCallbackFactory.afterMappingMethods( method, null, ctx, existingVariables ); + LifecycleMethodResolver.afterMappingMethods( method, null, ctx, existingVariables ); return new MapMappingMethod( method, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MapperReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MapperReference.java index d8a60211c..5fedcf285 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MapperReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MapperReference.java @@ -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 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; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java index b8449d975..fafce28fc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java @@ -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 getUsedVirtualMappings(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java new file mode 100644 index 000000000..8f779aed2 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -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> matchingFactoryMethods = + selectors.getMatchingMethods( + method, + getAllAvailableMethods( method, ctx.getSourceModel() ), + java.util.Collections. 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 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 getAllAvailableMethods(Method method, List sourceModelMethods) { + ParameterProvidedMethods contextProvidedMethods = method.getContextProvidedMethods(); + if ( contextProvidedMethods.isEmpty() ) { + return sourceModelMethods; + } + + List methodsProvidedByParams = contextProvidedMethods + .getAllProvidedMethodsInParameterOrder( method.getContextParameters() ); + + List availableMethods = + new ArrayList( 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; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 4b0300e78..8440cb72d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -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 ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java index c1568a2f0..6c9a057b3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java @@ -122,9 +122,9 @@ public class ValueMappingMethod extends MappingMethod { SelectionParameters selectionParameters = getSelectionParameters( method, ctx.getTypeUtils() ); Set existingVariables = new HashSet( method.getParameterNames() ); List beforeMappingMethods = - LifecycleCallbackFactory.beforeMappingMethods( method, selectionParameters, ctx, existingVariables ); + LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables ); List afterMappingMethods = - LifecycleCallbackFactory.afterMappingMethods( method, selectionParameters, ctx, existingVariables ); + LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables ); // finally return a mapping return new ValueMappingMethod( method, mappingEntries, nullTarget, defaultTarget, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java index 4cf01354d..a83029db2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java @@ -79,12 +79,12 @@ public class SourceMethod implements Method { private List applicablePrototypeMethods; private List 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; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index 8d2b4e8be..04eb86a13 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -245,7 +245,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor contextParameters, TypeElement mapperToImplement, MapperConfiguration mapperConfig) { ParameterProvidedMethods.Builder builder = ParameterProvidedMethods.builder(); @@ -289,14 +289,14 @@ public class MethodRetrievalProcessor implements ModelElementProcessor emptyList() ); - List lifecycleMethods = new ArrayList( contextParamMethods.size() ); + List contextProvidedMethods = new ArrayList( 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(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index e50dce92a..04a8dace0 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -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> matchingFactoryMethods = - methodSelectors.getMatchingMethods( - mappingMethod, - sourceModel, - java.util.Collections. 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 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() ) ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextObjectFactory.java b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextObjectFactory.java new file mode 100644 index 000000000..2ef29aa36 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextObjectFactory.java @@ -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"); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryMapper.java new file mode 100644 index 000000000..febf62e0e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryMapper.java @@ -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); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryTest.java new file mode 100644 index 000000000..15da66770 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryTest.java @@ -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(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/Valve.java b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/Valve.java new file mode 100644 index 000000000..4a04bdd81 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/Valve.java @@ -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; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ValveDto.java b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ValveDto.java new file mode 100644 index 000000000..1a4a96e34 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ValveDto.java @@ -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; + } + +}