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 ab820eceb..05f4e9b1c 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 @@ -5,8 +5,6 @@ */ package org.mapstruct.ap.internal.model; -import static org.mapstruct.ap.internal.util.Collections.first; - import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; @@ -20,7 +18,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; - import javax.lang.model.type.DeclaredType; import javax.tools.Diagnostic; @@ -53,6 +50,8 @@ import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; +import static org.mapstruct.ap.internal.util.Collections.first; + /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one bean type to another, optionally * configured by one or more {@link PropertyMapping}s. @@ -116,7 +115,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { for ( Parameter sourceParameter : method.getSourceParameters() ) { unprocessedSourceParameters.add( sourceParameter ); - if ( sourceParameter.getType().isPrimitive() ) { + if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() ) { continue; } Map readAccessors = sourceParameter.getType().getPropertyReadAccessors(); @@ -558,7 +557,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { Type sourceType = sourceParameter.getType(); - if ( sourceType.isPrimitive() ) { + if ( sourceType.isPrimitive() || sourceType.isArrayType() ) { continue; } 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 cfc9a9f50..ed65185f6 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 @@ -8,7 +8,6 @@ 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; @@ -30,17 +29,21 @@ public class Parameter extends ModelElement { private final boolean targetType; private final boolean mappingContext; - private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext) { + private final boolean varArgs; + + private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext, + boolean varArgs) { this.name = name; this.originalName = name; this.type = type; this.mappingTarget = mappingTarget; this.targetType = targetType; this.mappingContext = mappingContext; + this.varArgs = varArgs; } public Parameter(String name, Type type) { - this( name, type, false, false, false ); + this( name, type, false, false, false, false ); } public String getName() { @@ -80,6 +83,10 @@ public class Parameter extends ModelElement { return mappingContext; } + public boolean isVarArgs() { + return varArgs; + } + @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; @@ -105,13 +112,15 @@ public class Parameter extends ModelElement { } - public static Parameter forElementAndType(VariableElement element, Type parameterType) { + public static Parameter forElementAndType(VariableElement element, Type parameterType, boolean isVarArgs) { return new Parameter( element.getSimpleName().toString(), parameterType, MappingTargetPrism.getInstanceOn( element ) != null, TargetTypePrism.getInstanceOn( element ) != null, - ContextPrism.getInstanceOn( element ) != null ); + ContextPrism.getInstanceOn( element ) != null, + isVarArgs + ); } public static Parameter forForgedMappingTarget(Type parameterType) { @@ -120,7 +129,9 @@ public class Parameter extends ModelElement { parameterType, true, false, - 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 9b795e136..e2733cb6f 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 @@ -23,7 +23,6 @@ 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; @@ -377,7 +376,13 @@ public class TypeFactory { VariableElement parameter = varIt.next(); TypeMirror parameterType = typesIt.next(); - result.add( Parameter.forElementAndType( parameter, getType( parameterType ) ) ); + Type type = getType( parameterType ); + + // if the method has varargs and this is the last parameter + // we know that this parameter should be used as varargs + boolean isVarArgs = !varIt.hasNext() && method.isVarArgs(); + + result.add( Parameter.forElementAndType( parameter, type, isVarArgs ) ); } return result; 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 d2694907f..f74f8880f 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 @@ -5,8 +5,6 @@ */ package org.mapstruct.ap.internal.processor; -import static org.mapstruct.ap.internal.util.Executables.getAllEnclosedExecutableElements; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -14,7 +12,6 @@ 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; import javax.lang.model.element.TypeElement; @@ -49,6 +46,8 @@ import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.MapperConfiguration; import org.mapstruct.ap.internal.util.Message; +import static org.mapstruct.ap.internal.util.Executables.getAllEnclosedExecutableElements; + /** * A {@link ModelElementProcessor} which retrieves a list of {@link SourceMethod}s * representing all the mapping methods of the given bean mapper type as well as @@ -267,7 +266,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor contextParamMethods = retrieveMethods( diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Parameter.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Parameter.ftl index 917462429..821712188 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Parameter.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Parameter.ftl @@ -5,4 +5,4 @@ Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 --> -<@includeModel object=type/> ${name} \ No newline at end of file +<@includeModel object=type asVarArgs=varArgs/> ${name} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Type.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Type.ftl index 426dd6e16..1cb4fa629 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Type.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Type.ftl @@ -11,6 +11,6 @@ <#elseif wildCardSuperBound> ? super <@includeModel object=typeBound /> <#else> - ${referenceName}<#if (!ext.raw?? && typeParameters?size > 0) ><<#list typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, > + <#if ext.asVarArgs!false>${referenceName?remove_ending("[]")}...<#else>${referenceName}<#if (!ext.raw?? && typeParameters?size > 0) ><<#list typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, > \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java index 6e1463dea..68fee3bc8 100755 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java @@ -172,7 +172,7 @@ public class DateFormatValidatorFactoryTest { false, false, false, - false ); + false); } } diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java index cdbef06bd..40f7357e9 100755 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java @@ -123,7 +123,7 @@ public class DefaultConversionContextTest { false, false, false, - false ); + false); } private static class StatefulMessagerMock implements FormattingMessager { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Mapper.java new file mode 100644 index 000000000..caafee8d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Mapper.java @@ -0,0 +1,85 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1541; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeanMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Bandowski + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) // IGNORE to keep this test-mapper small and clean +public abstract class Issue1541Mapper { + + public static final Issue1541Mapper INSTANCE = Mappers.getMapper( Issue1541Mapper.class ); + + public abstract Target mapWithVarArgs(String code, String... parameters); + + public abstract Target mapWithArray(String code, String[] parameters); + + @Mapping(target = "parameters2", source = "parameters") + public abstract Target mapWithReassigningVarArgs(String code, String... parameters); + + public abstract Target mapWithArrayAndVarArgs(String code, String[] parameters, String... parameters2); + + @Mappings({ + @Mapping(target = "parameters", ignore = true) + }) + @BeanMapping(qualifiedByName = "afterMappingParametersAsArray") + public abstract Target mapParametersAsArrayInAfterMapping(String code, String... parameters); + + @AfterMapping + @Named( "afterMappingParametersAsArray" ) + protected void afterMappingParametersAsArray(@MappingTarget Target target, String[] parameters) { + target.setAfterMappingWithArrayCalled( true ); + target.setParameters( parameters ); + } + + @Mappings({ + @Mapping(target = "parameters", ignore = true) + }) + @BeanMapping(qualifiedByName = "afterMappingParametersAsVarArgs") + public abstract Target mapParametersAsVarArgsInAfterMapping(String code, String... parameters); + + @AfterMapping + @Named( "afterMappingParametersAsVarArgs" ) + protected void afterMappingParametersAsVarArgs(@MappingTarget Target target, String... parameters) { + target.setAfterMappingWithVarArgsCalled( true ); + target.setParameters( parameters ); + } + + @Mapping(target = "parameters2", ignore = true) + @BeanMapping(qualifiedByName = "afterMappingContextAsVarArgsUsingVarArgs") + public abstract Target mapContextWithVarArgsInAfterMappingWithVarArgs(String code, String[] parameters, + @Context String... context); + + @AfterMapping + @Named( "afterMappingContextAsVarArgsUsingVarArgs" ) + protected void afterMappingContextAsVarArgsUsingVarArgs(@MappingTarget Target target, @Context String... context) { + target.setAfterMappingContextWithVarArgsAsVarArgsCalled( true ); + target.setParameters2( context ); + } + + @Mapping(target = "parameters2", ignore = true) + @BeanMapping(qualifiedByName = "afterMappingContextAsVarArgsUsingArray") + public abstract Target mapContextWithVarArgsInAfterMappingWithArray(String code, String[] parameters, + @Context String... context); + + @AfterMapping + @Named( "afterMappingContextAsVarArgsUsingArray" ) + protected void afterMappingContextAsVarArgsUsingArray(@MappingTarget Target target, @Context String[] context) { + target.setAfterMappingContextWithVarArgsAsArrayCalled( true ); + target.setParameters2( context ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Test.java new file mode 100644 index 000000000..92a7f99ca --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Test.java @@ -0,0 +1,148 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1541; + +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; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Christian Bandowski + */ +@WithClasses({ + Issue1541Mapper.class, + Target.class +}) +@RunWith(AnnotationProcessorTestRunner.class) +@IssueKey("1541") +public class Issue1541Test { + + @Test + public void testMappingWithVarArgs() { + Target target = Issue1541Mapper.INSTANCE.mapWithVarArgs( "code", "1", "2" ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @Test + public void testMappingWithArray() { + Target target = Issue1541Mapper.INSTANCE.mapWithArray( "code", new String[] { "1", "2" } ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).isNull(); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @Test + public void testMappingWithVarArgsReassignment() { + Target target = Issue1541Mapper.INSTANCE.mapWithReassigningVarArgs( "code", "1", "2" ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).isNull(); + assertThat( target.getParameters2() ).contains( "1", "2" ); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @Test + public void testMappingWithArrayAndVarArgs() { + Target target = Issue1541Mapper.INSTANCE.mapWithArrayAndVarArgs( "code", new String[] { "1", "2" }, "3", "4" ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).contains( "3", "4" ); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @Test + public void testVarArgsInAfterMappingAsArray() { + Target target = Issue1541Mapper.INSTANCE.mapParametersAsArrayInAfterMapping( "code", "1", "2" ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).isNull(); + assertThat( target.isAfterMappingWithArrayCalled() ).isTrue(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @Test + public void testVarArgsInAfterMappingAsVarArgs() { + Target target = Issue1541Mapper.INSTANCE.mapParametersAsVarArgsInAfterMapping( "code", "1", "2" ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).isNull(); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isTrue(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @Test + public void testVarArgsInContextWithVarArgsAfterMapping() { + Target target = Issue1541Mapper.INSTANCE.mapContextWithVarArgsInAfterMappingWithVarArgs( + "code", + new String[] { "1", "2" }, + "3", + "4" + ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).contains( "3", "4" ); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isTrue(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @Test + public void testVarArgsInContextWithArrayAfterMapping() { + Target target = Issue1541Mapper.INSTANCE.mapContextWithVarArgsInAfterMappingWithArray( + "code", + new String[] { "1", "2" }, + "3", + "4" + ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).contains( "3", "4" ); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isTrue(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Target.java new file mode 100644 index 000000000..b57c7a476 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Target.java @@ -0,0 +1,74 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._1541; + +public class Target { + private String code; + private String[] parameters; + private String[] parameters2; + + private boolean afterMappingWithVarArgsCalled = false; + private boolean afterMappingWithArrayCalled = false; + private boolean afterMappingContextWithVarArgsAsVarArgsCalled = false; + private boolean afterMappingContextWithVarArgsAsArrayCalled = false; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String[] getParameters() { + return parameters; + } + + public void setParameters(String[] parameters) { + this.parameters = parameters; + } + + public String[] getParameters2() { + return parameters2; + } + + public void setParameters2(String[] parameters2) { + this.parameters2 = parameters2; + } + + public boolean isAfterMappingWithVarArgsCalled() { + return afterMappingWithVarArgsCalled; + } + + public void setAfterMappingWithVarArgsCalled(boolean afterMappingWithVarArgsCalled) { + this.afterMappingWithVarArgsCalled = afterMappingWithVarArgsCalled; + } + + public boolean isAfterMappingWithArrayCalled() { + return afterMappingWithArrayCalled; + } + + public void setAfterMappingWithArrayCalled(boolean afterMappingWithArrayCalled) { + this.afterMappingWithArrayCalled = afterMappingWithArrayCalled; + } + + public boolean isAfterMappingContextWithVarArgsAsVarArgsCalled() { + return afterMappingContextWithVarArgsAsVarArgsCalled; + } + + public void setAfterMappingContextWithVarArgsAsVarArgsCalled( + boolean afterMappingContextWithVarArgsAsVarArgsCalled) { + this.afterMappingContextWithVarArgsAsVarArgsCalled = afterMappingContextWithVarArgsAsVarArgsCalled; + } + + public boolean isAfterMappingContextWithVarArgsAsArrayCalled() { + return afterMappingContextWithVarArgsAsArrayCalled; + } + + public void setAfterMappingContextWithVarArgsAsArrayCalled(boolean afterMappingContextWithVarArgsAsArrayCalled) { + this.afterMappingContextWithVarArgsAsArrayCalled = afterMappingContextWithVarArgsAsArrayCalled; + } +}