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