diff --git a/core-common/src/main/java/org/mapstruct/Context.java b/core-common/src/main/java/org/mapstruct/Context.java new file mode 100644 index 000000000..b066f0ac9 --- /dev/null +++ b/core-common/src/main/java/org/mapstruct/Context.java @@ -0,0 +1,89 @@ +/** + * Copyright 2012-2016 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a parameter of a method to be treated as mapping context. Such parameters are passed to other mapping + * methods, {@link ObjectFactory} methods or {@link BeforeMapping}/{@link AfterMapping} methods when applicable and can + * thus be used in custom code. The {@link Context} parameters are otherwise ignored by MapStruct. + *

+ * For generated code to call a method that is declared with {@link Context} parameters, the declaration of the mapping + * method being generated needs to contain at least those (or assignable) {@link Context} parameters as well. MapStruct + * will not create new instances of missing {@link Context} parameters nor will it pass {@code null} instead. + *

+ * Example: + * + *

+ * 
+ * // multiple @Context parameters can be added
+ * public abstract CarDto toCar(Car car, @Context VehicleRegistration context, @Context Locale localeToUse);
+ *
+ * protected OwnerManualDto translateOwnerManual(OwnerManual ownerManual, @Context Locale locale) {
+ *     // manually implemented logic to translate the OwnerManual with the given Locale
+ * }
+ *
+ * @BeforeMapping
+ * protected void registerVehicle(Vehicle mappedVehicle, @Context VehicleRegistration context) {
+ *     context.register( mappedVehicle );
+ * }
+ *
+ * @BeforeMapping
+ * protected void logMappedVehicle(Vehicle mappedVehicle) {
+ *     // do something with the vehicle
+ * }
+ *
+ * @BeforeMapping
+ * protected void notCalled(Vehicle mappedVehicle, @Context DifferentMappingContextType context) {
+ *     // not called, because DifferentMappingContextType is not available
+ *     // within toCar(Car, VehicleRegistration, Locale)
+ * }
+ *
+ * // generates:
+ *
+ * public CarDto toCar(Car car, VehicleRegistration context, Locale localeToUse) {
+ *     registerVehicle( car, context );
+ *     logMappedVehicle( car );
+ *
+ *     if ( car == null ) {
+ *         return null;
+ *     }
+ *
+ *     CarDto carDto = new CarDto();
+ *
+ *     carDto.setOwnerManual( translateOwnerManual( car.getOwnerManual(), localeToUse );
+ *     // more generated mapping code
+ *
+ *     return carDto;
+ * }
+ * 
+ * 
+ * + * @author Andreas Gudian + * @since 1.2 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.CLASS) +public @interface Context { + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java index 8c63c727e..40c57d2db 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java @@ -23,7 +23,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; + import javax.lang.model.element.ExecutableElement; + import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Parameter; @@ -44,8 +46,6 @@ import org.mapstruct.ap.internal.util.Strings; * @author Sjaak Derksen */ public abstract class HelperMethod implements Method { - - /** * {@inheritDoc } * @@ -84,6 +84,11 @@ public abstract class HelperMethod implements Method { return getParameters(); } + @Override + public List getContextParameters() { + return Collections.emptyList(); + } + /** * {@inheritDoc} *

diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java index 2dd04235d..f59a577ec 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java @@ -189,6 +189,7 @@ public class IterableMappingMethod extends MappingMethod { targetType, method.getMapperConfiguration(), method.getExecutable(), + method.getContextParameters(), forgedMethodHistory ); @@ -238,7 +239,7 @@ public class IterableMappingMethod extends MappingMethod { public Parameter getSourceParameter() { for ( Parameter parameter : getParameters() ) { - if ( !parameter.isMappingTarget() ) { + if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) { return parameter; } } 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 35c8f81d2..4efece5ff 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 @@ -199,6 +199,7 @@ public class MapMappingMethod extends MappingMethod { targetType, method.getMapperConfiguration(), method.getExecutable(), + method.getContextParameters(), history ); @@ -247,7 +248,7 @@ public class MapMappingMethod extends MappingMethod { public Parameter getSourceParameter() { for ( Parameter parameter : getParameters() ) { - if ( !parameter.isMappingTarget() ) { + if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) { return parameter; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java index 26024250f..4c50ab320 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java @@ -43,7 +43,8 @@ import org.mapstruct.ap.internal.model.source.Method; public abstract class MappingMethod extends ModelElement { private final String name; - private List parameters; + private final List parameters; + private final List sourceParameters; private final Type returnType; private final Parameter targetParameter; private final Accessibility accessibility; @@ -77,6 +78,7 @@ public abstract class MappingMethod extends ModelElement { List forgedMethods) { this.name = method.getName(); this.parameters = parameters; + this.sourceParameters = Parameter.getSourceParameters( parameters ); this.returnType = method.getReturnType(); this.targetParameter = method.getMappingTargetParameter(); this.accessibility = method.getAccessibility(); @@ -144,14 +146,6 @@ public abstract class MappingMethod extends ModelElement { } public List getSourceParameters() { - List sourceParameters = new ArrayList(); - - for ( Parameter parameter : parameters ) { - if ( !parameter.isMappingTarget() ) { - sourceParameters.add( parameter ); - } - } - return sourceParameters; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java index 1a20414d6..007cda462 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java @@ -79,7 +79,7 @@ public class NestedPropertyMappingMethod extends MappingMethod { public Parameter getSourceParameter() { for ( Parameter parameter : getParameters() ) { - if ( !parameter.isMappingTarget() ) { + if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) { return parameter; } } 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 4192c5854..832693cf8 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 @@ -479,12 +479,14 @@ public class PropertyMapping extends ModelElement { // forge a method from the parameter type to the last entry type. String forgedName = Strings.joinAndCamelize( sourceReference.getElementNames() ); forgedName = Strings.getSaveVariableName( forgedName, ctx.getNamesOfMappingsToGenerate() ); - ForgedMethod methodRef = new ForgedMethod( forgedName, - sourceReference.getParameter().getType(), - sourceType, - config, - method.getExecutable() - ); + ForgedMethod methodRef = new ForgedMethod( + forgedName, + sourceReference.getParameter().getType(), + sourceType, + config, + method.getExecutable(), + method.getContextParameters() ); + NestedPropertyMappingMethod.Builder builder = new NestedPropertyMappingMethod.Builder(); NestedPropertyMappingMethod nestedPropertyMapping = builder .method( methodRef ) @@ -540,7 +542,13 @@ public class PropertyMapping extends ModelElement { // copy mapper configuration from the source method, its the same mapper MapperConfiguration config = method.getMapperConfiguration(); - ForgedMethod methodRef = new ForgedMethod( name, sourceType, targetType, config, element, + ForgedMethod methodRef = new ForgedMethod( + name, + sourceType, + targetType, + config, + element, + method.getContextParameters(), new ForgedMethodHistory( getForgedMethodHistory( source ), source.getSourceErrorMessagePart(), targetPropertyName, @@ -588,7 +596,14 @@ public class PropertyMapping extends ModelElement { // copy mapper configuration from the source method, its the same mapper MapperConfiguration config = method.getMapperConfiguration(); - ForgedMethod methodRef = new ForgedMethod( name, sourceType, targetType, config, element, + ForgedMethod methodRef = + new ForgedMethod( + name, + sourceType, + targetType, + config, + element, + method.getContextParameters(), new ForgedMethodHistory( getForgedMethodHistory( source ), source.getSourceErrorMessagePart(), targetPropertyName, @@ -638,6 +653,7 @@ public class PropertyMapping extends ModelElement { targetType, method.getMapperConfiguration(), method.getExecutable(), + method.getContextParameters(), getForgedMethodHistory( sourceRHS ) ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java index 76cffe221..2248fe34f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java @@ -18,8 +18,15 @@ */ package org.mapstruct.ap.internal.model.common; +import java.util.ArrayList; +import java.util.List; import java.util.Set; +import javax.lang.model.element.VariableElement; + +import org.mapstruct.ap.internal.prism.ContextPrism; +import org.mapstruct.ap.internal.prism.MappingTargetPrism; +import org.mapstruct.ap.internal.prism.TargetTypePrism; import org.mapstruct.ap.internal.util.Collections; /** @@ -34,18 +41,20 @@ public class Parameter extends ModelElement { private final Type type; private final boolean mappingTarget; private final boolean targetType; + private final boolean mappingContext; - public Parameter(String name, Type type, boolean mappingTarget, boolean targetType) { + private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext) { // issue #909: FreeMarker doesn't like "values" as a parameter name this.name = "values".equals( name ) ? "values_" : name; this.originalName = name; this.type = type; this.mappingTarget = mappingTarget; this.targetType = targetType; + this.mappingContext = mappingContext; } public Parameter(String name, Type type) { - this( name, type, false, false ); + this( name, type, false, false, false ); } public String getName() { @@ -66,7 +75,9 @@ public class Parameter extends ModelElement { @Override public String toString() { - return ( mappingTarget ? "@MappingTarget " : "" ) + ( targetType ? "@TargetType " : "" ) + return ( mappingTarget ? "@MappingTarget " : "" ) + + ( targetType ? "@TargetType " : "" ) + + ( mappingContext ? "@Context " : "" ) + type.toString() + " " + name; } @@ -79,6 +90,10 @@ public class Parameter extends ModelElement { return targetType; } + public boolean isMappingContext() { + return mappingContext; + } + @Override public int hashCode() { int hash = 5; @@ -100,4 +115,45 @@ public class Parameter extends ModelElement { } return true; } + + public static Parameter forElementAndType(VariableElement element, Type parameterType) { + return new Parameter( + element.getSimpleName().toString(), + parameterType, + MappingTargetPrism.getInstanceOn( element ) != null, + TargetTypePrism.getInstanceOn( element ) != null, + ContextPrism.getInstanceOn( element ) != null ); + } + + /** + * @param parameters the parameters to filter + * @return the parameters from the given list that are considered 'source parameters' + */ + public static List getSourceParameters(List parameters) { + List sourceParameters = new ArrayList( parameters.size() ); + + for ( Parameter parameter : parameters ) { + if ( !parameter.isMappingTarget() && !parameter.isTargetType() && !parameter.isMappingContext() ) { + sourceParameters.add( parameter ); + } + } + + return sourceParameters; + } + + /** + * @param parameters the parameters to filter + * @return the parameters from the given list that are marked as 'mapping context parameters' + */ + public static List getContextParameters(List parameters) { + List contextParameters = new ArrayList( parameters.size() ); + + for ( Parameter parameter : parameters ) { + if ( parameter.isMappingContext() ) { + contextParameters.add( parameter ); + } + } + + return contextParameters; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java index 0b3382a9d..a435fbcf4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java @@ -34,12 +34,15 @@ public class ParameterBinding { private final String variableName; private final boolean targetType; private final boolean mappingTarget; + private final boolean mappingContext; - private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType) { + private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType, + boolean mappingContext) { this.type = parameterType; this.variableName = variableName; this.targetType = targetType; this.mappingTarget = mappingTarget; + this.mappingContext = mappingContext; } /** @@ -63,6 +66,13 @@ public class ParameterBinding { return mappingTarget; } + /** + * @return {@code true}, if the parameter being bound is a {@code @MappingContext} parameter. + */ + public boolean isMappingContext() { + return mappingContext; + } + /** * @return the type of the parameter that is bound */ @@ -87,7 +97,8 @@ public class ParameterBinding { parameter.getType(), parameter.getName(), parameter.isMappingTarget(), - parameter.isTargetType() ); + parameter.isTargetType(), + parameter.isMappingContext() ); } public static List fromParameters(List parameters) { @@ -103,7 +114,7 @@ public class ParameterBinding { * @return a parameter binding representing a target type parameter */ public static ParameterBinding forTargetTypeBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, true ); + return new ParameterBinding( classTypeOf, null, false, true, false ); } /** @@ -111,7 +122,7 @@ public class ParameterBinding { * @return a parameter binding representing a mapping target parameter */ public static ParameterBinding forMappingTargetBinding(Type resultType) { - return new ParameterBinding( resultType, null, true, false ); + return new ParameterBinding( resultType, null, true, false, false ); } /** @@ -119,6 +130,6 @@ public class ParameterBinding { * @return a parameter binding representing a mapping source type */ public static ParameterBinding forSourceTypeBinding(Type sourceType) { - return new ParameterBinding( sourceType, null, false, false ); + return new ParameterBinding( sourceType, null, false, false, false ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index db5674b21..34fc1cbac 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -36,6 +36,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; + import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -52,8 +53,6 @@ import javax.lang.model.type.WildcardType; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; -import org.mapstruct.ap.internal.prism.MappingTargetPrism; -import org.mapstruct.ap.internal.prism.TargetTypePrism; import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.internal.util.accessor.Accessor; @@ -321,11 +320,7 @@ public class TypeFactory { VariableElement parameter = varIt.next(); TypeMirror parameterType = typesIt.next(); - result.add( new Parameter( - parameter.getSimpleName().toString(), - getType( parameterType ), - MappingTargetPrism.getInstanceOn( parameter ) != null, - TargetTypePrism.getInstanceOn( parameter ) != null ) ); + result.add( Parameter.forElementAndType( parameter, getType( parameterType ) ) ); } return result; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java index 2202f9671..1a6a7e629 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java @@ -18,10 +18,8 @@ */ package org.mapstruct.ap.internal.model.source; -import static org.mapstruct.ap.internal.util.Collections.first; - import java.util.ArrayList; -import java.util.Arrays; +import java.util.Iterator; import java.util.List; import javax.lang.model.element.ExecutableElement; @@ -45,7 +43,10 @@ public class ForgedMethod implements Method { private final ExecutableElement positionHintElement; private final List thrownTypes; private final MapperConfiguration mapperConfiguration; - private ForgedMethodHistory history; + private final ForgedMethodHistory history; + + private final List sourceParameters; + private final List contextParameters; /** * Creates a new forged method with the given name. @@ -55,17 +56,11 @@ public class ForgedMethod implements Method { * @param targetType the target type. * @param mapperConfiguration the mapper configuration * @param positionHintElement element used to for reference to the position in the source file. + * @param additionalParameters additional parameters to add to the forged method */ public ForgedMethod(String name, Type sourceType, Type targetType, MapperConfiguration mapperConfiguration, - ExecutableElement positionHintElement) { - String sourceParamName = Strings.decapitalize( sourceType.getName() ); - String sourceParamSafeName = Strings.getSaveVariableName( sourceParamName ); - this.parameters = Arrays.asList( new Parameter( sourceParamSafeName, sourceType ) ); - this.returnType = targetType; - this.thrownTypes = new ArrayList(); - this.name = Strings.sanitizeIdentifierName( name ); - this.mapperConfiguration = mapperConfiguration; - this.positionHintElement = positionHintElement; + ExecutableElement positionHintElement, List additionalParameters) { + this( name, sourceType, targetType, mapperConfiguration, positionHintElement, additionalParameters, null ); } /** @@ -76,13 +71,21 @@ public class ForgedMethod implements Method { * @param targetType the target type. * @param mapperConfiguration the mapper configuration * @param positionHintElement element used to for reference to the position in the source file. + * @param additionalParameters additional parameters to add to the forged method * @param history a parent forged method if this is a forged method within a forged method */ public ForgedMethod(String name, Type sourceType, Type targetType, MapperConfiguration mapperConfiguration, - ExecutableElement positionHintElement, ForgedMethodHistory history) { + ExecutableElement positionHintElement, List additionalParameters, + ForgedMethodHistory history) { String sourceParamName = Strings.decapitalize( sourceType.getName() ); String sourceParamSafeName = Strings.getSaveVariableName( sourceParamName ); - this.parameters = Arrays.asList( new Parameter( sourceParamSafeName, sourceType ) ); + + this.parameters = new ArrayList( 1 + additionalParameters.size() ); + this.parameters.add( new Parameter( sourceParamSafeName, sourceType ) ); + this.parameters.addAll( additionalParameters ); + this.sourceParameters = Parameter.getSourceParameters( parameters ); + this.contextParameters = Parameter.getContextParameters( parameters ); + this.returnType = targetType; this.thrownTypes = new ArrayList(); this.name = Strings.sanitizeIdentifierName( name ); @@ -102,6 +105,11 @@ public class ForgedMethod implements Method { this.thrownTypes = new ArrayList(); this.mapperConfiguration = forgedMethod.mapperConfiguration; this.positionHintElement = forgedMethod.positionHintElement; + this.history = forgedMethod.history; + + this.sourceParameters = Parameter.getSourceParameters( parameters ); + this.contextParameters = Parameter.getContextParameters( parameters ); + this.name = name; } @@ -112,12 +120,19 @@ public class ForgedMethod implements Method { return false; } - if ( parameters.size() != 1 || sourceTypes.size() != 1 ) { + if ( parameters.size() != sourceTypes.size() ) { return false; } - if ( !first( sourceTypes ).equals( first( parameters ).getType() ) ) { - return false; + Iterator srcTypeIt = sourceTypes.iterator(); + Iterator paramIt = parameters.iterator(); + + while ( srcTypeIt.hasNext() && paramIt.hasNext() ) { + Type sourceType = srcTypeIt.next(); + Parameter param = paramIt.next(); + if ( !sourceType.equals( param.getType() ) ) { + return false; + } } return true; @@ -140,7 +155,12 @@ public class ForgedMethod implements Method { @Override public List getSourceParameters() { - return parameters; + return sourceParameters; + } + + @Override + public List getContextParameters() { + return contextParameters; } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java index f292de91c..9ecbdff5e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java @@ -70,15 +70,23 @@ public interface Method { List getParameters(); /** - * returns the list of 'true' source parameters excluding the parameter(s) that is designated as - * target by means of the target annotation {@link #getMappingTargetParameter() }. + * returns the list of 'true' source parameters excluding the parameter(s) that are designated as target, target + * type or context parameter. * * @return list of 'true' source parameters */ List getSourceParameters(); /** - * Returns the parameter designated as mapping target (if present) {@link org.mapstruct.MappingTarget } + * returns the list of mapping context parameters, i.e. those parameters that are annotated with + * {@link org.mapstruct.Context}. + * + * @return list of context parameters + */ + List getContextParameters(); + + /** + * Returns the parameter designated as mapping target (if present) {@link org.mapstruct.MappingTarget} * * @return mapping target parameter (when present) null otherwise. */ 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 13becab3f..5bc711ade 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 @@ -69,7 +69,8 @@ public class SourceMethod implements Method { private final List prototypeMethods; private final Type mapperToImplement; - private List sourceParameters; + private final List sourceParameters; + private final List contextParameters; private List parameterNames; @@ -100,9 +101,6 @@ public class SourceMethod implements Method { private List prototypeMethods = Collections.emptyList(); private List valueMappings; - public Builder() { - } - public Builder setDeclaringMapper(Type declaringMapper) { this.declaringMapper = declaringMapper; return this; @@ -226,6 +224,9 @@ public class SourceMethod implements Method { this.mappingOptions = mappingOptions; + this.sourceParameters = Parameter.getSourceParameters( parameters ); + this.contextParameters = Parameter.getContextParameters( parameters ); + this.mappingTargetParameter = determineMappingTargetParameter( parameters ); this.targetTypeParameter = determineTargetTypeParameter( parameters ); this.isObjectFactory = determineIfIsObjectFactory( executable ); @@ -239,9 +240,11 @@ public class SourceMethod implements Method { private boolean determineIfIsObjectFactory(ExecutableElement executable) { boolean hasFactoryAnnotation = ObjectFactoryPrism.getInstanceOn( executable ) != null; - boolean isFactoryWithTargeTypeAnnotation = getTargetTypeParameter() != null && getSourceParameters().isEmpty(); - return !returnType.isVoid() - && ( hasFactoryAnnotation || isFactoryWithTargeTypeAnnotation || parameters.isEmpty() ); + boolean hasNoSourceParameters = getSourceParameters().isEmpty(); + boolean hasNoMappingTargetParam = getMappingTargetParameter() == null; + return !isLifecycleCallbackMethod() && !returnType.isVoid() + && hasNoMappingTargetParam + && ( hasFactoryAnnotation || hasNoSourceParameters ); } private Parameter determineMappingTargetParameter(Iterable parameters) { @@ -264,9 +267,6 @@ public class SourceMethod implements Method { return null; } - /** - * {@inheritDoc} {@link Method} - */ @Override public Type getDeclaringMapper() { return declaringMapper; @@ -277,40 +277,26 @@ public class SourceMethod implements Method { return executable; } - /** - * {@inheritDoc} {@link Method} - */ @Override public String getName() { return executable.getSimpleName().toString(); } - /** - * {@inheritDoc} {@link Method} - */ @Override public List getParameters() { return parameters; } - /** - * {@inheritDoc} {@link Method} - */ @Override public List getSourceParameters() { - if ( sourceParameters == null ) { - sourceParameters = new ArrayList(); - - for ( Parameter parameter : parameters ) { - if ( !parameter.isMappingTarget() && !parameter.isTargetType() ) { - sourceParameters.add( parameter ); - } - } - } - return sourceParameters; } + @Override + public List getContextParameters() { + return contextParameters; + } + @Override public List getParameterNames() { if ( parameterNames == null ) { @@ -331,9 +317,6 @@ public class SourceMethod implements Method { return mappingTargetParameter != null ? mappingTargetParameter.getType() : returnType; } - /** - * {@inheritDoc} {@link Method} - */ @Override public Type getReturnType() { return returnType; @@ -544,9 +527,6 @@ public class SourceMethod implements Method { return declaringMapper == null && executable.getModifiers().contains( Modifier.ABSTRACT ); } - /** - * {@inheritDoc} {@link Method} - */ @Override public boolean matches(List sourceTypes, Type targetType) { MethodMatcher matcher = new MethodMatcher( typeUtils, typeFactory, this ); @@ -614,5 +594,4 @@ public class SourceMethod implements Method { public boolean isUpdateMethod() { return getMappingTargetParameter() != null; } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java index a8aaa43c5..578a2519b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java @@ -103,6 +103,11 @@ public abstract class BuiltInMethod implements Method { return getParameters(); } + @Override + public List getContextParameters() { + return Collections.emptyList(); + } + /** * {@inheritDoc} *

diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java index 5ca492e65..99d212e0f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java @@ -62,7 +62,7 @@ public class TypeSelector implements MethodSelector { availableBindings = getAvailableParameterBindingsFromMethod( mappingMethod ); } else { - availableBindings = getAvailableParameterBindingsFromSourceTypes( sourceTypes, targetType ); + availableBindings = getAvailableParameterBindingsFromSourceTypes( sourceTypes, targetType, mappingMethod ); } for ( SelectedMethod method : methods ) { @@ -91,7 +91,7 @@ public class TypeSelector implements MethodSelector { } private List getAvailableParameterBindingsFromSourceTypes(List sourceTypes, - Type targetType) { + Type targetType, Method mappingMethod) { List availableParams = new ArrayList( sourceTypes.size() + 2 ); @@ -101,6 +101,12 @@ public class TypeSelector implements MethodSelector { availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) ); } + for ( Parameter param : mappingMethod.getParameters() ) { + if ( param.isMappingContext() ) { + availableParams.add( ParameterBinding.fromParameter( param ) ); + } + } + return availableParams; } @@ -185,7 +191,8 @@ public class TypeSelector implements MethodSelector { for ( ParameterBinding candidate : candidateParameters ) { if ( parameter.isTargetType() == candidate.isTargetType() - && parameter.isMappingTarget() == candidate.isMappingTarget() ) { + && parameter.isMappingTarget() == candidate.isMappingTarget() + && parameter.isMappingContext() == candidate.isMappingContext() ) { result.add( candidate ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java index 47336e8be..a681896f5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java @@ -24,6 +24,7 @@ import javax.xml.bind.annotation.XmlElementRef; import org.mapstruct.AfterMapping; import org.mapstruct.BeanMapping; import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; import org.mapstruct.DecoratedWith; import org.mapstruct.InheritConfiguration; import org.mapstruct.InheritInverseConfiguration; @@ -69,6 +70,7 @@ import net.java.dev.hickory.prism.GeneratePrisms; @GeneratePrism(value = BeforeMapping.class, publicAccess = true), @GeneratePrism(value = ValueMapping.class, publicAccess = true), @GeneratePrism(value = ValueMappings.class, publicAccess = true), + @GeneratePrism(value = Context.class, publicAccess = true), // external types @GeneratePrism(value = XmlElementDecl.class, publicAccess = true), 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 95f7a33ab..ff309d4c0 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 @@ -23,8 +23,10 @@ import static org.mapstruct.ap.internal.util.Executables.getAllEnclosedExecutabl import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; @@ -215,7 +217,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor prototypeMethods ) { Type returnType = typeFactory.getReturnType( methodType ); List exceptionTypes = typeFactory.getThrownTypes( methodType ); - List sourceParameters = extractSourceParameters( parameters ); + List sourceParameters = Parameter.getSourceParameters( parameters ); + List contextParameters = Parameter.getContextParameters( parameters ); Parameter targetParameter = extractTargetParameter( parameters ); Type resultType = selectResultType( returnType, targetParameter ); @@ -223,6 +226,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor parameters) { @@ -342,17 +343,6 @@ public class MethodRetrievalProcessor implements ModelElementProcessor extractSourceParameters(List parameters) { - List sourceParameters = new ArrayList( parameters.size() ); - for ( Parameter param : parameters ) { - if ( !param.isMappingTarget() ) { - sourceParameters.add( param ); - } - } - - return sourceParameters; - } - private Type selectResultType(Type returnType, Parameter targetParameter) { if ( null != targetParameter ) { return targetParameter.getType(); @@ -363,14 +353,16 @@ public class MethodRetrievalProcessor implements ModelElementProcessor sourceParameters, - Parameter targetParameter, Type resultType, Type returnType, + Parameter targetParameter, List contextParameters, + Type resultType, Type returnType, boolean containsTargetTypeParameter) { if ( sourceParameters.isEmpty() ) { messager.printMessage( method, Message.RETRIEVAL_NO_INPUT_ARGS ); return false; } - if ( targetParameter != null && ( sourceParameters.size() + 1 != method.getParameters().size() ) ) { + if ( targetParameter != null + && ( sourceParameters.size() + contextParameters.size() + 1 != method.getParameters().size() ) ) { messager.printMessage( method, Message.RETRIEVAL_DUPLICATE_MAPPING_TARGETS ); return false; } @@ -393,6 +385,14 @@ public class MethodRetrievalProcessor implements ModelElementProcessor contextParameterTypes = new HashSet(); + for ( Parameter contextParameter : contextParameters ) { + if ( !contextParameterTypes.add( contextParameter.getType() ) ) { + messager.printMessage( method, Message.RETRIEVAL_CONTEXT_PARAMS_WITH_SAME_TYPE ); + return false; + } + } + if ( returnType.isTypeVar() || resultType.isTypeVar() ) { messager.printMessage( method, Message.RETRIEVAL_TYPE_VAR_RESULT ); return false; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index f1fe92861..691a513cb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -98,6 +98,7 @@ public enum Message { RETRIEVAL_TYPE_VAR_RESULT( "Can't generate mapping method for a generic type variable target." ), RETRIEVAL_WILDCARD_SUPER_BOUND_SOURCE( "Can't generate mapping method for a wildcard super bound source." ), RETRIEVAL_WILDCARD_EXTENDS_BOUND_RESULT( "Can't generate mapping method for a wildcard extends bound result." ), + RETRIEVAL_CONTEXT_PARAMS_WITH_SAME_TYPE( "The types of @Context parameters must be unique." ), INHERITCONFIGURATION_BOTH( "Method cannot be annotated with both a @InheritConfiguration and @InheritInverseConfiguration." ), INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ), diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl index f098c9323..ea707f938 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl @@ -37,7 +37,9 @@ <#-- a class is passed on for casting, see @TargetType --> <@includeModel object=ext.targetType raw=true/>.class<#t> <#elseif param.mappingTarget> - ${ext.targetBeanName}<#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}<#t> + ${ext.targetBeanName}<#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}<#t> + <#elseif param.mappingContext> + ${param.variableName}<#t> <#elseif assignment??> <@_assignment/><#t> <#else> diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/AttributeDTO.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/AttributeDto.java similarity index 87% rename from processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/AttributeDTO.java rename to processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/AttributeDto.java index db2821cce..ae6588990 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/AttributeDTO.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/AttributeDto.java @@ -21,17 +21,17 @@ package org.mapstruct.ap.test.callbacks.returning; /** * @author Pascal Grün */ -public class AttributeDTO { - private NodeDTO node; +public class AttributeDto { + private NodeDto node; private String name; private String value; - public NodeDTO getNode() { + public NodeDto getNode() { return node; } - public void setNode(NodeDTO node) { + public void setNode(NodeDto node) { this.node = node; } @@ -53,6 +53,6 @@ public class AttributeDTO { @Override public String toString() { - return "AttributeDTO [name=" + name + ", value=" + value + "]"; + return "AttributeDto [name=" + name + ", value=" + value + "]"; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java index f609bf31e..f0cb5e039 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java @@ -36,7 +36,7 @@ import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; * @author Pascal Grün */ @IssueKey( "469" ) -@WithClasses( { Attribute.class, AttributeDTO.class, Node.class, NodeDTO.class, NodeMapperDefault.class, +@WithClasses( { Attribute.class, AttributeDto.class, Node.class, NodeDto.class, NodeMapperDefault.class, NodeMapperWithContext.class, NodeMapperContext.class, Number.class, NumberMapperDefault.class, NumberMapperContext.class, NumberMapperWithContext.class } ) @RunWith( AnnotationProcessorTestRunner.class ) @@ -44,13 +44,13 @@ public class CallbacksWithReturnValuesTest { @Test( expected = StackOverflowError.class ) public void mappingWithDefaultHandlingRaisesStackOverflowError() { Node root = buildNodes(); - NodeMapperDefault.INSTANCE.nodeToNodeDTO( root ); + NodeMapperDefault.INSTANCE.nodeToNodeDto( root ); } @Test( expected = StackOverflowError.class ) public void updatingWithDefaultHandlingRaisesStackOverflowError() { Node root = buildNodes(); - NodeMapperDefault.INSTANCE.nodeToNodeDTO( root, new NodeDTO() ); + NodeMapperDefault.INSTANCE.nodeToNodeDto( root, new NodeDto() ); } @Test @@ -66,8 +66,8 @@ public class CallbacksWithReturnValuesTest { NodeMapperContext.addContextListener( contextListener ); try { Node root = buildNodes(); - NodeDTO rootDTO = NodeMapperWithContext.INSTANCE.nodeToNodeDTO( root ); - assertThat( rootDTO ).isNotNull(); + NodeDto rootDto = NodeMapperWithContext.INSTANCE.nodeToNodeDto( root ); + assertThat( rootDto ).isNotNull(); assertThat( contextLevel.get() ).isEqualTo( Integer.valueOf( 1 ) ); } finally { diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeDTO.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeDto.java similarity index 74% rename from processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeDTO.java rename to processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeDto.java index 97339009a..3f74866a0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeDTO.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeDto.java @@ -23,19 +23,18 @@ import java.util.List; /** * @author Pascal Grün */ -public class NodeDTO { - private NodeDTO parent; +public class NodeDto { + private NodeDto parent; private String name; + private List children; + private List attributes; - private List children; - private List attributes; - - public NodeDTO getParent() { + public NodeDto getParent() { return parent; } - public void setParent(NodeDTO parent) { + public void setParent(NodeDto parent) { this.parent = parent; } @@ -47,24 +46,24 @@ public class NodeDTO { this.name = name; } - public List getChildren() { + public List getChildren() { return children; } - public void setChildren(List children) { + public void setChildren(List children) { this.children = children; } - public List getAttributes() { + public List getAttributes() { return attributes; } - public void setAttributes(List attributes) { + public void setAttributes(List attributes) { this.attributes = attributes; } @Override public String toString() { - return "NodeDTO [name=" + name + "]"; + return "NodeDto [name=" + name + "]"; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperDefault.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperDefault.java index 88be582b5..7cf98d527 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperDefault.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperDefault.java @@ -29,11 +29,11 @@ import org.mapstruct.factory.Mappers; public abstract class NodeMapperDefault { public static final NodeMapperDefault INSTANCE = Mappers.getMapper( NodeMapperDefault.class ); - public abstract NodeDTO nodeToNodeDTO(Node node); + public abstract NodeDto nodeToNodeDto(Node node); - public abstract void nodeToNodeDTO(Node node, @MappingTarget NodeDTO nodeDto); + public abstract void nodeToNodeDto(Node node, @MappingTarget NodeDto nodeDto); - protected abstract AttributeDTO attributeToAttributeDTO(Attribute attribute); + protected abstract AttributeDto attributeToAttributeDto(Attribute attribute); - protected abstract void attributeToAttributeDTO(Attribute attribute, @MappingTarget AttributeDTO nodeDto); + protected abstract void attributeToAttributeDto(Attribute attribute, @MappingTarget AttributeDto nodeDto); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperWithContext.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperWithContext.java index 6ac7bdda0..391930fd6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperWithContext.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperWithContext.java @@ -29,11 +29,11 @@ import org.mapstruct.factory.Mappers; public abstract class NodeMapperWithContext { public static final NodeMapperWithContext INSTANCE = Mappers.getMapper( NodeMapperWithContext.class ); - public abstract NodeDTO nodeToNodeDTO(Node node); + public abstract NodeDto nodeToNodeDto(Node node); - public abstract void nodeToNodeDTO(Node node, @MappingTarget NodeDTO nodeDto); + public abstract void nodeToNodeDto(Node node, @MappingTarget NodeDto nodeDto); - protected abstract AttributeDTO attributeToAttributeDTO(Attribute attribute); + protected abstract AttributeDto attributeToAttributeDto(Attribute attribute); - protected abstract void attributeToAttributeDTO(Attribute attribute, @MappingTarget AttributeDTO nodeDto); + protected abstract void attributeToAttributeDto(Attribute attribute, @MappingTarget AttributeDto nodeDto); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/AutomappingNodeMapperWithContext.java b/processor/src/test/java/org/mapstruct/ap/test/context/AutomappingNodeMapperWithContext.java new file mode 100644 index 000000000..19ef75a74 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/AutomappingNodeMapperWithContext.java @@ -0,0 +1,39 @@ +/** + * Copyright 2012-2016 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; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper(uses = CycleContextLifecycleMethods.class) +public interface AutomappingNodeMapperWithContext { + + AutomappingNodeMapperWithContext INSTANCE = + Mappers.getMapper( AutomappingNodeMapperWithContext.class ); + + NodeDto nodeToNodeDto(Node node, @Context CycleContext cycleContext, @Context FactoryContext factoryContext); + + void nodeToNodeDto(Node node, @MappingTarget NodeDto nodeDto, @Context CycleContext cycleContext, + @Context FactoryContext factoryContext); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java new file mode 100644 index 000000000..654d54807 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java @@ -0,0 +1,60 @@ +/** + * Copyright 2012-2016 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; + +import javax.tools.Diagnostic.Kind; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.Context; +import org.mapstruct.ap.test.context.erroneous.ErroneousNodeMapperWithNonUniqueContextTypes; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +/** + * Tests the erroneous usage of the {@link Context} annotation in the following situations: + *

+ * + * @author Andreas Gudian + */ +@IssueKey("975") +@WithClasses({ + Node.class, + NodeDto.class, + CycleContext.class }) +@RunWith(AnnotationProcessorTestRunner.class) +public class ContextParameterErroneousTest { + + @Test + @WithClasses(ErroneousNodeMapperWithNonUniqueContextTypes.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = @Diagnostic( + kind = Kind.ERROR, + line = 33, + type = ErroneousNodeMapperWithNonUniqueContextTypes.class, + messageRegExp = "The types of @Context parameters must be unique")) + public void reportsNonUniqueContextParamType() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterTest.java b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterTest.java new file mode 100644 index 000000000..4e0721ca8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterTest.java @@ -0,0 +1,117 @@ +/** + * Copyright 2012-2016 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; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.ObjectFactory; +import org.mapstruct.ap.test.context.Node.Attribute; +import org.mapstruct.ap.test.context.NodeDto.AttributeDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +/** + * Tests the usage of the {@link Context} annotation in the following situations: + * + * + * @author Andreas Gudian + */ +@IssueKey("975") +@WithClasses({ + Node.class, + NodeDto.class, + NodeMapperWithContext.class, + AutomappingNodeMapperWithContext.class, + CycleContext.class, + FactoryContext.class, + CycleContextLifecycleMethods.class }) +@RunWith(AnnotationProcessorTestRunner.class) +public class ContextParameterTest { + + private static final int MAGIC_NUMBER_OFFSET = 10; + + @Test + public void mappingWithContextCorrectlyResolvesCycles() { + Node root = buildNodes(); + NodeDto rootDto = + NodeMapperWithContext.INSTANCE.nodeToNodeDto( new FactoryContext( 0, 10 ), root, new CycleContext() ); + assertResult( rootDto ); + + NodeDto updated = new NodeDto( 0 ); + NodeMapperWithContext.INSTANCE.nodeToNodeDto( new FactoryContext( 1, 10 ), root, updated, new CycleContext() ); + assertResult( updated ); + } + + @Test + public void automappingWithContextCorrectlyResolvesCycles() { + Node root = buildNodes(); + NodeDto rootDto = AutomappingNodeMapperWithContext.INSTANCE + .nodeToNodeDto( root, new CycleContext(), new FactoryContext( 0, MAGIC_NUMBER_OFFSET ) ); + assertResult( rootDto ); + + NodeDto updated = new NodeDto( 0 ); + AutomappingNodeMapperWithContext.INSTANCE + .nodeToNodeDto( root, updated, new CycleContext(), new FactoryContext( 1, 10 ) ); + assertResult( updated ); + } + + private void assertResult(NodeDto rootDto) { + assertThat( rootDto ).isNotNull(); + assertThat( rootDto.getId() ).isEqualTo( 0 ); + + AttributeDto rootAttribute = rootDto.getAttributes().get( 0 ); + assertThat( rootAttribute.getNode() ).isSameAs( rootDto ); + assertThat( rootAttribute.getMagicNumber() ).isEqualTo( 1 + MAGIC_NUMBER_OFFSET ); + + assertThat( rootDto.getChildren() ).hasSize( 1 ); + + NodeDto node1 = rootDto.getChildren().get( 0 ); + assertThat( node1.getParent() ).isSameAs( rootDto ); + assertThat( node1.getId() ).isEqualTo( 1 ); + + AttributeDto node1Attribute = node1.getAttributes().get( 0 ); + assertThat( node1Attribute.getNode() ).isSameAs( node1 ); + assertThat( node1Attribute.getMagicNumber() ).isEqualTo( 2 + MAGIC_NUMBER_OFFSET ); + + } + + private static Node buildNodes() { + Node root = new Node( "root" ); + root.addAttribute( new Attribute( "name", "root", 1 ) ); + + Node node1 = new Node( "node1" ); + node1.addAttribute( new Attribute( "name", "node1", 2 ) ); + + root.addChild( node1 ); + + return root; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/CycleContext.java b/processor/src/test/java/org/mapstruct/ap/test/context/CycleContext.java new file mode 100644 index 000000000..842f42a4f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/CycleContext.java @@ -0,0 +1,42 @@ +/** + * Copyright 2012-2016 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; + +import java.util.IdentityHashMap; +import java.util.Map; + +import org.mapstruct.Context; + +/** + * A type to be used as {@link Context} parameter to track cycles in graphs + * + * @author Andreas Gudian + */ +public class CycleContext { + private Map knownInstances = new IdentityHashMap(); + + @SuppressWarnings("unchecked") + public T getMappedInstance(Object source, Class targetType) { + return (T) knownInstances.get( source ); + } + + public void storeMappedInstance(Object source, Object target) { + knownInstances.put( source, target ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/CycleContextLifecycleMethods.java b/processor/src/test/java/org/mapstruct/ap/test/context/CycleContextLifecycleMethods.java new file mode 100644 index 000000000..9997cf8f3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/CycleContextLifecycleMethods.java @@ -0,0 +1,52 @@ +/** + * Copyright 2012-2016 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; + +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; +import org.mapstruct.TargetType; +import org.mapstruct.ap.test.context.Node.Attribute; +import org.mapstruct.ap.test.context.NodeDto.AttributeDto; + +/** + * @author Andreas Gudian + */ +public class CycleContextLifecycleMethods { + + public NodeDto createNodeDto(@Context FactoryContext context) { + return context.createNode(); + } + + @ObjectFactory + public AttributeDto createAttributeDto(Attribute source, @Context FactoryContext context) { + return context.createAttributeDto( source ); + } + + @BeforeMapping + public T getInstance(Object source, @TargetType Class type, @Context CycleContext cycleContext) { + return cycleContext.getMappedInstance( source, type ); + } + + @BeforeMapping + public void setInstance(Object source, @MappingTarget Object target, @Context CycleContext cycleContext) { + cycleContext.storeMappedInstance( source, target ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/FactoryContext.java b/processor/src/test/java/org/mapstruct/ap/test/context/FactoryContext.java new file mode 100644 index 000000000..cc612cb1d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/FactoryContext.java @@ -0,0 +1,46 @@ +/** + * Copyright 2012-2016 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; + +import org.mapstruct.Context; +import org.mapstruct.ap.test.context.Node.Attribute; +import org.mapstruct.ap.test.context.NodeDto.AttributeDto; + +/** + * A type to be used as {@link Context} parameter to create NodeDto and AttributeDto instances + * + * @author Andreas Gudian + */ +public class FactoryContext { + private int nodeCounter; + private int attributeMagicNumberOffset; + + public FactoryContext(int initialCounter, int attributeMaticNumberOffset) { + this.nodeCounter = initialCounter; + this.attributeMagicNumberOffset = attributeMaticNumberOffset; + } + + public NodeDto createNode() { + return new NodeDto( nodeCounter++ ); + } + + public AttributeDto createAttributeDto(Attribute source) { + return new AttributeDto( source.getMagicNumber() + attributeMagicNumberOffset ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/Node.java b/processor/src/test/java/org/mapstruct/ap/test/context/Node.java new file mode 100644 index 000000000..f024cdd16 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/Node.java @@ -0,0 +1,100 @@ +/** + * Copyright 2012-2016 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; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Andreas Gudian + */ +public class Node { + private Node parent; + + private String name; + + private List children; + private List attributes; + + public Node(String name) { + this.name = name; + this.children = new ArrayList(); + this.attributes = new ArrayList(); + } + + public Node getParent() { + return parent; + } + + public String getName() { + return name; + } + + public List getChildren() { + return children; + } + + public void addChild(Node node) { + children.add( node ); + node.parent = this; + } + + public List getAttributes() { + return attributes; + } + + public void addAttribute(Attribute attribute) { + attributes.add( attribute ); + attribute.setNode( this ); + } + + public static class Attribute { + private Node node; + + private String name; + private String value; + private int magicNumber; + + public Attribute(String name, String value, int magicNumber) { + this.name = name; + this.value = value; + this.magicNumber = magicNumber; + } + + public Node getNode() { + return node; + } + + public void setNode(Node node) { + this.node = node; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + public int getMagicNumber() { + return magicNumber; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/NodeDto.java b/processor/src/test/java/org/mapstruct/ap/test/context/NodeDto.java new file mode 100644 index 000000000..e5f6ab1a7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/NodeDto.java @@ -0,0 +1,114 @@ +/** + * Copyright 2012-2016 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; + +import java.util.List; + +/** + * @author Andreas Gudian + */ +public class NodeDto { + private NodeDto parent; + + private int id; + private String name; + + private List children; + private List attributes; + + public NodeDto(int id) { + this.id = id; + } + + public NodeDto getParent() { + return parent; + } + + public void setParent(NodeDto parent) { + this.parent = parent; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public List getAttributes() { + return attributes; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + public static class AttributeDto { + private NodeDto node; + + private String name; + private String value; + private int magicNumber; + + public AttributeDto(int magicNumber) { + this.magicNumber = magicNumber; + } + + public NodeDto getNode() { + return node; + } + + public void setNode(NodeDto node) { + this.node = node; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public int getMagicNumber() { + return magicNumber; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/NodeMapperWithContext.java b/processor/src/test/java/org/mapstruct/ap/test/context/NodeMapperWithContext.java new file mode 100644 index 000000000..bb44f1d1d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/NodeMapperWithContext.java @@ -0,0 +1,45 @@ +/** + * Copyright 2012-2016 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; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ap.test.context.Node.Attribute; +import org.mapstruct.ap.test.context.NodeDto.AttributeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper(uses = CycleContextLifecycleMethods.class) +public interface NodeMapperWithContext { + NodeMapperWithContext INSTANCE = Mappers.getMapper( NodeMapperWithContext.class ); + + NodeDto nodeToNodeDto(@Context FactoryContext factoryContext, Node node, @Context CycleContext cycleContext); + + void nodeToNodeDto(@Context FactoryContext factoryContext, Node node, @MappingTarget NodeDto nodeDto, + @Context CycleContext cycleContext); + + AttributeDto attributeToAttributeDto(Attribute attribute, @Context CycleContext cycleContext, + @Context FactoryContext factoryContext); + + void attributeToAttributeDto(Attribute attribute, @MappingTarget AttributeDto nodeDto, + @Context CycleContext cycleContext, @Context FactoryContext factoryContext); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/erroneous/ErroneousNodeMapperWithNonUniqueContextTypes.java b/processor/src/test/java/org/mapstruct/ap/test/context/erroneous/ErroneousNodeMapperWithNonUniqueContextTypes.java new file mode 100644 index 000000000..2bda0290a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/erroneous/ErroneousNodeMapperWithNonUniqueContextTypes.java @@ -0,0 +1,34 @@ +/** + * Copyright 2012-2016 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.erroneous; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.context.CycleContext; +import org.mapstruct.ap.test.context.Node; +import org.mapstruct.ap.test.context.NodeDto; + +/** + * @author Andreas Gudian + */ +@Mapper +public interface ErroneousNodeMapperWithNonUniqueContextTypes { + + NodeDto nodeToNodeDto(Node node, @Context CycleContext cycleContext, @Context CycleContext otherCycleContext); +}