From c1fa9bd0bd1e29b01c2c7f951543788b60a4f356 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sat, 14 Aug 2021 08:37:20 +0200 Subject: [PATCH 001/363] #2537 Fix incorrect unmapped source property when only defined in Mapping#target --- .../ap/internal/model/BeanMappingMethod.java | 1 + .../test/bugs/_2537/ImplicitSourceTest.java | 24 ++++++++++ ...dSourcePolicyWithImplicitSourceMapper.java | 45 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/ImplicitSourceTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/UnmappedSourcePolicyWithImplicitSourceMapper.java 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 09cc20a6ed..4ebcd9f5ec 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 @@ -1170,6 +1170,7 @@ else if ( mapping.getJavaExpression() != null ) { .build(); handledTargets.add( targetPropertyName ); unprocessedSourceParameters.remove( sourceRef.getParameter() ); + unprocessedSourceProperties.remove( sourceRef.getShallowestPropertyName() ); } else { errorOccured = true; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/ImplicitSourceTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/ImplicitSourceTest.java new file mode 100644 index 0000000000..edcbbfd4d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/ImplicitSourceTest.java @@ -0,0 +1,24 @@ +/* + * 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._2537; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * implicit source-target mapping should list the source property as being mapped. + * + * @author Ben Zegveld + */ +@WithClasses( { UnmappedSourcePolicyWithImplicitSourceMapper.class } ) +@IssueKey( "2537" ) +public class ImplicitSourceTest { + + @ProcessorTest + public void situationCompilesWithoutErrors() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/UnmappedSourcePolicyWithImplicitSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/UnmappedSourcePolicyWithImplicitSourceMapper.java new file mode 100644 index 0000000000..21d8ca5332 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/UnmappedSourcePolicyWithImplicitSourceMapper.java @@ -0,0 +1,45 @@ +/* + * 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._2537; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +/** + * @author Ben Zegveld + */ +@Mapper( unmappedSourcePolicy = ReportingPolicy.ERROR ) +public interface UnmappedSourcePolicyWithImplicitSourceMapper { + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "property" ) + Target map(Source source); + + class Source { + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + } + + class Target { + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + } +} From 0d8729767be07b1c6496c585691089890e1cedf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20P=C3=B6ttker?= Date: Sat, 14 Aug 2021 08:38:36 +0200 Subject: [PATCH 002/363] Remove remaining references to Hickory (#2511) --- parent/pom.xml | 2 +- .../src/main/java/org/mapstruct/ap/MappingProcessor.java | 6 +++--- .../java/org/mapstruct/ap/internal/gem/GemGenerator.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index bb1229fcb8..8ce94764ea 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -34,7 +34,7 @@ 1 3.17.2 - + jdt_apt diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index e7e52be695..8369a8b270 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -67,9 +67,9 @@ *
  • if no error occurred, write out the model into Java source files
  • * *

    - * For reading annotation attributes, gems as generated with help of the Hickory tool are used. These gems allow a comfortable access to - * annotations and their attributes without depending on their class objects. + * For reading annotation attributes, gems as generated with help of Gem Tools. These gems allow comfortable access to annotations and + * their attributes without depending on their class objects. *

    * The creation of Java source files is done using the FreeMarker template engine. * Each node of the mapper model has a corresponding FreeMarker template file which provides the Java representation of diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index f8bdbc539d..df5a90d5c2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -36,7 +36,7 @@ import org.mapstruct.tools.gem.GemDefinition; /** - * Triggers the generation of ge types using Hickory. + * Triggers the generation of gem types using Gem Tools. * * @author Gunnar Morling */ From 06c416043ca361e075e4bb19e09edf539010c499 Mon Sep 17 00:00:00 2001 From: Bas Claessen <35045227+basclaessen@users.noreply.github.com> Date: Sat, 14 Aug 2021 09:06:54 +0200 Subject: [PATCH 003/363] #2515 add ambiguous constructors to ambiguous constructor error message --- .../mapstruct/ap/internal/model/BeanMappingMethod.java | 10 +++++++++- .../java/org/mapstruct/ap/internal/util/Message.java | 2 +- .../erroneous/ErroneousConstructorTest.java | 6 ++++-- 3 files changed, 14 insertions(+), 4 deletions(-) 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 4ebcd9f5ec..36dae0ba51 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 @@ -26,6 +26,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; @@ -699,7 +700,14 @@ private ConstructorAccessor getConstructorAccessor(Type type) { method.getExecutable(), GENERAL_AMBIGUOUS_CONSTRUCTORS, type, - Strings.join( constructors, ", " ) + constructors.stream() + .map( ExecutableElement::getParameters ) + .map( ps -> ps.stream() + .map( VariableElement::asType ) + .map( String::valueOf ) + .collect( Collectors.joining( ", ", type.getName() + "(", ")" ) ) + ) + .collect( Collectors.joining( ", " ) ) ); return null; } 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 f2226ab224..8d328d4796 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 @@ -125,7 +125,7 @@ public enum Message { GENERAL_AMBIGUOUS_MAPPING_METHOD( "Ambiguous mapping methods found for mapping %s to %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), GENERAL_AMBIGUOUS_FACTORY_METHOD( "Ambiguous factory methods found for creating %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD( "Ambiguous presence check methods found for checking %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), - GENERAL_AMBIGUOUS_CONSTRUCTORS( "Ambiguous constructors found for creating %s. Either declare parameterless constructor or annotate the default constructor with an annotation named @Default." ), + GENERAL_AMBIGUOUS_CONSTRUCTORS( "Ambiguous constructors found for creating %s: %s. Either declare parameterless constructor or annotate the default constructor with an annotation named @Default." ), GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS( "Incorrect @ConstructorProperties for %s. The size of the @ConstructorProperties does not match the number of constructor parameters" ), GENERAL_UNSUPPORTED_DATE_FORMAT_CHECK( "No dateFormat check is supported for types %s, %s" ), GENERAL_VALID_DATE( "Given date format \"%s\" is valid.", Diagnostic.Kind.NOTE ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorTest.java index 2799e10847..5a8483f23b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorTest.java @@ -29,8 +29,10 @@ public class ErroneousConstructorTest { kind = javax.tools.Diagnostic.Kind.ERROR, line = 17, message = "Ambiguous constructors found for creating org.mapstruct.ap.test.constructor.erroneous" + - ".ErroneousAmbiguousConstructorsMapper.PersonWithMultipleConstructors. Either declare parameterless " + - "constructor or annotate the default constructor with an annotation named @Default." + ".ErroneousAmbiguousConstructorsMapper.PersonWithMultipleConstructors: " + + "PersonWithMultipleConstructors(java.lang.String), " + + "PersonWithMultipleConstructors(java.lang.String, int). Either declare parameterless constructor " + + "or annotate the default constructor with an annotation named @Default." ) }) public void shouldUseMultipleConstructors() { From eb12c485eee83d60c48b4b00d299b4563aa8fbb6 Mon Sep 17 00:00:00 2001 From: Bas Claessen <35045227+basclaessen@users.noreply.github.com> Date: Sat, 14 Aug 2021 09:07:22 +0200 Subject: [PATCH 004/363] #2515 add ambiguous constructors to ambiguous constructor error message From c52ff812aa9a4c5fa9cc758e8d19bb77a1d09e0c Mon Sep 17 00:00:00 2001 From: Adam Szatyin Date: Tue, 17 Aug 2021 19:21:07 +0200 Subject: [PATCH 005/363] #2552 Add built in conversion between URL and String --- .../chapter-5-data-type-conversions.asciidoc | 3 + .../internal/conversion/ConversionUtils.java | 12 +++ .../ap/internal/conversion/Conversions.java | 13 ++++ .../conversion/URLToStringConversion.java | 46 +++++++++++ .../ap/test/conversion/url/Source.java | 33 ++++++++ .../ap/test/conversion/url/Target.java | 30 ++++++++ .../conversion/url/URLConversionTest.java | 76 +++++++++++++++++++ .../ap/test/conversion/url/URLMapper.java | 23 ++++++ 8 files changed, 236 insertions(+) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/URLToStringConversion.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/url/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/url/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLMapper.java diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index 24f019fa63..91f4afb1cf 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -122,6 +122,9 @@ public interface CarMapper { * Between `String` and `StringBuilder` +* Between `java.net.URL` and `String`. +** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/URL[URL] otherwise a `MalformedURLException` is thrown. + [[mapping-object-references]] === Mapping object references diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java index 20fb8a2fee..e88bb93b86 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URL; import java.sql.Time; import java.sql.Timestamp; import java.text.DecimalFormat; @@ -254,4 +255,15 @@ public static String stringBuilder(ConversionContext conversionContext) { public static String uuid(ConversionContext conversionContext) { return typeReferenceName( conversionContext, UUID.class ); } + + /** + * Name for {@link java.net.URL}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String url(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, URL.class ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 4b3ab2a818..e012481054 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URL; import java.sql.Time; import java.sql.Timestamp; import java.time.Duration; @@ -193,6 +194,8 @@ public Conversions(TypeFactory typeFactory) { register( Currency.class, String.class, new CurrencyToStringConversion() ); register( UUID.class, String.class, new UUIDToStringConversion() ); + + registerURLConversion(); } private void registerJodaConversions() { @@ -294,6 +297,16 @@ private void registerBigDecimalConversion(Class targetType) { } } + private void registerURLConversion() { + if ( isJavaURLAvailable() ) { + register( URL.class, String.class, new URLToStringConversion() ); + } + } + + private boolean isJavaURLAvailable() { + return typeFactory.isTypeAvailable( "java.net.URL" ); + } + private void register(Class sourceClass, Class targetClass, ConversionProvider conversion) { Type sourceType = typeFactory.getType( sourceClass ); Type targetType = typeFactory.getType( targetClass ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/URLToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/URLToStringConversion.java new file mode 100644 index 0000000000..716dfb3bb2 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/URLToStringConversion.java @@ -0,0 +1,46 @@ +/* + * 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.internal.conversion; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Set; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.url; +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Conversion between {@link java.net.URL} and {@link String}. + * + * @author Adam Szatyin + */ +public class URLToStringConversion extends SimpleConversion { + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toString()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return "new " + url( conversionContext ) + "( )"; + } + + @Override + protected Set getFromConversionImportTypes(final ConversionContext conversionContext) { + return asSet( conversionContext.getTypeFactory().getType( URL.class ) ); + } + + @Override + protected List getFromConversionExceptionTypes(ConversionContext conversionContext) { + return java.util.Collections.singletonList( + conversionContext.getTypeFactory().getType( MalformedURLException.class ) + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Source.java new file mode 100644 index 0000000000..ece0501a08 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Source.java @@ -0,0 +1,33 @@ +/* + * 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.conversion.url; + +import java.net.URL; + +/** + * @author Adam Szatyin + */ +public class Source { + private URL url; + + private URL invalidURL; + + public URL getURL() { + return url; + } + + public void setURL(URL url) { + this.url = url; + } + + public URL getInvalidURL() { + return invalidURL; + } + + public void setInvalidURL(URL invalidURL) { + this.invalidURL = invalidURL; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Target.java new file mode 100644 index 0000000000..060f5582e1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Target.java @@ -0,0 +1,30 @@ +/* + * 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.conversion.url; + +/** + * @author Adam Szatyin + */ +public class Target { + private String url; + private String invalidURL; + + public String getURL() { + return this.url; + } + + public void setURL(final String url) { + this.url = url; + } + + public String getInvalidURL() { + return this.invalidURL; + } + + public void setInvalidURL(final String invalidURL) { + this.invalidURL = invalidURL; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLConversionTest.java new file mode 100644 index 0000000000..bd1eed1480 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLConversionTest.java @@ -0,0 +1,76 @@ +/* + * 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.conversion.url; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import java.net.MalformedURLException; +import java.net.URL; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests conversions between {@link java.net.URL} and String. + * + * @author Adam Szatyin + */ +@WithClasses({ Source.class, Target.class, URLMapper.class }) +public class URLConversionTest { + + @ProcessorTest + public void shouldApplyURLConversion() throws MalformedURLException { + Source source = new Source(); + source.setURL( new URL("https://mapstruct.org/") ); + + Target target = URLMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getURL() ).isEqualTo( source.getURL().toString() ); + } + + @ProcessorTest + public void shouldApplyReverseURLConversion() throws MalformedURLException { + Target target = new Target(); + target.setURL( "https://mapstruct.org/" ); + + Source source = URLMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getURL() ).isEqualTo( new URL( target.getURL() ) ); + } + + @ProcessorTest + public void shouldHandleInvalidURLString() { + Target target = new Target(); + target.setInvalidURL( "XXXXXXXXX" ); + + assertThatThrownBy( () -> URLMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( RuntimeException.class ) + .getRootCause().isInstanceOf( MalformedURLException.class ); + } + + @ProcessorTest + public void shouldHandleInvalidURLStringWithMalformedURLException() { + Target target = new Target(); + target.setInvalidURL( "XXXXXXXXX" ); + + assertThatThrownBy( () -> URLMapper.INSTANCE.targetToSourceWithMalformedURLException( target ) ) + .isInstanceOf( MalformedURLException.class ); + } + + @ProcessorTest + public void shouldHandleNullURLString() { + Source source = new Source(); + + Target target = URLMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getURL() ).isNull(); + assertThat( target.getInvalidURL() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLMapper.java new file mode 100644 index 0000000000..18fc5bc7de --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLMapper.java @@ -0,0 +1,23 @@ +/* + * 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.conversion.url; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.net.MalformedURLException; + +@Mapper +public interface URLMapper { + + URLMapper INSTANCE = Mappers.getMapper( URLMapper.class ); + + Target sourceToTarget(Source source); + + Source targetToSource(Target target); + + Source targetToSourceWithMalformedURLException(Target target) throws MalformedURLException; +} From 7064e0bc977dc88222ff8fe8d23f9e36cc76fcdf Mon Sep 17 00:00:00 2001 From: Daniel Franco Date: Mon, 23 Aug 2021 15:19:02 +0100 Subject: [PATCH 006/363] Update maven wrapper version to 3.8.2 (#2557) --- .mvn/wrapper/maven-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 642d572ce9..abd303b673 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,2 +1,2 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar From 9ed4e389f8328b5f62655c82119f05cb59c4a439 Mon Sep 17 00:00:00 2001 From: Tobias Meggendorfer Date: Mon, 30 Aug 2021 16:40:09 +0200 Subject: [PATCH 007/363] #2560 Ignore source properties if ignoreByDefault = true --- .../main/java/org/mapstruct/BeanMapping.java | 2 +- .../ap/internal/model/BeanMappingMethod.java | 4 ++ .../ErroneousSourceTargetMapper.java | 21 ++++++++++ .../IgnoreByDefaultSourcesTest.java | 38 +++++++++++++++++++ .../ap/test/ignorebydefaultsource/Source.java | 27 +++++++++++++ .../SourceTargetMapper.java | 23 +++++++++++ .../ap/test/ignorebydefaultsource/Target.java | 18 +++++++++ 7 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/SourceTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Target.java diff --git a/core/src/main/java/org/mapstruct/BeanMapping.java b/core/src/main/java/org/mapstruct/BeanMapping.java index f8c749fbde..3359cd8914 100644 --- a/core/src/main/java/org/mapstruct/BeanMapping.java +++ b/core/src/main/java/org/mapstruct/BeanMapping.java @@ -118,7 +118,7 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() /** * Default ignore all mappings. All mappings have to be defined manually. No automatic mapping will take place. No - * warning will be issued on missing target properties. + * warning will be issued on missing source or target properties. * * @return The ignore strategy (default false). * 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 36dae0ba51..17bf2cb465 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 @@ -1514,6 +1514,10 @@ private ReportingPolicyGem getUnmappedSourcePolicy() { if ( mappingReferences.isForForgedMethods() ) { return ReportingPolicyGem.IGNORE; } + // If we have ignoreByDefault = true, unprocessed source properties are not an issue. + if ( method.getOptions().getBeanMapping().isignoreByDefault() ) { + return ReportingPolicyGem.IGNORE; + } return method.getOptions().getMapper().unmappedSourcePolicy(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapper.java new file mode 100644 index 0000000000..4ec3c64500 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapper.java @@ -0,0 +1,21 @@ +/* + * 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.ignorebydefaultsource; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( + unmappedTargetPolicy = ReportingPolicy.IGNORE, + unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface ErroneousSourceTargetMapper { + ErroneousSourceTargetMapper INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapper.class ); + + @Mapping(source = "one", target = "one") + Target sourceToTarget(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java new file mode 100644 index 0000000000..98f2cde4c0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java @@ -0,0 +1,38 @@ +/* + * 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.ignorebydefaultsource; + +import javax.tools.Diagnostic.Kind; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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; + +@IssueKey("2560") +public class IgnoreByDefaultSourcesTest { + + @ProcessorTest + @WithClasses({ SourceTargetMapper.class, Source.class, Target.class }) + public void shouldSucceed() { + } + + @ProcessorTest + @WithClasses({ ErroneousSourceTargetMapper.class, Source.class, Target.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousSourceTargetMapper.class, + kind = Kind.ERROR, + line = 20, + message = "Unmapped source property: \"other\".") + } + ) + public void shouldRaiseErrorDueToNonIgnoredSourceProperty() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Source.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Source.java new file mode 100644 index 0000000000..81d6441d57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Source.java @@ -0,0 +1,27 @@ +/* + * 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.ignorebydefaultsource; + +class Source { + private int one; + private int other; + + public int getOne() { + return one; + } + + public void setOne(int one) { + this.one = one; + } + + public int getOther() { + return other; + } + + public void setOther(int other) { + this.other = other; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/SourceTargetMapper.java new file mode 100644 index 0000000000..e029bd7391 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/SourceTargetMapper.java @@ -0,0 +1,23 @@ +/* + * 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.ignorebydefaultsource; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( + unmappedTargetPolicy = ReportingPolicy.IGNORE, + unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface SourceTargetMapper { + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + @Mapping(source = "one", target = "one") + @BeanMapping(ignoreByDefault = true) + Target sourceToTarget(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Target.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Target.java new file mode 100644 index 0000000000..68ec5c8933 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Target.java @@ -0,0 +1,18 @@ +/* + * 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.ignorebydefaultsource; + +class Target { + private int one; + + public int getOne() { + return one; + } + + public void setOne(int one) { + this.one = one; + } +} From 9057d68cd2411517df790e91b710244c7038e2ab Mon Sep 17 00:00:00 2001 From: Tobias Meggendorfer Date: Tue, 31 Aug 2021 21:45:25 +0200 Subject: [PATCH 008/363] Use DefaultLocale for more stable Issue2544MapperTest (#2569) --- .../ap/test/bugs/_2544/Issue2544Test.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2544/Issue2544Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2544/Issue2544Test.java index 5cc252bbf8..a070231839 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2544/Issue2544Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2544/Issue2544Test.java @@ -9,6 +9,7 @@ import java.math.BigDecimal; +import org.junitpioneer.jupiter.DefaultLocale; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -19,12 +20,23 @@ @IssueKey( "2544" ) @WithClasses( { Issue2544Mapper.class } ) public class Issue2544Test { + // Parsing numbers is sensitive to locale settings (e.g. decimal point) @ProcessorTest - public void shouldConvert() { + @DefaultLocale("en") + public void shouldConvertEn() { Issue2544Mapper.Target target = Issue2544Mapper.INSTANCE.map( "123.45679E6" ); assertThat( target ).isNotNull(); assertThat( target.getBigNumber() ).isEqualTo( new BigDecimal( "1.2345679E+8" ) ); } + + @ProcessorTest + @DefaultLocale("de") + public void shouldConvertDe() { + Issue2544Mapper.Target target = Issue2544Mapper.INSTANCE.map( "123,45679E6" ); + + assertThat( target ).isNotNull(); + assertThat( target.getBigNumber() ).isEqualTo( new BigDecimal( "1.2345679E+8" ) ); + } } From f0a13bb306e4e86f13d328512bde3be07ed0b72b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20Kemal=20=C3=96zcan?= Date: Mon, 6 Sep 2021 00:38:38 +0300 Subject: [PATCH 009/363] #2555 Add unmappedSourcePolicy annotation processor argument --- .../main/asciidoc/chapter-2-set-up.asciidoc | 13 ++++++ .../org/mapstruct/ap/MappingProcessor.java | 4 ++ .../internal/model/source/DefaultOptions.java | 3 ++ .../mapstruct/ap/internal/option/Options.java | 7 +++ ...SourceTargetMapperWithoutMapperConfig.java | 25 +++++++++++ .../unmappedsource/UnmappedSourceTest.java | 43 +++++++++++++++++++ 6 files changed, 95 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/unmappedsource/SourceTargetMapperWithoutMapperConfig.java diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index d26be72156..730433639d 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -247,6 +247,19 @@ Supported values are: If a policy is given for a specific mapper via `@Mapper#unmappedTargetPolicy()`, the value from the annotation takes precedence. If a policy is given for a specific bean mapping via `@BeanMapping#unmappedTargetPolicy()`, it takes precedence over both `@Mapper#unmappedTargetPolicy()` and the option. |`WARN` + +|`mapstruct.unmappedSourcePolicy` +|The default reporting policy to be applied in case an attribute of the source object of a mapping method is not populated with a target value. + +Supported values are: + +* `ERROR`: any unmapped source property will cause the mapping code generation to fail +* `WARN`: any unmapped source property will cause a warning at build time +* `IGNORE`: unmapped source properties are ignored + +If a policy is given for a specific mapper via `@Mapper#unmappedSourcePolicy()`, the value from the annotation takes precedence. +If a policy is given for a specific bean mapping via `@BeanMapping#ignoreUnmappedSourceProperties()`, it takes precedence over both `@Mapper#unmappedSourcePolicy()` and the option. +|`WARN` |=== === Using MapStruct with the Java Module System diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 8369a8b270..952257753a 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -84,6 +84,7 @@ MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT, MappingProcessor.UNMAPPED_TARGET_POLICY, + MappingProcessor.UNMAPPED_SOURCE_POLICY, MappingProcessor.DEFAULT_COMPONENT_MODEL, MappingProcessor.DEFAULT_INJECTION_STRATEGY, MappingProcessor.VERBOSE @@ -99,6 +100,7 @@ public class MappingProcessor extends AbstractProcessor { protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT = "mapstruct.suppressGeneratorVersionInfoComment"; protected static final String UNMAPPED_TARGET_POLICY = "mapstruct.unmappedTargetPolicy"; + protected static final String UNMAPPED_SOURCE_POLICY = "mapstruct.unmappedSourcePolicy"; protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel"; protected static final String DEFAULT_INJECTION_STRATEGY = "mapstruct.defaultInjectionStrategy"; protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; @@ -134,11 +136,13 @@ public synchronized void init(ProcessingEnvironment processingEnv) { private Options createOptions() { String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY ); + String unmappedSourcePolicy = processingEnv.getOptions().get( UNMAPPED_SOURCE_POLICY ); return new Options( Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ), unmappedTargetPolicy != null ? ReportingPolicyGem.valueOf( unmappedTargetPolicy.toUpperCase() ) : null, + unmappedSourcePolicy != null ? ReportingPolicyGem.valueOf( unmappedSourcePolicy.toUpperCase() ) : null, processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ), processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ), Boolean.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index 58f03ad55d..d52d094930 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -63,6 +63,9 @@ public ReportingPolicyGem unmappedTargetPolicy() { @Override public ReportingPolicyGem unmappedSourcePolicy() { + if ( options.getUnmappedSourcePolicy() != null ) { + return options.getUnmappedSourcePolicy(); + } return ReportingPolicyGem.valueOf( mapper.unmappedSourcePolicy().getDefaultValue() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index 54b69c28b3..46a90d4b43 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -17,6 +17,7 @@ public class Options { private final boolean suppressGeneratorTimestamp; private final boolean suppressGeneratorVersionComment; private final ReportingPolicyGem unmappedTargetPolicy; + private final ReportingPolicyGem unmappedSourcePolicy; private final boolean alwaysGenerateSpi; private final String defaultComponentModel; private final String defaultInjectionStrategy; @@ -24,11 +25,13 @@ public class Options { public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, ReportingPolicyGem unmappedTargetPolicy, + ReportingPolicyGem unmappedSourcePolicy, String defaultComponentModel, String defaultInjectionStrategy, boolean alwaysGenerateSpi, boolean verbose) { this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorVersionComment = suppressGeneratorVersionComment; this.unmappedTargetPolicy = unmappedTargetPolicy; + this.unmappedSourcePolicy = unmappedSourcePolicy; this.defaultComponentModel = defaultComponentModel; this.defaultInjectionStrategy = defaultInjectionStrategy; this.alwaysGenerateSpi = alwaysGenerateSpi; @@ -47,6 +50,10 @@ public ReportingPolicyGem getUnmappedTargetPolicy() { return unmappedTargetPolicy; } + public ReportingPolicyGem getUnmappedSourcePolicy() { + return unmappedSourcePolicy; + } + public String getDefaultComponentModel() { return defaultComponentModel; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/SourceTargetMapperWithoutMapperConfig.java b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/SourceTargetMapperWithoutMapperConfig.java new file mode 100644 index 0000000000..4ab2d3688c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/SourceTargetMapperWithoutMapperConfig.java @@ -0,0 +1,25 @@ +/* + * 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.unmappedsource; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.unmappedtarget.Source; +import org.mapstruct.ap.test.unmappedtarget.Target; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Yusuf Kemal Ozcan + */ +@Mapper +public interface SourceTargetMapperWithoutMapperConfig { + + SourceTargetMapperWithoutMapperConfig INSTANCE = Mappers.getMapper( SourceTargetMapperWithoutMapperConfig.class ); + + Target sourceToTarget(Source source); + + Source targetToSource(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/UnmappedSourceTest.java b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/UnmappedSourceTest.java index 59d44a6baf..9b244592a2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/UnmappedSourceTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/UnmappedSourceTest.java @@ -14,6 +14,7 @@ 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.compilation.annotation.ProcessorOption; import static org.assertj.core.api.Assertions.assertThat; @@ -67,4 +68,46 @@ public void shouldLeaveUnmappedSourcePropertyUnset() { ) public void shouldRaiseErrorDueToUnsetSourceProperty() { } + + @ProcessorTest + @WithClasses({ Source.class, Target.class, + org.mapstruct.ap.test.unmappedsource.SourceTargetMapperWithoutMapperConfig.class }) + @ProcessorOption(name = "mapstruct.unmappedTargetPolicy", value = "IGNORE") + @ProcessorOption(name = "mapstruct.unmappedSourcePolicy", value = "ERROR") + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = org.mapstruct.ap.test.unmappedsource.SourceTargetMapperWithoutMapperConfig.class, + kind = Kind.ERROR, + line = 22, + message = "Unmapped source property: \"qux\"."), + @Diagnostic(type = org.mapstruct.ap.test.unmappedsource.SourceTargetMapperWithoutMapperConfig.class, + kind = Kind.ERROR, + line = 24, + message = "Unmapped source property: \"bar\".") + } + ) + public void shouldRaiseErrorDueToUnsetSourcePropertyWithPolicySetViaProcessorOption() { + } + + @ProcessorTest + @WithClasses({ Source.class, Target.class, + org.mapstruct.ap.test.unmappedsource.SourceTargetMapperWithoutMapperConfig.class }) + @ProcessorOption(name = "mapstruct.unmappedTargetPolicy", value = "IGNORE") + @ProcessorOption(name = "mapstruct.unmappedSourcePolicy", value = "WARN") + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = org.mapstruct.ap.test.unmappedsource.SourceTargetMapperWithoutMapperConfig.class, + kind = Kind.WARNING, + line = 22, + message = "Unmapped source property: \"qux\"."), + @Diagnostic(type = org.mapstruct.ap.test.unmappedsource.SourceTargetMapperWithoutMapperConfig.class, + kind = Kind.WARNING, + line = 24, + message = "Unmapped source property: \"bar\".") + } + ) + public void shouldLeaveUnmappedSourcePropertyUnsetWithWarnPolicySetViaProcessorOption() { + } } From 2c23b935db4079ddeaeb65c47284d823b8451349 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 14 Aug 2021 13:50:20 +0200 Subject: [PATCH 010/363] #2541 fix incorrect name for TypeVar with ElementType.TYPE_USE for javac-with-errorprone --- .../ap/internal/model/common/TypeFactory.java | 12 ++++- .../ap/test/bugs/_2541/Issue2541Mapper.java | 47 +++++++++++++++++++ .../ap/test/bugs/_2541/Issue2541Test.java | 28 +++++++++++ .../ap/test/bugs/_2541/Nullable.java | 22 +++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Nullable.java 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 127ec74270..b209626725 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 @@ -278,7 +278,17 @@ else if (componentTypeMirror.getKind().isPrimitive()) { isInterface = false; // When the component type is primitive and is annotated with ElementType.TYPE_USE then // the typeMirror#toString returns (@CustomAnnotation :: byte) for the javac compiler - name = mirror.getKind().isPrimitive() ? NativeTypes.getName( mirror.getKind() ) : mirror.toString(); + if ( mirror.getKind().isPrimitive() ) { + name = NativeTypes.getName( mirror.getKind() ); + } + // When the component type is type var and is annotated with ElementType.TYPE_USE then + // the typeMirror#toString returns (@CustomAnnotation T) for the errorprone javac compiler + else if ( mirror.getKind() == TypeKind.TYPEVAR ) { + name = ( (TypeVariable) mirror ).asElement().getSimpleName().toString(); + } + else { + name = mirror.toString(); + } packageName = null; qualifiedName = name; typeElement = null; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Mapper.java new file mode 100644 index 0000000000..0531f5242c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Mapper.java @@ -0,0 +1,47 @@ +/* + * 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._2541; + +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2541Mapper { + + Issue2541Mapper INSTANCE = Mappers.getMapper( Issue2541Mapper.class ); + + Target map(Source source); + + default Optional toOptional(@Nullable T value) { + return Optional.ofNullable( value ); + } + + class Target { + private Optional value; + + public Optional getValue() { + return value; + } + + public void setValue(Optional value) { + this.value = value; + } + } + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Test.java new file mode 100644 index 0000000000..d1b8bd7f52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Test.java @@ -0,0 +1,28 @@ +/* + * 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._2541; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue2541Mapper.class, + Nullable.class, +}) +class Issue2541Test { + + @ProcessorTest + void shouldGenerateCorrectCode() { + Issue2541Mapper.Target target = Issue2541Mapper.INSTANCE.map( new Issue2541Mapper.Source( null ) ); + + assertThat( target.getValue() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Nullable.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Nullable.java new file mode 100644 index 0000000000..24c52eb906 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Nullable.java @@ -0,0 +1,22 @@ +/* + * 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._2541; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ + ElementType.METHOD, + ElementType.TYPE_USE +}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Nullable { + +} From b59a23965a0c77206851a118c5b1673819735dd4 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 29 Aug 2021 11:24:34 +0200 Subject: [PATCH 011/363] #2554 Records should not treat collections as alternative target accessors --- .../java/org/mapstruct/itest/records/Car.java | 24 ++++++++++++++ .../itest/records/CarAndWheelMapper.java | 31 +++++++++++++++++++ .../org/mapstruct/itest/records/CarDto.java | 15 +++++++++ .../itest/records/WheelPosition.java | 22 +++++++++++++ .../mapstruct/itest/records/RecordsTest.java | 14 +++++++++ .../ap/internal/model/common/Type.java | 7 +++++ 6 files changed, 113 insertions(+) create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Car.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarAndWheelMapper.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarDto.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/WheelPosition.java diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Car.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Car.java new file mode 100644 index 0000000000..9332c47dfb --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Car.java @@ -0,0 +1,24 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Car { + + private List wheelPositions; + + public List getWheelPositions() { + return wheelPositions; + } + + public void setWheelPositions(List wheelPositions) { + this.wheelPositions = wheelPositions; + } +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarAndWheelMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarAndWheelMapper.java new file mode 100644 index 0000000000..cc69ae7605 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarAndWheelMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface CarAndWheelMapper { + + CarAndWheelMapper INSTANCE = Mappers.getMapper( CarAndWheelMapper.class ); + + default String stringFromWheelPosition(WheelPosition source) { + return source == null ? null : source.getPosition(); + } + + default WheelPosition wheelPositionFromString(String source) { + return source == null ? null : new WheelPosition(source); + } + + CarDto carDtoFromCar(Car source); + + Car carFromCarDto(CarDto source); +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarDto.java new file mode 100644 index 0000000000..5470550060 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/CarDto.java @@ -0,0 +1,15 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public record CarDto(List wheelPositions) { + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/WheelPosition.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/WheelPosition.java new file mode 100644 index 0000000000..fe8016ddbf --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/WheelPosition.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records; + +/** + * @author Filip Hrisafov + */ +public class WheelPosition { + + private final String position; + + public WheelPosition(String position) { + this.position = position; + } + + public String getPosition() { + return position; + } +} diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java index b404681806..e3d055345e 100644 --- a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java +++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java @@ -5,6 +5,8 @@ */ package org.mapstruct.itest.records; +import java.util.Arrays; + import static org.assertj.core.api.Assertions.assertThat; import org.junit.Test; @@ -47,4 +49,16 @@ public void shouldMapIntoGenericRecord() { assertThat( value ).isNotNull(); assertThat( value.value() ).isEqualTo( "Kermit" ); } + + @Test + public void shouldMapIntoRecordWithList() { + Car car = new Car(); + car.setWheelPositions( Arrays.asList( new WheelPosition( "left" ) ) ); + + CarDto carDto = CarAndWheelMapper.INSTANCE.carDtoFromCar(car); + + assertThat( carDto ).isNotNull(); + assertThat( carDto.wheelPositions() ) + .containsExactly( "left" ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 4da9128f98..e13d7b9ddd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -874,6 +874,13 @@ private List getAdders() { * @return an unmodifiable list of alternative target accessors. */ private List getAlternativeTargetAccessors() { + if ( alternativeTargetAccessors != null ) { + return alternativeTargetAccessors; + } + + if ( isRecord() ) { + alternativeTargetAccessors = Collections.emptyList(); + } if ( alternativeTargetAccessors == null ) { From 8b84f5b7d78979be979940b8ded1cbbeb868e32c Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Tue, 21 Sep 2021 22:17:24 +0200 Subject: [PATCH 012/363] #2591 Update dependencies so tests run on Java 18 Update GitHub Actions for tests to run on Java 11, 13, 16, 17 and 18-ea --- .github/workflows/java-ea.yml | 4 ++-- .github/workflows/main.yml | 4 ++-- parent/pom.xml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index 598a8979a6..f2b2a99be4 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - java: [17-ea] + java: [18-ea] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: @@ -21,4 +21,4 @@ jobs: with: java-version: ${{ matrix.java }} - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} install -DskipDistribution=true + run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 741f8bce61..b023631628 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - java: [11, 13, 16] + java: [11, 13, 16, 17] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: @@ -23,7 +23,7 @@ jobs: with: java-version: ${{ matrix.java }} - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} install -DskipDistribution=true + run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true linux: name: 'Linux JDK 8' runs-on: ubuntu-latest diff --git a/parent/pom.xml b/parent/pom.xml index 8ce94764ea..f4edccfc85 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -25,7 +25,7 @@ 3.0.0-M3 3.0.0-M5 3.1.0 - 5.3.3 + 5.3.10 1.6.0 8.36.1 5.8.0-M1 From f167e7a20c7e1bf4f87aab8e760983a359b43bbb Mon Sep 17 00:00:00 2001 From: valery1707 Date: Tue, 21 Sep 2021 10:42:25 +0300 Subject: [PATCH 013/363] Fix typo in JavaDoc --- core/src/main/java/org/mapstruct/Mapping.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 0af73f3687..7ca637f0ca 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -54,7 +54,7 @@ *

    Example 2: Mapping properties with different names

    *
    
      * // We need map Human.companyName to HumanDto.company
    - * // we can use @Mapping with parameters {@link #source()} and {@link #source()}
    + * // we can use @Mapping with parameters {@link #source()} and {@link #target()}
      * @Mapper
      * public interface HumanMapper {
      *    @Mapping(source="companyName", target="company")
    @@ -476,6 +476,4 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy()
          */
         Class mappingControl() default MappingControl.class;
     
    -
    -
     }
    
    From 5df6b7a75b97e58687027470e9048e6f6534bca5 Mon Sep 17 00:00:00 2001
    From: Zegveld <41897697+Zegveld@users.noreply.github.com>
    Date: Tue, 19 Oct 2021 20:44:25 +0200
    Subject: [PATCH 014/363] #131, #2438, #366 Add support for Type-Refinement
     (Downcast) Mapping (#2512)
    
    Add new `@SubclassMapping` for creating Downcast mapping.
    When a parent mapping method is annotated with `@SubclassMapping`
    it will now generate an instanceof check inside the parent mapping
    and generate the subclass mappings if they are not manually defined.
    
    There is also `SubclassExhaustiveStrategy` for controlling what MapStruct should do in case the target type is abstract and there is no suitable way to create it.
    ---
     .../main/java/org/mapstruct/BeanMapping.java  |  12 +
     core/src/main/java/org/mapstruct/Mapper.java  |  12 +
     .../main/java/org/mapstruct/MapperConfig.java |  12 +
     .../mapstruct/SubclassExhaustiveStrategy.java |  28 +++
     .../java/org/mapstruct/SubclassMapping.java   |  84 +++++++
     .../java/org/mapstruct/SubclassMappings.java  |  58 +++++
     ...apter-10-advanced-mapping-options.asciidoc |  43 ++++
     .../ap/internal/gem/GemGenerator.java         |   4 +
     .../gem/SubclassExhaustiveStrategyGem.java    |  27 +++
     .../model/AbstractMappingMethodBuilder.java   |  47 +++-
     .../ap/internal/model/BeanMappingMethod.java  | 138 ++++++++++-
     .../ap/internal/model/ForgedMethod.java       |  35 ++-
     .../ap/internal/model/SubclassMapping.java    |  69 ++++++
     .../model/assignment/ReturnWrapper.java       |  20 ++
     .../model/source/BeanMappingOptions.java      |  20 +-
     .../internal/model/source/DefaultOptions.java |   9 +-
     .../model/source/DelegatingOptions.java       |   7 +-
     .../model/source/MapperConfigOptions.java     |   8 +
     .../internal/model/source/MapperOptions.java  |   8 +
     .../model/source/MappingMethodOptions.java    |  31 ++-
     .../internal/model/source/SourceMethod.java   |  13 +-
     .../model/source/SubclassMappingOptions.java  | 171 +++++++++++++
     .../model/source/SubclassValidator.java       |  53 ++++
     .../processor/MethodRetrievalProcessor.java   | 229 +++++++++++++++---
     .../mapstruct/ap/internal/util/Message.java   |   6 +-
     .../ap/internal/model/BeanMappingMethod.ftl   |  15 ++
     .../model/assignment/ReturnWrapper.ftl        |  17 ++
     .../ErroneousSubclassMapper1.java             |  24 ++
     .../ErroneousSubclassUpdateMapper.java        |  28 +++
     .../MappingInheritanceTest.java               |  50 ++++
     .../subclassmapping/SimpleSubclassMapper.java |  31 +++
     .../test/subclassmapping/SubclassMapper.java  |  31 +++
     .../SubclassMapperUsingExistingMappings.java  |  36 +++
     .../subclassmapping/SubclassMappingTest.java  | 139 +++++++++++
     .../SubclassOrderWarningMapper.java           |  31 +++
     .../AbstractSuperClassTest.java               |  70 ++++++
     .../abstractsuperclass/AbstractVehicle.java   |  18 ++
     .../abstractsuperclass/Bike.java              |  18 ++
     .../abstractsuperclass/BikeDto.java           |  18 ++
     .../abstractsuperclass/Car.java               |  18 ++
     .../abstractsuperclass/CarDto.java            |  18 ++
     ...sSubclassWithAbstractSuperClassMapper.java |  27 +++
     .../abstractsuperclass/Motorcycle.java        |  10 +
     .../abstractsuperclass/MotorcycleDto.java     |  10 +
     .../SubclassWithAbstractSuperClassMapper.java |  26 ++
     .../abstractsuperclass/VehicleCollection.java |  17 ++
     .../VehicleCollectionDto.java                 |  17 ++
     .../abstractsuperclass/VehicleDto.java        |  18 ++
     .../fixture/AbstractParentSource.java         |  10 +
     .../fixture/AbstractParentTarget.java         |  10 +
     .../fixture/ImplementedParentSource.java      |  10 +
     .../fixture/ImplementedParentTarget.java      |  10 +
     .../fixture/InterfaceParentSource.java        |  10 +
     .../fixture/InterfaceParentTarget.java        |  10 +
     .../subclassmapping/fixture/SubSource.java    |  18 ++
     .../fixture/SubSourceOther.java               |  18 ++
     .../subclassmapping/fixture/SubTarget.java    |  18 ++
     .../fixture/SubTargetOther.java               |  18 ++
     .../fixture/SubclassAbstractMapper.java       |  21 ++
     .../fixture/SubclassFixtureTest.java          |  53 ++++
     .../fixture/SubclassImplementedMapper.java    |  17 ++
     .../fixture/SubclassInterfaceMapper.java      |  19 ++
     .../test/subclassmapping/mappables/Bike.java  |  18 ++
     .../subclassmapping/mappables/BikeDto.java    |  18 ++
     .../test/subclassmapping/mappables/Car.java   |  18 ++
     .../subclassmapping/mappables/CarDto.java     |  18 ++
     .../subclassmapping/mappables/HatchBack.java  |  18 ++
     .../mappables/HatchBackDto.java               |  18 ++
     .../subclassmapping/mappables/Source.java     |  42 ++++
     .../mappables/SourceSubclass.java             |  33 +++
     .../subclassmapping/mappables/Target.java     |  54 +++++
     .../subclassmapping/mappables/Vehicle.java    |  27 +++
     .../mappables/VehicleCollection.java          |  17 ++
     .../mappables/VehicleCollectionDto.java       |  17 ++
     .../subclassmapping/mappables/VehicleDto.java |  27 +++
     .../fixture/SubclassAbstractMapperImpl.java   |  59 +++++
     .../SubclassImplementedMapperImpl.java        |  61 +++++
     .../fixture/SubclassInterfaceMapperImpl.java  |  59 +++++
     78 files changed, 2505 insertions(+), 72 deletions(-)
     create mode 100644 core/src/main/java/org/mapstruct/SubclassExhaustiveStrategy.java
     create mode 100644 core/src/main/java/org/mapstruct/SubclassMapping.java
     create mode 100644 core/src/main/java/org/mapstruct/SubclassMappings.java
     create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/gem/SubclassExhaustiveStrategyGem.java
     create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/SubclassMapping.java
     create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.java
     create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java
     create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java
     create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousSubclassMapper1.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousSubclassUpdateMapper.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/MappingInheritanceTest.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SimpleSubclassMapper.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMapper.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMapperUsingExistingMappings.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassOrderWarningMapper.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/AbstractSuperClassTest.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/AbstractVehicle.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Bike.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/BikeDto.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Car.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CarDto.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/ErroneousSubclassWithAbstractSuperClassMapper.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Motorcycle.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MotorcycleDto.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/SubclassWithAbstractSuperClassMapper.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleCollection.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleCollectionDto.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleDto.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/InterfaceParentSource.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/InterfaceParentTarget.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSource.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceOther.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTarget.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTargetOther.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapper.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapper.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Bike.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/BikeDto.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/CarDto.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/HatchBack.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/HatchBackDto.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Source.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/SourceSubclass.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Target.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Vehicle.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleCollection.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleCollectionDto.java
     create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleDto.java
     create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java
     create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java
     create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java
    
    diff --git a/core/src/main/java/org/mapstruct/BeanMapping.java b/core/src/main/java/org/mapstruct/BeanMapping.java
    index 3359cd8914..be3df0d29d 100644
    --- a/core/src/main/java/org/mapstruct/BeanMapping.java
    +++ b/core/src/main/java/org/mapstruct/BeanMapping.java
    @@ -14,6 +14,7 @@
     import org.mapstruct.control.MappingControl;
     
     import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
    +import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR;
     
     /**
      * Configures the mapping between two bean types.
    @@ -116,6 +117,17 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy()
          */
         NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
     
    +    /**
    +     * Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}.
    +     *
    +     * Overrides the setting on {@link MapperConfig} and {@link Mapper}.
    +     *
    +     * @return strategy to handle missing implementation combined with {@link SubclassMappings}.
    +     *
    +     * @since 1.5
    +     */
    +    SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
    +
         /**
          * Default ignore all mappings. All mappings have to be defined manually. No automatic mapping will take place. No
          * warning will be issued on missing source or target properties.
    diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java
    index 98022f1a6c..9ddd9bff5c 100644
    --- a/core/src/main/java/org/mapstruct/Mapper.java
    +++ b/core/src/main/java/org/mapstruct/Mapper.java
    @@ -15,6 +15,7 @@
     import org.mapstruct.factory.Mappers;
     
     import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
    +import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR;
     
     /**
      * Marks an interface or abstract class as a mapper and activates the generation of a implementation of that type via
    @@ -237,6 +238,17 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default
          */
         NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
     
    +    /**
    +     * Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}.
    +     *
    +     * Can be overridden by the one on {@link BeanMapping}, but overrides {@link MapperConfig}.
    +     *
    +     * @return strategy to handle missing implementation combined with {@link SubclassMappings}.
    +     *
    +     * @since 1.5
    +     */
    +    SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
    +
         /**
          * Determines whether to use field or constructor injection. This is only used on annotated based component models
          * such as CDI, Spring and JSR 330.
    diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java
    index ecfd888ca2..877495867c 100644
    --- a/core/src/main/java/org/mapstruct/MapperConfig.java
    +++ b/core/src/main/java/org/mapstruct/MapperConfig.java
    @@ -15,6 +15,7 @@
     import org.mapstruct.factory.Mappers;
     
     import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION;
    +import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR;
     
     /**
      * Marks a class or interface as configuration source for generated mappers. This allows to share common configurations
    @@ -210,6 +211,17 @@ MappingInheritanceStrategy mappingInheritanceStrategy()
          */
         NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION;
     
    +    /**
    +     * Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}.
    +     *
    +     * Can be overridden by the one on {@link BeanMapping} or {@link Mapper}.
    +     *
    +     * @return strategy to handle missing implementation combined with {@link SubclassMappings}.
    +     *
    +     * @since 1.5
    +     */
    +    SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR;
    +
         /**
          * Determines whether to use field or constructor injection. This is only used on annotated based component models
          * such as CDI, Spring and JSR 330.
    diff --git a/core/src/main/java/org/mapstruct/SubclassExhaustiveStrategy.java b/core/src/main/java/org/mapstruct/SubclassExhaustiveStrategy.java
    new file mode 100644
    index 0000000000..a60d067faa
    --- /dev/null
    +++ b/core/src/main/java/org/mapstruct/SubclassExhaustiveStrategy.java
    @@ -0,0 +1,28 @@
    +/*
    + * Copyright MapStruct Authors.
    + *
    + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
    + */
    +package org.mapstruct;
    +
    +/**
    + * Strategy for dealing with subclassMapping annotated methods.
    + *
    + * @since 1.5
    + * @author Ben Zegveld
    + */
    +public enum SubclassExhaustiveStrategy {
    +
    +    /**
    +     * If there is no valid constructor or known method to create the return value of a with `@SubclassMapping`
    +     * annotated mapping then a compilation error will be thrown.
    +     */
    +    COMPILE_ERROR,
    +
    +    /**
    +     * If there is no valid constructor or known method to create the return value of a with `@SubclassMapping`
    +     * annotated mapping then an {@link IllegalArgumentException} will be thrown if a call is made with a type for which
    +     * there is no {@link SubclassMapping} available.
    +     */
    +    RUNTIME_EXCEPTION;
    +}
    diff --git a/core/src/main/java/org/mapstruct/SubclassMapping.java b/core/src/main/java/org/mapstruct/SubclassMapping.java
    new file mode 100644
    index 0000000000..bfd4f9bec5
    --- /dev/null
    +++ b/core/src/main/java/org/mapstruct/SubclassMapping.java
    @@ -0,0 +1,84 @@
    +/*
    + * Copyright MapStruct Authors.
    + *
    + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
    + */
    +package org.mapstruct;
    +
    +import java.lang.annotation.ElementType;
    +import java.lang.annotation.Repeatable;
    +import java.lang.annotation.Retention;
    +import java.lang.annotation.RetentionPolicy;
    +import java.lang.annotation.Target;
    +
    +import org.mapstruct.util.Experimental;
    +
    +/**
    + * Configures the mapping to handle hierarchy of the source type.
    + * 

    + * The subclass to be mapped is to be specified via {@link #source()}. + * The subclass to map to is to be specified via {@link #target()}. + *

    + *

    + * This annotation can be combined with @Mapping annotations. + *

    + * + *
    
    + * @Mapper
    + * public interface MyMapper {
    + *    @SubclassMapping (target = TargetSubclass.class, source = SourceSubclass.class)
    + *    TargetParent mapParent(SourceParent parent);
    + *
    + *    TargetSubclass mapSubclass(SourceSubclass subInstant);
    + * }
    + * 
    + * Below follow examples of the implementation for the mapParent method. + * Example 1: For parents that cannot be created. (e.g. abstract classes or interfaces) + *
    
    + * // generates
    + * @Override
    + * public TargetParent mapParent(SourceParent parent) {
    + *     if (parent instanceof SourceSubclass) {
    + *         return mapSubclass( (SourceSubclass) parent );
    + *     }
    + *     else {
    + *         throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for "
    + *                    + parent.getClass());
    + *     }
    + * }
    + * 
    + * Example 2: For parents that can be created. (e.g. normal classes or interfaces with + * @Mappper( uses = ObjectFactory.class ) ) + *
    
    + * // generates
    + * @Override
    + * public TargetParent mapParent(SourceParent parent) {
    + *     TargetParent targetParent1;
    + *     if (parent instanceof SourceSubclass) {
    + *         targetParent1 = mapSubclass( (SourceSubclass) parent );
    + *     }
    + *     else {
    + *         targetParent1 = new TargetParent();
    + *         // ...
    + *     }
    + * }
    + * 
    + * + * @author Ben Zegveld + * @since 1.5 + */ +@Repeatable(value = SubclassMappings.class) +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Experimental +public @interface SubclassMapping { + /** + * @return the source subclass to check for before using the default mapping as fallback. + */ + Class source(); + + /** + * @return the target subclass to map the source to. + */ + Class target(); +} diff --git a/core/src/main/java/org/mapstruct/SubclassMappings.java b/core/src/main/java/org/mapstruct/SubclassMappings.java new file mode 100644 index 0000000000..938b836f77 --- /dev/null +++ b/core/src/main/java/org/mapstruct/SubclassMappings.java @@ -0,0 +1,58 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.mapstruct.util.Experimental; + +/** + * Configures the SubclassMappings of several subclasses. + *

    + * TIP: When using java 8 or later, you can omit the @SubclassMappings + * Wrapper annotation and directly specify several @SubclassMapping annotations + * on one method. + *

    + *

    These two examples are equal. + *

    + *
    
    + * // before java 8
    + * @Mapper
    + * public interface MyMapper {
    + *     @SubclassMappings({
    + *         @SubclassMapping(source = FirstSub.class, target = FirstTargetSub.class),
    + *         @SubclassMapping(source = SecondSub.class, target = SecondTargetSub.class)
    + *     })
    + *     ParentTarget toParentTarget(Parent parent);
    + * }
    + * 
    + *
    
    + * // java 8 and later
    + * @Mapper
    + * public interface MyMapper {
    + *     @SubclassMapping(source = First.class, target = FirstTargetSub.class),
    + *     @SubclassMapping(source = SecondSub.class, target = SecondTargetSub.class)
    + *     ParentTarget toParentTarget(Parent parent);
    + * }
    + * 
    + * + * @author Ben Zegveld + * @since 1.5 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +@Experimental +public @interface SubclassMappings { + + /** + * @return the subclassMappings to apply. + */ + SubclassMapping[] value(); + +} diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index d3ab2424b1..5d7da371a1 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -110,6 +110,49 @@ public interface SourceTargetMapper { The example demonstrates how to use defaultExpression to set an `ID` field if the source field is null, this could be used to take the existing `sourceId` from the source object if it is set, or create a new `Id` if it isn't. Please note that the fully qualified package name is specified because MapStruct does not take care of the import of the `UUID` class (unless it’s used otherwise explicitly in the `SourceTargetMapper`). This can be resolved by defining imports on the @Mapper annotation (see <>). +[[sub-class-mappings]] +=== Subclass Mapping + +When both input and result types have an inheritance relation, you would want the correct specialization be mapped to the matching specialization. +Suppose an `Apple` and a `Banana`, which are both specializations of `Fruit`. + +.Specifying the sub class mappings of a fruit mapping +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface FruitMapper { + + @SubclassMapping( source = AppleDto.class, target = Apple.class ) + @SubclassMapping( source = BananaDto.class, target = Banana.class ) + Fruit map( FruitDto source ); + +} +---- +==== + +If you would just use a normal mapping both the `AppleDto` and the `BananaDto` would be made into a `Fruit` object, instead of an `Apple` and a `Banana` object. +By using the subclass mapping an `AppleDtoToApple` mapping will be used for `AppleDto` objects, and an `BananaDtoToBanana` mapping will be used for `BananaDto` objects. +If you try to map a `GrapeDto` it would still turn it into a `Fruit`. + +In the case that the `Fruit` is an abstract class or an interface, you would get a compile error. + +To allow mappings for abstract classes or interfaces you need to set the `subclassExhaustiveStrategy` to `RUNTIME_EXCEPTION`, you can do this at the `@MapperConfig`, `@Mapper` or `@BeanMapping` annotations. If you then pass a `GrapeDto` an `IllegalArgumentException` will be thrown because it is unknown how to map a `GrapeDto`. +Adding the missing (`@SubclassMapping`) for it will fix that. + +[TIP] +==== +If the mapping method for the subclasses does not exist it will be created and any other annotations on the fruit mapping method will be inherited by the newly generated mappings. +==== + +[NOTE] +==== +Combining `@SubclassMapping` with update methods is not supported. +If you try to use subclass mappings there will be a compile error. +The same issue exists for the `@Context` and `@TargetType` parameters. +==== + [[determining-result-type]] === Determining the result type diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index df5a90d5c2..e0491cbc4e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -28,6 +28,8 @@ import org.mapstruct.Named; import org.mapstruct.ObjectFactory; import org.mapstruct.Qualifier; +import org.mapstruct.SubclassMapping; +import org.mapstruct.SubclassMappings; import org.mapstruct.TargetType; import org.mapstruct.ValueMapping; import org.mapstruct.ValueMappings; @@ -47,6 +49,8 @@ @GemDefinition(BeanMapping.class) @GemDefinition(EnumMapping.class) @GemDefinition(MapMapping.class) +@GemDefinition(SubclassMapping.class) +@GemDefinition(SubclassMappings.class) @GemDefinition(TargetType.class) @GemDefinition(MappingTarget.class) @GemDefinition(DecoratedWith.class) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/SubclassExhaustiveStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/SubclassExhaustiveStrategyGem.java new file mode 100644 index 0000000000..2367d649a9 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/SubclassExhaustiveStrategyGem.java @@ -0,0 +1,27 @@ +/* + * 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.internal.gem; + +/** + * Gem for the enum {@link org.mapstruct.SubclassExhaustiveStrategy} + * + * @author Ben Zegveld + */ +public enum SubclassExhaustiveStrategyGem { + + COMPILE_ERROR( false ), + RUNTIME_EXCEPTION( true ); + + private final boolean abstractReturnTypeAllowed; + + SubclassExhaustiveStrategyGem(boolean abstractReturnTypeAllowed) { + this.abstractReturnTypeAllowed = abstractReturnTypeAllowed; + } + + public boolean isAbstractReturnTypeAllowed() { + return abstractReturnTypeAllowed; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index 91b8427630..efc7a71469 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -5,26 +5,45 @@ */ package org.mapstruct.ap.internal.model; +import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.util.Strings; -import static org.mapstruct.ap.internal.model.ForgedMethod.forElementMapping; - /** * An abstract builder that can be reused for building {@link MappingMethod}(s). * * @author Filip Hrisafov */ public abstract class AbstractMappingMethodBuilder, - M extends MappingMethod> extends AbstractBaseBuilder { + M extends MappingMethod> + extends AbstractBaseBuilder { public AbstractMappingMethodBuilder(Class selfType) { super( selfType ); } + private interface ForgeMethodCreator { + ForgedMethod createMethod(String name, Type sourceType, Type returnType, Method basedOn, + ForgedMethodHistory history, boolean forgedNameBased); + + static ForgeMethodCreator forSubclassMapping(MappingReferences mappingReferences) { + return (name, sourceType, targetType, method, description, + forgedNameBased) -> ForgedMethod + .forSubclassMapping( + name, + sourceType, + targetType, + method, + mappingReferences, + description, + forgedNameBased ); + } + } + public abstract M build(); private ForgedMethodHistory description; @@ -35,6 +54,20 @@ public AbstractMappingMethodBuilder(Class selfType) { protected abstract boolean shouldUsePropertyNamesInHistory(); Assignment forgeMapping(SourceRHS sourceRHS, Type sourceType, Type targetType) { + return forgeMapping( sourceRHS, sourceType, targetType, ForgedMethod::forElementMapping ); + } + + Assignment forgeSubclassMapping(SourceRHS sourceRHS, Type sourceType, Type targetType, + MappingReferences mappingReferences) { + return forgeMapping( + sourceRHS, + sourceType, + targetType, + ForgeMethodCreator.forSubclassMapping( mappingReferences ) ); + } + + private Assignment forgeMapping(SourceRHS sourceRHS, Type sourceType, Type targetType, + ForgeMethodCreator forgeMethodCreator) { if ( !canGenerateAutoSubMappingBetween( sourceType, targetType ) ) { return null; } @@ -55,14 +88,14 @@ Assignment forgeMapping(SourceRHS sourceRHS, Type sourceType, Type targetType) { shouldUsePropertyNamesInHistory(), sourceRHS.getSourceErrorMessagePart() ); - ForgedMethod forgedMethod = forElementMapping( name, sourceType, targetType, method, description, true ); + ForgedMethod forgedMethod = + forgeMethodCreator.createMethod( name, sourceType, targetType, method, description, true ); BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); return createForgedAssignment( sourceRHS, ctx.getTypeFactory().builderTypeFor( targetType, builder ), - forgedMethod - ); + forgedMethod ); } private String getName(Type sourceType, Type targetType) { 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 17bf2cb465..0b299067f5 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 @@ -41,10 +41,14 @@ import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; import org.mapstruct.ap.internal.model.beanmapping.SourceReference; import org.mapstruct.ap.internal.model.beanmapping.TargetReference; +import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.BuilderType; +import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder; import org.mapstruct.ap.internal.model.source.BeanMappingOptions; @@ -52,7 +56,9 @@ import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.model.source.SubclassMappingOptions; 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; import org.mapstruct.ap.internal.util.accessor.Accessor; @@ -82,16 +88,15 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { private final Map> constructorMappingsByParameter; private final List constantMappings; private final List constructorConstantMappings; + private final List subclassMappings; private final Type returnTypeToConstruct; private final BuilderType returnTypeBuilder; private final MethodReference finalizerMethod; private final MappingReferences mappingReferences; - public static class Builder { + public static class Builder extends AbstractMappingMethodBuilder { - private MappingBuilderContext ctx; - private Method method; private Type userDefinedReturnType; /* returnType to construct can have a builder */ @@ -110,9 +115,13 @@ public static class Builder { private MethodReference factoryMethod; private boolean hasFactoryMethod; - public Builder mappingContext(MappingBuilderContext mappingContext) { - this.ctx = mappingContext; - return this; + public Builder() { + super( Builder.class ); + } + + @Override + protected boolean shouldUsePropertyNamesInHistory() { + return true; } public Builder userDefinedReturnType(Type userDefinedReturnType) { @@ -126,12 +135,12 @@ public Builder returnTypeBuilder( BuilderType returnTypeBuilder ) { } public Builder sourceMethod(SourceMethod sourceMethod) { - this.method = sourceMethod; + method( sourceMethod ); return this; } public Builder forgedMethod(ForgedMethod forgedMethod) { - this.method = forgedMethod; + method( forgedMethod ); mappingReferences = forgedMethod.getMappingReferences(); Parameter sourceParameter = first( Parameter.getSourceParameters( forgedMethod.getParameters() ) ); for ( MappingReference mappingReference: mappingReferences.getMappingReferences() ) { @@ -165,7 +174,9 @@ public BeanMappingMethod build() { // the userDefinedReturn type can also require a builder. That buildertype is already set returnTypeImpl = returnTypeBuilder.getBuilder(); initializeFactoryMethod( returnTypeImpl, selectionParameters ); - if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) { + if ( factoryMethod != null + || allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed( returnTypeImpl ) + || doesNotAllowAbstractReturnTypeAndCanBeConstructed( returnTypeImpl ) ) { returnTypeToConstruct = returnTypeImpl; } else { @@ -185,7 +196,9 @@ else if ( userDefinedReturnType != null ) { else if ( !method.isUpdateMethod() ) { returnTypeImpl = method.getReturnType(); initializeFactoryMethod( returnTypeImpl, selectionParameters ); - if ( factoryMethod != null || canReturnTypeBeConstructed( returnTypeImpl ) ) { + if ( factoryMethod != null + || allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed( returnTypeImpl ) + || doesNotAllowAbstractReturnTypeAndCanBeConstructed( returnTypeImpl ) ) { returnTypeToConstruct = returnTypeImpl; } else { @@ -333,6 +346,11 @@ else if ( !method.isUpdateMethod() ) { } + List subclasses = new ArrayList<>(); + for ( SubclassMappingOptions subclassMappingOptions : method.getOptions().getSubclassMappings() ) { + subclasses.add( createSubclassMapping( subclassMappingOptions ) ); + } + MethodReference finalizeMethod = null; if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) { @@ -350,10 +368,74 @@ else if ( !method.isUpdateMethod() ) { beforeMappingMethods, afterMappingMethods, finalizeMethod, - mappingReferences + mappingReferences, + subclasses ); } + private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) { + return !isAbstractReturnTypeAllowed() + && canReturnTypeBeConstructed( returnTypeImpl ); + } + + private boolean allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed(Type returnTypeImpl) { + return isAbstractReturnTypeAllowed() + && isReturnTypeAbstractOrCanBeConstructed( returnTypeImpl ); + } + + private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMappingOptions) { + TypeFactory typeFactory = ctx.getTypeFactory(); + Type sourceType = typeFactory.getType( subclassMappingOptions.getSource() ); + Type targetType = typeFactory.getType( subclassMappingOptions.getTarget() ); + + SourceRHS rightHandSide = new SourceRHS( + "subclassMapping", + sourceType, + Collections.emptySet(), + "SubclassMapping for " + sourceType.getFullyQualifiedName() ); + SelectionCriteria criteria = + SelectionCriteria + .forMappingMethods( + new SelectionParameters( + Collections.emptyList(), + Collections.emptyList(), + subclassMappingOptions.getTarget(), + ctx.getTypeUtils() ).withSourceRHS( rightHandSide ), + null, + null, + false ); + Assignment assignment = ctx + .getMappingResolver() + .getTargetAssignment( + method, + null, + targetType, + FormattingParameters.EMPTY, + criteria, + rightHandSide, + null, + () -> forgeSubclassMapping( + rightHandSide, + sourceType, + targetType, + mappingReferences ) ); + String sourceArgument = null; + for ( Parameter parameter : method.getSourceParameters() ) { + if ( ctx + .getTypeUtils() + .isAssignable( sourceType.getTypeMirror(), parameter.getType().getTypeMirror() ) ) { + sourceArgument = parameter.getName(); + assignment.setSourceLocalVarName( "(" + sourceType.createReferenceName() + ") " + sourceArgument ); + } + } + return new SubclassMapping( sourceType, sourceArgument, targetType, assignment ); + } + + private boolean isAbstractReturnTypeAllowed() { + return method.getOptions().getBeanMapping().getSubclassExhaustiveStrategy().isAbstractReturnTypeAllowed() + && !method.getOptions().getSubclassMappings().isEmpty(); + } + private void initializeMappingReferencesIfNeeded(Type resultTypeToMap) { if ( mappingReferences == null && method instanceof SourceMethod ) { Set readAndWriteTargetProperties = new HashSet<>( unprocessedTargetProperties.keySet() ); @@ -561,6 +643,20 @@ else if ( !returnType.hasAccessibleConstructor() ) { return error; } + private boolean isReturnTypeAbstractOrCanBeConstructed(Type returnType) { + boolean error = true; + if ( !returnType.isAbstract() && !returnType.hasAccessibleConstructor() ) { + ctx + .getMessager() + .printMessage( + method.getExecutable(), + Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, + returnType.describe() ); + error = false; + } + return error; + } + /** * Find a factory method for a return type or for a builder. * @param returnTypeImpl the return type implementation to construct @@ -1613,7 +1709,8 @@ private BeanMappingMethod(Method method, List beforeMappingReferences, List afterMappingReferences, MethodReference finalizerMethod, - MappingReferences mappingReferences) { + MappingReferences mappingReferences, + List subclassMappings) { super( method, existingVariableNames, @@ -1659,6 +1756,7 @@ else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { } } this.returnTypeToConstruct = returnTypeToConstruct; + this.subclassMappings = subclassMappings; } public List getConstantMappings() { @@ -1669,6 +1767,10 @@ public List getConstructorConstantMappings() { return constructorConstantMappings; } + public List getSubclassMappings() { + return subclassMappings; + } + public List propertyMappingsByParameter(Parameter parameter) { // issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value return mappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() ); @@ -1683,6 +1785,15 @@ public Type getReturnTypeToConstruct() { return returnTypeToConstruct; } + public boolean hasSubclassMappings() { + return !subclassMappings.isEmpty(); + } + + public boolean isAbstractReturnType() { + return getFactoryMethod() == null && !hasConstructorMappings() && returnTypeToConstruct != null + && returnTypeToConstruct.isAbstract(); + } + public boolean hasConstructorMappings() { return !constructorMappingsByParameter.isEmpty() || !constructorConstantMappings.isEmpty(); } @@ -1702,6 +1813,9 @@ public Set getImportTypes() { types.addAll( propertyMapping.getTargetType().getImportTypes() ); } } + for ( SubclassMapping subclassMapping : subclassMappings ) { + types.addAll( subclassMapping.getImportTypes() ); + } if ( returnTypeToConstruct != null ) { types.addAll( returnTypeToConstruct.getImportTypes() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java index b97d8be111..a1f5b091ce 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; + import javax.lang.model.element.ExecutableElement; import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; @@ -42,6 +43,7 @@ public class ForgedMethod implements Method { private final Method basedOn; private final boolean forgedNameBased; + private MappingMethodOptions options; /** * Creates a new forged method with the given name for mapping a method parameter to a property. @@ -121,6 +123,33 @@ public static ForgedMethod forElementMapping(String name, Type sourceType, Type ); } + /** + * Creates a new forged method for mapping a SubclassMapping element + * + * @param name the (unique name) for this method + * @param sourceType the source type + * @param returnType the return type. + * @param basedOn the method that (originally) triggered this nested method generation. + * @param history a parent forged method if this is a forged method within a forged method + * @param forgedNameBased forges a name based (matched) mapping method + * + * @return a new forge method + */ + public static ForgedMethod forSubclassMapping(String name, Type sourceType, Type returnType, Method basedOn, + MappingReferences mappingReferences, ForgedMethodHistory history, + boolean forgedNameBased) { + return new ForgedMethod( + name, + sourceType, + returnType, + basedOn.getContextParameters(), + basedOn, + history, + mappingReferences == null ? MappingReferences.empty() : mappingReferences, + forgedNameBased + ); + } + private ForgedMethod(String name, Type sourceType, Type returnType, List additionalParameters, Method basedOn, ForgedMethodHistory history, MappingReferences mappingReferences, boolean forgedNameBased) { @@ -155,6 +184,8 @@ private ForgedMethod(String name, Type sourceType, Type returnType, List getImportTypes() { + return Collections.singleton( sourceType ); + } + + public AssignmentWrapper getAssignment() { + return new ReturnWrapper( assignment ); + } + + public String getSourceArgument() { + return sourceArgument; + } + + @Override + public boolean equals(final Object other) { + if ( !( other instanceof SubclassMapping ) ) { + return false; + } + SubclassMapping castOther = (SubclassMapping) other; + return Objects.equals( sourceType, castOther.sourceType ) && Objects.equals( targetType, castOther.targetType ); + } + + @Override + public int hashCode() { + return Objects.hash( sourceType, targetType ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.java new file mode 100644 index 0000000000..76e2c19fa7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.java @@ -0,0 +1,20 @@ +/* + * 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.internal.model.assignment; + +import org.mapstruct.ap.internal.model.common.Assignment; + +/** + * Decorates an assignment as a return variable. + * + * @author Ben Zegveld + */ +public class ReturnWrapper extends AssignmentWrapper { + + public ReturnWrapper(Assignment decoratedAssignment) { + super( decoratedAssignment, false ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index 9567dd80bf..cc03878362 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -12,18 +12,18 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; -import org.mapstruct.ap.internal.gem.ReportingPolicyGem; -import org.mapstruct.ap.internal.util.ElementUtils; -import org.mapstruct.ap.internal.util.TypeUtils; - -import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.gem.BeanMappingGem; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ReportingPolicyGem; +import org.mapstruct.ap.internal.gem.SubclassExhaustiveStrategyGem; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.tools.gem.GemValue; /** @@ -92,6 +92,7 @@ private static boolean isConsistent(BeanMappingGem gem, ExecutableElement method && !gem.nullValueCheckStrategy().hasValue() && !gem.nullValuePropertyMappingStrategy().hasValue() && !gem.nullValueMappingStrategy().hasValue() + && !gem.subclassExhaustiveStrategy().hasValue() && !gem.unmappedTargetPolicy().hasValue() && !gem.ignoreByDefault().hasValue() && !gem.builder().hasValue() ) { @@ -139,6 +140,15 @@ public NullValueMappingStrategyGem getNullValueMappingStrategy() { .orElse( next().getNullValueMappingStrategy() ); } + @Override + public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::subclassExhaustiveStrategy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( SubclassExhaustiveStrategyGem::valueOf ) + .orElse( next().getSubclassExhaustiveStrategy() ); + } + @Override public ReportingPolicyGem unmappedTargetPolicy() { return Optional.ofNullable( beanMapping ).map( BeanMappingGem::unmappedTargetPolicy ) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index d52d094930..5a7161b4c8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -9,9 +9,7 @@ import java.util.Set; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; -import org.mapstruct.ap.internal.util.ElementUtils; -import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; @@ -21,6 +19,9 @@ import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem; +import org.mapstruct.ap.internal.gem.SubclassExhaustiveStrategyGem; +import org.mapstruct.ap.internal.option.Options; +import org.mapstruct.ap.internal.util.ElementUtils; public class DefaultOptions extends DelegatingOptions { @@ -119,6 +120,10 @@ public NullValueMappingStrategyGem getNullValueMappingStrategy() { return NullValueMappingStrategyGem.valueOf( mapper.nullValueMappingStrategy().getDefaultValue() ); } + public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { + return SubclassExhaustiveStrategyGem.valueOf( mapper.subclassExhaustiveStrategy().getDefaultValue() ); + } + public BuilderGem getBuilder() { // TODO: I realized this is not correct, however it needs to be null in order to keep downward compatibility // but assuming a default @Builder will make testcases fail. Not having a default means that you need to diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java index 564358ec98..b2b36b68a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java @@ -10,7 +10,6 @@ import java.util.Set; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; -import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; @@ -20,6 +19,8 @@ import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem; +import org.mapstruct.ap.internal.gem.SubclassExhaustiveStrategyGem; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.spi.TypeHierarchyErroneousException; /** @@ -97,6 +98,10 @@ public NullValueMappingStrategyGem getNullValueMappingStrategy() { return next.getNullValueMappingStrategy(); } + public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { + return next.getSubclassExhaustiveStrategy(); + } + public BuilderGem getBuilder() { return next.getBuilder(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java index 7f8df7bf42..5288ab1304 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -19,6 +19,7 @@ import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem; +import org.mapstruct.ap.internal.gem.SubclassExhaustiveStrategyGem; public class MapperConfigOptions extends DelegatingOptions { @@ -126,6 +127,13 @@ public NullValueMappingStrategyGem getNullValueMappingStrategy() { next().getNullValueMappingStrategy(); } + @Override + public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { + return mapperConfig.subclassExhaustiveStrategy().hasValue() ? + SubclassExhaustiveStrategyGem.valueOf( mapperConfig.subclassExhaustiveStrategy().get() ) : + next().getSubclassExhaustiveStrategy(); + } + @Override public BuilderGem getBuilder() { return mapperConfig.builder().hasValue() ? mapperConfig.builder().get() : next().getBuilder(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java index 82d17a9846..86aae34052 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -25,6 +25,7 @@ import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem; +import org.mapstruct.ap.internal.gem.SubclassExhaustiveStrategyGem; public class MapperOptions extends DelegatingOptions { @@ -155,6 +156,13 @@ public NullValueMappingStrategyGem getNullValueMappingStrategy() { next().getNullValueMappingStrategy(); } + @Override + public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { + return mapper.subclassExhaustiveStrategy().hasValue() ? + SubclassExhaustiveStrategyGem.valueOf( mapper.subclassExhaustiveStrategy().get() ) : + next().getSubclassExhaustiveStrategy(); + } + @Override public BuilderGem getBuilder() { return mapper.builder().hasValue() ? mapper.builder().get() : next().getBuilder(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java index 859a4c6f5f..45b1175f8d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -34,7 +34,8 @@ public class MappingMethodOptions { null, null, null, - Collections.emptyList() + Collections.emptyList(), + Collections.emptySet() ); private MapperOptions mapper; @@ -45,12 +46,14 @@ public class MappingMethodOptions { private EnumMappingOptions enumMappingOptions; private List valueMappings; private boolean fullyInitialized; + private Set subclassMapping; public MappingMethodOptions(MapperOptions mapper, Set mappings, IterableMappingOptions iterableMapping, MapMappingOptions mapMapping, BeanMappingOptions beanMapping, EnumMappingOptions enumMappingOptions, - List valueMappings) { + List valueMappings, + Set subclassMapping) { this.mapper = mapper; this.mappings = mappings; this.iterableMapping = iterableMapping; @@ -58,6 +61,7 @@ public MappingMethodOptions(MapperOptions mapper, Set mappings, this.beanMapping = beanMapping; this.enumMappingOptions = enumMappingOptions; this.valueMappings = valueMappings; + this.subclassMapping = subclassMapping; } /** @@ -97,6 +101,10 @@ public List getValueMappings() { return valueMappings; } + public Set getSubclassMappings() { + return subclassMapping; + } + public void setIterableMapping(IterableMappingOptions iterableMapping) { this.iterableMapping = iterableMapping; } @@ -188,6 +196,8 @@ public void applyInheritedOptions(SourceMethod templateMethod, boolean isInverse } } + // Do NOT inherit subclass mapping options!!! + Set newMappings = new LinkedHashSet<>(); for ( MappingOptions mapping : templateOptions.getMappings() ) { if ( isInverse ) { @@ -315,4 +325,21 @@ private String getFirstTargetPropertyName(MappingOptions mapping) { return getPropertyEntries( mapping )[0]; } + /** + * SubclassMappingOptions are not inherited to forged methods. They would result in an infinite loop if they were. + * + * @return a MappingMethodOptions without SubclassMappingOptions. + */ + public static MappingMethodOptions getForgedMethodInheritedOptions(MappingMethodOptions options) { + return new MappingMethodOptions( + options.mapper, + options.mappings, + options.iterableMapping, + options.mapMapping, + options.beanMapping, + options.enumMappingOptions, + options.valueMappings, + Collections.emptySet() ); + } + } 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 86f19618d4..5895ecba2a 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 @@ -95,6 +95,7 @@ public static class Builder { private EnumMappingOptions enumMappingOptions; private ParameterProvidedMethods contextProvidedMethods; private List typeParameters; + private Set subclassMappings; private boolean verboseLogging; @@ -153,6 +154,11 @@ public Builder setEnumMappingOptions(EnumMappingOptions enumMappingOptions) { return this; } + public Builder setSubclassMappings(Set subclassMappings) { + this.subclassMappings = subclassMappings; + return this; + } + public Builder setTypeUtils(TypeUtils typeUtils) { this.typeUtils = typeUtils; return this; @@ -194,6 +200,10 @@ public SourceMethod build() { mappings = Collections.emptySet(); } + if ( subclassMappings == null ) { + subclassMappings = Collections.emptySet(); + } + MappingMethodOptions mappingMethodOptions = new MappingMethodOptions( mapper, mappings, @@ -201,7 +211,8 @@ public SourceMethod build() { mapMapping, beanMapping, enumMappingOptions, - valueMappings + valueMappings, + subclassMappings ); this.typeParameters = this.executable.getTypeParameters() diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java new file mode 100644 index 0000000000..54a0ba4565 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java @@ -0,0 +1,171 @@ +/* + * 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.internal.model.source; + +import java.util.List; +import java.util.Set; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.SubclassMappingGem; +import org.mapstruct.ap.internal.gem.SubclassMappingsGem; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; + +import static org.mapstruct.ap.internal.util.Message.SUBCLASSMAPPING_ILLEGAL_SUBCLASS; +import static org.mapstruct.ap.internal.util.Message.SUBCLASSMAPPING_NO_VALID_SUPERCLASS; +import static org.mapstruct.ap.internal.util.Message.SUBCLASSMAPPING_UPDATE_METHODS_NOT_SUPPORTED; + +/** + * Represents a subclass mapping as configured via {@code @SubclassMapping}. + * + * @author Ben Zegveld + */ +public class SubclassMappingOptions extends DelegatingOptions { + + private final TypeMirror source; + private final TypeMirror target; + + public SubclassMappingOptions(TypeMirror source, TypeMirror target, DelegatingOptions next) { + super( next ); + this.source = source; + this.target = target; + } + + @Override + public boolean hasAnnotation() { + return source != null && target != null; + } + + private static boolean isConsistent(SubclassMappingGem gem, ExecutableElement method, FormattingMessager messager, + TypeUtils typeUtils, List sourceParameters, Type resultType, + SubclassValidator subclassValidator) { + + if ( resultType == null ) { + messager.printMessage( method, gem.mirror(), SUBCLASSMAPPING_UPDATE_METHODS_NOT_SUPPORTED ); + return false; + } + + TypeMirror sourceSubclass = gem.source().getValue(); + TypeMirror targetSubclass = gem.target().getValue(); + TypeMirror targetParentType = resultType.getTypeMirror(); + validateTypeMirrors( sourceSubclass, targetSubclass, targetParentType ); + + boolean isConsistent = true; + + boolean isChildOfAParameter = false; + for ( Parameter sourceParameter : sourceParameters ) { + TypeMirror sourceParentType = sourceParameter.getType().getTypeMirror(); + validateTypeMirrors( sourceParentType ); + isChildOfAParameter = isChildOfAParameter || isChildOfParent( typeUtils, sourceSubclass, sourceParentType ); + } + if ( !isChildOfAParameter ) { + messager + .printMessage( + method, + gem.mirror(), + SUBCLASSMAPPING_NO_VALID_SUPERCLASS, + sourceSubclass.toString() ); + isConsistent = false; + } + if ( !isChildOfParent( typeUtils, targetSubclass, targetParentType ) ) { + messager + .printMessage( + method, + gem.mirror(), + SUBCLASSMAPPING_ILLEGAL_SUBCLASS, + targetParentType.toString(), + targetSubclass.toString() ); + isConsistent = false; + } + subclassValidator.isInCorrectOrder( method, gem.mirror(), targetSubclass ); + return isConsistent; + } + + private static void validateTypeMirrors(TypeMirror... typeMirrors) { + for ( TypeMirror typeMirror : typeMirrors ) { + if ( typeMirror == null ) { + // When a class used in uses or imports is created by another annotation processor + // then javac will not return correct TypeMirror with TypeKind#ERROR, but rather a string "" + // the gem tools would return a null TypeMirror in that case. + // Therefore throw TypeHierarchyErroneousException so we can postpone the generation of the mapper + throw new TypeHierarchyErroneousException( typeMirror ); + } + } + } + + private static boolean isChildOfParent(TypeUtils typeUtils, TypeMirror childType, TypeMirror parentType) { + return typeUtils.isSubtype( childType, parentType ); + } + + public TypeMirror getSource() { + return source; + } + + public TypeMirror getTarget() { + return target; + } + + public static void addInstances(SubclassMappingsGem gem, ExecutableElement method, + BeanMappingOptions beanMappingOptions, FormattingMessager messager, + TypeUtils typeUtils, Set mappings, + List sourceParameters, Type resultType) { + SubclassValidator subclassValidator = new SubclassValidator( messager, typeUtils ); + for ( SubclassMappingGem subclassMappingGem : gem.value().get() ) { + addAndValidateInstance( + subclassMappingGem, + method, + beanMappingOptions, + messager, + typeUtils, + mappings, + sourceParameters, + resultType, + subclassValidator ); + } + } + + public static void addInstance(SubclassMappingGem subclassMapping, ExecutableElement method, + BeanMappingOptions beanMappingOptions, FormattingMessager messager, + TypeUtils typeUtils, Set mappings, + List sourceParameters, Type resultType) { + addAndValidateInstance( + subclassMapping, + method, + beanMappingOptions, + messager, + typeUtils, + mappings, + sourceParameters, + resultType, + new SubclassValidator( messager, typeUtils ) ); + } + + private static void addAndValidateInstance(SubclassMappingGem subclassMapping, ExecutableElement method, + BeanMappingOptions beanMappingOptions, FormattingMessager messager, + TypeUtils typeUtils, Set mappings, + List sourceParameters, Type resultType, + SubclassValidator subclassValidator) { + if ( !isConsistent( + subclassMapping, + method, + messager, + typeUtils, + sourceParameters, + resultType, + subclassValidator ) ) { + return; + } + + TypeMirror sourceSubclass = subclassMapping.source().getValue(); + TypeMirror targetSubclass = subclassMapping.target().getValue(); + + mappings.add( new SubclassMappingOptions( sourceSubclass, targetSubclass, beanMappingOptions ) ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java new file mode 100644 index 0000000000..dd79ba0c02 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java @@ -0,0 +1,53 @@ +/* + * 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.internal.model.source; + +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; + +/** + * Handles the validation of multiple @SubclassMapping annotations on the same method. + * + * @author Ben Zegveld + */ +class SubclassValidator { + + private final FormattingMessager messager; + private final List handledSubclasses = new ArrayList<>(); + private final TypeUtils typeUtils; + + SubclassValidator(FormattingMessager messager, TypeUtils typeUtils) { + this.messager = messager; + this.typeUtils = typeUtils; + } + + public boolean isInCorrectOrder(Element e, AnnotationMirror annotation, TypeMirror sourceType) { + for ( TypeMirror typeMirror : handledSubclasses ) { + if ( typeUtils.isAssignable( sourceType, typeMirror ) ) { + messager + .printMessage( + e, + annotation, + Message.SUBCLASSMAPPING_ILLOGICAL_ORDER, + sourceType, + typeMirror, + sourceType, + typeMirror ); + return true; + } + } + handledSubclasses.add( sourceType ); + return false; + } + +} 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 c6e510d315..9bf55e731f 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 @@ -29,6 +29,8 @@ import org.mapstruct.ap.internal.gem.MappingGem; import org.mapstruct.ap.internal.gem.MappingsGem; import org.mapstruct.ap.internal.gem.ObjectFactoryGem; +import org.mapstruct.ap.internal.gem.SubclassMappingGem; +import org.mapstruct.ap.internal.gem.SubclassMappingsGem; import org.mapstruct.ap.internal.gem.ValueMappingGem; import org.mapstruct.ap.internal.gem.ValueMappingsGem; import org.mapstruct.ap.internal.model.common.Parameter; @@ -42,6 +44,7 @@ import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.model.source.SubclassMappingOptions; import org.mapstruct.ap.internal.model.source.ValueMappingOptions; import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.util.AccessorNamingUtils; @@ -52,6 +55,7 @@ import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.spi.EnumTransformationStrategy; +import org.mapstruct.tools.gem.Gem; /** * A {@link ModelElementProcessor} which retrieves a list of {@link SourceMethod}s @@ -67,6 +71,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor mappingOptions = - getMappings( method, method, beanMappingOptions, new LinkedHashSet<>(), new HashSet<>() ); + RepeatableMappings repeatableMappings = new RepeatableMappings(); + Set mappingOptions = repeatableMappings.getMappings( method, beanMappingOptions ); IterableMappingOptions iterableMappingOptions = IterableMappingOptions.fromGem( IterableMappingGem.instanceOn( method ), @@ -299,6 +305,15 @@ private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, messager ); + // We want to get as much error reporting as possible. + // If targetParameter is not null it means we have an update method + Set subclassMappingOptions = getSubclassMappings( + sourceParameters, + targetParameter != null ? null : resultType, + method, + beanMappingOptions + ); + return new SourceMethod.Builder() .setExecutable( method ) .setParameters( parameters ) @@ -311,6 +326,7 @@ private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, .setMapMappingOptions( mapMappingOptions ) .setValueMappingOptionss( getValueMappings( method ) ) .setEnumMappingOptions( enumMappingOptions ) + .setSubclassMappings( subclassMappingOptions ) .setTypeUtils( typeUtils ) .setTypeFactory( typeFactory ) .setPrototypeMethods( prototypeMethods ) @@ -554,56 +570,189 @@ private boolean isStreamTypeOrIterableFromJavaStdLib(Type type) { } /** - * Retrieves the mappings configured via {@code @Mapping} from the given - * method. + * Retrieves the mappings configured via {@code @Mapping} from the given method. * * @param method The method of interest - * @param element Element of interest: method, or (meta) annotation * @param beanMapping options coming from bean mapping method - * @param mappingOptions LinkedSet of mappings found so far - * * @return The mappings for the given method, keyed by target property name */ - private Set getMappings(ExecutableElement method, Element element, - BeanMappingOptions beanMapping, Set mappingOptions, - Set handledElements) { - - for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { - Element lElement = annotationMirror.getAnnotationType().asElement(); - if ( isAnnotation( lElement, MAPPING_FQN ) ) { - // although getInstanceOn does a search on annotation mirrors, the order is preserved - MappingGem mapping = MappingGem.instanceOn( element ); - MappingOptions.addInstance( mapping, method, beanMapping, messager, typeUtils, mappingOptions ); - } - else if ( isAnnotation( lElement, MAPPINGS_FQN ) ) { - // although getInstanceOn does a search on annotation mirrors, the order is preserved - MappingsGem mappings = MappingsGem.instanceOn( element ); - MappingOptions.addInstances( mappings, method, beanMapping, messager, typeUtils, mappingOptions ); - } - else if ( !isAnnotationInPackage( lElement, JAVA_LANG_ANNOTATION_PGK ) - && !isAnnotationInPackage( lElement, ORG_MAPSTRUCT_PKG ) - && !handledElements.contains( lElement ) - ) { - // recur over annotation mirrors - handledElements.add( lElement ); - getMappings( method, lElement, beanMapping, mappingOptions, handledElements ); - } + private Set getMappings(ExecutableElement method, BeanMappingOptions beanMapping) { + return new RepeatableMappings().getMappings( method, beanMapping ); + } + + /** + * Retrieves the subclass mappings configured via {@code @SubclassMapping} from the given method. + * + * @param method The method of interest + * @param beanMapping options coming from bean mapping method + * + * @return The subclass mappings for the given method + */ + private Set getSubclassMappings(List sourceParameters, Type resultType, + ExecutableElement method, BeanMappingOptions beanMapping) { + return new RepeatableSubclassMappings( sourceParameters, resultType ).getMappings( method, beanMapping ); + } + + private class RepeatableMappings extends RepeatableMappingAnnotations { + RepeatableMappings() { + super( MAPPING_FQN, MAPPINGS_FQN ); + } + + @Override + MappingGem singularInstanceOn(Element element) { + return MappingGem.instanceOn( element ); + } + + @Override + MappingsGem multipleInstanceOn(Element element) { + return MappingsGem.instanceOn( element ); + } + + @Override + void addInstance(MappingGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, + Set mappings) { + MappingOptions.addInstance( gem, method, beanMappingOptions, messager, typeUtils, mappings ); + } + + @Override + void addInstances(MappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, + Set mappings) { + MappingOptions.addInstances( gem, method, beanMappingOptions, messager, typeUtils, mappings ); } - return mappingOptions; } - private boolean isAnnotationInPackage(Element element, String packageFQN ) { - if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { - return packageFQN.equals( elementUtils.getPackageOf( element ).getQualifiedName().toString() ); + private class RepeatableSubclassMappings + extends RepeatableMappingAnnotations { + private final List sourceParameters; + private final Type resultType; + + RepeatableSubclassMappings(List sourceParameters, Type resultType) { + super( SUB_CLASS_MAPPING_FQN, SUB_CLASS_MAPPINGS_FQN ); + this.sourceParameters = sourceParameters; + this.resultType = resultType; + } + + @Override + SubclassMappingGem singularInstanceOn(Element element) { + return SubclassMappingGem.instanceOn( element ); + } + + @Override + SubclassMappingsGem multipleInstanceOn(Element element) { + return SubclassMappingsGem.instanceOn( element ); + } + + @Override + void addInstance(SubclassMappingGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, + Set mappings) { + SubclassMappingOptions + .addInstance( + gem, + method, + beanMappingOptions, + messager, + typeUtils, + mappings, + sourceParameters, + resultType ); + } + + @Override + void addInstances(SubclassMappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, + Set mappings) { + SubclassMappingOptions + .addInstances( + gem, + method, + beanMappingOptions, + messager, + typeUtils, + mappings, + sourceParameters, + resultType ); } - return false; } - private boolean isAnnotation(Element element, String annotationFQN) { - if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { - return annotationFQN.equals( ( (TypeElement) element ).getQualifiedName().toString() ); + private abstract class RepeatableMappingAnnotations { + + private final String singularFqn; + private final String multipleFqn; + + RepeatableMappingAnnotations(String singularFqn, String multipleFqn) { + this.singularFqn = singularFqn; + this.multipleFqn = multipleFqn; + } + + abstract SINGULAR singularInstanceOn(Element element); + + abstract MULTIPLE multipleInstanceOn(Element element); + + abstract void addInstance(SINGULAR gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, + Set mappings); + + abstract void addInstances(MULTIPLE gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, + Set mappings); + + /** + * Retrieves the mappings configured via {@code @Mapping} from the given method. + * + * @param method The method of interest + * @param beanMapping options coming from bean mapping method + * @return The mappings for the given method, keyed by target property name + */ + public Set getMappings(ExecutableElement method, BeanMappingOptions beanMapping) { + return getMappings( method, method, beanMapping, new LinkedHashSet<>(), new HashSet<>() ); + } + + /** + * Retrieves the mappings configured via {@code @Mapping} from the given method. + * + * @param method The method of interest + * @param element Element of interest: method, or (meta) annotation + * @param beanMapping options coming from bean mapping method + * @param mappingOptions LinkedSet of mappings found so far + * @return The mappings for the given method, keyed by target property name + */ + private Set getMappings(ExecutableElement method, Element element, + BeanMappingOptions beanMapping, LinkedHashSet mappingOptions, + Set handledElements) { + + for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { + Element lElement = annotationMirror.getAnnotationType().asElement(); + if ( isAnnotation( lElement, singularFqn ) ) { + // although getInstanceOn does a search on annotation mirrors, the order is preserved + SINGULAR mapping = singularInstanceOn( element ); + addInstance( mapping, method, beanMapping, mappingOptions ); + } + else if ( isAnnotation( lElement, multipleFqn ) ) { + // although getInstanceOn does a search on annotation mirrors, the order is preserved + MULTIPLE mappings = multipleInstanceOn( element ); + addInstances( mappings, method, beanMapping, mappingOptions ); + } + else if ( !isAnnotationInPackage( lElement, JAVA_LANG_ANNOTATION_PGK ) + && !isAnnotationInPackage( lElement, ORG_MAPSTRUCT_PKG ) + && !handledElements.contains( lElement ) ) { + // recur over annotation mirrors + handledElements.add( lElement ); + getMappings( method, lElement, beanMapping, mappingOptions, handledElements ); + } + } + return mappingOptions; + } + + private boolean isAnnotationInPackage(Element element, String packageFQN) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return packageFQN.equals( elementUtils.getPackageOf( element ).getQualifiedName().toString() ); + } + return false; + } + + private boolean isAnnotation(Element element, String annotationFQN) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return annotationFQN.equals( ( (TypeElement) element ).getQualifiedName().toString() ); + } + return false; } - 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 8d328d4796..66b37de7a1 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 @@ -114,6 +114,11 @@ public enum Message { ENUMMAPPING_NO_ELEMENTS( "'nameTransformationStrategy', 'configuration' and 'unexpectedValueMappingException' are undefined in @EnumMapping, define at least one of them." ), ENUMMAPPING_ILLEGAL_TRANSFORMATION( "Illegal transformation for '%s' EnumTransformationStrategy. Error: '%s'." ), + SUBCLASSMAPPING_ILLEGAL_SUBCLASS( "Class '%s' is not a subclass of '%s'." ), + SUBCLASSMAPPING_NO_VALID_SUPERCLASS( "Could not find a parameter that is a superclass for '%s'." ), + SUBCLASSMAPPING_UPDATE_METHODS_NOT_SUPPORTED( "SubclassMapping annotation can not be used for update methods." ), + SUBCLASSMAPPING_ILLOGICAL_ORDER( "SubclassMapping annotation for '%s' found after '%s', but all '%s' objects are also instances of '%s'.", Diagnostic.Kind.WARNING ), + LIFECYCLEMETHOD_AMBIGUOUS_PARAMETERS( "Lifecycle method has multiple matching parameters (e. g. same type), in this case please ensure to name the parameters in the lifecycle and mapping method identical. This lifecycle method will not be used for the mapping method '%s'.", Diagnostic.Kind.WARNING), DECORATOR_NO_SUBTYPE( "Specified decorator type is no subtype of the annotated mapper type." ), @@ -188,7 +193,6 @@ public enum Message { ; // CHECKSTYLE:ON - private final String description; private final Diagnostic.Kind kind; diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index f5d6e799e1..12a03c06ca 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -24,6 +24,17 @@ } + <#if hasSubclassMappings()> + <#list subclassMappings as subclass> + <#if subclass_index > 0>else if (${subclass.sourceArgument} instanceof <@includeModel object=subclass.sourceType/>) { + <@includeModel object=subclass.assignment existingInstanceMapping=existingInstanceMapping/> + } + + else { + + <#if isAbstractReturnType()> + throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for " + ${subclassMappings[0].sourceArgument}.getClass()); + <#else> <#if !existingInstanceMapping> <#if hasConstructorMappings()> <#if (sourceParameters?size > 1)> @@ -120,6 +131,10 @@ return ${resultName}; + + <#if hasSubclassMappings()> + } + } <#macro throws> <#if (thrownTypes?size > 0)><#lt> throws <@compress single_line=true> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl new file mode 100644 index 0000000000..9f46f3604f --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl @@ -0,0 +1,17 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.ReturnWrapper" --> +return <@_assignment/>; +<#macro _assignment> + <@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + targetType=ext.targetType/> + diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousSubclassMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousSubclassMapper1.java new file mode 100644 index 0000000000..fc5e37d7b5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousSubclassMapper1.java @@ -0,0 +1,24 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Bike; +import org.mapstruct.ap.test.subclassmapping.mappables.BikeDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.CarDto; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ErroneousSubclassMapper1 { + ErroneousSubclassMapper1 INSTANCE = Mappers.getMapper( ErroneousSubclassMapper1.class ); + + @SubclassMapping( source = Bike.class, target = BikeDto.class ) + @Mapping( target = "maker", ignore = true ) + CarDto map(Car vehicle); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousSubclassUpdateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousSubclassUpdateMapper.java new file mode 100644 index 0000000000..b002b1e32d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousSubclassUpdateMapper.java @@ -0,0 +1,28 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.CarDto; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ErroneousSubclassUpdateMapper { + ErroneousSubclassUpdateMapper INSTANCE = Mappers.getMapper( ErroneousSubclassUpdateMapper.class ); + + @SubclassMapping( source = Car.class, target = CarDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker" ) + void map(@MappingTarget VehicleDto target, Car vehicle); + + @SubclassMapping( source = Car.class, target = CarDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker" ) + VehicleDto mapWithReturnType(@MappingTarget VehicleDto target, Car vehicle); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/MappingInheritanceTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/MappingInheritanceTest.java new file mode 100644 index 0000000000..4cedd30510 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/MappingInheritanceTest.java @@ -0,0 +1,50 @@ +/* + * 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.subclassmapping; + +import org.assertj.core.api.Assertions; +import org.mapstruct.ap.test.subclassmapping.mappables.Source; +import org.mapstruct.ap.test.subclassmapping.mappables.SourceSubclass; +import org.mapstruct.ap.test.subclassmapping.mappables.Target; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +@IssueKey( "2438" ) +@WithClasses( { Source.class, SourceSubclass.class, Target.class } ) +class MappingInheritanceTest { + + @ProcessorTest + @WithClasses( { SubclassMapper.class } ) + void inheritance() { + SubclassMapper mapper = Mappers.getMapper( SubclassMapper.class ); + SourceSubclass sourceSubclass = new SourceSubclass( "f1", "f2", "f3", "f4", "f5" ); + + Target result = mapper.mapSuperclass( sourceSubclass ); + + Assertions.assertThat( result.getTarget1() ).isEqualTo( "f1" ); + Assertions.assertThat( result.getTarget2() ).isEqualTo( "f2" ); + Assertions.assertThat( result.getTarget3() ).isEqualTo( "f3" ); + Assertions.assertThat( result.getTarget4() ).isEqualTo( "f4" ); + Assertions.assertThat( result.getTarget5() ).isEqualTo( "f5" ); + } + + @ProcessorTest + @WithClasses( { SubclassMapper.class } ) + void superclass() { + SubclassMapper mapper = Mappers.getMapper( SubclassMapper.class ); + Source source = new Source( "f1", "f2", "f3" ); + + Target result = mapper.mapSuperclass( source ); + + Assertions.assertThat( result.getTarget1() ).isEqualTo( "f1" ); + Assertions.assertThat( result.getTarget2() ).isEqualTo( "f2" ); + Assertions.assertThat( result.getTarget3() ).isEqualTo( "f3" ); + Assertions.assertThat( result.getTarget4() ).isNull(); + Assertions.assertThat( result.getTarget5() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SimpleSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SimpleSubclassMapper.java new file mode 100644 index 0000000000..eb71797864 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SimpleSubclassMapper.java @@ -0,0 +1,31 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Bike; +import org.mapstruct.ap.test.subclassmapping.mappables.BikeDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.CarDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollection; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollectionDto; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SimpleSubclassMapper { + SimpleSubclassMapper INSTANCE = Mappers.getMapper( SimpleSubclassMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + @SubclassMapping( source = Car.class, target = CarDto.class ) + @SubclassMapping( source = Bike.class, target = BikeDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker") + VehicleDto map(Vehicle vehicle); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMapper.java new file mode 100644 index 0000000000..8e77297dda --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMapper.java @@ -0,0 +1,31 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Source; +import org.mapstruct.ap.test.subclassmapping.mappables.SourceSubclass; +import org.mapstruct.ap.test.subclassmapping.mappables.Target; + +@Mapper +public interface SubclassMapper { + + @SubclassMapping( source = SourceSubclass.class, target = Target.class ) + @Mapping( target = "target1", source = "property1" ) + @Mapping( target = "target2", source = "property2" ) + @Mapping( target = "target3", source = "property3" ) + @Mapping( target = "target4", ignore = true ) + @Mapping( target = "target5", ignore = true ) + Target mapSuperclass(Source source); + + @InheritConfiguration( name = "mapSuperclass" ) + @Mapping( target = "target4", source = "subclassProperty" ) + @Mapping( target = "target5" ) // Have to declare in order to override ignore with default behavior + Target mapSubclass(SourceSubclass source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMapperUsingExistingMappings.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMapperUsingExistingMappings.java new file mode 100644 index 0000000000..efdd357e3d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMapperUsingExistingMappings.java @@ -0,0 +1,36 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Bike; +import org.mapstruct.ap.test.subclassmapping.mappables.BikeDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.CarDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollection; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollectionDto; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto; +import org.mapstruct.factory.Mappers; + +@Mapper( unmappedTargetPolicy = ReportingPolicy.IGNORE ) +public interface SubclassMapperUsingExistingMappings { + SubclassMapperUsingExistingMappings INSTANCE = Mappers.getMapper( SubclassMapperUsingExistingMappings.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + default CarDto existingMappingMethod(Car domain) { + CarDto dto = new CarDto(); + dto.setName( "created through existing mapping." ); + return dto; + } + + @SubclassMapping( source = Car.class, target = CarDto.class ) + @SubclassMapping( source = Bike.class, target = BikeDto.class ) + VehicleDto map(Vehicle vehicle); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java new file mode 100644 index 0000000000..e5a77b56d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java @@ -0,0 +1,139 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.ap.test.subclassmapping.mappables.Bike; +import org.mapstruct.ap.test.subclassmapping.mappables.BikeDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.CarDto; +import org.mapstruct.ap.test.subclassmapping.mappables.HatchBack; +import org.mapstruct.ap.test.subclassmapping.mappables.HatchBackDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollection; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollectionDto; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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 static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("131") +@WithClasses({ + Bike.class, + BikeDto.class, + Car.class, + CarDto.class, + VehicleCollection.class, + VehicleCollectionDto.class, + Vehicle.class, + VehicleDto.class, + SimpleSubclassMapper.class, + SubclassMapperUsingExistingMappings.class, +}) +public class SubclassMappingTest { + + @ProcessorTest + void mappingIsDoneUsingSubclassMapping() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Bike() ); + + VehicleCollectionDto result = SimpleSubclassMapper.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( CarDto.class, BikeDto.class ); + } + + @ProcessorTest + void existingMappingsAreUsedWhenFound() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + + VehicleCollectionDto result = SubclassMapperUsingExistingMappings.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ) + .extracting( VehicleDto::getName ) + .containsExactly( "created through existing mapping." ); + } + + @ProcessorTest + void subclassMappingInheritsMapping() { + VehicleCollection vehicles = new VehicleCollection(); + Car car = new Car(); + car.setVehicleManufacturingCompany( "BenZ" ); + vehicles.getVehicles().add( car ); + + VehicleCollectionDto result = SimpleSubclassMapper.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ) + .extracting( VehicleDto::getMaker ) + .containsExactly( "BenZ" ); + } + + @ProcessorTest + @WithClasses({ + HatchBack.class, + HatchBackDto.class, + SubclassOrderWarningMapper.class, + }) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, diagnostics = { + @Diagnostic(type = SubclassOrderWarningMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 28, + alternativeLine = 30, + message = "SubclassMapping annotation for " + + "'org.mapstruct.ap.test.subclassmapping.mappables.HatchBackDto' found after " + + "'org.mapstruct.ap.test.subclassmapping.mappables.CarDto', but all " + + "'org.mapstruct.ap.test.subclassmapping.mappables.HatchBackDto' " + + "objects are also instances of " + + "'org.mapstruct.ap.test.subclassmapping.mappables.CarDto'.") + }) + void subclassOrderWarning() { + } + + @ProcessorTest + @WithClasses({ ErroneousSubclassUpdateMapper.class }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { + @Diagnostic(type = ErroneousSubclassUpdateMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 21, + message = "SubclassMapping annotation can not be used for update methods." + ), + @Diagnostic(type = ErroneousSubclassUpdateMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 25, + message = "SubclassMapping annotation can not be used for update methods." + ) + }) + void unsupportedUpdateMethod() { + } + + @ProcessorTest + @WithClasses({ ErroneousSubclassMapper1.class }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { + @Diagnostic(type = ErroneousSubclassMapper1.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 21, + message = "Could not find a parameter that is a superclass for " + + "'org.mapstruct.ap.test.subclassmapping.mappables.Bike'." + ), + @Diagnostic(type = ErroneousSubclassMapper1.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 21, + message = "Class 'org.mapstruct.ap.test.subclassmapping.mappables.CarDto'" + + " is not a subclass of " + + "'org.mapstruct.ap.test.subclassmapping.mappables.BikeDto'." + ) + }) + void erroneousMethodWithSourceTargetType() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassOrderWarningMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassOrderWarningMapper.java new file mode 100644 index 0000000000..2ff7a7d836 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassOrderWarningMapper.java @@ -0,0 +1,31 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.CarDto; +import org.mapstruct.ap.test.subclassmapping.mappables.HatchBack; +import org.mapstruct.ap.test.subclassmapping.mappables.HatchBackDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollection; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollectionDto; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SubclassOrderWarningMapper { + SubclassOrderWarningMapper INSTANCE = Mappers.getMapper( SubclassOrderWarningMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + @SubclassMapping( source = Car.class, target = CarDto.class ) + @SubclassMapping( source = HatchBack.class, target = HatchBackDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker") + VehicleDto map(Vehicle vehicle); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/AbstractSuperClassTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/AbstractSuperClassTest.java new file mode 100644 index 0000000000..31fbf1e00e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/AbstractSuperClassTest.java @@ -0,0 +1,70 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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 static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@IssueKey("366") +@WithClasses({ + AbstractVehicle.class, + VehicleCollection.class, + Bike.class, + BikeDto.class, + Car.class, + CarDto.class, + VehicleCollectionDto.class, + VehicleDto.class, +}) +public class AbstractSuperClassTest { + + @ProcessorTest + @WithClasses( SubclassWithAbstractSuperClassMapper.class ) + void downcastMappingInCollection() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Bike() ); + + VehicleCollectionDto result = SubclassWithAbstractSuperClassMapper.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( CarDto.class, BikeDto.class ); + } + + @ProcessorTest + @WithClasses( SubclassWithAbstractSuperClassMapper.class ) + void mappingOfUnknownChildThrowsIllegalArgumentException() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Motorcycle() ); + + assertThatThrownBy( () -> SubclassWithAbstractSuperClassMapper.INSTANCE.map( vehicles ) ) + .isInstanceOf( IllegalArgumentException.class ) + .hasMessage( "Not all subclasses are supported for this mapping. " + + "Missing for class org.mapstruct.ap.test.subclassmapping.abstractsuperclass.Motorcycle" ); + } + + @WithClasses( ErroneousSubclassWithAbstractSuperClassMapper.class ) + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { + @Diagnostic(type = ErroneousSubclassWithAbstractSuperClassMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 26, + message = "The return type VehicleDto is an abstract class or interface. " + + "Provide a non abstract / non interface result type or a factory method.") + }) + void compileErrorWithAbstractClass() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/AbstractVehicle.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/AbstractVehicle.java new file mode 100644 index 0000000000..7cb155c402 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/AbstractVehicle.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +public abstract class AbstractVehicle { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Bike.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Bike.java new file mode 100644 index 0000000000..72a84f629e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Bike.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +public class Bike extends AbstractVehicle { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/BikeDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/BikeDto.java new file mode 100644 index 0000000000..8950e26086 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/BikeDto.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +public class BikeDto extends VehicleDto { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Car.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Car.java new file mode 100644 index 0000000000..acd7e85f99 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Car.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +public class Car extends AbstractVehicle { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CarDto.java new file mode 100644 index 0000000000..6353b4c590 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CarDto.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +public class CarDto extends VehicleDto { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/ErroneousSubclassWithAbstractSuperClassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/ErroneousSubclassWithAbstractSuperClassMapper.java new file mode 100644 index 0000000000..a3406bff08 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/ErroneousSubclassWithAbstractSuperClassMapper.java @@ -0,0 +1,27 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper( subclassExhaustiveStrategy = SubclassExhaustiveStrategy.COMPILE_ERROR ) +public interface ErroneousSubclassWithAbstractSuperClassMapper { + ErroneousSubclassWithAbstractSuperClassMapper INSTANCE = + Mappers.getMapper( ErroneousSubclassWithAbstractSuperClassMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + CarDto map(Car car); + + BikeDto map(Bike bike); + + @SubclassMapping( source = Car.class, target = CarDto.class ) + @SubclassMapping( source = Bike.class, target = BikeDto.class ) + VehicleDto map(AbstractVehicle vehicle); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Motorcycle.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Motorcycle.java new file mode 100644 index 0000000000..f52edfd71f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/Motorcycle.java @@ -0,0 +1,10 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +public class Motorcycle extends AbstractVehicle { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MotorcycleDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MotorcycleDto.java new file mode 100644 index 0000000000..cada5e8e65 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MotorcycleDto.java @@ -0,0 +1,10 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +public class MotorcycleDto extends VehicleDto { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/SubclassWithAbstractSuperClassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/SubclassWithAbstractSuperClassMapper.java new file mode 100644 index 0000000000..b0185f1f87 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/SubclassWithAbstractSuperClassMapper.java @@ -0,0 +1,26 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper( subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION ) +public interface SubclassWithAbstractSuperClassMapper { + SubclassWithAbstractSuperClassMapper INSTANCE = Mappers.getMapper( SubclassWithAbstractSuperClassMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + CarDto map(Car car); + + BikeDto map(Bike bike); + + @SubclassMapping( source = Car.class, target = CarDto.class ) + @SubclassMapping( source = Bike.class, target = BikeDto.class ) + VehicleDto map(AbstractVehicle vehicle); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleCollection.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleCollection.java new file mode 100644 index 0000000000..cbc847ff42 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleCollection.java @@ -0,0 +1,17 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollection { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleCollectionDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleCollectionDto.java new file mode 100644 index 0000000000..1ea4e018a5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleCollectionDto.java @@ -0,0 +1,17 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollectionDto { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleDto.java new file mode 100644 index 0000000000..5f38db68d2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/VehicleDto.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +public abstract class VehicleDto { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java new file mode 100644 index 0000000000..7f9f19a2ab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java @@ -0,0 +1,10 @@ +/* + * 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.subclassmapping.fixture; + +public abstract class AbstractParentSource { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java new file mode 100644 index 0000000000..3215aee644 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java @@ -0,0 +1,10 @@ +/* + * 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.subclassmapping.fixture; + +public abstract class AbstractParentTarget { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java new file mode 100644 index 0000000000..94dde481b0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java @@ -0,0 +1,10 @@ +/* + * 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.subclassmapping.fixture; + +public class ImplementedParentSource extends AbstractParentSource { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java new file mode 100644 index 0000000000..7f3d254e21 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java @@ -0,0 +1,10 @@ +/* + * 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.subclassmapping.fixture; + +public class ImplementedParentTarget extends AbstractParentTarget { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/InterfaceParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/InterfaceParentSource.java new file mode 100644 index 0000000000..b1ad034102 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/InterfaceParentSource.java @@ -0,0 +1,10 @@ +/* + * 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.subclassmapping.fixture; + +public interface InterfaceParentSource { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/InterfaceParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/InterfaceParentTarget.java new file mode 100644 index 0000000000..0e41f58b36 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/InterfaceParentTarget.java @@ -0,0 +1,10 @@ +/* + * 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.subclassmapping.fixture; + +public interface InterfaceParentTarget { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSource.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSource.java new file mode 100644 index 0000000000..abb4c0dfba --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSource.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.fixture; + +public class SubSource extends ImplementedParentSource implements InterfaceParentSource { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceOther.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceOther.java new file mode 100644 index 0000000000..6b83b78973 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceOther.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.fixture; + +public class SubSourceOther extends ImplementedParentSource implements InterfaceParentSource { + private final String finalValue; + + public SubSourceOther(String finalValue) { + this.finalValue = finalValue; + } + + public String getFinalValue() { + return finalValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTarget.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTarget.java new file mode 100644 index 0000000000..0227ec325d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTarget.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.fixture; + +public class SubTarget extends ImplementedParentTarget implements InterfaceParentTarget { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTargetOther.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTargetOther.java new file mode 100644 index 0000000000..f77c7c7a24 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTargetOther.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.fixture; + +public class SubTargetOther extends ImplementedParentTarget implements InterfaceParentTarget { + private final String finalValue; + + public SubTargetOther(String finalValue) { + this.finalValue = finalValue; + } + + public String getFinalValue() { + return finalValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java new file mode 100644 index 0000000000..8ef7908c30 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java @@ -0,0 +1,21 @@ +/* + * 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.subclassmapping.fixture; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.SubclassMapping; + +import static org.mapstruct.SubclassExhaustiveStrategy.RUNTIME_EXCEPTION; + +@Mapper +public interface SubclassAbstractMapper { + + @BeanMapping( subclassExhaustiveStrategy = RUNTIME_EXCEPTION ) + @SubclassMapping( source = SubSource.class, target = SubTarget.class ) + @SubclassMapping( source = SubSourceOther.class, target = SubTargetOther.class ) + AbstractParentTarget map(AbstractParentSource item); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java new file mode 100644 index 0000000000..14dc16d4ab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java @@ -0,0 +1,53 @@ +/* + * 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.subclassmapping.fixture; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +@WithClasses({ + AbstractParentSource.class, + AbstractParentTarget.class, + ImplementedParentSource.class, + ImplementedParentTarget.class, + InterfaceParentSource.class, + InterfaceParentTarget.class, + SubSource.class, + SubSourceOther.class, + SubTarget.class, + SubTargetOther.class, +}) +public class SubclassFixtureTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses( { + SubclassInterfaceMapper.class, + } ) + void subclassInterfaceParentFixture() { + generatedSource.addComparisonToFixtureFor( SubclassInterfaceMapper.class ); + } + + @ProcessorTest + @WithClasses( { + SubclassAbstractMapper.class, + } ) + void subclassAbstractParentFixture() { + generatedSource.addComparisonToFixtureFor( SubclassAbstractMapper.class ); + } + + @ProcessorTest + @WithClasses( { + SubclassImplementedMapper.class, + } ) + void subclassImplementationParentFixture() { + generatedSource.addComparisonToFixtureFor( SubclassImplementedMapper.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapper.java new file mode 100644 index 0000000000..0ebb2a26ff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapper.java @@ -0,0 +1,17 @@ +/* + * 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.subclassmapping.fixture; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassMapping; + +@Mapper +public interface SubclassImplementedMapper { + + @SubclassMapping( source = SubSource.class, target = SubTarget.class ) + @SubclassMapping( source = SubSourceOther.class, target = SubTargetOther.class ) + ImplementedParentTarget map(ImplementedParentSource item); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapper.java new file mode 100644 index 0000000000..ac7acaafdb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapper.java @@ -0,0 +1,19 @@ +/* + * 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.subclassmapping.fixture; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassMapping; + +import static org.mapstruct.SubclassExhaustiveStrategy.RUNTIME_EXCEPTION; + +@Mapper( subclassExhaustiveStrategy = RUNTIME_EXCEPTION ) +public interface SubclassInterfaceMapper { + + @SubclassMapping( source = SubSource.class, target = SubTarget.class ) + @SubclassMapping( source = SubSourceOther.class, target = SubTargetOther.class ) + InterfaceParentTarget map(InterfaceParentSource item); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Bike.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Bike.java new file mode 100644 index 0000000000..b414f733c4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Bike.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.mappables; + +public class Bike extends Vehicle { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/BikeDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/BikeDto.java new file mode 100644 index 0000000000..0eefc8f0c9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/BikeDto.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.mappables; + +public class BikeDto extends VehicleDto { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java new file mode 100644 index 0000000000..71b2446d34 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.mappables; + +public class Car extends Vehicle { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/CarDto.java new file mode 100644 index 0000000000..bfaba22685 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/CarDto.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.mappables; + +public class CarDto extends VehicleDto { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/HatchBack.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/HatchBack.java new file mode 100644 index 0000000000..d03a57aa30 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/HatchBack.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.mappables; + +public class HatchBack extends Car { + private String doorType; + + public String getDoorType() { + return doorType; + } + + public void setDoorType(String doorType) { + this.doorType = doorType; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/HatchBackDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/HatchBackDto.java new file mode 100644 index 0000000000..6ec4d90f1b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/HatchBackDto.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.mappables; + +public class HatchBackDto extends CarDto { + private String doorType; + + public String getDoorType() { + return doorType; + } + + public void setDoorType(String doorType) { + this.doorType = doorType; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Source.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Source.java new file mode 100644 index 0000000000..52f07943ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Source.java @@ -0,0 +1,42 @@ +/* + * 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.subclassmapping.mappables; + +public class Source { + private String property1; + private String property2; + private String property3; + + public Source(String prop1, String prop2, String prop3) { + setProperty1( prop1 ); + setProperty2( prop2 ); + setProperty3( prop3 ); + } + + public String getProperty1() { + return property1; + } + + public String getProperty2() { + return property2; + } + + public String getProperty3() { + return property3; + } + + public void setProperty1(String property1) { + this.property1 = property1; + } + + public void setProperty2(String property2) { + this.property2 = property2; + } + + public void setProperty3(String property3) { + this.property3 = property3; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/SourceSubclass.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/SourceSubclass.java new file mode 100644 index 0000000000..600d4f383e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/SourceSubclass.java @@ -0,0 +1,33 @@ +/* + * 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.subclassmapping.mappables; + +public class SourceSubclass extends Source { + private String subclassProperty; + private String target5; + + public SourceSubclass(String prop1, String prop2, String prop3, String subProp, String target5) { + super( prop1, prop2, prop3 ); + setSubclassProperty( subProp ); + setTarget5( target5 ); + } + + public String getSubclassProperty() { + return subclassProperty; + } + + public String getTarget5() { + return target5; + } + + public void setSubclassProperty(String subclassProperty) { + this.subclassProperty = subclassProperty; + } + + public void setTarget5(String target5) { + this.target5 = target5; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Target.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Target.java new file mode 100644 index 0000000000..cb5e3d6bc3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Target.java @@ -0,0 +1,54 @@ +/* + * 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.subclassmapping.mappables; + +public class Target { + private String target1; + private String target2; + private String target3; + private String target4; + private String target5; + + public String getTarget1() { + return target1; + } + + public String getTarget2() { + return target2; + } + + public String getTarget3() { + return target3; + } + + public String getTarget4() { + return target4; + } + + public String getTarget5() { + return target5; + } + + public void setTarget1(String target1) { + this.target1 = target1; + } + + public void setTarget2(String target2) { + this.target2 = target2; + } + + public void setTarget3(String target3) { + this.target3 = target3; + } + + public void setTarget4(String target4) { + this.target4 = target4; + } + + public void setTarget5(String target5) { + this.target5 = target5; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Vehicle.java new file mode 100644 index 0000000000..9c3406fe53 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Vehicle.java @@ -0,0 +1,27 @@ +/* + * 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.subclassmapping.mappables; + +public class Vehicle { + private String name; + private String vehicleManufacturingCompany; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVehicleManufacturingCompany() { + return vehicleManufacturingCompany; + } + + public void setVehicleManufacturingCompany(String vehicleManufacturingCompany) { + this.vehicleManufacturingCompany = vehicleManufacturingCompany; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleCollection.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleCollection.java new file mode 100644 index 0000000000..af4efbaef4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleCollection.java @@ -0,0 +1,17 @@ +/* + * 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.subclassmapping.mappables; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollection { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleCollectionDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleCollectionDto.java new file mode 100644 index 0000000000..9f06ee4b72 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleCollectionDto.java @@ -0,0 +1,17 @@ +/* + * 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.subclassmapping.mappables; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollectionDto { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleDto.java new file mode 100644 index 0000000000..fb6b40e93b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/VehicleDto.java @@ -0,0 +1,27 @@ +/* + * 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.subclassmapping.mappables; + +public class VehicleDto { + private String name; + private String maker; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMaker() { + return maker; + } + + public void setMaker(String maker) { + this.maker = maker; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java new file mode 100644 index 0000000000..6deed338c1 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java @@ -0,0 +1,59 @@ +/* + * 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.subclassmapping.fixture; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2021-09-12T14:37:10+0200", + comments = "version: , compiler: Eclipse JDT (Batch) 3.20.0.v20191203-2131, environment: Java 11.0.12 (Azul Systems, Inc.)" +) +public class SubclassAbstractMapperImpl implements SubclassAbstractMapper { + + @Override + public AbstractParentTarget map(AbstractParentSource item) { + if ( item == null ) { + return null; + } + + if (item instanceof SubSource) { + return subSourceToSubTarget( (SubSource) item ); + } + else if (item instanceof SubSourceOther) { + return subSourceOtherToSubTargetOther( (SubSourceOther) item ); + } + else { + throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for " + item.getClass()); + } + } + + protected SubTarget subSourceToSubTarget(SubSource subSource) { + if ( subSource == null ) { + return null; + } + + SubTarget subTarget = new SubTarget(); + + subTarget.setValue( subSource.getValue() ); + + return subTarget; + } + + protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSourceOther) { + if ( subSourceOther == null ) { + return null; + } + + String finalValue = null; + + finalValue = subSourceOther.getFinalValue(); + + SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + + return subTargetOther; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java new file mode 100644 index 0000000000..444b012078 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java @@ -0,0 +1,61 @@ +/* + * 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.subclassmapping.fixture; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2021-09-12T14:37:10+0200", + comments = "version: , compiler: Eclipse JDT (Batch) 3.20.0.v20191203-2131, environment: Java 11.0.12 (Azul Systems, Inc.)" +) +public class SubclassImplementedMapperImpl implements SubclassImplementedMapper { + + @Override + public ImplementedParentTarget map(ImplementedParentSource item) { + if ( item == null ) { + return null; + } + + if (item instanceof SubSource) { + return subSourceToSubTarget( (SubSource) item ); + } + else if (item instanceof SubSourceOther) { + return subSourceOtherToSubTargetOther( (SubSourceOther) item ); + } + else { + ImplementedParentTarget implementedParentTarget = new ImplementedParentTarget(); + + return implementedParentTarget; + } + } + + protected SubTarget subSourceToSubTarget(SubSource subSource) { + if ( subSource == null ) { + return null; + } + + SubTarget subTarget = new SubTarget(); + + subTarget.setValue( subSource.getValue() ); + + return subTarget; + } + + protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSourceOther) { + if ( subSourceOther == null ) { + return null; + } + + String finalValue = null; + + finalValue = subSourceOther.getFinalValue(); + + SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + + return subTargetOther; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java new file mode 100644 index 0000000000..8366d53ea9 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java @@ -0,0 +1,59 @@ +/* + * 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.subclassmapping.fixture; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2021-09-12T14:37:10+0200", + comments = "version: , compiler: Eclipse JDT (Batch) 3.20.0.v20191203-2131, environment: Java 11.0.12 (Azul Systems, Inc.)" +) +public class SubclassInterfaceMapperImpl implements SubclassInterfaceMapper { + + @Override + public InterfaceParentTarget map(InterfaceParentSource item) { + if ( item == null ) { + return null; + } + + if (item instanceof SubSource) { + return subSourceToSubTarget( (SubSource) item ); + } + else if (item instanceof SubSourceOther) { + return subSourceOtherToSubTargetOther( (SubSourceOther) item ); + } + else { + throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for " + item.getClass()); + } + } + + protected SubTarget subSourceToSubTarget(SubSource subSource) { + if ( subSource == null ) { + return null; + } + + SubTarget subTarget = new SubTarget(); + + subTarget.setValue( subSource.getValue() ); + + return subTarget; + } + + protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSourceOther) { + if ( subSourceOther == null ) { + return null; + } + + String finalValue = null; + + finalValue = subSourceOther.getFinalValue(); + + SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + + return subTargetOther; + } +} From e86c0faf04109a99ac4049ce9c510653ce973f11 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 16 Oct 2021 19:10:40 +0200 Subject: [PATCH 015/363] #2611 Scope org.eclipse.tycho:tycho-compiler-jdt under test Since the removal of the Eclipse Specific compiler workarounds in c2e803403027f3fae92bd15b0ba50ab7df5063e6 the org.eclipse.tycho:tycho-compiler-jdt dependency is no longer needed in the compile code, we only need it for tests --- processor/pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/processor/pom.xml b/processor/pom.xml index fcc6dbbab8..4f2476ceb0 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -44,8 +44,7 @@ org.eclipse.tycho tycho-compiler-jdt - provided - true + test From 935c03e822d0df436f374349d4272d3e62fe9874 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 16 Oct 2021 22:00:30 +0200 Subject: [PATCH 016/363] #2614 Do not use FQN when mapping Stream to Array --- .../java/org/mapstruct/ap/internal/model/common/Type.java | 7 +++++-- .../mapstruct/ap/internal/model/StreamMappingMethod.ftl | 2 +- .../org/mapstruct/ap/test/bugs/_1707/ConverterImpl.java | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index e13d7b9ddd..caa7a9c626 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -444,8 +444,11 @@ public boolean isToBeImported() { } private boolean shouldUseSimpleName() { - String fqn = notToBeImportedTypes.get( name ); - return this.qualifiedName.equals( fqn ); + // Using trimSimpleClassName since the same is used in the isToBeImported() + // to check whether notToBeImportedTypes contains it + String trimmedName = trimSimpleClassName( name ); + String fqn = notToBeImportedTypes.get( trimmedName ); + return trimSimpleClassName( this.qualifiedName ).equals( fqn ); } public Type erasure() { diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl index f9a7309b5d..ae48a3477a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl @@ -55,7 +55,7 @@ <#if needVarDefine> <#assign needVarDefine = false /> <#-- We create a null array which later will be directly assigned from the stream--> - ${resultElementType}[] ${resultName} = null; + <@includeModel object=resultElementType/>[] ${resultName} = null; <#elseif resultType.iterableType> <#if existingInstanceMapping> diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1707/ConverterImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1707/ConverterImpl.java index abe4a62a3d..b822f3a61d 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1707/ConverterImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1707/ConverterImpl.java @@ -36,12 +36,12 @@ public Set convert(Stream source) { } @Override - public org.mapstruct.ap.test.bugs._1707.Converter.Target[] convertArray(Stream source) { + public Target[] convertArray(Stream source) { if ( source == null ) { return null; } - org.mapstruct.ap.test.bugs._1707.Converter.Target[] targetTmp = null; + Target[] targetTmp = null; targetTmp = source.map( source1 -> convert( source1 ) ) .toArray( Target[]::new ); From 564455ee452b0b7b3862be969b69d501f5ea118b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 16 Oct 2021 18:50:04 +0200 Subject: [PATCH 017/363] #2596 Record components should have the highest priority for read accessors --- .../mapstruct/itest/records/MemberDto.java | 13 ++++++++ .../mapstruct/itest/records/MemberEntity.java | 31 +++++++++++++++++++ .../mapstruct/itest/records/MemberMapper.java | 24 ++++++++++++++ .../mapstruct/itest/records/RecordsTest.java | 22 +++++++++++++ .../ap/internal/model/common/Type.java | 28 ++++++++++++----- 5 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberDto.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberEntity.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberMapper.java diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberDto.java new file mode 100644 index 0000000000..bf3ce6cd94 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberDto.java @@ -0,0 +1,13 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records; + +/** + * @author Filip Hrisafov + */ +public record MemberDto(Boolean isActive, Boolean premium) { + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberEntity.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberEntity.java new file mode 100644 index 0000000000..50a7b1ce00 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberEntity.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records; + +/** + * @author Filip Hrisafov + */ +public class MemberEntity { + + private Boolean isActive; + private Boolean premium; + + public Boolean getIsActive() { + return isActive; + } + + public void setIsActive(Boolean active) { + isActive = active; + } + + public Boolean getPremium() { + return premium; + } + + public void setPremium(Boolean premium) { + this.premium = premium; + } +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberMapper.java new file mode 100644 index 0000000000..3460aeed69 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/MemberMapper.java @@ -0,0 +1,24 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface MemberMapper { + + MemberMapper INSTANCE = Mappers.getMapper( MemberMapper.class ); + + MemberEntity fromRecord(MemberDto record); + + MemberDto toRecord(MemberEntity entity); + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java index e3d055345e..e98df8797e 100644 --- a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java +++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java @@ -61,4 +61,26 @@ public void shouldMapIntoRecordWithList() { assertThat( carDto.wheelPositions() ) .containsExactly( "left" ); } + + @Test + public void shouldMapMemberRecord() { + MemberEntity member = MemberMapper.INSTANCE.fromRecord( new MemberDto( true, false ) ); + + assertThat( member ).isNotNull(); + assertThat( member.getIsActive() ).isTrue(); + assertThat( member.getPremium() ).isFalse(); + } + + @Test + public void shouldMapIntoMemberRecord() { + MemberEntity entity = new MemberEntity(); + entity.setIsActive( false ); + entity.setPremium( true ); + + MemberDto value = MemberMapper.INSTANCE.toRecord( entity ); + + assertThat( value ).isNotNull(); + assertThat( value.isActive() ).isEqualTo( false ); + assertThat( value.premium() ).isEqualTo( true ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index caa7a9c626..bba33a2764 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -576,14 +576,33 @@ public Type asRawType() { */ public Map getPropertyReadAccessors() { if ( readAccessors == null ) { - List getterList = filters.getterMethodsIn( getAllMethods() ); Map modifiableGetters = new LinkedHashMap<>(); + + Map recordAccessors = filters.recordAccessorsIn( getRecordComponents() ); + modifiableGetters.putAll( recordAccessors ); + + List getterList = filters.getterMethodsIn( getAllMethods() ); for ( Accessor getter : getterList ) { + String simpleName = getter.getSimpleName(); + if ( recordAccessors.containsKey( simpleName ) ) { + // If there is already a record accessor that contains the simple name + // then it means that the getter is actually a record component. + // In that case we need to ignore it. + // e.g. record component named isActive. + // The DefaultAccessorNamingStrategy will return active as property name, + // but the property name is isActive, since it is a record + continue; + } String propertyName = getPropertyName( getter ); + + if ( recordAccessors.containsKey( propertyName ) ) { + // If there is already a record accessor, the property needs to be ignored + continue; + } if ( modifiableGetters.containsKey( propertyName ) ) { // In the DefaultAccessorNamingStrategy, this can only be the case for Booleans: isFoo() and // getFoo(); The latter is preferred. - if ( !getter.getSimpleName().startsWith( "is" ) ) { + if ( !simpleName.startsWith( "is" ) ) { modifiableGetters.put( propertyName, getter ); } @@ -593,11 +612,6 @@ public Map getPropertyReadAccessors() { } } - Map recordAccessors = filters.recordAccessorsIn( getRecordComponents() ); - for ( Map.Entry recordEntry : recordAccessors.entrySet() ) { - modifiableGetters.putIfAbsent( recordEntry.getKey(), recordEntry.getValue() ); - } - List fieldsList = filters.fieldsIn( getAllFields() ); for ( Accessor field : fieldsList ) { String propertyName = getPropertyName( field ); From 80d26a1a9c0ce2a7c5312d9ed498ade8d5cdedc3 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 25 Oct 2021 08:22:26 +0200 Subject: [PATCH 018/363] #148, #1386, #2593 Only import top level classes Instead of importing all classes, inner classes will be used through their top level classes only. This also fixes the problem in #2593 since inner classes are no longer imported but used through their top classes --- .../itest/tests/MavenIntegrationTest.java | 5 + .../main/java/DefaultPackageObject.java | 97 +++++++++++++++++++ .../src/test/resources/defaultPackage/pom.xml | 21 ++++ .../test/java/DefaultPackageTest.java | 31 ++++++ .../ap/internal/model/common/Type.java | 69 ++++++++++++- .../test/imports/InnerClassesImportsTest.java | 9 +- .../imports/nested/NestedImportsTest.java | 54 +++++++++++ .../NestedSourceInOtherPackageMapper.java | 18 ++++ .../ap/test/imports/nested/Source.java | 48 +++++++++ .../nested/SourceInOtherPackageMapper.java | 18 ++++ .../ap/test/imports/nested/Target.java | 48 +++++++++ .../nested/TargetInOtherPackageMapper.java | 18 ++++ .../nested/other/SourceInOtherPackage.java | 48 +++++++++ .../nested/other/TargetInOtherPackage.java | 48 +++++++++ .../BeanWithInnerEnumMapperImpl.java | 48 +++++++++ .../innerclasses/InnerClassMapperImpl.java | 55 +++++++++++ .../NestedSourceInOtherPackageMapperImpl.java | 42 ++++++++ .../SourceInOtherPackageMapperImpl.java | 54 +++++++++++ .../TargetInOtherPackageMapperImpl.java | 54 +++++++++++ 19 files changed, 776 insertions(+), 9 deletions(-) create mode 100644 integrationtest/src/test/resources/defaultPackage/main/java/DefaultPackageObject.java create mode 100644 integrationtest/src/test/resources/defaultPackage/pom.xml create mode 100644 integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedImportsTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedSourceInOtherPackageMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/imports/nested/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/imports/nested/SourceInOtherPackageMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/imports/nested/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/imports/nested/TargetInOtherPackageMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/SourceInOtherPackage.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/TargetInOtherPackage.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/innerclasses/BeanWithInnerEnumMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/innerclasses/InnerClassMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/NestedSourceInOtherPackageMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/SourceInOtherPackageMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/TargetInOtherPackageMapperImpl.java diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 2ec0ba9aa9..59aa48b053 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -128,6 +128,11 @@ void kotlinDataTest() { void simpleTest() { } + // for issue #2593 + @ProcessorTest(baseDir = "defaultPackage") + void defaultPackageTest() { + } + @ProcessorTest(baseDir = "springTest") void springTest() { } diff --git a/integrationtest/src/test/resources/defaultPackage/main/java/DefaultPackageObject.java b/integrationtest/src/test/resources/defaultPackage/main/java/DefaultPackageObject.java new file mode 100644 index 0000000000..ba684850b4 --- /dev/null +++ b/integrationtest/src/test/resources/defaultPackage/main/java/DefaultPackageObject.java @@ -0,0 +1,97 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +public class DefaultPackageObject { + public enum CarType { + SEDAN, CAMPER, X4, TRUCK; + } + + static public class Car { + + private String make; + private int numberOfSeats; + private CarType type; + + public Car(String string, int numberOfSeats, CarType sedan) { + this.make = string; + this.numberOfSeats = numberOfSeats; + this.type = sedan; + } + + + public String getMake() { + return make; + } + + public void setMake(String make) { + this.make = make; + } + + public int getNumberOfSeats() { + return numberOfSeats; + } + + public void setNumberOfSeats(int numberOfSeats) { + this.numberOfSeats = numberOfSeats; + } + + public CarType getType() { + return type; + } + + public void setType(CarType type) { + this.type = type; + } + } + + static public class CarDto { + + private String make; + private int seatCount; + private String type; + + public String getMake() { + return make; + } + + public void setMake(String make) { + this.make = make; + } + + public int getSeatCount() { + return seatCount; + } + + public void setSeatCount(int seatCount) { + this.seatCount = seatCount; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + } + + + @Mapper + public interface CarMapper { + + CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); + + @Mapping(source = "numberOfSeats", target = "seatCount") + CarDto carToCarDto(Car car); + } +} diff --git a/integrationtest/src/test/resources/defaultPackage/pom.xml b/integrationtest/src/test/resources/defaultPackage/pom.xml new file mode 100644 index 0000000000..d72300f618 --- /dev/null +++ b/integrationtest/src/test/resources/defaultPackage/pom.xml @@ -0,0 +1,21 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + defaultPackage + jar + diff --git a/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java b/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java new file mode 100644 index 0000000000..ddc834cc8e --- /dev/null +++ b/integrationtest/src/test/resources/defaultPackage/test/java/DefaultPackageTest.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ + +public class DefaultPackageTest { + + @Test + public void shouldWorkCorrectlyInDefaultPackage() { + DefaultPackageObject.CarDto carDto = DefaultPackageObject.CarMapper.INSTANCE.carToCarDto( + new DefaultPackageObject.Car( + "Morris", + 5, + DefaultPackageObject.CarType.SEDAN + ) ); + + assertThat( carDto ).isNotNull(); + assertThat( carDto.getMake() ).isEqualTo( "Morris" ); + assertThat( carDto.getSeatCount() ).isEqualTo( 5 ); + assertThat( carDto.getType() ).isEqualTo( "SEDAN" ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index bba33a2764..00bcae371c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -5,9 +5,11 @@ */ package org.mapstruct.ap.internal.model.common; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -22,6 +24,7 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; @@ -71,9 +74,11 @@ public class Type extends ModelElement implements Comparable { private final ImplementationType implementationType; private final Type componentType; + private final Type topLevelType; private final String packageName; private final String name; + private final String nameWithTopLevelTypeName; private final String qualifiedName; private final boolean isInterface; @@ -172,6 +177,9 @@ public Type(TypeUtils typeUtils, ElementUtils elementUtils, TypeFactory typeFact this.filters = new Filters( accessorNaming, typeUtils, typeMirror ); this.loggingVerbose = loggingVerbose; + + this.topLevelType = topLevelType( this.typeElement, this.typeFactory ); + this.nameWithTopLevelTypeName = nameWithTopLevelTypeName( this.typeElement ); } //CHECKSTYLE:ON @@ -199,11 +207,23 @@ public String getName() { *

    * If the {@code java.time} variant is referred to first, the {@code java.time.LocalDateTime} will be imported * and the {@code org.joda} variant will be referred to with its FQN. + *

    + * If the type is nested and its top level type is to be imported + * then the name including its top level type will be returned. * - * @return Just the name if this {@link Type} will be imported, otherwise the fully-qualified name. + * @return Just the name if this {@link Type} will be imported, the name up to the top level {@link Type} + * (if the top level type is important, otherwise the fully-qualified name. */ public String createReferenceName() { - return isToBeImported() ? name : ( shouldUseSimpleName() ? name : qualifiedName ); + if ( isToBeImported() || shouldUseSimpleName() ) { + return name; + } + + if ( isTopLevelTypeToBeImported() && nameWithTopLevelTypeName != null ) { + return nameWithTopLevelTypeName; + } + + return qualifiedName; } public List getTypeParameters() { @@ -402,6 +422,10 @@ public Set getImportTypes() { result.addAll( componentType.getImportTypes() ); } + if ( topLevelType != null ) { + result.addAll( topLevelType.getImportTypes() ); + } + for ( Type parameter : typeParameters ) { result.addAll( parameter.getImportTypes() ); } @@ -413,6 +437,10 @@ public Set getImportTypes() { return result; } + protected boolean isTopLevelTypeToBeImported() { + return topLevelType != null && topLevelType.isToBeImported(); + } + /** * Whether this type is to be imported by means of an import statement in the currently generated source file * (it can be referenced in the generated source using its simple name) or not (referenced using the FQN). @@ -435,7 +463,7 @@ public boolean isToBeImported() { isToBeImported = true; } } - else { + else if ( typeElement == null || !typeElement.getNestingKind().isNested() ) { toBeImportedTypes.put( trimmedName, trimmedQualifiedName ); isToBeImported = true; } @@ -1492,4 +1520,39 @@ private String trimSimpleClassName(String className) { return trimmedClassName; } + private static String nameWithTopLevelTypeName(TypeElement element) { + if ( element == null ) { + return null; + } + if ( !element.getNestingKind().isNested() ) { + return element.getSimpleName().toString(); + } + + Deque elements = new ArrayDeque<>(); + elements.addFirst( element.getSimpleName() ); + Element parent = element.getEnclosingElement(); + while ( parent != null && parent.getKind() != ElementKind.PACKAGE ) { + elements.addFirst( parent.getSimpleName() ); + parent = parent.getEnclosingElement(); + } + + return String.join( ".", elements ); + } + + private static Type topLevelType(TypeElement typeElement, TypeFactory typeFactory) { + if ( typeElement == null || typeElement.getNestingKind() == NestingKind.TOP_LEVEL ) { + return null; + } + + Element parent = typeElement.getEnclosingElement(); + while ( parent != null ) { + if ( parent.getEnclosingElement() != null && + parent.getEnclosingElement().getKind() == ElementKind.PACKAGE ) { + break; + } + parent = parent.getEnclosingElement(); + } + return parent == null ? null : typeFactory.getType( parent.asType() ); + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/InnerClassesImportsTest.java b/processor/src/test/java/org/mapstruct/ap/test/imports/InnerClassesImportsTest.java index 91e5df6a27..abad5e4177 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/imports/InnerClassesImportsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/InnerClassesImportsTest.java @@ -14,7 +14,6 @@ import org.mapstruct.ap.test.imports.innerclasses.SourceWithInnerClass; import org.mapstruct.ap.test.imports.innerclasses.SourceWithInnerClass.SourceInnerClass; import org.mapstruct.ap.test.imports.innerclasses.TargetWithInnerClass; -import org.mapstruct.ap.test.imports.innerclasses.TargetWithInnerClass.TargetInnerClass; import org.mapstruct.ap.test.imports.innerclasses.TargetWithInnerClass.TargetInnerClass.TargetInnerInnerClass; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; @@ -47,8 +46,7 @@ public void mapperRequiresInnerClassImports() { assertThat( target ).isNotNull(); assertThat( target.getInnerClassMember().getValue() ).isEqualTo( 412 ); - generatedSource.forMapper( InnerClassMapper.class ).containsImportFor( SourceInnerClass.class ); - generatedSource.forMapper( InnerClassMapper.class ).containsImportFor( TargetInnerClass.class ); + generatedSource.addComparisonToFixtureFor( InnerClassMapper.class ); } @ProcessorTest @@ -61,8 +59,7 @@ public void mapperRequiresInnerInnerClassImports() { assertThat( target ).isNotNull(); assertThat( target.getValue() ).isEqualTo( 412 ); - generatedSource.forMapper( InnerClassMapper.class ).containsImportFor( SourceInnerClass.class ); - generatedSource.forMapper( InnerClassMapper.class ).containsImportFor( TargetInnerInnerClass.class ); + generatedSource.addComparisonToFixtureFor( InnerClassMapper.class ); } @ProcessorTest @@ -82,6 +79,6 @@ public void mapperRequiresInnerEnumImports() { assertThat( sourceAgain ).isNotNull(); assertThat( sourceAgain.getInnerEnum() ).isEqualTo( InnerEnum.A ); - generatedSource.forMapper( BeanWithInnerEnumMapper.class ).containsImportFor( InnerEnum.class ); + generatedSource.addComparisonToFixtureFor( BeanWithInnerEnumMapper.class ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedImportsTest.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedImportsTest.java new file mode 100644 index 0000000000..37c522e4cd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedImportsTest.java @@ -0,0 +1,54 @@ +/* + * 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.imports.nested; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.imports.nested.other.SourceInOtherPackage; +import org.mapstruct.ap.test.imports.nested.other.TargetInOtherPackage; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + SourceInOtherPackage.class, + TargetInOtherPackage.class, + Source.class, + Target.class +}) +@IssueKey("1386,148") +class NestedImportsTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses( { + SourceInOtherPackageMapper.class + } ) + void shouldGenerateNestedInnerClassesForSourceInOtherPackage() { + generatedSource.addComparisonToFixtureFor( SourceInOtherPackageMapper.class ); + } + + @ProcessorTest + @WithClasses( { + NestedSourceInOtherPackageMapper.class + } ) + void shouldGenerateCorrectImportsForTopLevelClassesFromOnlyNestedInnerClasses() { + generatedSource.addComparisonToFixtureFor( NestedSourceInOtherPackageMapper.class ); + } + + @ProcessorTest + @WithClasses( { + TargetInOtherPackageMapper.class + } ) + void shouldGenerateNestedInnerClassesForTargetInOtherPackage() { + generatedSource.addComparisonToFixtureFor( TargetInOtherPackageMapper.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedSourceInOtherPackageMapper.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedSourceInOtherPackageMapper.java new file mode 100644 index 0000000000..7f98737d1d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedSourceInOtherPackageMapper.java @@ -0,0 +1,18 @@ +/* + * 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.imports.nested; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.imports.nested.other.SourceInOtherPackage; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface NestedSourceInOtherPackageMapper { + + Target.Nested map(SourceInOtherPackage.Nested source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/Source.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/Source.java new file mode 100644 index 0000000000..2d2e9aa215 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/Source.java @@ -0,0 +1,48 @@ +/* + * 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.imports.nested; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private Nested value; + + public Nested getValue() { + return value; + } + + public void setValue(Nested value) { + this.value = value; + } + + public static class Nested { + + private Inner inner; + + public static class Inner { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public Inner getInner() { + return inner; + } + + public void setInner(Inner inner) { + this.inner = inner; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/SourceInOtherPackageMapper.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/SourceInOtherPackageMapper.java new file mode 100644 index 0000000000..7150823c1a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/SourceInOtherPackageMapper.java @@ -0,0 +1,18 @@ +/* + * 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.imports.nested; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.imports.nested.other.SourceInOtherPackage; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SourceInOtherPackageMapper { + + Target map(SourceInOtherPackage source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/Target.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/Target.java new file mode 100644 index 0000000000..78a50fcb15 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/Target.java @@ -0,0 +1,48 @@ +/* + * 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.imports.nested; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private Nested value; + + public Nested getValue() { + return value; + } + + public void setValue(Nested value) { + this.value = value; + } + + public static class Nested { + + private Inner inner; + + public static class Inner { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public Inner getInner() { + return inner; + } + + public void setInner(Inner inner) { + this.inner = inner; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/TargetInOtherPackageMapper.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/TargetInOtherPackageMapper.java new file mode 100644 index 0000000000..a40d52eb1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/TargetInOtherPackageMapper.java @@ -0,0 +1,18 @@ +/* + * 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.imports.nested; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.imports.nested.other.TargetInOtherPackage; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface TargetInOtherPackageMapper { + + TargetInOtherPackage map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/SourceInOtherPackage.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/SourceInOtherPackage.java new file mode 100644 index 0000000000..f6646c7160 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/SourceInOtherPackage.java @@ -0,0 +1,48 @@ +/* + * 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.imports.nested.other; + +/** + * @author Filip Hrisafov + */ +public class SourceInOtherPackage { + + private Nested value; + + public Nested getValue() { + return value; + } + + public void setValue(Nested value) { + this.value = value; + } + + public static class Nested { + + private Inner inner; + + public static class Inner { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public Inner getInner() { + return inner; + } + + public void setInner(Inner inner) { + this.inner = inner; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/TargetInOtherPackage.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/TargetInOtherPackage.java new file mode 100644 index 0000000000..da6b596c06 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/TargetInOtherPackage.java @@ -0,0 +1,48 @@ +/* + * 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.imports.nested.other; + +/** + * @author Filip Hrisafov + */ +public class TargetInOtherPackage { + + private Nested value; + + public Nested getValue() { + return value; + } + + public void setValue(Nested value) { + this.value = value; + } + + public static class Nested { + + private Inner inner; + + public static class Inner { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public Inner getInner() { + return inner; + } + + public void setInner(Inner inner) { + this.inner = inner; + } + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/innerclasses/BeanWithInnerEnumMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/innerclasses/BeanWithInnerEnumMapperImpl.java new file mode 100644 index 0000000000..1428fabd8f --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/innerclasses/BeanWithInnerEnumMapperImpl.java @@ -0,0 +1,48 @@ +/* + * 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.imports.innerclasses; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2021-10-16T21:06:53+0200", + comments = "version: , compiler: javac, environment: Java 17 (Oracle Corporation)" +) +public class BeanWithInnerEnumMapperImpl implements BeanWithInnerEnumMapper { + + @Override + public BeanWithInnerEnum fromFacade(BeanFacade beanFacade) { + if ( beanFacade == null ) { + return null; + } + + BeanWithInnerEnum beanWithInnerEnum = new BeanWithInnerEnum(); + + beanWithInnerEnum.setTest( beanFacade.getTest() ); + if ( beanFacade.getInnerEnum() != null ) { + beanWithInnerEnum.setInnerEnum( Enum.valueOf( BeanWithInnerEnum.InnerEnum.class, beanFacade.getInnerEnum() ) ); + } + + return beanWithInnerEnum; + } + + @Override + public BeanFacade toFacade(BeanWithInnerEnum beanWithInnerEnum) { + if ( beanWithInnerEnum == null ) { + return null; + } + + BeanFacade beanFacade = new BeanFacade(); + + beanFacade.setTest( beanWithInnerEnum.getTest() ); + if ( beanWithInnerEnum.getInnerEnum() != null ) { + beanFacade.setInnerEnum( beanWithInnerEnum.getInnerEnum().name() ); + } + + return beanFacade; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/innerclasses/InnerClassMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/innerclasses/InnerClassMapperImpl.java new file mode 100644 index 0000000000..bd85a7abee --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/innerclasses/InnerClassMapperImpl.java @@ -0,0 +1,55 @@ +/* + * 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.imports.innerclasses; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2021-10-16T21:06:53+0200", + comments = "version: , compiler: javac, environment: Java 17 (Oracle Corporation)" +) +public class InnerClassMapperImpl implements InnerClassMapper { + + @Override + public TargetWithInnerClass sourceToTarget(SourceWithInnerClass source) { + if ( source == null ) { + return null; + } + + TargetWithInnerClass targetWithInnerClass = new TargetWithInnerClass(); + + targetWithInnerClass.setInnerClassMember( innerSourceToInnerTarget( source.getInnerClassMember() ) ); + + return targetWithInnerClass; + } + + @Override + public TargetWithInnerClass.TargetInnerClass innerSourceToInnerTarget(SourceWithInnerClass.SourceInnerClass source) { + if ( source == null ) { + return null; + } + + TargetWithInnerClass.TargetInnerClass targetInnerClass = new TargetWithInnerClass.TargetInnerClass(); + + targetInnerClass.setValue( source.getValue() ); + + return targetInnerClass; + } + + @Override + public TargetWithInnerClass.TargetInnerClass.TargetInnerInnerClass innerSourceToInnerInnerTarget(SourceWithInnerClass.SourceInnerClass source) { + if ( source == null ) { + return null; + } + + TargetWithInnerClass.TargetInnerClass.TargetInnerInnerClass targetInnerInnerClass = new TargetWithInnerClass.TargetInnerClass.TargetInnerInnerClass(); + + targetInnerInnerClass.setValue( source.getValue() ); + + return targetInnerInnerClass; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/NestedSourceInOtherPackageMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/NestedSourceInOtherPackageMapperImpl.java new file mode 100644 index 0000000000..53dd91b9e2 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/NestedSourceInOtherPackageMapperImpl.java @@ -0,0 +1,42 @@ +/* + * 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.imports.nested; + +import javax.annotation.processing.Generated; +import org.mapstruct.ap.test.imports.nested.other.SourceInOtherPackage; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2021-10-24T19:26:14+0200", + comments = "version: , compiler: javac, environment: Java 1.8.0_161 (Oracle Corporation)" +) +public class NestedSourceInOtherPackageMapperImpl implements NestedSourceInOtherPackageMapper { + + @Override + public Target.Nested map(SourceInOtherPackage.Nested source) { + if ( source == null ) { + return null; + } + + Target.Nested nested = new Target.Nested(); + + nested.setInner( innerToInner( source.getInner() ) ); + + return nested; + } + + protected Target.Nested.Inner innerToInner(SourceInOtherPackage.Nested.Inner inner) { + if ( inner == null ) { + return null; + } + + Target.Nested.Inner inner1 = new Target.Nested.Inner(); + + inner1.setValue( inner.getValue() ); + + return inner1; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/SourceInOtherPackageMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/SourceInOtherPackageMapperImpl.java new file mode 100644 index 0000000000..a167518464 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/SourceInOtherPackageMapperImpl.java @@ -0,0 +1,54 @@ +/* + * 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.imports.nested; + +import javax.annotation.Generated; +import org.mapstruct.ap.test.imports.nested.other.SourceInOtherPackage; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2018-08-19T19:13:35+0200", + comments = "version: , compiler: javac, environment: Java 1.8.0_161 (Oracle Corporation)" +) +public class SourceInOtherPackageMapperImpl implements SourceInOtherPackageMapper { + + @Override + public Target map(SourceInOtherPackage source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + target.setValue( nestedToNested( source.getValue() ) ); + + return target; + } + + protected Target.Nested.Inner innerToInner(SourceInOtherPackage.Nested.Inner inner) { + if ( inner == null ) { + return null; + } + + Target.Nested.Inner inner1 = new Target.Nested.Inner(); + + inner1.setValue( inner.getValue() ); + + return inner1; + } + + protected Target.Nested nestedToNested(SourceInOtherPackage.Nested nested) { + if ( nested == null ) { + return null; + } + + Target.Nested nested1 = new Target.Nested(); + + nested1.setInner( innerToInner( nested.getInner() ) ); + + return nested1; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/TargetInOtherPackageMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/TargetInOtherPackageMapperImpl.java new file mode 100644 index 0000000000..b1e5d09f40 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/imports/nested/TargetInOtherPackageMapperImpl.java @@ -0,0 +1,54 @@ +/* + * 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.imports.nested; + +import javax.annotation.processing.Generated; +import org.mapstruct.ap.test.imports.nested.other.TargetInOtherPackage; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2021-10-24T13:45:02+0200", + comments = "version: , compiler: javac, environment: Java 1.8.0_161 (Oracle Corporation)" +) +public class TargetInOtherPackageMapperImpl implements TargetInOtherPackageMapper { + + @Override + public TargetInOtherPackage map(Source source) { + if ( source == null ) { + return null; + } + + TargetInOtherPackage targetInOtherPackage = new TargetInOtherPackage(); + + targetInOtherPackage.setValue( nestedToNested( source.getValue() ) ); + + return targetInOtherPackage; + } + + protected TargetInOtherPackage.Nested.Inner innerToInner(Source.Nested.Inner inner) { + if ( inner == null ) { + return null; + } + + TargetInOtherPackage.Nested.Inner inner1 = new TargetInOtherPackage.Nested.Inner(); + + inner1.setValue( inner.getValue() ); + + return inner1; + } + + protected TargetInOtherPackage.Nested nestedToNested(Source.Nested nested) { + if ( nested == null ) { + return null; + } + + TargetInOtherPackage.Nested nested1 = new TargetInOtherPackage.Nested(); + + nested1.setInner( innerToInner( nested.getInner() ) ); + + return nested1; + } +} From e32fc8c283085da5080044c9a41b1dd1e71198ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20P=C3=B6ttker?= Date: Mon, 25 Oct 2021 12:20:11 +0200 Subject: [PATCH 019/363] #2351 NullValueMappingStrategy for Maps and Iterables With two new parameters for Mapper and MapperConfig, it is now possible to override the nullValueMappingStrategy specifically for MapMapping and IterableMapping. --- core/src/main/java/org/mapstruct/Mapper.java | 26 +++ .../main/java/org/mapstruct/MapperConfig.java | 22 +++ .../internal/model/source/DefaultOptions.java | 8 + .../model/source/DelegatingOptions.java | 8 + .../model/source/IterableMappingOptions.java | 8 +- .../model/source/MapMappingOptions.java | 2 +- .../model/source/MapperConfigOptions.java | 22 +++ .../internal/model/source/MapperOptions.java | 22 +++ .../ap/test/nullvaluemapping/CarMapper.java | 15 +- .../CarMapperIterableSettingOnConfig.java | 35 ++++ .../CarMapperIterableSettingOnMapper.java | 40 +++++ .../CarMapperMapSettingOnConfig.java | 35 ++++ .../CarMapperMapSettingOnMapper.java | 40 +++++ .../CarMapperSettingOnConfig.java | 9 +- .../CarMapperSettingOnMapper.java | 9 +- .../CentralIterableMappingConfig.java | 17 ++ .../CentralMapMappingConfig.java | 17 ++ .../NullValueMappingTest.java | 150 +++++++++++++++++- 18 files changed, 453 insertions(+), 32 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralIterableMappingConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralMapMappingConfig.java diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index 9ddd9bff5c..26442e4ff0 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -203,6 +203,32 @@ */ NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + /** + * The strategy to be applied when {@code null} is passed as source argument value to an {@link IterableMapping} of + * this mapper. If unset, the strategy set with {@link #nullValueMappingStrategy()} will be applied. If neither + * strategy is configured, the strategy given via {@link MapperConfig#nullValueIterableMappingStrategy()} will be + * applied, using {@link NullValueMappingStrategy#RETURN_NULL} by default. + * + * @since 1.5 + * + * @return The strategy to be applied when {@code null} is passed as source value to an {@link IterableMapping} of + * this mapper. + */ + NullValueMappingStrategy nullValueIterableMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + + /** + * The strategy to be applied when {@code null} is passed as source argument value to a {@link MapMapping} of this + * mapper. If unset, the strategy set with {@link #nullValueMappingStrategy()} will be applied. If neither strategy + * is configured, the strategy given via {@link MapperConfig#nullValueMapMappingStrategy()} will be applied, using + * {@link NullValueMappingStrategy#RETURN_NULL} by default. + * + * @since 1.5 + * + * @return The strategy to be applied when {@code null} is passed as source value to a {@link MapMapping} of this + * mapper. + */ + NullValueMappingStrategy nullValueMapMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + /** * The strategy to be applied when a source bean property is {@code null} or not present. If no strategy is * configured, the strategy given via {@link MapperConfig#nullValuePropertyMappingStrategy()} will be applied, diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index 877495867c..ce239322a2 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -177,6 +177,28 @@ */ NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + /** + * The strategy to be applied when {@code null} is passed as source argument value to an {@link IterableMapping}. + * If no strategy is configured, the strategy given via {@link #nullValueMappingStrategy()} will be applied, using + * {@link NullValueMappingStrategy#RETURN_NULL} by default. + * + * @since 1.5 + * + * @return The strategy to be applied when {@code null} is passed as source value to an {@link IterableMapping}. + */ + NullValueMappingStrategy nullValueIterableMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + + /** + * The strategy to be applied when {@code null} is passed as source argument value to a {@link MapMapping}. + * If no strategy is configured, the strategy given via {@link #nullValueMappingStrategy()} will be applied, using + * {@link NullValueMappingStrategy#RETURN_NULL} by default. + * + * @since 1.5 + * + * @return The strategy to be applied when {@code null} is passed as source value to a {@link MapMapping}. + */ + NullValueMappingStrategy nullValueMapMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + /** * The strategy to be applied when a source bean property is {@code null} or not present. If no strategy is * configured, {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index 5a7161b4c8..1258707e43 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -124,6 +124,14 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { return SubclassExhaustiveStrategyGem.valueOf( mapper.subclassExhaustiveStrategy().getDefaultValue() ); } + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { + return NullValueMappingStrategyGem.valueOf( mapper.nullValueIterableMappingStrategy().getDefaultValue() ); + } + + public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { + return NullValueMappingStrategyGem.valueOf( mapper.nullValueMapMappingStrategy().getDefaultValue() ); + } + public BuilderGem getBuilder() { // TODO: I realized this is not correct, however it needs to be null in order to keep downward compatibility // but assuming a default @Builder will make testcases fail. Not having a default means that you need to diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java index b2b36b68a6..12f610f1ac 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java @@ -102,6 +102,14 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { return next.getSubclassExhaustiveStrategy(); } + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { + return next.getNullValueIterableMappingStrategy(); + } + + public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { + return next.getNullValueMapMappingStrategy(); + } + public BuilderGem getBuilder() { return next.getBuilder(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java index 40b5b25353..4affcd93e2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java @@ -30,11 +30,11 @@ public class IterableMappingOptions extends DelegatingOptions { private final IterableMappingGem iterableMapping; public static IterableMappingOptions fromGem(IterableMappingGem iterableMapping, - MapperOptions mappperOptions, ExecutableElement method, + MapperOptions mapperOptions, ExecutableElement method, FormattingMessager messager, TypeUtils typeUtils) { if ( iterableMapping == null || !isConsistent( iterableMapping, method, messager ) ) { - IterableMappingOptions options = new IterableMappingOptions( null, null, null, mappperOptions ); + IterableMappingOptions options = new IterableMappingOptions( null, null, null, mapperOptions ); return options; } @@ -54,7 +54,7 @@ public static IterableMappingOptions fromGem(IterableMappingGem iterableMapping, ); IterableMappingOptions options = - new IterableMappingOptions( formatting, selection, iterableMapping, mappperOptions ); + new IterableMappingOptions( formatting, selection, iterableMapping, mapperOptions ); return options; } @@ -99,7 +99,7 @@ public NullValueMappingStrategyGem getNullValueMappingStrategy() { .filter( GemValue::hasValue ) .map( GemValue::getValue ) .map( NullValueMappingStrategyGem::valueOf ) - .orElse( next().getNullValueMappingStrategy() ); + .orElse( next().getNullValueIterableMappingStrategy() ); } public MappingControl getElementMappingControl(ElementUtils elementUtils) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java index c35a8e363d..ca8f1ea544 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java @@ -144,7 +144,7 @@ public NullValueMappingStrategyGem getNullValueMappingStrategy() { .filter( GemValue::hasValue ) .map( GemValue::getValue ) .map( NullValueMappingStrategyGem::valueOf ) - .orElse( next().getNullValueMappingStrategy() ); + .orElse( next().getNullValueMapMappingStrategy() ); } public MappingControl getKeyMappingControl(ElementUtils elementUtils) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java index 5288ab1304..623b97bc78 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -134,6 +134,28 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { next().getSubclassExhaustiveStrategy(); } + @Override + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { + if ( mapperConfig.nullValueIterableMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapperConfig.nullValueIterableMappingStrategy().get() ); + } + if ( mapperConfig.nullValueMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapperConfig.nullValueMappingStrategy().get() ); + } + return next().getNullValueIterableMappingStrategy(); + } + + @Override + public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { + if ( mapperConfig.nullValueMapMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapperConfig.nullValueMapMappingStrategy().get() ); + } + if ( mapperConfig.nullValueMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapperConfig.nullValueMappingStrategy().get() ); + } + return next().getNullValueMapMappingStrategy(); + } + @Override public BuilderGem getBuilder() { return mapperConfig.builder().hasValue() ? mapperConfig.builder().get() : next().getBuilder(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java index 86aae34052..3261205116 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -163,6 +163,28 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { next().getSubclassExhaustiveStrategy(); } + @Override + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { + if ( mapper.nullValueIterableMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapper.nullValueIterableMappingStrategy().get() ); + } + if ( mapper.nullValueMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapper.nullValueMappingStrategy().get() ); + } + return next().getNullValueIterableMappingStrategy(); + } + + @Override + public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { + if ( mapper.nullValueMapMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapper.nullValueMapMappingStrategy().get() ); + } + if ( mapper.nullValueMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapper.nullValueMappingStrategy().get() ); + } + return next().getNullValueMapMappingStrategy(); + } + @Override public BuilderGem getBuilder() { return mapper.builder().hasValue() ? mapper.builder().get() : next().getBuilder(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapper.java index d087d5ef4a..26e3f30e6b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapper.java @@ -16,7 +16,6 @@ import org.mapstruct.MapMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Mappings; import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; import org.mapstruct.ap.test.nullvaluemapping._target.DriverAndCarDto; import org.mapstruct.ap.test.nullvaluemapping.source.Car; @@ -29,18 +28,14 @@ public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); @BeanMapping(nullValueMappingStrategy = RETURN_DEFAULT) - @Mappings({ - @Mapping(target = "seatCount", source = "numberOfSeats"), - @Mapping(target = "model", constant = "ModelT"), - @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") - }) + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") CarDto carToCarDto(Car car); @BeanMapping(nullValueMappingStrategy = RETURN_DEFAULT) - @Mappings({ - @Mapping(target = "seatCount", source = "car.numberOfSeats"), - @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") - }) + @Mapping(target = "seatCount", source = "car.numberOfSeats") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") CarDto carToCarDto(Car car, String model); @IterableMapping(nullValueMappingStrategy = RETURN_DEFAULT) diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnConfig.java new file mode 100644 index 0000000000..8fd9447073 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnConfig.java @@ -0,0 +1,35 @@ +/* + * 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.nullvaluemapping; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.mapstruct.IterableMapping; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +@Mapper(imports = UUID.class, config = CentralIterableMappingConfig.class) +public interface CarMapperIterableSettingOnConfig { + + CarMapperIterableSettingOnConfig INSTANCE = Mappers.getMapper( CarMapperIterableSettingOnConfig.class ); + + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") + CarDto carToCarDto(Car car); + + @IterableMapping(dateFormat = "dummy") + List carsToCarDtos(List cars); + + @MapMapping(valueDateFormat = "dummy") + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnMapper.java new file mode 100644 index 0000000000..0e1e123c52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnMapper.java @@ -0,0 +1,40 @@ +/* + * 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.nullvaluemapping; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.mapstruct.IterableMapping; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +@Mapper( + imports = UUID.class, + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_NULL +) +public interface CarMapperIterableSettingOnMapper { + + CarMapperIterableSettingOnMapper INSTANCE = Mappers.getMapper( CarMapperIterableSettingOnMapper.class ); + + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") + CarDto carToCarDto(Car car); + + @IterableMapping(dateFormat = "dummy") + List carsToCarDtos(List cars); + + @MapMapping(valueDateFormat = "dummy") + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnConfig.java new file mode 100644 index 0000000000..47f0f34f63 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnConfig.java @@ -0,0 +1,35 @@ +/* + * 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.nullvaluemapping; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.mapstruct.IterableMapping; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +@Mapper(imports = UUID.class, config = CentralMapMappingConfig.class) +public interface CarMapperMapSettingOnConfig { + + CarMapperMapSettingOnConfig INSTANCE = Mappers.getMapper( CarMapperMapSettingOnConfig.class ); + + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") + CarDto carToCarDto(Car car); + + @IterableMapping(dateFormat = "dummy") + List carsToCarDtos(List cars); + + @MapMapping(valueDateFormat = "dummy") + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnMapper.java new file mode 100644 index 0000000000..0caad062c5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnMapper.java @@ -0,0 +1,40 @@ +/* + * 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.nullvaluemapping; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.mapstruct.IterableMapping; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +@Mapper( + imports = UUID.class, + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + nullValueMapMappingStrategy = NullValueMappingStrategy.RETURN_NULL +) +public interface CarMapperMapSettingOnMapper { + + CarMapperMapSettingOnMapper INSTANCE = Mappers.getMapper( CarMapperMapSettingOnMapper.class ); + + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") + CarDto carToCarDto(Car car); + + @IterableMapping(dateFormat = "dummy") + List carsToCarDtos(List cars); + + @MapMapping(valueDateFormat = "dummy") + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnConfig.java index f8f15a3e77..8e4ca2dac6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnConfig.java @@ -13,7 +13,6 @@ import org.mapstruct.MapMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Mappings; import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; import org.mapstruct.ap.test.nullvaluemapping.source.Car; @@ -24,11 +23,9 @@ public interface CarMapperSettingOnConfig { CarMapperSettingOnConfig INSTANCE = Mappers.getMapper( CarMapperSettingOnConfig.class ); - @Mappings({ - @Mapping(target = "seatCount", source = "numberOfSeats"), - @Mapping(target = "model", constant = "ModelT"), - @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") - }) + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") CarDto carToCarDto(Car car); @IterableMapping(dateFormat = "dummy") diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnMapper.java index c3f7d5f2c7..2306b3add4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnMapper.java @@ -13,7 +13,6 @@ import org.mapstruct.MapMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Mappings; import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; import org.mapstruct.ap.test.nullvaluemapping.source.Car; @@ -24,11 +23,9 @@ public interface CarMapperSettingOnMapper { CarMapperSettingOnMapper INSTANCE = Mappers.getMapper( CarMapperSettingOnMapper.class ); - @Mappings({ - @Mapping(target = "seatCount", source = "numberOfSeats"), - @Mapping(target = "model", constant = "ModelT"), - @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") - }) + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") CarDto carToCarDto(Car car); @IterableMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralIterableMappingConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralIterableMappingConfig.java new file mode 100644 index 0000000000..559c3520a9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralIterableMappingConfig.java @@ -0,0 +1,17 @@ +/* + * 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.nullvaluemapping; + +import org.mapstruct.MapperConfig; +import org.mapstruct.NullValueMappingStrategy; + +@MapperConfig( + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_NULL +) +public class CentralIterableMappingConfig { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralMapMappingConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralMapMappingConfig.java new file mode 100644 index 0000000000..7737e07de6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralMapMappingConfig.java @@ -0,0 +1,17 @@ +/* + * 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.nullvaluemapping; + +import org.mapstruct.MapperConfig; +import org.mapstruct.NullValueMappingStrategy; + +@MapperConfig( + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + nullValueMapMappingStrategy = NullValueMappingStrategy.RETURN_NULL +) +public class CentralMapMappingConfig { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMappingTest.java index 4bd9324a9a..a027acaba3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMappingTest.java @@ -33,8 +33,14 @@ DriverAndCarDto.class, CarMapper.class, CarMapperSettingOnMapper.class, + CarMapperIterableSettingOnMapper.class, + CarMapperMapSettingOnMapper.class, CentralConfig.class, - CarMapperSettingOnConfig.class + CarMapperSettingOnConfig.class, + CentralIterableMappingConfig.class, + CarMapperIterableSettingOnConfig.class, + CentralMapMappingConfig.class, + CarMapperMapSettingOnConfig.class, }) public class NullValueMappingTest { @@ -161,8 +167,7 @@ public void shouldMapIterableWithNullArgOnMapper() { List carDtos = CarMapperSettingOnMapper.INSTANCE.carsToCarDtos( null ); //then - assertThat( carDtos ).isNotNull(); - assertThat( carDtos.isEmpty() ).isTrue(); + assertThat( carDtos ).isEmpty(); } @ProcessorTest @@ -175,6 +180,74 @@ public void shouldMapMapWithNullArgOnMapper() { assertThat( carDtoMap ).isNull(); } + @ProcessorTest + public void shouldMapExpressionAndConstantRegardlessOfIterableNullArgOnMapper() { + + //when + CarDto carDto = CarMapperIterableSettingOnMapper.INSTANCE.carToCarDto( null ); + + //then + assertThat( carDto ).isNotNull(); + assertThat( carDto.getMake() ).isNull(); + assertThat( carDto.getSeatCount() ).isEqualTo( 0 ); + assertThat( carDto.getModel() ).isEqualTo( "ModelT" ); + assertThat( carDto.getCatalogId() ).isNotEmpty(); + } + + @ProcessorTest + public void shouldMapIterableToNullWithIterableNullArgOnMapper() { + + //when + List carDtos = CarMapperIterableSettingOnMapper.INSTANCE.carsToCarDtos( null ); + + //then + assertThat( carDtos ).isNull(); + } + + @ProcessorTest + public void shouldMapMapRegardlessOfIterableNullArgOnMapper() { + + //when + Map carDtoMap = CarMapperIterableSettingOnMapper.INSTANCE.carsToCarDtoMap( null ); + + //then + assertThat( carDtoMap ).isEmpty(); + } + + @ProcessorTest + public void shouldMapExpressionAndConstantRegardlessMapNullArgOnMapper() { + + //when + CarDto carDto = CarMapperMapSettingOnMapper.INSTANCE.carToCarDto( null ); + + //then + assertThat( carDto ).isNotNull(); + assertThat( carDto.getMake() ).isNull(); + assertThat( carDto.getSeatCount() ).isEqualTo( 0 ); + assertThat( carDto.getModel() ).isEqualTo( "ModelT" ); + assertThat( carDto.getCatalogId() ).isNotEmpty(); + } + + @ProcessorTest + public void shouldMapIterableRegardlessOfMapNullArgOnMapper() { + + //when + List carDtos = CarMapperMapSettingOnMapper.INSTANCE.carsToCarDtos( null ); + + //then + assertThat( carDtos ).isEmpty(); + } + + @ProcessorTest + public void shouldMapMapToWithMapNullArgOnMapper() { + + //when + Map carDtoMap = CarMapperMapSettingOnMapper.INSTANCE.carsToCarDtoMap( null ); + + //then + assertThat( carDtoMap ).isNull(); + } + @ProcessorTest public void shouldMapExpressionAndConstantRegardlessNullArgOnConfig() { @@ -196,8 +269,7 @@ public void shouldMapIterableWithNullArgOnConfig() { List carDtos = CarMapperSettingOnConfig.INSTANCE.carsToCarDtos( null ); //then - assertThat( carDtos ).isNotNull(); - assertThat( carDtos.isEmpty() ).isTrue(); + assertThat( carDtos ).isEmpty(); } @ProcessorTest @@ -210,6 +282,74 @@ public void shouldMapMapWithNullArgOnConfig() { assertThat( carDtoMap ).isNull(); } + @ProcessorTest + public void shouldMapExpressionAndConstantRegardlessOfIterableNullArgOnConfig() { + + //when + CarDto carDto = CarMapperIterableSettingOnConfig.INSTANCE.carToCarDto( null ); + + //then + assertThat( carDto ).isNotNull(); + assertThat( carDto.getMake() ).isNull(); + assertThat( carDto.getSeatCount() ).isEqualTo( 0 ); + assertThat( carDto.getModel() ).isEqualTo( "ModelT" ); + assertThat( carDto.getCatalogId() ).isNotEmpty(); + } + + @ProcessorTest + public void shouldMapIterableToNullWithIterableNullArgOnConfig() { + + //when + List carDtos = CarMapperIterableSettingOnConfig.INSTANCE.carsToCarDtos( null ); + + //then + assertThat( carDtos ).isNull(); + } + + @ProcessorTest + public void shouldMapMapRegardlessOfIterableNullArgOnConfig() { + + //when + Map carDtoMap = CarMapperIterableSettingOnConfig.INSTANCE.carsToCarDtoMap( null ); + + //then + assertThat( carDtoMap ).isEmpty(); + } + + @ProcessorTest + public void shouldMapExpressionAndConstantRegardlessOfMapNullArgOnConfig() { + + //when + CarDto carDto = CarMapperMapSettingOnConfig.INSTANCE.carToCarDto( null ); + + //then + assertThat( carDto ).isNotNull(); + assertThat( carDto.getMake() ).isNull(); + assertThat( carDto.getSeatCount() ).isEqualTo( 0 ); + assertThat( carDto.getModel() ).isEqualTo( "ModelT" ); + assertThat( carDto.getCatalogId() ).isNotEmpty(); + } + + @ProcessorTest + public void shouldMapIterableRegardlessOfMapNullArgOnConfig() { + + //when + List carDtos = CarMapperMapSettingOnConfig.INSTANCE.carsToCarDtos( null ); + + //then + assertThat( carDtos ).isEmpty(); + } + + @ProcessorTest + public void shouldMapMapToNullWithMapNullArgOnConfig() { + + //when + Map carDtoMap = CarMapperMapSettingOnConfig.INSTANCE.carsToCarDtoMap( null ); + + //then + assertThat( carDtoMap ).isNull(); + } + @ProcessorTest public void shouldApplyConfiguredStrategyForMethodWithSeveralSourceParams() { //when From ca2529f862c2e439a1341d2f3b85189513c140ad Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sun, 31 Oct 2021 16:46:35 +0100 Subject: [PATCH 020/363] #598: Errors/Warnings now end up in the @Mapper annotated class. (#2634) #598: Errors/Warnings now end up in the @Mapper annotated class. Co-authored-by: Ben Zegveld --- .../MapperAnnotatedFormattingMessenger.java | 136 ++++++++++++++++++ .../processor/MapperCreationProcessor.java | 3 +- .../mapstruct/ap/internal/util/Message.java | 2 + .../AbstractMapper.java | 11 ++ .../ErroneousMapper1.java | 13 ++ .../ErroneousMapper2.java | 14 ++ .../ErroneousPropertyMappingTest.java | 51 +++++++ .../Source.java | 19 +++ .../Target.java | 20 +++ .../UnmappableClass.java | 9 ++ .../nestedbeans/DottedErrorMessageTest.java | 84 ++++++----- 11 files changed, 325 insertions(+), 37 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/processor/MapperAnnotatedFormattingMessenger.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/AbstractMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper1.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper2.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousPropertyMappingTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/UnmappableClass.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperAnnotatedFormattingMessenger.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperAnnotatedFormattingMessenger.java new file mode 100644 index 0000000000..eeb84570b0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperAnnotatedFormattingMessenger.java @@ -0,0 +1,136 @@ +/* + * 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.internal.processor; + +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.tools.Diagnostic.Kind; + +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; + +/** + * Handles redirection of errors/warnings so that they're shown on the mapper instead of hidden on a superclass. + * + * @author Ben Zegveld + */ +public class MapperAnnotatedFormattingMessenger implements FormattingMessager { + + private FormattingMessager delegateMessager; + private TypeElement mapperTypeElement; + private TypeUtils typeUtils; + + public MapperAnnotatedFormattingMessenger(FormattingMessager delegateMessager, TypeElement mapperTypeElement, + TypeUtils typeUtils) { + this.delegateMessager = delegateMessager; + this.mapperTypeElement = mapperTypeElement; + this.typeUtils = typeUtils; + } + + @Override + public void printMessage(Message msg, Object... args) { + delegateMessager.printMessage( msg, args ); + } + + @Override + public void printMessage(Element e, Message msg, Object... args) { + delegateMessager + .printMessage( + determineDelegationElement( e ), + determineDelegationMessage( e, msg ), + determineDelegationArguments( e, msg, args ) ); + } + + @Override + public void printMessage(Element e, AnnotationMirror a, Message msg, Object... args) { + delegateMessager + .printMessage( + determineDelegationElement( e ), + a, + determineDelegationMessage( e, msg ), + determineDelegationArguments( e, msg, args ) ); + } + + @Override + public void printMessage(Element e, AnnotationMirror a, AnnotationValue v, Message msg, Object... args) { + delegateMessager + .printMessage( + determineDelegationElement( e ), + a, + v, + determineDelegationMessage( e, msg ), + determineDelegationArguments( e, msg, args ) ); + } + + @Override + public void note(int level, Message log, Object... args) { + delegateMessager.note( level, log, args ); + } + + @Override + public boolean isErroneous() { + return delegateMessager.isErroneous(); + } + + private Object[] determineDelegationArguments(Element e, Message msg, Object[] args) { + if ( methodInMapperClass( e ) ) { + return args; + } + String originalMessage = String.format( msg.getDescription(), args ); + return new Object[] { originalMessage, constructMethod( e ), e.getEnclosingElement().getSimpleName() }; + } + + /** + * ExecutableElement.toString() has different values depending on the compiler. Constructing the method itself + * manually will ensure that the message is always traceable to it's source. + */ + private String constructMethod(Element e) { + if ( e instanceof ExecutableElement ) { + ExecutableElement ee = (ExecutableElement) e; + StringBuilder method = new StringBuilder(); + method.append( typeUtils.asElement( ee.getReturnType() ).getSimpleName() ); + method.append( " " ); + method.append( ee.getSimpleName() ); + method.append( "(" ); + method.append( ee.getParameters() + .stream() + .map( this::parameterToString ) + .collect( Collectors.joining( ", " ) ) ); + method.append( ")" ); + return method.toString(); + } + return e.toString(); + } + + private String parameterToString(VariableElement element) { + return typeUtils.asElement( element.asType() ).getSimpleName() + " " + element.getSimpleName(); + } + + private Message determineDelegationMessage(Element e, Message msg) { + if ( methodInMapperClass( e ) ) { + return msg; + } + if ( msg.getDiagnosticKind() == Kind.ERROR ) { + return Message.MESSAGE_MOVED_TO_MAPPER_ERROR; + } + return Message.MESSAGE_MOVED_TO_MAPPER_WARNING; + } + + private Element determineDelegationElement(Element e) { + return methodInMapperClass( e ) ? e : mapperTypeElement; + } + + private boolean methodInMapperClass(Element e) { + return mapperTypeElement == null || e.equals( mapperTypeElement ) + || e.getEnclosingElement().equals( mapperTypeElement ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 13e0010171..539e40daf5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -96,7 +96,8 @@ public class MapperCreationProcessor implements ModelElementProcessor sourceModel) { this.elementUtils = context.getElementUtils(); this.typeUtils = context.getTypeUtils(); - this.messager = context.getMessager(); + this.messager = + new MapperAnnotatedFormattingMessenger( context.getMessager(), mapperTypeElement, context.getTypeUtils() ); this.options = context.getOptions(); this.versionInformation = context.getVersionInformation(); this.typeFactory = context.getTypeFactory(); 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 66b37de7a1..9b21e4090f 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 @@ -20,6 +20,8 @@ public enum Message { // CHECKSTYLE:OFF PROCESSING_NOTE( "processing: %s.", Diagnostic.Kind.NOTE ), CONFIG_NOTE( "applying mapper configuration: %s.", Diagnostic.Kind.NOTE ), + MESSAGE_MOVED_TO_MAPPER_ERROR( "%s Occured at '%s' in '%s'." ), + MESSAGE_MOVED_TO_MAPPER_WARNING( "%s Occured at '%s' in '%s'.", Diagnostic.Kind.WARNING ), BEANMAPPING_CREATE_NOTE( "creating bean mapping method implementation for %s.", Diagnostic.Kind.NOTE ), BEANMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'nullValuePropertyMappingStrategy', 'resultType' and 'qualifiedBy' are undefined in @BeanMapping, define at least one of them." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/AbstractMapper.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/AbstractMapper.java new file mode 100644 index 0000000000..5155ac386c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/AbstractMapper.java @@ -0,0 +1,11 @@ +/* + * 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.erroneous.supermappingwithsubclassmapper; + +public interface AbstractMapper { + + TARGET map(SOURCE source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper1.java new file mode 100644 index 0000000000..95a0784934 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper1.java @@ -0,0 +1,13 @@ +/* + * 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.erroneous.supermappingwithsubclassmapper; + +import org.mapstruct.Mapper; + +@Mapper +public interface ErroneousMapper1 extends AbstractMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper2.java new file mode 100644 index 0000000000..b75f1a0e73 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper2.java @@ -0,0 +1,14 @@ +/* + * 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.erroneous.supermappingwithsubclassmapper; + +import org.mapstruct.Mapper; + +@Mapper +public interface ErroneousMapper2 extends AbstractMapper { + @Override + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousPropertyMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousPropertyMappingTest.java new file mode 100644 index 0000000000..147f1d6929 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousPropertyMappingTest.java @@ -0,0 +1,51 @@ +/* + * 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.erroneous.supermappingwithsubclassmapper; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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; + +@IssueKey( "598" ) +@WithClasses( { Source.class, Target.class, UnmappableClass.class, AbstractMapper.class } ) +public class ErroneousPropertyMappingTest { + + @ProcessorTest + @WithClasses( ErroneousMapper1.class ) + @IssueKey( "598" ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapper1.class, + line = 11, + messageRegExp = "Can't map property \"UnmappableClass property\" to \"String property\"\\. " + + "Consider to declare/implement a mapping method: \"String map\\(UnmappableClass value\\)\"\\. " + + "Occured at 'TARGET map\\(SOURCE source\\)' in 'AbstractMapper'\\.") + } + ) + public void testUnmappableSourcePropertyInSuperclass() { + } + + @ProcessorTest + @WithClasses( ErroneousMapper2.class ) + @IssueKey( "598" ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapper2.class, + line = 13, + message = "Can't map property \"UnmappableClass property\" to \"String property\". " + + "Consider to declare/implement a mapping method: \"String map(UnmappableClass value)\".") } ) + public void testMethodOverride() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Source.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Source.java new file mode 100644 index 0000000000..383eb1dbe6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Source.java @@ -0,0 +1,19 @@ +/* + * 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.erroneous.supermappingwithsubclassmapper; + +public class Source { + + private UnmappableClass property; + + public UnmappableClass getProperty() { + return property; + } + + public void setProperty(UnmappableClass property) { + this.property = property; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Target.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Target.java new file mode 100644 index 0000000000..f647ad7afa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Target.java @@ -0,0 +1,20 @@ +/* + * 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.erroneous.supermappingwithsubclassmapper; + +public class Target { + + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/UnmappableClass.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/UnmappableClass.java new file mode 100644 index 0000000000..af239fb046 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/UnmappableClass.java @@ -0,0 +1,9 @@ +/* + * 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.erroneous.supermappingwithsubclassmapper; + +public class UnmappableClass { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DottedErrorMessageTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DottedErrorMessageTest.java index fb10616577..f98da19a75 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DottedErrorMessageTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DottedErrorMessageTest.java @@ -93,11 +93,12 @@ public class DottedErrorMessageTest { @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseDeepNestingMapper.class, + @Diagnostic(type = UnmappableDeepNestingMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, + line = 14, message = "Unmapped target property: \"rgb\". Mapping from " + PROPERTY + - " \"Color house.roof.color\" to \"ColorDto house.roof.color\".") + " \"Color house.roof.color\" to \"ColorDto house.roof.color\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepNestingMapper'.") } ) public void testDeepNestedBeans() { @@ -110,11 +111,12 @@ public void testDeepNestedBeans() { @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseDeepListMapper.class, + @Diagnostic(type = UnmappableDeepListMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, + line = 14, message = "Unmapped target property: \"left\". Mapping from " + COLLECTION_ELEMENT + - " \"Wheel car.wheels\" to \"WheelDto car.wheels\".") + " \"Wheel car.wheels\" to \"WheelDto car.wheels\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepListMapper'.") } ) public void testIterables() { @@ -127,11 +129,12 @@ public void testIterables() { @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseDeepMapKeyMapper.class, + @Diagnostic(type = UnmappableDeepMapKeyMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, + line = 14, message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_KEY + - " \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\".") + " \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapKeyMapper'.") } ) public void testMapKeys() { @@ -144,11 +147,12 @@ public void testMapKeys() { @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseDeepMapValueMapper.class, + @Diagnostic(type = UnmappableDeepMapValueMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, + line = 14, message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_VALUE + - " \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\".") + " \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapValueMapper'.") } ) public void testMapValues() { @@ -161,11 +165,12 @@ public void testMapValues() { @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseCollectionElementPropertyMapper.class, + @Diagnostic(type = UnmappableCollectionElementPropertyMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, + line = 14, message = "Unmapped target property: \"color\". Mapping from " + PROPERTY + - " \"Info computers[].info\" to \"InfoDto computers[].info\".") + " \"Info computers[].info\" to \"InfoDto computers[].info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseCollectionElementPropertyMapper'.") } ) public void testCollectionElementProperty() { @@ -178,11 +183,12 @@ public void testCollectionElementProperty() { @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseValuePropertyMapper.class, + @Diagnostic(type = UnmappableValuePropertyMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, + line = 14, message = "Unmapped target property: \"color\". Mapping from " + PROPERTY + - " \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\".") + " \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseValuePropertyMapper'.") } ) public void testMapValueProperty() { @@ -220,36 +226,42 @@ public void testMapEnumProperty() { @ExpectedCompilationOutcome( value = CompilationResult.SUCCEEDED, diagnostics = { - @Diagnostic(type = BaseDeepNestingMapper.class, + @Diagnostic(type = UnmappableWarnDeepNestingMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, + line = 13, message = "Unmapped target property: \"rgb\". Mapping from " + PROPERTY + - " \"Color house.roof.color\" to \"ColorDto house.roof.color\"."), - @Diagnostic(type = BaseDeepListMapper.class, + " \"Color house.roof.color\" to \"ColorDto house.roof.color\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepNestingMapper'."), + @Diagnostic(type = UnmappableWarnDeepListMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, + line = 13, message = "Unmapped target property: \"left\". Mapping from " + COLLECTION_ELEMENT + - " \"Wheel car.wheels\" to \"WheelDto car.wheels\"."), - @Diagnostic(type = BaseDeepMapKeyMapper.class, + " \"Wheel car.wheels\" to \"WheelDto car.wheels\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepListMapper'."), + @Diagnostic(type = UnmappableWarnDeepMapKeyMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, + line = 13, message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_KEY + - " \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\"."), - @Diagnostic(type = BaseDeepMapValueMapper.class, + " \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapKeyMapper'."), + @Diagnostic(type = UnmappableWarnDeepMapValueMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, + line = 13, message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_VALUE + - " \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\"."), - @Diagnostic(type = BaseCollectionElementPropertyMapper.class, + " \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapValueMapper'."), + @Diagnostic(type = UnmappableWarnCollectionElementPropertyMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, + line = 13, message = "Unmapped target property: \"color\". Mapping from " + PROPERTY + - " \"Info computers[].info\" to \"InfoDto computers[].info\"."), - @Diagnostic(type = BaseValuePropertyMapper.class, + " \"Info computers[].info\" to \"InfoDto computers[].info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseCollectionElementPropertyMapper'."), + @Diagnostic(type = UnmappableWarnValuePropertyMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, + line = 13, message = "Unmapped target property: \"color\". Mapping from " + PROPERTY + - " \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\".") + " \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseValuePropertyMapper'.") } ) public void testWarnUnmappedTargetProperties() { From 907d605160386d818e8df0a577fef32da30b147e Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 24 Oct 2021 17:20:56 +0200 Subject: [PATCH 021/363] #2624 Nested target methods should be inherited for forged Map to Bean methods --- .../ap/internal/model/PropertyMapping.java | 19 ++--- .../ap/test/bugs/_2624/Issue2624Mapper.java | 76 +++++++++++++++++++ .../ap/test/bugs/_2624/Issue2624Test.java | 43 +++++++++++ 3 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Test.java 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 82d2dd53fa..1d902e54e7 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 @@ -309,7 +309,7 @@ else if ( sourceType.isMapType() && targetType.isMapType() ) { assignment = forgeMapMapping( sourceType, targetType, rightHandSide ); } else if ( sourceType.isMapType() && !targetType.isMapType()) { - assignment = forgeMapToBeanMapping( sourceType, targetType, rightHandSide ); + assignment = forgeMapping( sourceType, targetType.withoutBounds(), rightHandSide ); } else if ( ( sourceType.isIterableType() && targetType.isStreamType() ) || ( sourceType.isStreamType() && targetType.isStreamType() ) @@ -753,19 +753,6 @@ private Assignment forgeMapMapping(Type sourceType, Type targetType, SourceRHS s return createForgedAssignment( source, methodRef, mapMappingMethod ); } - private Assignment forgeMapToBeanMapping(Type sourceType, Type targetType, SourceRHS source) { - - targetType = targetType.withoutBounds(); - ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, "{}" ); - - BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder(); - final BeanMappingMethod mapToBeanMappingMethod = builder.mappingContext( ctx ) - .forgedMethod( methodRef ) - .build(); - - return createForgedAssignment( source, methodRef, mapToBeanMappingMethod ); - } - private Assignment forgeMapping(SourceRHS sourceRHS) { Type sourceType; if ( targetWriteAccessorType == AccessorType.ADDER ) { @@ -778,6 +765,10 @@ private Assignment forgeMapping(SourceRHS sourceRHS) { return null; } + return forgeMapping( sourceType, targetType, sourceRHS ); + } + + private Assignment forgeMapping(Type sourceType, Type targetType, SourceRHS sourceRHS) { //Fail fast. If we could not find the method by now, no need to try if ( sourceType.isPrimitive() || targetType.isPrimitive() ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Mapper.java new file mode 100644 index 0000000000..f4d3c3d22d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Mapper.java @@ -0,0 +1,76 @@ +/* + * 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._2624; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2624Mapper { + + Issue2624Mapper INSTANCE = Mappers.getMapper( Issue2624Mapper.class ); + + @Mapping( target = "department.id", source = "did") + @Mapping( target = "department.name", source = "dname") + Employee fromMap(Map source); + + class Employee { + private String id; + private String name; + private Department department; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Department getDepartment() { + return department; + } + + public void setDepartment(Department department) { + this.department = department; + } + } + + class Department { + private String id; + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Test.java new file mode 100644 index 0000000000..2fb04ef3a0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Test.java @@ -0,0 +1,43 @@ +/* + * 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._2624; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue2624Mapper.class +}) +class Issue2624Test { + + @ProcessorTest + void shouldCorrectlyMapNestedTargetFromMap() { + Map map = new HashMap<>(); + map.put( "id", "1234" ); + map.put( "name", "Tester" ); + map.put( "did", "4321" ); //Department Id + map.put( "dname", "Test" ); // Department name + + Issue2624Mapper.Employee employee = Issue2624Mapper.INSTANCE.fromMap( map ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "1234" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + + Issue2624Mapper.Department department = employee.getDepartment(); + assertThat( department ).isNotNull(); + assertThat( department.getId() ).isEqualTo( "4321" ); + assertThat( department.getName() ).isEqualTo( "Test" ); + } +} From 166eb699c75b60979c3713c1492f98150ccfcac4 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 30 Oct 2021 14:27:24 +0200 Subject: [PATCH 022/363] #1752 Always return mapping target when using update methods with return --- .../ap/internal/model/BeanMappingMethod.ftl | 2 +- .../internal/model/IterableMappingMethod.ftl | 3 +-- .../ap/internal/model/MapMappingMethod.ftl | 2 +- .../ap/internal/model/StreamMappingMethod.ftl | 3 +-- .../ap/test/array/ArrayMappingTest.java | 13 ++++++--- .../ap/test/bugs/_374/Issue374Test.java | 3 ++- .../simple/BuilderInfoTargetTest.java | 13 +++++++++ .../DefaultCollectionImplementationTest.java | 16 ++++++++++- .../test/collection/map/MapMappingTest.java | 14 ++++++++++ .../ap/test/inheritance/InheritanceTest.java | 14 ++++++++++ .../DefaultStreamImplementationTest.java | 27 +++++++++++++++++++ .../ap/test/array/ScienceMapperImpl.java | 2 +- .../DomainDtoWithNcvsAlwaysMapperImpl.java | 2 +- .../_913/DomainDtoWithNvmsNullMapperImpl.java | 2 +- .../DomainDtoWithPresenceCheckMapperImpl.java | 2 +- .../SourceTargetMapperImpl.java | 2 +- 16 files changed, 104 insertions(+), 16 deletions(-) diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 12a03c06ca..81e3b61fd7 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -20,7 +20,7 @@ <#if !mapNullToDefault> if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && ) { - return<#if returnType.name != "void"> null; + return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /><#else>null; } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl index 3af75079b9..495111b7a1 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl @@ -16,8 +16,7 @@ if ( ${sourceParameter.name} == null ) { <#if !mapNullToDefault> - <#-- returned target type starts to miss-align here with target handed via param, TODO is this right? --> - return<#if returnType.name != "void"> null; + return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null; <#else> <#if resultType.arrayType> <#if existingInstanceMapping> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl index 1c6e6bad35..443d87cba7 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl @@ -16,7 +16,7 @@ if ( ${sourceParameter.name} == null ) { <#if !mapNullToDefault> - return<#if returnType.name != "void"> null; + return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null; <#else> <#if existingInstanceMapping> ${resultName}.clear(); diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl index ae48a3477a..860859fc3a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl @@ -17,8 +17,7 @@ if ( ${sourceParameter.name} == null ) { <#if !mapNullToDefault> - <#-- returned target type starts to miss-align here with target handed via param, TODO is this right? --> - return<#if returnType.name != "void"> null; + return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null; <#else> <#if resultType.arrayType> <#if existingInstanceMapping> diff --git a/processor/src/test/java/org/mapstruct/ap/test/array/ArrayMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/array/ArrayMappingTest.java index 07113f7cf4..67ef2216d8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/array/ArrayMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/array/ArrayMappingTest.java @@ -123,15 +123,15 @@ public void shouldMapArrayToArrayExistingLargerSizedTarget() { } @ProcessorTest - public void shouldMapTargetToNullWhenNullSource() { - // TODO: What about existing target? + public void shouldReturnMapTargetWhenNullSource() { ScientistDto[] existingTarget = new ScientistDto[]{ new ScientistDto( "Jim" ) }; ScientistDto[] target = ScienceMapper.INSTANCE.scientistsToDtos( null, existingTarget ); - assertThat( target ).isNull(); + assertThat( target ).isNotNull(); + assertThat( target ).isEqualTo( existingTarget ); assertThat( existingTarget ).extracting( "name" ).containsOnly( "Jim" ); } @@ -143,6 +143,7 @@ public void shouldMapBooleanWhenReturnDefault() { boolean[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); assertThat( target ).containsOnly( false ); + assertThat( target ).isEqualTo( existingTarget ); assertThat( existingTarget ).containsOnly( false ); assertThat( ScienceMapper.INSTANCE.nvmMapping( null ) ).isEmpty(); @@ -154,6 +155,7 @@ public void shouldMapShortWhenReturnDefault() { short[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); assertThat( target ).containsOnly( new short[] { 0 } ); + assertThat( target ).isEqualTo( existingTarget ); assertThat( existingTarget ).containsOnly( new short[] { 0 } ); } @@ -163,6 +165,7 @@ public void shouldMapCharWhenReturnDefault() { char[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); assertThat( target ).containsOnly( new char[] { 0 } ); + assertThat( target ).isEqualTo( existingTarget ); assertThat( existingTarget ).containsOnly( new char[] { 0 } ); } @@ -172,6 +175,7 @@ public void shouldMapIntWhenReturnDefault() { int[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); assertThat( target ).containsOnly( 0 ); + assertThat( target ).isEqualTo( existingTarget ); assertThat( existingTarget ).containsOnly( 0 ); } @@ -181,6 +185,7 @@ public void shouldMapLongWhenReturnDefault() { long[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); assertThat( target ).containsOnly( 0L ); + assertThat( target ).isEqualTo( existingTarget ); assertThat( existingTarget ).containsOnly( 0L ); } @@ -190,6 +195,7 @@ public void shouldMapFloatWhenReturnDefault() { float[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); assertThat( target ).containsOnly( 0.0f ); + assertThat( target ).isEqualTo( existingTarget ); assertThat( existingTarget ).containsOnly( 0.0f ); } @@ -199,6 +205,7 @@ public void shouldMapDoubleWhenReturnDefault() { double[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); assertThat( target ).containsOnly( 0.0d ); + assertThat( target ).isEqualTo( existingTarget ); assertThat( existingTarget ).containsOnly( 0.0d ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Test.java index 7f174732ff..2bc6d6a170 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Test.java @@ -41,7 +41,8 @@ public void shouldMapExistingTargetWithConstantToDefault() { Target target2 = new Target(); Target result2 = Issue374Mapper.INSTANCE.map2( null, target2 ); - assertThat( result2 ).isNull(); + assertThat( result2 ).isNotNull(); + assertThat( result2 ).isEqualTo( target2 ); assertThat( target2.getTest() ).isNull(); assertThat( target2.getConstant() ).isNull(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java index 3682f2ccc5..4f3d8b85f4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.builder.mappingTarget.simple; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.runner.GeneratedSource; @@ -36,6 +37,18 @@ public void testSimpleImmutableBuilderHappyPath() { assertThat( targetObject.getName() ).isEqualTo( "Bob" ); } + @ProcessorTest + @IssueKey("1752") + public void testSimpleImmutableBuilderFromNullSource() { + SimpleImmutableTarget targetObject = SimpleBuilderMapper.INSTANCE.toImmutable( + null, + SimpleImmutableTarget.builder().age( 3 ).name( "Bob" ) + ); + assertThat( targetObject ).isNotNull(); + assertThat( targetObject.getAge() ).isEqualTo( 3 ); + assertThat( targetObject.getName() ).isEqualTo( "Bob" ); + } + @ProcessorTest public void testMutableTargetWithBuilder() { SimpleMutableSource source = new SimpleMutableSource(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java index 9d360656a0..dccfcf01ca 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java @@ -159,7 +159,21 @@ public void shouldUseAndReturnTargetParameterForMapping() { SourceTargetMapper.INSTANCE .sourceFoosToTargetFoosUsingTargetParameterAndReturn( createSourceFooList(), target ); - assertThat( target == result ).isTrue(); + assertThat( result ).isSameAs( target ); + assertResultList( target ); + } + + @ProcessorTest + @IssueKey("1752") + public void shouldUseAndReturnTargetParameterForNullMapping() { + List target = new ArrayList<>(); + target.add( new TargetFoo( "Bob" ) ); + target.add( new TargetFoo( "Alice" ) ); + Iterable result = + SourceTargetMapper.INSTANCE + .sourceFoosToTargetFoosUsingTargetParameterAndReturn( null, target ); + + assertThat( result ).isSameAs( target ); assertResultList( target ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java index ddba51f826..d0dd66d22b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java @@ -82,6 +82,20 @@ public void shouldCreateMapMethodImplementationWithReturnedTargetParameter() { assertResult( target ); } + @ProcessorTest + @IssueKey("1752") + public void shouldCreateMapMethodImplementationWithReturnedTargetParameterAndNullSource() { + Map target = new HashMap<>(); + target.put( 42L, new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime() ); + target.put( 121L, new GregorianCalendar( 2013, Calendar.JULY, 20 ).getTime() ); + + Map returnedTarget = SourceTargetMapper.INSTANCE + .stringStringMapToLongDateMapUsingTargetParameterAndReturn( null, target ); + + assertThat( target ).isSameAs( returnedTarget ); + assertResult( target ); + } + private void assertResult(Map target) { assertThat( target ).isNotNull(); assertThat( target ).hasSize( 2 ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java b/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java index d127a9ac11..aaf299635e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java @@ -53,6 +53,20 @@ public void shouldMapAttributeFromSuperTypeUsingReturnedTargetParameter() { assertResult( target ); } + @ProcessorTest + @IssueKey("1752") + public void shouldMapAttributeFromSuperTypeUsingReturnedTargetParameterAndNullSource() { + + TargetExt target = new TargetExt(); + target.setFoo( 42L ); + target.publicFoo = 52L; + target.setBar( 23 ); + TargetBase result = SourceTargetMapper.INSTANCE.sourceToTargetWithTargetParameterAndReturn( null, target ); + + assertThat( target ).isSameAs( result ); + assertResult( target ); + } + private void assertResult(TargetExt target) { assertThat( target ).isNotNull(); assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 42 ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/DefaultStreamImplementationTest.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/DefaultStreamImplementationTest.java index 91be53fe64..8e69bafce2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/DefaultStreamImplementationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/DefaultStreamImplementationTest.java @@ -141,6 +141,19 @@ public void shouldUseAndReturnTargetParameterForArrayMappingAndSmallerArray() { assertThat( target ).containsOnly( new TargetFoo( "Bob" ) ); } + @ProcessorTest + @IssueKey("1752") + public void shouldUseAndReturnTargetParameterArrayForNullSource() { + TargetFoo[] target = new TargetFoo[1]; + target[0] = new TargetFoo( "Bob" ); + TargetFoo[] result = + SourceTargetMapper.INSTANCE.streamToArrayUsingTargetParameterAndReturn( null, target ); + + assertThat( result ).isSameAs( target ); + assertThat( target ).isNotNull(); + assertThat( target ).containsOnly( new TargetFoo( "Bob" ) ); + } + @ProcessorTest public void shouldUseAndReturnTargetParameterForMapping() { List target = new ArrayList<>(); @@ -152,6 +165,20 @@ public void shouldUseAndReturnTargetParameterForMapping() { assertResultList( target ); } + @ProcessorTest + @IssueKey("1752") + public void shouldUseAndReturnTargetParameterForNullMapping() { + List target = new ArrayList<>(); + target.add( new TargetFoo( "Bob" ) ); + target.add( new TargetFoo( "Alice" ) ); + Iterable result = + SourceTargetMapper.INSTANCE + .sourceFoosToTargetFoosUsingTargetParameterAndReturn( null, target ); + + assertThat( result ).isSameAs( target ); + assertResultList( target ); + } + @ProcessorTest public void shouldUseDefaultImplementationForListWithoutSetter() { Source source = new Source(); diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/array/ScienceMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/array/ScienceMapperImpl.java index 066901bc45..c1f4557fbf 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/array/ScienceMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/array/ScienceMapperImpl.java @@ -92,7 +92,7 @@ public List scientistsToDtosAsList(Scientist[] scientists) { @Override public ScientistDto[] scientistsToDtos(Scientist[] scientists, ScientistDto[] target) { if ( scientists == null ) { - return null; + return target; } int i = 0; diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java index b3a8c89c60..22fed55509 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java @@ -128,7 +128,7 @@ public void update(DtoWithPresenceCheck source, Domain target) { @Override public Domain updateWithReturn(DtoWithPresenceCheck source, Domain target) { if ( source == null ) { - return null; + return target; } if ( target.getStrings() != null ) { diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java index 59a770d049..e257ac8443 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java @@ -143,7 +143,7 @@ public void update(Dto source, Domain target) { @Override public Domain updateWithReturn(Dto source, Domain target) { if ( source == null ) { - return null; + return target; } if ( target.getStrings() != null ) { diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java index 8cc41c47d5..ddba54dbd1 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java @@ -128,7 +128,7 @@ public void update(DtoWithPresenceCheck source, Domain target) { @Override public Domain updateWithReturn(DtoWithPresenceCheck source, Domain target) { if ( source == null ) { - return null; + return target; } if ( target.getStrings() != null ) { diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java index 853ad59560..d9e60ad1e1 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java @@ -133,7 +133,7 @@ public void sourceFoosToTargetFoosUsingTargetParameter(List targetFoo @Override public Iterable sourceFoosToTargetFoosUsingTargetParameterAndReturn(Iterable sourceFoos, List targetFoos) { if ( sourceFoos == null ) { - return null; + return targetFoos; } targetFoos.clear(); From 735a5bef6a36644a48bfc5bebde31e217574551c Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 6 Nov 2021 09:21:16 +0100 Subject: [PATCH 023/363] #2225 Add support for suppressing the generation of the timestamp through Mapper and MapperConfig --- core/src/main/java/org/mapstruct/Mapper.java | 13 +++++++++++ .../main/java/org/mapstruct/MapperConfig.java | 11 ++++++++++ .../ap/internal/model/Decorator.java | 9 ++++++++ .../ap/internal/model/GeneratedType.java | 3 ++- .../mapstruct/ap/internal/model/Mapper.java | 9 ++++++++ .../internal/model/source/DefaultOptions.java | 7 ++++++ .../model/source/DelegatingOptions.java | 4 ++++ .../model/source/MapperConfigOptions.java | 7 ++++++ .../internal/model/source/MapperOptions.java | 7 ++++++ .../processor/MapperCreationProcessor.java | 14 ++++++------ .../SuppressTimestampViaMapper.java | 16 ++++++++++++++ .../SuppressTimestampViaMapperConfig.java | 22 +++++++++++++++++++ .../ap/test/versioninfo/VersionInfoTest.java | 16 ++++++++++++++ 13 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index 26442e4ff0..c5aed661f3 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -357,4 +357,17 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default * @since 1.4 */ Class unexpectedValueMappingException() default IllegalArgumentException.class; + + /** + * Flag indicating whether the addition of a time stamp in the {@code @Generated} annotation should be suppressed. + * i.e. not be added. + * + * The method overrides the flag set in a central configuration set by {@link #config()} + * or through an annotation processor option. + * + * @return whether the addition of a timestamp should be suppressed + * + * @since 1.5 + */ + boolean suppressTimestampInGenerated() default false; } diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index ce239322a2..757a4ab0af 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -329,5 +329,16 @@ MappingInheritanceStrategy mappingInheritanceStrategy() */ Class unexpectedValueMappingException() default IllegalArgumentException.class; + /** + * Flag indicating whether the addition of a time stamp in the {@code @Generated} annotation should be suppressed. + * i.e. not be added. + * + * The method overrides the flag set through an annotation processor option. + * + * @return whether the addition of a timestamp should be suppressed + * + * @since 1.5 + */ + boolean suppressTimestampInGenerated() default false; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java index a54045dca0..b7dc0effcb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java @@ -32,6 +32,7 @@ public static class Builder extends GeneratedTypeBuilder { private boolean hasDelegateConstructor; private String implName; private String implPackage; + private boolean suppressGeneratorTimestamp; public Builder() { super( Builder.class ); @@ -62,6 +63,11 @@ public Builder implPackage(String implPackage) { return this; } + public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) { + this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; + return this; + } + public Decorator build() { String implementationName = implName.replace( Mapper.CLASS_NAME_PLACEHOLDER, Mapper.getFlatName( mapperElement ) ); @@ -86,6 +92,7 @@ public Decorator build() { methods, options, versionInformation, + suppressGeneratorTimestamp, Accessibility.fromModifiers( mapperElement.getModifiers() ), extraImportedTypes, decoratorConstructor @@ -101,6 +108,7 @@ private Decorator(TypeFactory typeFactory, String packageName, String name, Type Type mapperType, List methods, Options options, VersionInformation versionInformation, + boolean suppressGeneratorTimestamp, Accessibility accessibility, SortedSet extraImports, DecoratorConstructor decoratorConstructor) { super( @@ -112,6 +120,7 @@ private Decorator(TypeFactory typeFactory, String packageName, String name, Type Arrays.asList( new Field( mapperType, "delegate", true ) ), options, versionInformation, + suppressGeneratorTimestamp, accessibility, extraImports, decoratorConstructor diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index 620559ea24..134ab081dc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -103,6 +103,7 @@ public T methods(List methods) { protected GeneratedType(TypeFactory typeFactory, String packageName, String name, Type mapperDefinitionType, List methods, List fields, Options options, VersionInformation versionInformation, + boolean suppressGeneratorTimestamp, Accessibility accessibility, SortedSet extraImportedTypes, Constructor constructor) { this.packageName = packageName; this.name = name; @@ -113,7 +114,7 @@ protected GeneratedType(TypeFactory typeFactory, String packageName, String name this.methods = methods; this.fields = fields; - this.suppressGeneratorTimestamp = options.isSuppressGeneratorTimestamp(); + this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorVersionComment = options.isSuppressGeneratorVersionComment(); this.versionInformation = versionInformation; this.accessibility = accessibility; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java index a066b123c9..fa15cd8ed0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java @@ -42,6 +42,7 @@ public static class Builder extends GeneratedTypeBuilder { private boolean customName; private String implPackage; private boolean customPackage; + private boolean suppressGeneratorTimestamp; public Builder() { super( Builder.class ); @@ -79,6 +80,11 @@ public Builder implPackage(String implPackage) { return this; } + public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) { + this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; + return this; + } + public Mapper build() { String implementationName = implName.replace( CLASS_NAME_PLACEHOLDER, getFlatName( element ) ) + ( decorator == null ? "" : "_" ); @@ -102,6 +108,7 @@ public Mapper build() { methods, options, versionInformation, + suppressGeneratorTimestamp, Accessibility.fromModifiers( element.getModifiers() ), fields, constructor, @@ -121,6 +128,7 @@ private Mapper(TypeFactory typeFactory, String packageName, String name, Type mapperDefinitionType, boolean customPackage, boolean customImplName, List methods, Options options, VersionInformation versionInformation, + boolean suppressGeneratorTimestamp, Accessibility accessibility, List fields, Constructor constructor, Decorator decorator, SortedSet extraImportedTypes ) { @@ -133,6 +141,7 @@ private Mapper(TypeFactory typeFactory, String packageName, String name, fields, options, versionInformation, + suppressGeneratorTimestamp, accessibility, extraImportedTypes, constructor diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index 1258707e43..5c76e7face 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -83,6 +83,13 @@ public String componentModel() { return mapper.componentModel().getDefaultValue(); } + public boolean suppressTimestampInGenerated() { + if ( mapper.suppressTimestampInGenerated().hasValue() ) { + return mapper.suppressTimestampInGenerated().getValue(); + } + return options.isSuppressGeneratorTimestamp(); + } + @Override public MappingInheritanceStrategyGem getMappingInheritanceStrategy() { return MappingInheritanceStrategyGem.valueOf( mapper.mappingInheritanceStrategy().getDefaultValue() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java index 12f610f1ac..50c1d84541 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java @@ -68,6 +68,10 @@ public String componentModel() { return next.componentModel(); } + public boolean suppressTimestampInGenerated() { + return next.suppressTimestampInGenerated(); + } + public MappingInheritanceStrategyGem getMappingInheritanceStrategy() { return next.getMappingInheritanceStrategy(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java index 623b97bc78..e3ca6162a5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -76,6 +76,13 @@ public String componentModel() { return mapperConfig.componentModel().hasValue() ? mapperConfig.componentModel().get() : next().componentModel(); } + @Override + public boolean suppressTimestampInGenerated() { + return mapperConfig.suppressTimestampInGenerated().hasValue() ? + mapperConfig.suppressTimestampInGenerated().get() : + next().suppressTimestampInGenerated(); + } + @Override public MappingInheritanceStrategyGem getMappingInheritanceStrategy() { return mapperConfig.mappingInheritanceStrategy().hasValue() ? diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java index 3261205116..ed1af34f79 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -105,6 +105,13 @@ public String componentModel() { return mapper.componentModel().hasValue() ? mapper.componentModel().get() : next().componentModel(); } + @Override + public boolean suppressTimestampInGenerated() { + return mapper.suppressTimestampInGenerated().hasValue() ? + mapper.suppressTimestampInGenerated().get() : + next().suppressTimestampInGenerated(); + } + @Override public MappingInheritanceStrategyGem getMappingInheritanceStrategy() { return mapper.mappingInheritanceStrategy().hasValue() ? diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 539e40daf5..bae0cda1ae 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -198,13 +198,13 @@ private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List< .constructorFragments( constructorFragments ) .options( options ) .versionInformation( versionInformation ) - .decorator( getDecorator( element, methods, mapperOptions.implementationName(), - mapperOptions.implementationPackage(), getExtraImports( element, mapperOptions ) ) ) + .decorator( getDecorator( element, methods, mapperOptions ) ) .typeFactory( typeFactory ) .elementUtils( elementUtils ) .extraImports( getExtraImports( element, mapperOptions ) ) .implName( mapperOptions.implementationName() ) .implPackage( mapperOptions.implementationPackage() ) + .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) .build(); if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) { @@ -226,8 +226,7 @@ private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List< return mapper; } - private Decorator getDecorator(TypeElement element, List methods, String implName, - String implPackage, SortedSet extraImports) { + private Decorator getDecorator(TypeElement element, List methods, MapperOptions mapperOptions) { DecoratedWithGem decoratedWith = DecoratedWithGem.instanceOn( element ); if ( decoratedWith == null ) { @@ -287,9 +286,10 @@ else if ( constructor.getParameters().size() == 1 ) { .hasDelegateConstructor( hasDelegateConstructor ) .options( options ) .versionInformation( versionInformation ) - .implName( implName ) - .implPackage( implPackage ) - .extraImports( extraImports ) + .implName( mapperOptions.implementationName() ) + .implPackage( mapperOptions.implementationPackage() ) + .extraImports( getExtraImports( element, mapperOptions ) ) + .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) .build(); return decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java new file mode 100644 index 0000000000..02a7b467b1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java @@ -0,0 +1,16 @@ +/* + * 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.versioninfo; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(suppressTimestampInGenerated = true) +public interface SuppressTimestampViaMapper { + Object toObject(Object object); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java new file mode 100644 index 0000000000..a471cad028 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java @@ -0,0 +1,22 @@ +/* + * 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.versioninfo; + +import org.mapstruct.Mapper; +import org.mapstruct.MapperConfig; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = SuppressTimestampViaMapperConfig.Config.class) +public interface SuppressTimestampViaMapperConfig { + Object toObject(Object object); + + @MapperConfig(suppressTimestampInGenerated = true) + interface Config { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java index 1eec91b951..f3192b85ad 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java @@ -49,4 +49,20 @@ public void includesCommentAndTimestamp() { .contains( "comments = \"version: " ); } + @ProcessorTest + @WithClasses(SuppressTimestampViaMapper.class) + @IssueKey("2225") + void includesNoTimestampViaMapper() { + generatedSource.forMapper( SuppressTimestampViaMapper.class ).content() + .doesNotContain( "date = \"" ); + } + + @ProcessorTest + @WithClasses(SuppressTimestampViaMapperConfig.class) + @IssueKey("2225") + void includesNoTimestampViaMapperConfig() { + generatedSource.forMapper( SuppressTimestampViaMapperConfig.class ).content() + .doesNotContain( "date = \"" ); + } + } From 72e6b1feb56c228eae5dfb91ee0a6b6c7acc9b42 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sun, 14 Nov 2021 20:11:05 +0100 Subject: [PATCH 024/363] #2636: defaultValue combined with qualified should not convert if not needed (#2637) --- core/src/main/java/org/mapstruct/Mapping.java | 8 +++ .../chapter-5-data-type-conversions.asciidoc | 70 +++++++++++++++++++ .../DefaultExpressionUsageMapper.java | 27 +++++++ .../defaults/DefaultValueUsageMapper.java | 27 +++++++ .../qualifier/defaults/DirectoryNode.java | 21 ++++++ .../FaultyDefaultValueUsageMapper.java | 26 +++++++ .../selection/qualifier/defaults/Folder.java | 29 ++++++++ .../defaults/QualifierWithDefaultsTest.java | 51 ++++++++++++++ 8 files changed, 259 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DefaultExpressionUsageMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DefaultValueUsageMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DirectoryNode.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/FaultyDefaultValueUsageMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/Folder.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/QualifierWithDefaultsTest.java diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 7ca637f0ca..92ae29eb54 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -218,6 +218,10 @@ * * *

    + * You can use {@link #qualifiedBy()} or {@link #qualifiedByName()} to force the use of a conversion method + * even when one would not apply. (e.g. {@code String} to {@code String}) + *

    + *

    * This attribute can not be used together with {@link #source()}, {@link #defaultValue()}, * {@link #defaultExpression()} or {@link #expression()}. * @@ -295,6 +299,8 @@ * A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple * mapping methods (hand written or generated) qualify and thus would result in an 'Ambiguous mapping methods found' * error. A qualifier is a custom annotation and can be placed on a hand written mapper class or a method. + *

    + * Note that {@link #defaultValue()} usage will also be converted using this qualifier. * * @return the qualifiers * @see Qualifier @@ -309,6 +315,8 @@ * Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and * are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large * number of qualifiers as no custom annotation types are needed. + *

    + * Note that {@link #defaultValue()} usage will also be converted using this qualifier. * * @return One or more qualifier name(s) * @see #qualifiedBy() diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index 91f4afb1cf..e456af01ba 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -703,3 +703,73 @@ public interface MovieMapper { ==== Although the used mechanism is the same, the user has to be a bit more careful. Refactoring the name of a defined qualifier in an IDE will neatly refactor all other occurrences as well. This is obviously not the case for changing a name. ==== + +=== Combining qualifiers with defaults +Please note that the `Mapping#defaultValue` is in essence a `String`, which needs to be converted to the `Mapping#target`. Providing a `Mapping#qualifiedByName` or `Mapping#qualifiedBy` will force MapStruct to use that method. If you want different behavior for the `Mapping#defaultValue`, then please provide an appropriate mapping method. This mapping method needs to transforms a `String` into the desired type of `Mapping#target` and also be annotated so that it can be found by the `Mapping#qualifiedByName` or `Mapping#qualifiedBy`. + +.Mapper using defaultValue +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface MovieMapper { + + @Mapping( target = "category", qualifiedByName = "CategoryToString", defaultValue = "DEFAULT" ) + GermanRelease toGerman( OriginalRelease movies ); + + @Named("CategoryToString") + default String defaultValueForQualifier(Category cat) { + // some mapping logic + } +} +---- +==== + +In the above example in case that category is null, the method `CategoryToString( Enum.valueOf( Category.class, "DEFAULT" ) )` will be called and the result will be set to the category field. + +.Mapper using defaultValue and default method. +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface MovieMapper { + + @Mapping( target = "category", qualifiedByName = "CategoryToString", defaultValue = "Unknown" ) + GermanRelease toGerman( OriginalRelease movies ); + + @Named("CategoryToString") + default String defaultValueForQualifier(Category cat) { + // some mapping logic + } + + @Named("CategoryToString") + default String defaultValueForQualifier(String value) { + return value; + } +} +---- +==== +In the above example in case that category is null, the method `defaultValueForQualifier( "Unknown" )` will be called and the result will be set to the category field. + +If the above mentioned methods do not work there is the option to use `defaultExpression` to set the default value. + +.Mapper using defaultExpression +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface MovieMapper { + + @Mapping( target = "category", qualifiedByName = "CategoryToString", defaultExpression = "java(\"Unknown\")" ) + GermanRelease toGerman( OriginalRelease movies ); + + @Named("CategoryToString") + default String defaultValueForQualifier(Category cat) { + // some mapping logic + } +} +---- +==== diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DefaultExpressionUsageMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DefaultExpressionUsageMapper.java new file mode 100644 index 0000000000..69c025983e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DefaultExpressionUsageMapper.java @@ -0,0 +1,27 @@ +/* + * 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.selection.qualifier.defaults; + +import java.util.UUID; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface DefaultExpressionUsageMapper { + @Mapping( source = "folder.ancestor.id", target = "parent", + defaultExpression = "java(\"#\")", qualifiedByName = "uuidToString" ) + DirectoryNode convert(Folder folder); + + @Named( "uuidToString" ) + default String uuidToString(UUID id) { + return id.toString(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DefaultValueUsageMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DefaultValueUsageMapper.java new file mode 100644 index 0000000000..622cd9193a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DefaultValueUsageMapper.java @@ -0,0 +1,27 @@ +/* + * 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.selection.qualifier.defaults; + +import java.util.UUID; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface DefaultValueUsageMapper { + @Mapping( source = "folder.ancestor.id", target = "parent", + defaultValue = "00000000-0000-4000-0000-000000000000", qualifiedByName = "uuidToString" ) + DirectoryNode convert(Folder folder); + + @Named( "uuidToString" ) + default String uuidToString(UUID id) { + return id.toString(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DirectoryNode.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DirectoryNode.java new file mode 100644 index 0000000000..5f52f07275 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/DirectoryNode.java @@ -0,0 +1,21 @@ +/* + * 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.selection.qualifier.defaults; + +/** + * @author Ben Zegveld + */ +class DirectoryNode { + private String parent; + + public void setParent(String parent) { + this.parent = parent; + } + + public String getParent() { + return parent; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/FaultyDefaultValueUsageMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/FaultyDefaultValueUsageMapper.java new file mode 100644 index 0000000000..e394536daa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/FaultyDefaultValueUsageMapper.java @@ -0,0 +1,26 @@ +/* + * 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.selection.qualifier.defaults; + +import java.util.UUID; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface FaultyDefaultValueUsageMapper { + @Mapping( source = "folder.ancestor.id", target = "parent", defaultValue = "#", qualifiedByName = "uuidToString" ) + DirectoryNode convert(Folder folder); + + @Named( "uuidToString" ) + default String uuidToString(UUID id) { + return id.toString(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/Folder.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/Folder.java new file mode 100644 index 0000000000..a6e7b5a9b2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/Folder.java @@ -0,0 +1,29 @@ +/* + * 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.selection.qualifier.defaults; + +import java.util.UUID; + +/** + * @author Ben Zegveld + */ +class Folder { + private UUID id; + private Folder ancestor; + + Folder(UUID id, Folder ancestor) { + this.id = id; + this.ancestor = ancestor; + } + + public Folder getAncestor() { + return ancestor; + } + + public UUID getId() { + return id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/QualifierWithDefaultsTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/QualifierWithDefaultsTest.java new file mode 100644 index 0000000000..63af9911f6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/qualifier/defaults/QualifierWithDefaultsTest.java @@ -0,0 +1,51 @@ +/* + * 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.selection.qualifier.defaults; + +import java.util.UUID; +import org.assertj.core.api.Assertions; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@WithClasses( { DirectoryNode.class, Folder.class } ) +public class QualifierWithDefaultsTest { + + @ProcessorTest + @WithClasses( FaultyDefaultValueUsageMapper.class ) + void defaultValueHasInvalidValue() { + Folder rootFolder = new Folder( UUID.randomUUID(), null ); + FaultyDefaultValueUsageMapper faultyMapper = Mappers.getMapper( FaultyDefaultValueUsageMapper.class ); + + Assertions + .assertThatThrownBy( () -> faultyMapper.convert( rootFolder ) ) + .isInstanceOf( IllegalArgumentException.class ); // UUID.valueOf should throw this. + } + + @ProcessorTest + @WithClasses( DefaultValueUsageMapper.class ) + void defaultValuehasUsableValue() { + Folder rootFolder = new Folder( UUID.randomUUID(), null ); + + DirectoryNode node = Mappers.getMapper( DefaultValueUsageMapper.class ).convert( rootFolder ); + + Assertions.assertThat( node.getParent() ).isEqualTo( "00000000-0000-4000-0000-000000000000" ); + } + + @ProcessorTest + @WithClasses( DefaultExpressionUsageMapper.class ) + void defaultExpressionDoesNotGetConverted() { + Folder rootFolder = new Folder( UUID.randomUUID(), null ); + + DirectoryNode node = Mappers.getMapper( DefaultExpressionUsageMapper.class ).convert( rootFolder ); + + Assertions.assertThat( node.getParent() ).isEqualTo( "#" ); + } +} From 29008e12bf4d8f5c2136f8ab3cb6aec8547d3325 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 7 Nov 2021 09:14:13 +0100 Subject: [PATCH 025/363] #2005 Parameter type should only be checked if we are mapping from a single argument source --- .../processor/MethodRetrievalProcessor.java | 54 ++++++++++--------- .../test/multisource/MultiSourceMapper.java | 46 ++++++++++++++++ .../multisource/MultiSourceMapperTest.java | 45 ++++++++++++++++ 3 files changed, 119 insertions(+), 26 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/multisource/MultiSourceMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/multisource/MultiSourceMapperTest.java 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 9bf55e731f..b226c1a050 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 @@ -512,25 +512,39 @@ private boolean checkParameterAndReturnType(ExecutableElement method, List elements); + + Target mapFromCollectionAndPrimitive(Collection elements, int value); + + class Target { + private int value; + private Collection elements; + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + public Collection getElements() { + return elements; + } + + public void setElements(Collection elements) { + this.elements = elements; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/multisource/MultiSourceMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/multisource/MultiSourceMapperTest.java new file mode 100644 index 0000000000..c4e1eaaee4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/multisource/MultiSourceMapperTest.java @@ -0,0 +1,45 @@ +/* + * 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.multisource; + +import java.util.Collections; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +public class MultiSourceMapperTest { + + @IssueKey("2005") + @ProcessorTest + @WithClasses({ + MultiSourceMapper.class + }) + void shouldBeAbleToMapFromPrimitiveAndCollectionAsMultiSource() { + MultiSourceMapper.Target target = MultiSourceMapper.INSTANCE.mapFromPrimitiveAndCollection( + 10, + Collections.singleton( "test" ) + ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( 10 ); + assertThat( target.getElements() ).containsExactly( "test" ); + + target = MultiSourceMapper.INSTANCE.mapFromCollectionAndPrimitive( + Collections.singleton( "otherTest" ), + 20 + ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( 20 ); + assertThat( target.getElements() ).containsExactly( "otherTest" ); + } +} From 13bc0c023c82e40f6cb14b41f5fba462859bd308 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 20 Nov 2021 08:48:08 +0100 Subject: [PATCH 026/363] #2553 Support source property paths for maps --- .../ap/internal/model/BeanMappingMethod.java | 23 ++--- .../model/NestedPropertyMappingMethod.java | 22 +++-- .../NestedTargetPropertyMappingHolder.java | 2 +- .../model/beanmapping/SourceReference.java | 84 ++++--------------- .../ap/internal/model/common/Type.java | 50 +++++++++++ .../ap/test/frommap/FromMapMappingTest.java | 14 ++++ ...FromMapAndNestedMapWithDefinedMapping.java | 57 +++++++++++++ 7 files changed, 162 insertions(+), 90 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMapWithDefinedMapping.java 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 0b299067f5..37dec16410 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 @@ -504,7 +504,7 @@ private void handleUnprocessedDefinedTargets() { .build(); Accessor targetPropertyReadAccessor = - method.getResultType().getPropertyReadAccessors().get( propertyName ); + method.getResultType().getReadAccessor( propertyName ); MappingReferences mappingRefs = extractMappingReferences( propertyName, true ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) @@ -1047,7 +1047,7 @@ private boolean handleDefinedMapping(MappingReference mappingRef, Type resultTyp } Accessor targetWriteAccessor = unprocessedTargetProperties.get( targetPropertyName ); - Accessor targetReadAccessor = resultTypeToMap.getPropertyReadAccessors().get( targetPropertyName ); + Accessor targetReadAccessor = resultTypeToMap.getReadAccessor( targetPropertyName ); if ( targetWriteAccessor == null ) { if ( targetReadAccessor == null ) { @@ -1389,7 +1389,7 @@ private void applyPropertyNameBasedMapping(List sourceReference } Accessor targetPropertyReadAccessor = - method.getResultType().getPropertyReadAccessors().get( targetPropertyName ); + method.getResultType().getReadAccessor( targetPropertyName ); MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) .sourceMethod( method ) @@ -1432,7 +1432,7 @@ private void applyParameterNameBasedMapping() { .build(); Accessor targetPropertyReadAccessor = - method.getResultType().getPropertyReadAccessors().get( targetProperty.getKey() ); + method.getResultType().getReadAccessor( targetProperty.getKey() ); MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) @@ -1473,22 +1473,11 @@ private SourceReference getSourceRefByTargetName(Parameter sourceParameter, Stri return sourceRef; } - if ( sourceParameter.getType().isMapType() ) { - List typeParameters = sourceParameter.getType().getTypeParameters(); - if ( typeParameters.size() == 2 && typeParameters.get( 0 ).isString() ) { - return SourceReference.fromMapSource( - new String[] { targetPropertyName }, - sourceParameter - ); - } - } - - Accessor sourceReadAccessor = - sourceParameter.getType().getPropertyReadAccessors().get( targetPropertyName ); + Accessor sourceReadAccessor = sourceParameter.getType().getReadAccessor( targetPropertyName ); if ( sourceReadAccessor != null ) { // property mapping Accessor sourcePresenceChecker = - sourceParameter.getType().getPropertyPresenceCheckers().get( targetPropertyName ); + sourceParameter.getType().getPresenceChecker( targetPropertyName ); DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); Type returnType = ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ); 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 82c02f5385..dd87f8e005 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 @@ -14,9 +14,12 @@ import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.beanmapping.PropertyEntry; +import org.mapstruct.ap.internal.model.presence.SourceReferenceContainsKeyPresenceCheck; import org.mapstruct.ap.internal.model.presence.SourceReferenceMethodPresenceCheck; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.ValueProvider; +import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.AccessorType; /** * This method is used to convert the nested properties as listed in propertyEntries into a method @@ -165,11 +168,20 @@ public static class SafePropertyEntry { public SafePropertyEntry(PropertyEntry entry, String safeName, String previousPropertyName) { this.safeName = safeName; this.readAccessorName = ValueProvider.of( entry.getReadAccessor() ).getValue(); - if ( entry.getPresenceChecker() != null ) { - this.presenceChecker = new SourceReferenceMethodPresenceCheck( - previousPropertyName, - entry.getPresenceChecker().getSimpleName() - ); + Accessor presenceChecker = entry.getPresenceChecker(); + if ( presenceChecker != null ) { + if ( presenceChecker.getAccessorType() == AccessorType.MAP_CONTAINS ) { + this.presenceChecker = new SourceReferenceContainsKeyPresenceCheck( + previousPropertyName, + presenceChecker.getSimpleName() + ); + } + else { + this.presenceChecker = new SourceReferenceMethodPresenceCheck( + previousPropertyName, + presenceChecker.getSimpleName() + ); + } } else { this.presenceChecker = null; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 254ba62871..402c2447b2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -642,7 +642,7 @@ private PropertyMapping createPropertyMappingForNestedTarget(MappingReferences m boolean forceUpdateMethod) { Accessor targetWriteAccessor = targetPropertiesWriteAccessors.get( targetPropertyName ); - Accessor targetReadAccessor = targetType.getPropertyReadAccessors().get( targetPropertyName ); + Accessor targetReadAccessor = targetType.getReadAccessor( targetPropertyName ); if ( targetWriteAccessor == null ) { Set readAccessors = targetType.getPropertyReadAccessors().keySet(); String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 15b10b5782..47f4830838 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -7,16 +7,13 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; @@ -27,8 +24,6 @@ import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; -import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; -import org.mapstruct.ap.internal.util.accessor.MapValuePresenceChecker; import static org.mapstruct.ap.internal.model.beanmapping.PropertyEntry.forSourceReference; import static org.mapstruct.ap.internal.util.Collections.last; @@ -51,32 +46,13 @@ *

  • {@code propertyEntries[1]} will describe {@code propB}
  • * * - * After building, {@link #isValid()} will return true when when no problems are detected during building. + * After building, {@link #isValid()} will return true when no problems are detected during building. * * @author Sjaak Derksen + * @author Filip Hrisafov */ public class SourceReference extends AbstractReference { - public static SourceReference fromMapSource(String[] segments, Parameter parameter) { - Type parameterType = parameter.getType(); - Type valueType = parameterType.getTypeParameters().get( 1 ); - - TypeElement typeElement = parameterType.getTypeElement(); - TypeMirror typeMirror = valueType.getTypeMirror(); - String simpleName = String.join( ".", segments ); - - MapValueAccessor mapValueAccessor = new MapValueAccessor( typeElement, typeMirror, simpleName ); - MapValuePresenceChecker mapValuePresenceChecker = new MapValuePresenceChecker( - typeElement, - typeMirror, - simpleName - ); - List entries = Collections.singletonList( - PropertyEntry.forSourceReference( segments, mapValueAccessor, mapValuePresenceChecker, valueType ) - ); - return new SourceReference( parameter, entries, true ); - } - /** * Builds a {@link SourceReference} from an {@code @Mappping}. */ @@ -173,11 +149,6 @@ public SourceReference build() { * @return the source reference */ private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) { - - if ( canBeTreatedAsMapSourceType( parameter.getType() ) ) { - return fromMapSource( segments, parameter ); - } - boolean foundEntryMatch; String[] propertyNames = segments; @@ -214,14 +185,6 @@ private SourceReference buildFromSingleSourceParameters(String[] segments, Param */ private SourceReference buildFromMultipleSourceParameters(String[] segments, Parameter parameter) { - if (parameter != null && canBeTreatedAsMapSourceType( parameter.getType() )) { - String[] propertyNames = new String[0]; - if ( segments.length > 1 ) { - propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); - } - return fromMapSource( propertyNames, parameter ); - } - boolean foundEntryMatch; String[] propertyNames = new String[0]; @@ -244,17 +207,8 @@ private SourceReference buildFromMultipleSourceParameters(String[] segments, Par return new SourceReference( parameter, entries, foundEntryMatch ); } - private boolean canBeTreatedAsMapSourceType(Type type) { - if ( !type.isMapType() ) { - return false; - } - - List typeParameters = type.getTypeParameters(); - return typeParameters.size() == 2 && typeParameters.get( 0 ).isString(); - } - /** - * When there are more than one source parameters, the first segment name of the propery + * When there are more than one source parameters, the first segment name of the property * needs to match the parameter name to avoid ambiguity * * consider: {@code Target map( Source1 source1, Source2 source2 )} @@ -356,24 +310,20 @@ private List matchWithSourceAccessorTypes(Type type, String[] ent Type newType = type; for ( int i = 0; i < entryNames.length; i++ ) { boolean matchFound = false; - Map sourceReadAccessors = newType.getPropertyReadAccessors(); - Map sourcePresenceCheckers = newType.getPropertyPresenceCheckers(); - - for ( Map.Entry getter : sourceReadAccessors.entrySet() ) { - if ( getter.getKey().equals( entryNames[i] ) ) { - newType = typeFactory.getReturnType( - (DeclaredType) newType.getTypeMirror(), - getter.getValue() - ); - sourceEntries.add( forSourceReference( - Arrays.copyOf( entryNames, i + 1 ), - getter.getValue(), - sourcePresenceCheckers.get( entryNames[i] ), - newType - ) ); - matchFound = true; - break; - } + Accessor readAccessor = newType.getReadAccessor( entryNames[i] ); + if ( readAccessor != null ) { + Accessor presenceChecker = newType.getPresenceChecker( entryNames[i] ); + newType = typeFactory.getReturnType( + (DeclaredType) newType.getTypeMirror(), + readAccessor + ); + sourceEntries.add( forSourceReference( + Arrays.copyOf( entryNames, i + 1 ), + readAccessor, + presenceChecker, + newType + ) ); + matchFound = true; } if ( !matchFound ) { break; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 00bcae371c..4abec925cf 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -48,6 +48,8 @@ import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; +import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; +import org.mapstruct.ap.internal.util.accessor.MapValuePresenceChecker; import static org.mapstruct.ap.internal.util.Collections.first; @@ -60,6 +62,7 @@ * through {@link TypeFactory}. * * @author Gunnar Morling + * @author Filip Hrisafov */ public class Type extends ModelElement implements Comparable { @@ -308,6 +311,17 @@ public boolean isMapType() { return isMapType; } + private boolean hasStringMapSignature() { + if ( isMapType() ) { + List typeParameters = getTypeParameters(); + if ( typeParameters.size() == 2 && typeParameters.get( 0 ).isString() ) { + return true; + } + } + + return false; + } + public boolean isCollectionOrMapType() { return isCollectionType || isMapType; } @@ -597,6 +611,42 @@ public Type asRawType() { } } + public Accessor getReadAccessor(String propertyName) { + if ( hasStringMapSignature() ) { + ExecutableElement getMethod = getAllMethods() + .stream() + .filter( m -> m.getSimpleName().contentEquals( "get" ) ) + .filter( m -> m.getParameters().size() == 1 ) + .findAny() + .orElse( null ); + return new MapValueAccessor( getMethod, typeParameters.get( 1 ).getTypeMirror(), propertyName ); + } + + Map readAccessors = getPropertyReadAccessors(); + + return readAccessors.get( propertyName ); + } + + public Accessor getPresenceChecker(String propertyName) { + if ( hasStringMapSignature() ) { + ExecutableElement containsKeyMethod = getAllMethods() + .stream() + .filter( m -> m.getSimpleName().contentEquals( "containsKey" ) ) + .filter( m -> m.getParameters().size() == 1 ) + .findAny() + .orElse( null ); + + return new MapValuePresenceChecker( + containsKeyMethod, + typeParameters.get( 1 ).getTypeMirror(), + propertyName + ); + } + + Map presenceCheckers = getPropertyPresenceCheckers(); + return presenceCheckers.get( propertyName ); + } + /** * getPropertyReadAccessors * diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java index 548aecc1ec..bcb78d8dcc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -261,6 +261,20 @@ void shouldMapFromNestedMap() { assertThat( target.getNestedTarget().getStringFromNestedMap() ).isEqualTo( "valueFromNestedMap" ); } + @IssueKey("2553") + @ProcessorTest + @WithClasses(MapToBeanFromMapAndNestedMapWithDefinedMapping.class) + void shouldMapFromNestedMapWithDefinedMapping() { + + MapToBeanFromMapAndNestedMapWithDefinedMapping.Source source = + new MapToBeanFromMapAndNestedMapWithDefinedMapping.Source(); + MapToBeanFromMapAndNestedMapWithDefinedMapping.Target target = + MapToBeanFromMapAndNestedMapWithDefinedMapping.INSTANCE.toTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getNested() ).isEqualTo( "valueFromNestedMap" ); + } + @ProcessorTest @WithClasses(ObjectMapToBeanWithQualifierMapper.class) void shouldUseObjectQualifiedMethod() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMapWithDefinedMapping.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMapWithDefinedMapping.java new file mode 100644 index 0000000000..2203836ea6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMapWithDefinedMapping.java @@ -0,0 +1,57 @@ +/* + * 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.frommap; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MapToBeanFromMapAndNestedMapWithDefinedMapping { + + MapToBeanFromMapAndNestedMapWithDefinedMapping INSTANCE = Mappers.getMapper( + MapToBeanFromMapAndNestedMapWithDefinedMapping.class ); + + @Mapping(target = "nested", source = "nestedTarget.nested") + Target toTarget(Source source); + + class Source { + + private Map nestedTarget = new HashMap<>(); + + public Map getNestedTarget() { + return nestedTarget; + } + + public void setNestedTarget(Map nestedTarget) { + this.nestedTarget = nestedTarget; + } + + public Source() { + nestedTarget.put( "nested", "valueFromNestedMap" ); + } + } + + class Target { + + private String nested; + + public String getNested() { + return nested; + } + + public void setNested(String nested) { + this.nested = nested; + } + } + +} From 754aaf2ef4a15e7956abb4178c4e79c3d821882d Mon Sep 17 00:00:00 2001 From: dersvenhesse Date: Sun, 21 Nov 2021 22:01:09 +0100 Subject: [PATCH 027/363] [DOCS] Fixed reference variable --- .../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index 2d2b930114..370c09e249 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -696,7 +696,7 @@ public class CustomerMapperImpl implements CustomerMapper { customer.setId( Integer.parseInt( map.get( "id" ) ) ); } if ( map.containsKey( "customerName" ) ) { - customer.setName( source.get( "customerName" ) ); + customer.setName( map.get( "customerName" ) ); } // ... } From 00df0bc3d0ab347fb79d726c3b19417b04b503c2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 3 Dec 2021 19:13:41 +0100 Subject: [PATCH 028/363] #2680 Refactor accessors Split the `PresenceCheck`s accessor from the current `Accessor`. Introduce a `ReadAccessor` that would allow us to more easily implement certain things. Remove `MAP_GET` and `MAP_CONTAINS` from the AccessorType and use the new refactored mechanism --- .../ap/internal/model/BeanMappingMethod.java | 21 +++--- .../model/NestedPropertyMappingMethod.java | 29 +++------ .../NestedTargetPropertyMappingHolder.java | 3 +- .../ap/internal/model/PropertyMapping.java | 42 +++++------- .../model/beanmapping/PropertyEntry.java | 18 +++--- .../model/beanmapping/SourceReference.java | 19 +++--- .../ap/internal/model/common/Type.java | 64 +++++++++---------- ...urceReferenceContainsKeyPresenceCheck.java | 59 ----------------- ...nceCheck.java => SuffixPresenceCheck.java} | 20 +++--- .../mapstruct/ap/internal/util/Filters.java | 25 +++----- .../ap/internal/util/ValueProvider.java | 58 ----------------- .../internal/util/accessor/AccessorType.java | 5 +- .../util/accessor/DelegateAccessor.java | 48 ++++++++++++++ .../util/accessor/MapValueAccessor.java | 9 ++- .../accessor/MapValuePresenceChecker.java | 55 ---------------- .../util/accessor/PresenceCheckAccessor.java | 29 +++++++++ .../internal/util/accessor/ReadAccessor.java | 36 +++++++++++ .../util/accessor/ReadDelegateAccessor.java | 17 +++++ ...ourceReferenceContainsKeyPresenceCheck.ftl | 9 --- ...senceCheck.ftl => SuffixPresenceCheck.ftl} | 4 +- 20 files changed, 251 insertions(+), 319 deletions(-) delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/presence/SourceReferenceContainsKeyPresenceCheck.java rename processor/src/main/java/org/mapstruct/ap/internal/model/presence/{SourceReferenceMethodPresenceCheck.java => SuffixPresenceCheck.java} (66%) delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java delete mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SourceReferenceContainsKeyPresenceCheck.ftl rename processor/src/main/resources/org/mapstruct/ap/internal/model/presence/{SourceReferenceMethodPresenceCheck.ftl => SuffixPresenceCheck.ftl} (72%) 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 37dec16410..bdfd8cf54a 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 @@ -64,6 +64,8 @@ import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; import org.mapstruct.ap.internal.util.accessor.ParameterElementAccessor; +import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; import static org.mapstruct.ap.internal.model.beanmapping.MappingReferences.forSourceMethod; import static org.mapstruct.ap.internal.util.Collections.first; @@ -257,9 +259,9 @@ else if ( !method.isUpdateMethod() ) { continue; } - Map readAccessors = sourceParameter.getType().getPropertyReadAccessors(); + Map readAccessors = sourceParameter.getType().getPropertyReadAccessors(); - for ( Entry entry : readAccessors.entrySet() ) { + for ( Entry entry : readAccessors.entrySet() ) { unprocessedSourceProperties.put( entry.getKey(), entry.getValue() ); } } @@ -503,7 +505,7 @@ private void handleUnprocessedDefinedTargets() { .name( propertyName ) .build(); - Accessor targetPropertyReadAccessor = + ReadAccessor targetPropertyReadAccessor = method.getResultType().getReadAccessor( propertyName ); MappingReferences mappingRefs = extractMappingReferences( propertyName, true ); PropertyMapping propertyMapping = new PropertyMappingBuilder() @@ -1047,7 +1049,7 @@ private boolean handleDefinedMapping(MappingReference mappingRef, Type resultTyp } Accessor targetWriteAccessor = unprocessedTargetProperties.get( targetPropertyName ); - Accessor targetReadAccessor = resultTypeToMap.getReadAccessor( targetPropertyName ); + ReadAccessor targetReadAccessor = resultTypeToMap.getReadAccessor( targetPropertyName ); if ( targetWriteAccessor == null ) { if ( targetReadAccessor == null ) { @@ -1388,7 +1390,7 @@ private void applyPropertyNameBasedMapping(List sourceReference continue; } - Accessor targetPropertyReadAccessor = + ReadAccessor targetPropertyReadAccessor = method.getResultType().getReadAccessor( targetPropertyName ); MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) @@ -1431,7 +1433,7 @@ private void applyParameterNameBasedMapping() { .name( targetProperty.getKey() ) .build(); - Accessor targetPropertyReadAccessor = + ReadAccessor targetPropertyReadAccessor = method.getResultType().getReadAccessor( targetProperty.getKey() ); MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false ); PropertyMapping propertyMapping = new PropertyMappingBuilder() @@ -1453,7 +1455,8 @@ private void applyParameterNameBasedMapping() { // The source parameter was directly mapped so ignore all of its source properties completely if ( !sourceParameter.getType().isPrimitive() && !sourceParameter.getType().isArrayType() ) { // We explicitly ignore source properties from primitives or array types - Map readAccessors = sourceParameter.getType().getPropertyReadAccessors(); + Map readAccessors = sourceParameter.getType() + .getPropertyReadAccessors(); for ( String sourceProperty : readAccessors.keySet() ) { unprocessedSourceProperties.remove( sourceProperty ); } @@ -1473,10 +1476,10 @@ private SourceReference getSourceRefByTargetName(Parameter sourceParameter, Stri return sourceRef; } - Accessor sourceReadAccessor = sourceParameter.getType().getReadAccessor( targetPropertyName ); + ReadAccessor sourceReadAccessor = sourceParameter.getType().getReadAccessor( targetPropertyName ); if ( sourceReadAccessor != null ) { // property mapping - Accessor sourcePresenceChecker = + PresenceCheckAccessor sourcePresenceChecker = sourceParameter.getType().getPresenceChecker( targetPropertyName ); DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); 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 dd87f8e005..96004cb474 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 @@ -10,16 +10,13 @@ import java.util.Objects; import java.util.Set; +import org.mapstruct.ap.internal.model.beanmapping.PropertyEntry; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.beanmapping.PropertyEntry; -import org.mapstruct.ap.internal.model.presence.SourceReferenceContainsKeyPresenceCheck; -import org.mapstruct.ap.internal.model.presence.SourceReferenceMethodPresenceCheck; +import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; import org.mapstruct.ap.internal.util.Strings; -import org.mapstruct.ap.internal.util.ValueProvider; -import org.mapstruct.ap.internal.util.accessor.Accessor; -import org.mapstruct.ap.internal.util.accessor.AccessorType; +import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; /** * This method is used to convert the nested properties as listed in propertyEntries into a method @@ -167,21 +164,13 @@ public static class SafePropertyEntry { public SafePropertyEntry(PropertyEntry entry, String safeName, String previousPropertyName) { this.safeName = safeName; - this.readAccessorName = ValueProvider.of( entry.getReadAccessor() ).getValue(); - Accessor presenceChecker = entry.getPresenceChecker(); + this.readAccessorName = entry.getReadAccessor().getReadValueSource(); + PresenceCheckAccessor presenceChecker = entry.getPresenceChecker(); if ( presenceChecker != null ) { - if ( presenceChecker.getAccessorType() == AccessorType.MAP_CONTAINS ) { - this.presenceChecker = new SourceReferenceContainsKeyPresenceCheck( - previousPropertyName, - presenceChecker.getSimpleName() - ); - } - else { - this.presenceChecker = new SourceReferenceMethodPresenceCheck( - previousPropertyName, - presenceChecker.getSimpleName() - ); - } + this.presenceChecker = new SuffixPresenceCheck( + previousPropertyName, + presenceChecker.getPresenceCheckSuffix() + ); } else { this.presenceChecker = null; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 402c2447b2..61e3926047 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -21,6 +21,7 @@ import org.mapstruct.ap.internal.model.beanmapping.SourceReference; import org.mapstruct.ap.internal.model.beanmapping.TargetReference; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; @@ -642,7 +643,7 @@ private PropertyMapping createPropertyMappingForNestedTarget(MappingReferences m boolean forceUpdateMethod) { Accessor targetWriteAccessor = targetPropertiesWriteAccessors.get( targetPropertyName ); - Accessor targetReadAccessor = targetType.getReadAccessor( targetPropertyName ); + ReadAccessor targetReadAccessor = targetType.getReadAccessor( targetPropertyName ); if ( targetWriteAccessor == null ) { Set readAccessors = targetType.getPropertyReadAccessors().keySet(); String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); 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 1d902e54e7..c83f856587 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 @@ -37,8 +37,7 @@ import org.mapstruct.ap.internal.model.presence.AllPresenceChecksPresenceCheck; import org.mapstruct.ap.internal.model.presence.JavaExpressionPresenceCheck; import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; -import org.mapstruct.ap.internal.model.presence.SourceReferenceContainsKeyPresenceCheck; -import org.mapstruct.ap.internal.model.presence.SourceReferenceMethodPresenceCheck; +import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; import org.mapstruct.ap.internal.model.source.DelegatingOptions; import org.mapstruct.ap.internal.model.source.MappingControl; import org.mapstruct.ap.internal.model.source.MappingOptions; @@ -48,9 +47,9 @@ 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 org.mapstruct.ap.internal.util.accessor.AccessorType; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS; import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; @@ -73,7 +72,7 @@ public class PropertyMapping extends ModelElement { private final String name; private final String sourceBeanName; private final String targetWriteAccessorName; - private final ValueProvider targetReadAccessorProvider; + private final ReadAccessor targetReadAccessorProvider; private final Type targetType; private final Assignment assignment; private final Set dependsOn; @@ -87,7 +86,7 @@ private static class MappingBuilderBase> extends protected AccessorType targetWriteAccessorType; protected Type targetType; protected BuilderType targetBuilderType; - protected Accessor targetReadAccessor; + protected ReadAccessor targetReadAccessor; protected String targetPropertyName; protected String sourcePropertyName; @@ -103,7 +102,7 @@ public T sourceMethod(Method sourceMethod) { return super.method( sourceMethod ); } - public T target(String targetPropertyName, Accessor targetReadAccessor, Accessor targetWriteAccessor) { + public T target(String targetPropertyName, ReadAccessor targetReadAccessor, Accessor targetWriteAccessor) { this.targetPropertyName = targetPropertyName; this.targetReadAccessor = targetReadAccessor; this.targetWriteAccessor = targetWriteAccessor; @@ -290,7 +289,7 @@ else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.get targetPropertyName, rightHandSide.getSourceParameterName(), targetWriteAccessor.getSimpleName(), - ValueProvider.of( targetReadAccessor ), + targetReadAccessor, targetType, assignment, dependsOn, @@ -567,7 +566,7 @@ private SourceRHS getSourceRHS( SourceReference sourceReference ) { } // simple property else if ( !sourceReference.isNested() ) { - String sourceRef = sourceParam.getName() + "." + ValueProvider.of( propertyEntry.getReadAccessor() ); + String sourceRef = sourceParam.getName() + "." + propertyEntry.getReadAccessor().getReadValueSource(); SourceRHS sourceRHS = new SourceRHS( sourceParam.getName(), sourceRef, @@ -660,28 +659,21 @@ private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReferenc // in the forged method? PropertyEntry propertyEntry = sourceReference.getShallowestProperty(); if ( propertyEntry.getPresenceChecker() != null ) { - if (propertyEntry.getPresenceChecker().getAccessorType() == AccessorType.MAP_CONTAINS ) { - return new SourceReferenceContainsKeyPresenceCheck( - sourceParam.getName(), - propertyEntry.getPresenceChecker().getSimpleName() - ); - } - List presenceChecks = new ArrayList<>(); - presenceChecks.add( new SourceReferenceMethodPresenceCheck( + presenceChecks.add( new SuffixPresenceCheck( sourceParam.getName(), - propertyEntry.getPresenceChecker().getSimpleName() + propertyEntry.getPresenceChecker().getPresenceCheckSuffix() ) ); String variableName = sourceParam.getName() + "." - + propertyEntry.getReadAccessor().getSimpleName() + "()"; + + propertyEntry.getReadAccessor().getReadValueSource(); for (int i = 1; i < sourceReference.getPropertyEntries().size(); i++) { PropertyEntry entry = sourceReference.getPropertyEntries().get( i ); if (entry.getPresenceChecker() != null && entry.getReadAccessor() != null) { presenceChecks.add( new NullPresenceCheck( variableName ) ); - presenceChecks.add( new SourceReferenceMethodPresenceCheck( + presenceChecks.add( new SuffixPresenceCheck( variableName, - entry.getPresenceChecker().getSimpleName() + entry.getPresenceChecker().getPresenceCheckSuffix() ) ); variableName = variableName + "." + entry.getReadAccessor().getSimpleName() + "()"; } @@ -992,7 +984,7 @@ else if ( errorMessageDetails == null ) { return new PropertyMapping( targetPropertyName, targetWriteAccessor.getSimpleName(), - ValueProvider.of( targetReadAccessor ), + targetReadAccessor, targetType, assignment, dependsOn, @@ -1058,7 +1050,7 @@ public PropertyMapping build() { return new PropertyMapping( targetPropertyName, targetWriteAccessor.getSimpleName(), - ValueProvider.of( targetReadAccessor ), + targetReadAccessor, targetType, assignment, dependsOn, @@ -1071,7 +1063,7 @@ public PropertyMapping build() { // Constructor for creating mappings of constant expressions. private PropertyMapping(String name, String targetWriteAccessorName, - ValueProvider targetReadAccessorProvider, + ReadAccessor targetReadAccessorProvider, Type targetType, Assignment propertyAssignment, Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { this( name, null, targetWriteAccessorName, targetReadAccessorProvider, @@ -1081,7 +1073,7 @@ private PropertyMapping(String name, String targetWriteAccessorName, } private PropertyMapping(String name, String sourceBeanName, String targetWriteAccessorName, - ValueProvider targetReadAccessorProvider, Type targetType, + ReadAccessor targetReadAccessorProvider, Type targetType, Assignment assignment, Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { this.name = name; @@ -1112,7 +1104,7 @@ public String getTargetWriteAccessorName() { } public String getTargetReadAccessorName() { - return targetReadAccessorProvider == null ? null : targetReadAccessorProvider.getValue(); + return targetReadAccessorProvider == null ? null : targetReadAccessorProvider.getReadValueSource(); } public Type getTargetType() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java index 8ac5e46671..cb6cd8af79 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java @@ -9,7 +9,8 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.util.Strings; -import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; /** * A PropertyEntry contains information on the name, readAccessor and presenceCheck (for source) @@ -18,8 +19,8 @@ public class PropertyEntry { private final String[] fullName; - private final Accessor readAccessor; - private final Accessor presenceChecker; + private final ReadAccessor readAccessor; + private final PresenceCheckAccessor presenceChecker; private final Type type; /** @@ -29,7 +30,8 @@ public class PropertyEntry { * @param readAccessor * @param type */ - private PropertyEntry(String[] fullName, Accessor readAccessor, Accessor presenceChecker, Type type) { + private PropertyEntry(String[] fullName, ReadAccessor readAccessor, PresenceCheckAccessor presenceChecker, + Type type) { this.fullName = fullName; this.readAccessor = readAccessor; this.presenceChecker = presenceChecker; @@ -45,8 +47,8 @@ private PropertyEntry(String[] fullName, Accessor readAccessor, Accessor presenc * @param type type of the property * @return the property entry for given parameters. */ - public static PropertyEntry forSourceReference(String[] name, Accessor readAccessor, - Accessor presenceChecker, Type type) { + public static PropertyEntry forSourceReference(String[] name, ReadAccessor readAccessor, + PresenceCheckAccessor presenceChecker, Type type) { return new PropertyEntry( name, readAccessor, presenceChecker, type ); } @@ -54,11 +56,11 @@ public String getName() { return fullName[fullName.length - 1]; } - public Accessor getReadAccessor() { + public ReadAccessor getReadAccessor() { return readAccessor; } - public Accessor getPresenceChecker() { + public PresenceCheckAccessor getPresenceChecker() { return presenceChecker; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 47f4830838..d5c18a99f1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -23,7 +23,8 @@ import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; -import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; import static org.mapstruct.ap.internal.model.beanmapping.PropertyEntry.forSourceReference; import static org.mapstruct.ap.internal.util.Collections.last; @@ -310,9 +311,9 @@ private List matchWithSourceAccessorTypes(Type type, String[] ent Type newType = type; for ( int i = 0; i < entryNames.length; i++ ) { boolean matchFound = false; - Accessor readAccessor = newType.getReadAccessor( entryNames[i] ); + ReadAccessor readAccessor = newType.getReadAccessor( entryNames[i] ); if ( readAccessor != null ) { - Accessor presenceChecker = newType.getPresenceChecker( entryNames[i] ); + PresenceCheckAccessor presenceChecker = newType.getPresenceChecker( entryNames[i] ); newType = typeFactory.getReturnType( (DeclaredType) newType.getTypeMirror(), readAccessor @@ -343,8 +344,8 @@ private void reportMappingError(Message msg, Object... objects) { public static class BuilderFromProperty { private String name; - private Accessor readAccessor; - private Accessor presenceChecker; + private ReadAccessor readAccessor; + private PresenceCheckAccessor presenceChecker; private Type type; private Parameter sourceParameter; @@ -353,12 +354,12 @@ public BuilderFromProperty name(String name) { return this; } - public BuilderFromProperty readAccessor(Accessor readAccessor) { + public BuilderFromProperty readAccessor(ReadAccessor readAccessor) { this.readAccessor = readAccessor; return this; } - public BuilderFromProperty presenceChecker(Accessor presenceChecker) { + public BuilderFromProperty presenceChecker(PresenceCheckAccessor presenceChecker) { this.presenceChecker = presenceChecker; return this; } @@ -430,10 +431,10 @@ public List push(TypeFactory typeFactory, FormattingMessager me PropertyEntry deepestProperty = getDeepestProperty(); if ( deepestProperty != null ) { Type type = deepestProperty.getType(); - Map newDeepestReadAccessors = type.getPropertyReadAccessors(); + Map newDeepestReadAccessors = type.getPropertyReadAccessors(); String parameterName = getParameter().getName(); String deepestPropertyFullName = deepestProperty.getFullName(); - for ( Map.Entry newDeepestReadAccessorEntry : newDeepestReadAccessors.entrySet() ) { + for ( Map.Entry newDeepestReadAccessorEntry : newDeepestReadAccessors.entrySet() ) { // Always include the parameter name in the new full name. // Otherwise multi source parameters might be reported incorrectly String newFullName = diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 4abec925cf..ab2871c69d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -48,8 +48,10 @@ import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; +import org.mapstruct.ap.internal.util.accessor.FieldElementAccessor; import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; -import org.mapstruct.ap.internal.util.accessor.MapValuePresenceChecker; +import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; import static org.mapstruct.ap.internal.util.Collections.first; @@ -101,8 +103,8 @@ public class Type extends ModelElement implements Comparable { private final Map notToBeImportedTypes; private Boolean isToBeImported; - private Map readAccessors = null; - private Map presenceCheckers = null; + private Map readAccessors = null; + private Map presenceCheckers = null; private List allMethods = null; private List allFields = null; @@ -611,7 +613,7 @@ public Type asRawType() { } } - public Accessor getReadAccessor(String propertyName) { + public ReadAccessor getReadAccessor(String propertyName) { if ( hasStringMapSignature() ) { ExecutableElement getMethod = getAllMethods() .stream() @@ -622,28 +624,17 @@ public Accessor getReadAccessor(String propertyName) { return new MapValueAccessor( getMethod, typeParameters.get( 1 ).getTypeMirror(), propertyName ); } - Map readAccessors = getPropertyReadAccessors(); + Map readAccessors = getPropertyReadAccessors(); return readAccessors.get( propertyName ); } - public Accessor getPresenceChecker(String propertyName) { + public PresenceCheckAccessor getPresenceChecker(String propertyName) { if ( hasStringMapSignature() ) { - ExecutableElement containsKeyMethod = getAllMethods() - .stream() - .filter( m -> m.getSimpleName().contentEquals( "containsKey" ) ) - .filter( m -> m.getParameters().size() == 1 ) - .findAny() - .orElse( null ); - - return new MapValuePresenceChecker( - containsKeyMethod, - typeParameters.get( 1 ).getTypeMirror(), - propertyName - ); + return PresenceCheckAccessor.mapContainsKey( propertyName ); } - Map presenceCheckers = getPropertyPresenceCheckers(); + Map presenceCheckers = getPropertyPresenceCheckers(); return presenceCheckers.get( propertyName ); } @@ -652,15 +643,15 @@ public Accessor getPresenceChecker(String propertyName) { * * @return an unmodifiable map of all read accessors (including 'is' for booleans), indexed by property name */ - public Map getPropertyReadAccessors() { + public Map getPropertyReadAccessors() { if ( readAccessors == null ) { - Map modifiableGetters = new LinkedHashMap<>(); + Map modifiableGetters = new LinkedHashMap<>(); - Map recordAccessors = filters.recordAccessorsIn( getRecordComponents() ); + Map recordAccessors = filters.recordAccessorsIn( getRecordComponents() ); modifiableGetters.putAll( recordAccessors ); - List getterList = filters.getterMethodsIn( getAllMethods() ); - for ( Accessor getter : getterList ) { + List getterList = filters.getterMethodsIn( getAllMethods() ); + for ( ReadAccessor getter : getterList ) { String simpleName = getter.getSimpleName(); if ( recordAccessors.containsKey( simpleName ) ) { // If there is already a record accessor that contains the simple name @@ -690,8 +681,8 @@ public Map getPropertyReadAccessors() { } } - List fieldsList = filters.fieldsIn( getAllFields() ); - for ( Accessor field : fieldsList ) { + List fieldsList = filters.fieldsIn( getAllFields(), ReadAccessor::fromField ); + for ( ReadAccessor field : fieldsList ) { String propertyName = getPropertyName( field ); // If there was no getter or is method for booleans, then resort to the field. // If a field was already added do not add it again. @@ -707,12 +698,15 @@ public Map getPropertyReadAccessors() { * * @return an unmodifiable map of all presence checkers, indexed by property name */ - public Map getPropertyPresenceCheckers() { + public Map getPropertyPresenceCheckers() { if ( presenceCheckers == null ) { - List checkerList = filters.presenceCheckMethodsIn( getAllMethods() ); - Map modifiableCheckers = new LinkedHashMap<>(); - for ( Accessor checker : checkerList ) { - modifiableCheckers.put( getPropertyName( checker ), checker ); + List checkerList = filters.presenceCheckMethodsIn( getAllMethods() ); + Map modifiableCheckers = new LinkedHashMap<>(); + for ( ExecutableElement checker : checkerList ) { + modifiableCheckers.put( + getPropertyName( checker ), + PresenceCheckAccessor.methodInvocation( checker ) + ); } presenceCheckers = Collections.unmodifiableMap( modifiableCheckers ); } @@ -841,13 +835,17 @@ private List nullSafeTypeElementListConversion(Function getAlternativeTargetAccessors() { List setterMethods = getSetters(); List readAccessors = new ArrayList<>( getPropertyReadAccessors().values() ); // All the fields are also alternative accessors - readAccessors.addAll( filters.fieldsIn( getAllFields() ) ); + readAccessors.addAll( filters.fieldsIn( getAllFields(), FieldElementAccessor::new ) ); // there could be a read accessor (field or method) for a list/map that is not present as setter. // an accessor could substitute the setter in that case and act as setter. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SourceReferenceContainsKeyPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SourceReferenceContainsKeyPresenceCheck.java deleted file mode 100644 index d4c011a051..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SourceReferenceContainsKeyPresenceCheck.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.internal.model.presence; - -import java.util.Collections; -import java.util.Objects; -import java.util.Set; - -import org.mapstruct.ap.internal.model.common.ModelElement; -import org.mapstruct.ap.internal.model.common.PresenceCheck; -import org.mapstruct.ap.internal.model.common.Type; - -/** - * @author Filip Hrisafov - */ -public class SourceReferenceContainsKeyPresenceCheck extends ModelElement implements PresenceCheck { - - private final String sourceReference; - private final String propertyName; - - public SourceReferenceContainsKeyPresenceCheck(String sourceReference, String propertyName) { - this.sourceReference = sourceReference; - this.propertyName = propertyName; - } - - public String getSourceReference() { - return sourceReference; - } - - public String getPropertyName() { - return propertyName; - } - - @Override - public Set getImportTypes() { - return Collections.emptySet(); - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( o == null || getClass() != o.getClass() ) { - return false; - } - SourceReferenceContainsKeyPresenceCheck that = (SourceReferenceContainsKeyPresenceCheck) o; - return Objects.equals( sourceReference, that.sourceReference ) && - Objects.equals( propertyName, that.propertyName ); - } - - @Override - public int hashCode() { - return Objects.hash( sourceReference, propertyName ); - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SourceReferenceMethodPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java similarity index 66% rename from processor/src/main/java/org/mapstruct/ap/internal/model/presence/SourceReferenceMethodPresenceCheck.java rename to processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java index 374eac0c8e..3710e97173 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SourceReferenceMethodPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java @@ -16,22 +16,22 @@ /** * @author Filip Hrisafov */ -public class SourceReferenceMethodPresenceCheck extends ModelElement implements PresenceCheck { +public class SuffixPresenceCheck extends ModelElement implements PresenceCheck { private final String sourceReference; - private final String methodName; + private final String suffix; - public SourceReferenceMethodPresenceCheck(String sourceReference, String methodName) { + public SuffixPresenceCheck(String sourceReference, String suffix) { this.sourceReference = sourceReference; - this.methodName = methodName; + this.suffix = suffix; } public String getSourceReference() { return sourceReference; } - public String getMethodName() { - return methodName; + public String getSuffix() { + return suffix; } @Override @@ -47,13 +47,13 @@ public boolean equals(Object o) { if ( o == null || getClass() != o.getClass() ) { return false; } - SourceReferenceMethodPresenceCheck that = (SourceReferenceMethodPresenceCheck) o; - return Objects.equals( sourceReference, that.sourceReference ) && - Objects.equals( methodName, that.methodName ); + SuffixPresenceCheck that = (SuffixPresenceCheck) o; + return Objects.equals( sourceReference, that.sourceReference ) + && Objects.equals( suffix, that.suffix ); } @Override public int hashCode() { - return Objects.hash( sourceReference, methodName ); + return Objects.hash( sourceReference, suffix ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java index 9cb923b3c8..123f72832b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java @@ -12,6 +12,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -23,12 +24,10 @@ import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; -import org.mapstruct.ap.internal.util.accessor.FieldElementAccessor; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; import static org.mapstruct.ap.internal.util.Collections.first; import static org.mapstruct.ap.internal.util.accessor.AccessorType.ADDER; -import static org.mapstruct.ap.internal.util.accessor.AccessorType.GETTER; -import static org.mapstruct.ap.internal.util.accessor.AccessorType.PRESENCE_CHECKER; import static org.mapstruct.ap.internal.util.accessor.AccessorType.SETTER; /** @@ -68,10 +67,10 @@ public Filters(AccessorNamingUtils accessorNaming, TypeUtils typeUtils, TypeMirr this.typeMirror = typeMirror; } - public List getterMethodsIn(List elements) { + public List getterMethodsIn(List elements) { return elements.stream() .filter( accessorNaming::isGetterMethod ) - .map( method -> new ExecutableElementAccessor( method, getReturnType( method ), GETTER ) ) + .map( method -> ReadAccessor.fromGetter( method, getReturnType( method ) ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } @@ -89,21 +88,18 @@ public List recordComponentsIn(TypeElement typeElement) { } } - public Map recordAccessorsIn(Collection recordComponents) { + public Map recordAccessorsIn(Collection recordComponents) { if ( RECORD_COMPONENT_ACCESSOR_METHOD == null ) { return java.util.Collections.emptyMap(); } try { - Map recordAccessors = new LinkedHashMap<>(); + Map recordAccessors = new LinkedHashMap<>(); for ( Element recordComponent : recordComponents ) { ExecutableElement recordExecutableElement = (ExecutableElement) RECORD_COMPONENT_ACCESSOR_METHOD.invoke( recordComponent ); recordAccessors.put( recordComponent.getSimpleName().toString(), - new ExecutableElementAccessor( recordExecutableElement, - getReturnType( recordExecutableElement ), - GETTER - ) + ReadAccessor.fromGetter( recordExecutableElement, getReturnType( recordExecutableElement ) ) ); } @@ -118,17 +114,16 @@ private TypeMirror getReturnType(ExecutableElement executableElement) { return getWithinContext( executableElement ).getReturnType(); } - public List fieldsIn(List accessors) { + public List fieldsIn(List accessors, Function creator) { return accessors.stream() .filter( Fields::isFieldAccessor ) - .map( FieldElementAccessor::new ) + .map( creator ) .collect( Collectors.toCollection( LinkedList::new ) ); } - public List presenceCheckMethodsIn(List elements) { + public List presenceCheckMethodsIn(List elements) { return elements.stream() .filter( accessorNaming::isPresenceCheckMethod ) - .map( method -> new ExecutableElementAccessor( method, getReturnType( method ), PRESENCE_CHECKER ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java deleted file mode 100644 index 16abd074dd..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.internal.util; - -import org.mapstruct.ap.internal.util.accessor.Accessor; -import org.mapstruct.ap.internal.util.accessor.AccessorType; - -/** - * This a wrapper class which provides the value that needs to be used in the models. - * - * It is used to provide the read value for a difference kind of {@link Accessor}. - * - * @author Filip Hrisafov - */ -public class ValueProvider { - - private final String value; - - private ValueProvider(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - - @Override - public String toString() { - return value; - } - - /** - * Creates a {@link ValueProvider} from the provided {@code accessor}. The base value is - * {@link Accessor#getSimpleName()}. If the {@code accessor} is for an executable, then {@code ()} is - * appended. - * - * @param accessor that provides the value - * - * @return a {@link ValueProvider} tha provides a read value for the {@code accessor} - */ - public static ValueProvider of(Accessor accessor) { - if ( accessor == null ) { - return null; - } - String value = accessor.getSimpleName(); - if (accessor.getAccessorType() == AccessorType.MAP_GET ) { - value = "get( \"" + value + "\" )"; - return new ValueProvider( value ); - } - if ( !accessor.getAccessorType().isFieldAssignment() ) { - value += "()"; - } - return new ValueProvider( value ); - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java index 4e3b7a4776..112c1c512d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java @@ -11,10 +11,7 @@ public enum AccessorType { FIELD, GETTER, SETTER, - ADDER, - MAP_GET, - MAP_CONTAINS, - PRESENCE_CHECKER; + ADDER; public boolean isFieldAssignment() { return this == FIELD || this == PARAMETER; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java new file mode 100644 index 0000000000..9b2981d7c5 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java @@ -0,0 +1,48 @@ +/* + * 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.internal.util.accessor; + +import java.util.Set; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeMirror; + +/** + * @author Filip Hrisafov + */ +public abstract class DelegateAccessor implements Accessor { + + protected final Accessor delegate; + + protected DelegateAccessor(Accessor delegate) { + this.delegate = delegate; + } + + @Override + public TypeMirror getAccessedType() { + return delegate.getAccessedType(); + } + + @Override + public String getSimpleName() { + return delegate.getSimpleName(); + } + + @Override + public Set getModifiers() { + return delegate.getModifiers(); + } + + @Override + public Element getElement() { + return delegate.getElement(); + } + + @Override + public AccessorType getAccessorType() { + return delegate.getAccessorType(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java index ab086d9b0e..7a708995cd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java @@ -16,7 +16,7 @@ * * @author Christian Kosmowski */ -public class MapValueAccessor implements Accessor { +public class MapValueAccessor implements ReadAccessor { private final TypeMirror valueTypeMirror; private final String simpleName; @@ -50,6 +50,11 @@ public Element getElement() { @Override public AccessorType getAccessorType() { - return AccessorType.MAP_GET; + return AccessorType.GETTER; + } + + @Override + public String getReadValueSource() { + return "get( \"" + getSimpleName() + "\" )"; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java deleted file mode 100644 index 3f72ed86c1..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValuePresenceChecker.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.internal.util.accessor; - -import java.util.Collections; -import java.util.Set; -import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps a Map value. - * - * @author Christian Kosmowski - */ -public class MapValuePresenceChecker implements Accessor { - - private final Element element; - private final TypeMirror valueTypeMirror; - private final String simpleName; - - public MapValuePresenceChecker(Element element, TypeMirror valueTypeMirror, String simpleName) { - this.element = element; - this.valueTypeMirror = valueTypeMirror; - this.simpleName = simpleName; - } - - @Override - public TypeMirror getAccessedType() { - return valueTypeMirror; - } - - @Override - public String getSimpleName() { - return this.simpleName; - } - - @Override - public Set getModifiers() { - return Collections.emptySet(); - } - - @Override - public Element getElement() { - return this.element; - } - - @Override - public AccessorType getAccessorType() { - return AccessorType.MAP_CONTAINS; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java new file mode 100644 index 0000000000..cc974b939f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java @@ -0,0 +1,29 @@ +/* + * 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.internal.util.accessor; + +import javax.lang.model.element.ExecutableElement; + +/** + * @author Filip Hrisafov + */ +public interface PresenceCheckAccessor { + + String getPresenceCheckSuffix(); + + static PresenceCheckAccessor methodInvocation(ExecutableElement element) { + return suffix( "." + element.getSimpleName() + "()" ); + } + + static PresenceCheckAccessor mapContainsKey(String propertyName) { + return suffix( ".containsKey( \"" + propertyName + "\" )" ); + } + + static PresenceCheckAccessor suffix(String suffix) { + return () -> suffix; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java new file mode 100644 index 0000000000..a790a3361b --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java @@ -0,0 +1,36 @@ +/* + * 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.internal.util.accessor; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * @author Filip Hrisafov + */ +public interface ReadAccessor extends Accessor { + + String getReadValueSource(); + + static ReadAccessor fromField(VariableElement variableElement) { + return new ReadDelegateAccessor( new FieldElementAccessor( variableElement ) ) { + @Override + public String getReadValueSource() { + return getSimpleName(); + } + }; + } + + static ReadAccessor fromGetter(ExecutableElement element, TypeMirror accessedType) { + return new ReadDelegateAccessor( new ExecutableElementAccessor( element, accessedType, AccessorType.GETTER ) ) { + @Override + public String getReadValueSource() { + return getSimpleName() + "()"; + } + }; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java new file mode 100644 index 0000000000..608c1b21cf --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java @@ -0,0 +1,17 @@ +/* + * 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.internal.util.accessor; + +/** + * @author Filip Hrisafov + */ +public abstract class ReadDelegateAccessor extends DelegateAccessor implements ReadAccessor { + + protected ReadDelegateAccessor(Accessor delegate) { + super( delegate ); + } + +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SourceReferenceContainsKeyPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SourceReferenceContainsKeyPresenceCheck.ftl deleted file mode 100644 index 5cffc481a1..0000000000 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SourceReferenceContainsKeyPresenceCheck.ftl +++ /dev/null @@ -1,9 +0,0 @@ -<#-- - - Copyright MapStruct Authors. - - Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - ---> -<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.SourceReferenceContainsKeyPresenceCheck" --> -${sourceReference}.containsKey( "${propertyName}" ) \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SourceReferenceMethodPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.ftl similarity index 72% rename from processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SourceReferenceMethodPresenceCheck.ftl rename to processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.ftl index 930d5e24bc..103b4e7f9c 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SourceReferenceMethodPresenceCheck.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.ftl @@ -5,5 +5,5 @@ Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 --> -<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.SourceReferenceMethodPresenceCheck" --> -${sourceReference}.${methodName}() \ No newline at end of file +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck" --> +${sourceReference}${suffix} \ No newline at end of file From 5de813c16f19050e6cd0a8cd08dc18799a37271b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 21 Nov 2021 13:48:24 +0100 Subject: [PATCH 029/363] #2666 Presence Check should be applied to source parameters when used in `@Mapping` --- .../ap/internal/model/PropertyMapping.java | 14 +- .../expression/ConditionalExpressionTest.java | 37 +++++ ...nalWithSourceToTargetExpressionMapper.java | 132 ++++++++++++++++++ ...itionalMethodWithSourceToTargetMapper.java | 132 ++++++++++++++++++ .../qualifier/ConditionalQualifierTest.java | 37 +++++ 5 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalWithSourceToTargetExpressionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java 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 c83f856587..f17ab9ddb1 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 @@ -558,11 +558,17 @@ private SourceRHS getSourceRHS( SourceReference sourceReference ) { // parameter reference if ( propertyEntry == null ) { - return new SourceRHS( sourceParam.getName(), - sourceParam.getType(), - existingVariableNames, - sourceReference.toString() + SourceRHS sourceRHS = new SourceRHS( + sourceParam.getName(), + sourceParam.getType(), + existingVariableNames, + sourceReference.toString() ); + sourceRHS.setSourcePresenceCheckerReference( getSourcePresenceCheckerRef( + sourceReference, + sourceRHS + ) ); + return sourceRHS; } // simple property else if ( !sourceReference.isNested() ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalExpressionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalExpressionTest.java index 60179cdeac..42219cfb6a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalExpressionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalExpressionTest.java @@ -74,6 +74,43 @@ public void conditionalSimpleExpression() { assertThat( target.getValue() ).isEqualTo( 0 ); } + @ProcessorTest + @WithClasses({ + ConditionalWithSourceToTargetExpressionMapper.class + }) + @IssueKey("2666") + public void conditionalExpressionForSourceToTarget() { + ConditionalWithSourceToTargetExpressionMapper mapper = ConditionalWithSourceToTargetExpressionMapper.INSTANCE; + + ConditionalWithSourceToTargetExpressionMapper.OrderDTO orderDto = + new ConditionalWithSourceToTargetExpressionMapper.OrderDTO(); + + ConditionalWithSourceToTargetExpressionMapper.Order order = mapper.convertToOrder( orderDto ); + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNull(); + + orderDto = new ConditionalWithSourceToTargetExpressionMapper.OrderDTO(); + orderDto.setCustomerName( "Tester" ); + order = mapper.convertToOrder( orderDto ); + + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNotNull(); + assertThat( order.getCustomer().getName() ).isEqualTo( "Tester" ); + assertThat( order.getCustomer().getAddress() ).isNull(); + + orderDto = new ConditionalWithSourceToTargetExpressionMapper.OrderDTO(); + orderDto.setLine1( "Line 1" ); + order = mapper.convertToOrder( orderDto ); + + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNotNull(); + assertThat( order.getCustomer().getName() ).isNull(); + assertThat( order.getCustomer().getAddress() ).isNotNull(); + assertThat( order.getCustomer().getAddress().getLine1() ).isEqualTo( "Line 1" ); + assertThat( order.getCustomer().getAddress().getLine2() ).isNull(); + + } + @ProcessorTest @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalWithSourceToTargetExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalWithSourceToTargetExpressionMapper.java new file mode 100644 index 0000000000..c32fe99e03 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalWithSourceToTargetExpressionMapper.java @@ -0,0 +1,132 @@ +/* + * 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.conditional.expression; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(imports = ConditionalWithSourceToTargetExpressionMapper.Util.class) +public interface ConditionalWithSourceToTargetExpressionMapper { + + ConditionalWithSourceToTargetExpressionMapper INSTANCE = + Mappers.getMapper( ConditionalWithSourceToTargetExpressionMapper.class ); + + @Mapping(source = "orderDTO", target = "customer", + conditionExpression = "java(Util.mapCustomerFromOrder( orderDTO ))") + Order convertToOrder(OrderDTO orderDTO); + + @Mapping(source = "customerName", target = "name") + @Mapping(source = "orderDTO", target = "address", + conditionExpression = "java(Util.mapAddressFromOrder( orderDTO ))") + Customer convertToCustomer(OrderDTO orderDTO); + + Address convertToAddress(OrderDTO orderDTO); + + class OrderDTO { + + private String customerName; + private String line1; + private String line2; + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } + + } + + class Order { + + private Customer customer; + + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + } + + class Customer { + private String name; + private Address address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + class Address { + + private String line1; + private String line2; + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } + } + + interface Util { + + static boolean mapCustomerFromOrder(OrderDTO orderDTO) { + return orderDTO != null && ( orderDTO.getCustomerName() != null || mapAddressFromOrder( orderDTO ) ); + } + + static boolean mapAddressFromOrder(OrderDTO orderDTO) { + return orderDTO != null && ( orderDTO.getLine1() != null || orderDTO.getLine2() != null ); + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java new file mode 100644 index 0000000000..35864fe62c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java @@ -0,0 +1,132 @@ +/* + * 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.conditional.qualifier; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodWithSourceToTargetMapper { + + ConditionalMethodWithSourceToTargetMapper INSTANCE = + Mappers.getMapper( ConditionalMethodWithSourceToTargetMapper.class ); + + @Mapping(source = "orderDTO", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder") + Order convertToOrder(OrderDTO orderDTO); + + @Mapping(source = "customerName", target = "name") + @Mapping(source = "orderDTO", target = "address", conditionQualifiedByName = "mapAddressFromOrder") + Customer convertToCustomer(OrderDTO orderDTO); + + Address convertToAddress(OrderDTO orderDTO); + + @Condition + @Named("mapCustomerFromOrder") + default boolean mapCustomerFromOrder(OrderDTO orderDTO) { + return orderDTO != null && ( orderDTO.getCustomerName() != null || mapAddressFromOrder( orderDTO ) ); + } + + @Condition + @Named("mapAddressFromOrder") + default boolean mapAddressFromOrder(OrderDTO orderDTO) { + return orderDTO != null && ( orderDTO.getLine1() != null || orderDTO.getLine2() != null ); + } + + class OrderDTO { + + private String customerName; + private String line1; + private String line2; + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } + + } + + class Order { + + private Customer customer; + + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + } + + class Customer { + private String name; + private Address address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + class Address { + + private String line1; + private String line2; + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalQualifierTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalQualifierTest.java index 5d13787e65..69c9a048cd 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalQualifierTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalQualifierTest.java @@ -86,4 +86,41 @@ public void conditionalClassQualifiers() { assertThat( employee.getNin() ).isNull(); assertThat( employee.getSsid() ).isNull(); } + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithSourceToTargetMapper.class + }) + @IssueKey("2666") + public void conditionalQualifiersForSourceToTarget() { + ConditionalMethodWithSourceToTargetMapper mapper = ConditionalMethodWithSourceToTargetMapper.INSTANCE; + + ConditionalMethodWithSourceToTargetMapper.OrderDTO orderDto = + new ConditionalMethodWithSourceToTargetMapper.OrderDTO(); + + ConditionalMethodWithSourceToTargetMapper.Order order = mapper.convertToOrder( orderDto ); + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNull(); + + orderDto = new ConditionalMethodWithSourceToTargetMapper.OrderDTO(); + orderDto.setCustomerName( "Tester" ); + order = mapper.convertToOrder( orderDto ); + + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNotNull(); + assertThat( order.getCustomer().getName() ).isEqualTo( "Tester" ); + assertThat( order.getCustomer().getAddress() ).isNull(); + + orderDto = new ConditionalMethodWithSourceToTargetMapper.OrderDTO(); + orderDto.setLine1( "Line 1" ); + order = mapper.convertToOrder( orderDto ); + + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNotNull(); + assertThat( order.getCustomer().getName() ).isNull(); + assertThat( order.getCustomer().getAddress() ).isNotNull(); + assertThat( order.getCustomer().getAddress().getLine1() ).isEqualTo( "Line 1" ); + assertThat( order.getCustomer().getAddress().getLine2() ).isNull(); + + } } From ea45666d665c51c02a79bb9a670855b9fab06059 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sat, 11 Dec 2021 14:16:19 +0100 Subject: [PATCH 030/363] #2673: Fix optional wrapping pattern throwing exception when primitive types are present MethodMatcher incorrectly reported that a primitive type matches a candidate for a type var --- .../internal/model/source/MethodMatcher.java | 7 +- .../ap/test/bugs/_2673/Issue2673Mapper.java | 66 +++++++++++++++++++ .../ap/test/bugs/_2673/Issue2673Test.java | 24 +++++++ 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2673/Issue2673Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2673/Issue2673Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java index f08e4a9152..f0d9536799 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; - import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; @@ -239,6 +238,8 @@ boolean getCandidates(Type aCandidateMethodType, Type matchingType, Map String method( T par ); @@ -256,6 +257,8 @@ boolean getCandidates(Type aCandidateMethodType, Type matchingType, Map T map(Optional opt) { + return opt.orElse( null ); + } + + default Optional map(T t) { + return Optional.ofNullable( t ); + } + + class Target { + private final int primitive; + private final String nonPrimitive; + + public Target(int primitive, String nonPrimitive) { + this.primitive = primitive; + this.nonPrimitive = nonPrimitive; + } + + public int getPrimitive() { + return primitive; + } + + public String getNonPrimitive() { + return nonPrimitive; + } + } + + class Source { + private final int primitive; + private final Optional nonPrimitive; + + public Source(int primitive, Optional nonPrimitive) { + this.primitive = primitive; + this.nonPrimitive = nonPrimitive; + } + + public int getPrimitive() { + return primitive; + } + + public Optional getNonPrimitive() { + return nonPrimitive; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2673/Issue2673Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2673/Issue2673Test.java new file mode 100644 index 0000000000..40a5e79276 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2673/Issue2673Test.java @@ -0,0 +1,24 @@ +/* + * 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._2673; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Ben Zegveld + */ +@WithClasses({ + Issue2673Mapper.class +}) +class Issue2673Test { + + @ProcessorTest + @IssueKey( "2673" ) + void shouldCompile() { + } +} From 0de10ca83c682303f4082c27b3a22eec5fbcb258 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 12 Dec 2021 12:48:55 +0100 Subject: [PATCH 031/363] [maven-release-plugin] prepare release 1.5.0.Beta2 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index aaae7b5404..6eaf876961 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Beta2 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 293043e6f9..ef963caadd 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Beta2 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 10c95cf3b5..8012b3be5e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Beta2 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 337ad35e51..d2bf1509d5 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Beta2 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 8850da641c..3341ffb5cc 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Beta2 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 4b48c19496..69b81e4321 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Beta2 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index f4edccfc85..f5f241762a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Beta2 pom MapStruct Parent @@ -64,7 +64,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.5.0.Beta2 diff --git a/pom.xml b/pom.xml index 28c35aea74..2179424cbd 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Beta2 parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.5.0.Beta2 diff --git a/processor/pom.xml b/processor/pom.xml index 4f2476ceb0..c1751f395f 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Beta2 ../parent/pom.xml From 930b07aab82d9270a871f89b957717825a693385 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 12 Dec 2021 12:48:56 +0100 Subject: [PATCH 032/363] [maven-release-plugin] prepare for next development iteration --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 6eaf876961..aaae7b5404 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Beta2 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index ef963caadd..293043e6f9 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Beta2 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 8012b3be5e..10c95cf3b5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Beta2 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index d2bf1509d5..337ad35e51 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Beta2 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 3341ffb5cc..8850da641c 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Beta2 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 69b81e4321..4b48c19496 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Beta2 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index f5f241762a..f4edccfc85 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Beta2 + 1.5.0-SNAPSHOT pom MapStruct Parent @@ -64,7 +64,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.5.0.Beta2 + HEAD diff --git a/pom.xml b/pom.xml index 2179424cbd..28c35aea74 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Beta2 + 1.5.0-SNAPSHOT parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.5.0.Beta2 + HEAD diff --git a/processor/pom.xml b/processor/pom.xml index c1751f395f..4f2476ceb0 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Beta2 + 1.5.0-SNAPSHOT ../parent/pom.xml From 0a7b8134d4eaf5430e392403f33881129eaeba98 Mon Sep 17 00:00:00 2001 From: Ben Zegveld Date: Fri, 24 Dec 2021 14:44:59 +0100 Subject: [PATCH 033/363] #2689: documentation: fix example title. --- .../main/asciidoc/chapter-10-advanced-mapping-options.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 5d7da371a1..fdbe5f8059 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -303,7 +303,7 @@ public interface CarMapper { The generated mapper will look like: -.try-catch block in generated implementation +.Custom condition check in generated implementation ==== [source, java, linenums] [subs="verbatim,attributes"] From 42cfa05c40d1cd8d68397ed5581de147929c1250 Mon Sep 17 00:00:00 2001 From: Valentin Kulesh Date: Sun, 26 Dec 2021 13:22:05 +0300 Subject: [PATCH 034/363] #2704 Fix broken reference to constants within nested enums --- .../model/assignment/EnumConstantWrapper.ftl | 2 +- .../ap/test/bugs/_2704/Issue2704Test.java | 21 +++++++++++++++++++ .../ap/test/bugs/_2704/TestMapper.java | 19 +++++++++++++++++ .../ap/test/bugs/_2704/TopLevel.java | 21 +++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/Issue2704Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TestMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TopLevel.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.ftl index 7364f7922e..449a273614 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.ftl @@ -6,4 +6,4 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.EnumConstantWrapper" --> -${ext.targetType.name}.${assignment} \ No newline at end of file +${ext.targetType.createReferenceName()}.${assignment} \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/Issue2704Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/Issue2704Test.java new file mode 100644 index 0000000000..5920a6022b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/Issue2704Test.java @@ -0,0 +1,21 @@ +/* + * 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._2704; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Valentin Kulesh + */ +@IssueKey("2704") +@WithClasses({ TestMapper.class, TopLevel.class }) +public class Issue2704Test { + @ProcessorTest + public void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TestMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TestMapper.java new file mode 100644 index 0000000000..846a783437 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TestMapper.java @@ -0,0 +1,19 @@ +/* + * 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._2704; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.bugs._2704.TopLevel.Target; + +/** + * @author Valentin Kulesh + */ +@Mapper(implementationPackage = "") +public interface TestMapper { + @Mapping(target = "e", constant = "VALUE1") + Target test(Object unused); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TopLevel.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TopLevel.java new file mode 100644 index 0000000000..ef30c2cd6e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TopLevel.java @@ -0,0 +1,21 @@ +/* + * 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._2704; + +/** + * @author Valentin Kulesh + */ +public interface TopLevel { + enum InnerEnum { + VALUE1, + VALUE2, + } + + class Target { + public void setE(@SuppressWarnings("unused") InnerEnum e) { + } + } +} From f7f65ac1dee0db41daf702382fba0284bff5419a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 25 Dec 2021 13:12:29 +0100 Subject: [PATCH 035/363] #2687 Add documentation for NullValueMappingStrategy for collections and maps --- ...apter-10-advanced-mapping-options.asciidoc | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index fdbe5f8059..463ebcf7c4 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -212,6 +212,29 @@ However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETU The strategy works in a hierarchical fashion. Setting `nullValueMappingStrategy` on mapping method level will override `@Mapper#nullValueMappingStrategy`, and `@Mapper#nullValueMappingStrategy` will override `@MapperConfig#nullValueMappingStrategy`. +[[mapping-result-for-null-collection-or-map-arguments]] +=== Controlling mapping result for 'null' collection or map arguments + +With <> it is possible to control how the return type should be constructed when the source argument of the mapping method is `null`. +That is applied for all mapping methods (bean, iterable or map mapping methods). + +However, MapStruct also offers a more dedicated way to control how collections / maps should be mapped. +e.g. return default (empty) collections / maps, but return `null` for beans. + +For collections (iterables) this can be controlled through: + +* `MapperConfig#nullValueIterableMappingStrategy` +* `Mapper#nullValueIterableMappingStrategy` +* `IterableMapping#nullValueMappingStrategy` + +For maps this can be controlled through: + +* `MapperConfig#nullValueMapMappingStrategy` +* `Mapper#nullValueMapMappingStrategy` +* `MapMapping#nullValueMappingStrategy` + +How the value of the `NullValueMappingStrategy` is applied is the same as in <> + [[mapping-result-for-null-properties]] === Controlling mapping result for 'null' properties in bean mappings (update mapping methods only). From 59c5f40ac3c6f83e0e4c333a9ed67ae23b6ae664 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Tue, 18 Jan 2022 18:22:47 +0100 Subject: [PATCH 036/363] #2686 Add documentation about when mappers are injected --- .../src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc index 724b42fbb7..67db28c7c0 100644 --- a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc @@ -117,7 +117,7 @@ public interface CarMapper { ---- ==== -The generated mapper will inject all classes defined in the **uses** attribute. +The generated mapper will inject classes defined in the **uses** attribute if MapStruct has detected that it needs to use an instance of it for a mapping. When `InjectionStrategy#CONSTRUCTOR` is used, the constructor will have the appropriate annotation and the fields won't. When `InjectionStrategy#FIELD` is used, the annotation is on the field itself. For now, the default injection strategy is field injection, but it can be configured with <>. From 277b6f5d2b357097a968d1201d29b3012928b72d Mon Sep 17 00:00:00 2001 From: Ben Zegveld Date: Thu, 20 Jan 2022 23:02:12 +0100 Subject: [PATCH 037/363] #2719: added a note at the builder documentation to point towards the Before-/AfterMapping documentation. --- .../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index 370c09e249..9d5124ee97 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -445,6 +445,11 @@ The <> are also considered for the builder type. E.g. If an object factory exists for our `PersonBuilder` then this factory would be used instead of the builder creation method. ====== +[NOTE] +====== +Detected builders influence `@BeforeMapping` and `@AfterMapping` behavior. See chapter `Mapping customization with before-mapping and after-mapping methods` for more information. +====== + .Person with Builder example ==== [source, java, linenums] From 0f297ae60f2a839578576b46c38b08832f167bc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Jan 2022 22:33:51 +0000 Subject: [PATCH 038/363] Bump protobuf-java from 3.6.0 to 3.16.1 in /parent Bumps [protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.6.0 to 3.16.1. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.6.0...v3.16.1) --- updated-dependencies: - dependency-name: com.google.protobuf:protobuf-java dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index f4edccfc85..263aa73503 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -223,7 +223,7 @@ com.google.protobuf protobuf-java - 3.6.0 + 3.16.1 org.inferred From 0a8e9b738cb2868628d2d8d12833c92b8ede1d04 Mon Sep 17 00:00:00 2001 From: Goni-Dev <56092853+Goni-Dev@users.noreply.github.com> Date: Sun, 23 Jan 2022 17:26:21 +0100 Subject: [PATCH 039/363] #2709 Corrected description for example demonstrating default expression --- .../main/asciidoc/chapter-10-advanced-mapping-options.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 463ebcf7c4..0bbec1fe57 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -88,7 +88,7 @@ Default expressions are a combination of default values and expressions. They wi The same warnings and restrictions apply to default expressions that apply to expressions. Only Java is supported, and MapStruct will not validate the expression at generation-time. -The example below demonstrates how two source properties can be mapped to one target: +The example below demonstrates how a default expression can be used to set a value when the source attribute is not present (e.g. is `null`): .Mapping method using a default expression ==== From b22efd9ad7d8878c45d8f5c906b39dbbfe3aeaec Mon Sep 17 00:00:00 2001 From: Justyna <98126210+JKLedzion@users.noreply.github.com> Date: Mon, 24 Jan 2022 14:17:24 +0100 Subject: [PATCH 040/363] =?UTF-8?q?#2674:=20Add=20check=20if=20method=20wi?= =?UTF-8?q?thout=20implementation=20doesn=E2=80=99t=20have=20`@AfterMappin?= =?UTF-8?q?g`=20/=20`@BeforeMapping`=20annotation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../processor/MethodRetrievalProcessor.java | 10 +++++ .../mapstruct/ap/internal/util/Message.java | 2 + .../_2674/ErroneousSourceTargetMapping.java | 28 +++++++++++++ .../ap/test/bugs/_2674/Issue2674Test.java | 40 +++++++++++++++++++ .../mapstruct/ap/test/bugs/_2674/Source.java | 28 +++++++++++++ .../mapstruct/ap/test/bugs/_2674/Target.java | 28 +++++++++++++ 6 files changed, 136 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/ErroneousSourceTargetMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Issue2674Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Target.java 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 b226c1a050..a31fb41a7a 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 @@ -564,6 +564,16 @@ private boolean checkParameterAndReturnType(ExecutableElement method, List Date: Sat, 29 Jan 2022 00:37:24 +0100 Subject: [PATCH 041/363] #2668: Added support for collections and maps with a no-args constructor (#2671) #2668: Added support for collections and maps with a no-args constructor. Added a compiler error in case of a collection or map without either a no-arg constructor or a copy constructor. --- .../model/CollectionAssignmentBuilder.java | 84 +++++++++++- ...nceSetterWrapperForCollectionsAndMaps.java | 43 +++++++ ...perForCollectionsAndMapsWithNullCheck.java | 2 +- .../ap/internal/model/common/Type.java | 6 +- .../mapstruct/ap/internal/util/Message.java | 1 + ...anceSetterWrapperForCollectionsAndMaps.ftl | 23 ++++ .../bugs/_2668/Erroneous2668ListMapper.java | 54 ++++++++ .../bugs/_2668/Erroneous2668MapMapper.java | 54 ++++++++ .../ap/test/bugs/_2668/Issue2668Mapper.java | 120 ++++++++++++++++++ .../ap/test/bugs/_2668/Issue2668Test.java | 59 +++++++++ 10 files changed, 438 insertions(+), 8 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.ftl create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668ListMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668MapMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Test.java 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 dd6db1b0ee..e3bc85342a 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 @@ -5,19 +5,27 @@ */ package org.mapstruct.ap.internal.model; +import java.util.function.Predicate; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; + +import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.model.assignment.ExistingInstanceSetterWrapperForCollectionsAndMaps; import org.mapstruct.ap.internal.model.assignment.GetterWrapperForCollectionsAndMaps; +import org.mapstruct.ap.internal.model.assignment.NewInstanceSetterWrapperForCollectionsAndMaps; import org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMaps; import org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMapsWithNullCheck; import org.mapstruct.ap.internal.model.assignment.UpdateWrapper; import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Assignment.AssignmentType; import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SelectionParameters; -import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; -import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; -import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; @@ -169,7 +177,8 @@ else if ( method.isUpdateMethod() && !targetImmutable ) { targetAccessorType.isFieldAssignment() ); } - else if ( setterWrapperNeedsSourceNullCheck( result ) ) { + else if ( setterWrapperNeedsSourceNullCheck( result ) + && canBeMappedOrDirectlyAssigned( result ) ) { result = new SetterWrapperForCollectionsAndMapsWithNullCheck( result, @@ -179,7 +188,7 @@ else if ( setterWrapperNeedsSourceNullCheck( result ) ) { targetAccessorType.isFieldAssignment() ); } - else { + else if ( canBeMappedOrDirectlyAssigned( result ) ) { //TODO init default value // target accessor is setter, so wrap the setter in setter map/ collection handling @@ -190,6 +199,21 @@ else if ( setterWrapperNeedsSourceNullCheck( result ) ) { targetAccessorType.isFieldAssignment() ); } + else if ( hasNoArgsConstructor() ) { + result = new NewInstanceSetterWrapperForCollectionsAndMaps( + result, + method.getThrownTypes(), + targetType, + ctx.getTypeFactory(), + targetAccessorType.isFieldAssignment() ); + } + else { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.PROPERTYMAPPING_NO_SUITABLE_COLLECTION_OR_MAP_CONSTRUCTOR, + targetType + ); + } } else { if ( targetImmutable ) { @@ -212,6 +236,12 @@ else if ( setterWrapperNeedsSourceNullCheck( result ) ) { return result; } + private boolean canBeMappedOrDirectlyAssigned(Assignment result) { + return result.getType() != AssignmentType.DIRECT + || hasCopyConstructor() + || targetType.isEnumSet(); + } + /** * Checks whether the setter wrapper should include a null / presence check or not * @@ -236,4 +266,48 @@ private boolean setterWrapperNeedsSourceNullCheck(Assignment rhs) { return false; } + private boolean hasCopyConstructor() { + return checkConstructorForPredicate( this::hasCopyConstructor ); + } + + private boolean hasNoArgsConstructor() { + return checkConstructorForPredicate( this::hasNoArgsConstructor ); + } + + private boolean checkConstructorForPredicate(Predicate predicate) { + if ( targetType.isCollectionOrMapType() ) { + if ( "java.util".equals( targetType.getPackageName() ) ) { + return true; + } + else { + Element sourceElement = targetType.getImplementationType() != null + ? targetType.getImplementationType().getTypeElement() + : targetType.getTypeElement(); + if ( sourceElement != null ) { + for ( Element element : sourceElement.getEnclosedElements() ) { + if ( element.getKind() == ElementKind.CONSTRUCTOR + && element.getModifiers().contains( Modifier.PUBLIC ) ) { + if ( predicate.test( element ) ) { + return true; + } + } + } + } + } + } + return false; + } + + private boolean hasNoArgsConstructor(Element element) { + return ( (ExecutableElement) element ).getParameters().isEmpty(); + } + + private boolean hasCopyConstructor(Element element) { + if ( element instanceof ExecutableElement ) { + ExecutableElement ee = (ExecutableElement) element; + return ee.getParameters().size() == 1 + && ctx.getTypeUtils().isAssignable( targetType.getTypeMirror(), ee.getParameters().get( 0 ).asType() ); + } + return false; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.java new file mode 100644 index 0000000000..f0e23470a7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.java @@ -0,0 +1,43 @@ +/* + * 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.internal.model.assignment; + +import java.util.List; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; + +/** + * This wrapper handles the situation where an assignment is done via the setter, while creating the collection or map + * using a no-args constructor. + * + * @author Ben Zegveld + */ +public class NewInstanceSetterWrapperForCollectionsAndMaps extends SetterWrapperForCollectionsAndMapsWithNullCheck { + + private String instanceVar; + + public NewInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, + List thrownTypesToExclude, + Type targetType, + TypeFactory typeFactory, + boolean fieldAssignment) { + + super( + decoratedAssignment, + thrownTypesToExclude, + targetType, + typeFactory, + fieldAssignment + ); + this.instanceVar = decoratedAssignment.createUniqueVarName( targetType.getName() ); + } + + public String getInstanceVar() { + return instanceVar; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java index 5e3f4d63fe..a0c9f799e9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java @@ -68,7 +68,7 @@ public boolean isDirectAssignment() { } public boolean isEnumSet() { - return "java.util.EnumSet".equals( targetType.getFullyQualifiedName() ); + return targetType.isEnumSet(); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index ab2871c69d..ba5f62254d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -19,7 +19,6 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; - import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -113,7 +112,6 @@ public class Type extends ModelElement implements Comparable { private List setters = null; private List adders = null; private List alternativeTargetAccessors = null; - private Map constructorAccessors = null; private Type boundingBase = null; @@ -1603,4 +1601,8 @@ private static Type topLevelType(TypeElement typeElement, TypeFactory typeFactor return parent == null ? null : typeFactory.getType( parent.asType() ); } + public boolean isEnumSet() { + return "java.util.EnumSet".equals( getFullyQualifiedName() ); + } + } 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 26f4a33c5d..8588ec183a 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 @@ -80,6 +80,7 @@ public enum Message { PROPERTYMAPPING_WHITESPACE_TRIMMED( "The property named \"%s\" has whitespaces, using trimmed property \"%s\" instead.", Diagnostic.Kind.WARNING ), PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PROPERTY_FROM_TARGET("The type of parameter \"%s\" has no property named \"%s\". Please define the source property explicitly."), PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET("No property named \"%s\" exists in source parameter(s). Please define the source explicitly."), + PROPERTYMAPPING_NO_SUITABLE_COLLECTION_OR_MAP_CONSTRUCTOR( "%s does not have an accessible copy or no-args constructor." ), CONVERSION_LOSSY_WARNING( "%s has a possibly lossy conversion from %s to %s.", Diagnostic.Kind.WARNING ), CONVERSION_LOSSY_ERROR( "Can't map %s. It has a possibly lossy conversion from %s to %s." ), diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.ftl new file mode 100644 index 0000000000..4582e68a1e --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.ftl @@ -0,0 +1,23 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMapsWithNullCheck" --> +<#import "../macro/CommonMacros.ftl" as lib> +<@lib.sourceLocalVarAssignment/> +<@lib.handleExceptions> + <@callTargetWriteAccessor/> + +<#-- + assigns the target via the regular target write accessor (usually the setter) +--> +<#macro callTargetWriteAccessor> + <@lib.handleLocalVarNullCheck needs_explicit_local_var=directAssignment> + <#if ext.targetType.implementationType??><@includeModel object=ext.targetType.implementationType/><#else><@includeModel object=ext.targetType/> ${instanceVar} = new <#if ext.targetType.implementationType??><@includeModel object=ext.targetType.implementationType/><#else><@includeModel object=ext.targetType/>(); + ${instanceVar}.<#if ext.targetType.collectionType>addAll<#else>putAll( ${nullCheckLocalVarName} ); + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite>${instanceVar}; + + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668ListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668ListMapper.java new file mode 100644 index 0000000000..beb1d3283a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668ListMapper.java @@ -0,0 +1,54 @@ +/* + * 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._2668; + +import java.util.ArrayList; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface Erroneous2668ListMapper { + + Erroneous2668ListMapper INSTANCE = Mappers.getMapper( Erroneous2668ListMapper.class ); + + CollectionTarget map(CollectionSource source); + + class CollectionTarget { + MyArrayList list; + + public MyArrayList getList() { + return list; + } + + public void setList(MyArrayList list) { + this.list = list; + } + } + + class CollectionSource { + MyArrayList list; + + public MyArrayList getList() { + return list; + } + + public void setList(MyArrayList list) { + this.list = list; + } + } + + class MyArrayList extends ArrayList { + private String unusable; + + public MyArrayList(String unusable) { + this.unusable = unusable; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668MapMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668MapMapper.java new file mode 100644 index 0000000000..3c9bcd3926 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668MapMapper.java @@ -0,0 +1,54 @@ +/* + * 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._2668; + +import java.util.HashMap; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface Erroneous2668MapMapper { + + Erroneous2668MapMapper INSTANCE = Mappers.getMapper( Erroneous2668MapMapper.class ); + + CollectionTarget map(CollectionSource source); + + class CollectionTarget { + MyHashMap map; + + public MyHashMap getMap() { + return map; + } + + public void setMap(MyHashMap map) { + this.map = map; + } + } + + class CollectionSource { + MyHashMap map; + + public MyHashMap getMap() { + return map; + } + + public void setMap(MyHashMap map) { + this.map = map; + } + } + + class MyHashMap extends HashMap { + private String unusable; + + public MyHashMap(String unusable) { + this.unusable = unusable; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Mapper.java new file mode 100644 index 0000000000..1b72e2f636 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Mapper.java @@ -0,0 +1,120 @@ +/* + * 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._2668; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface Issue2668Mapper { + + Issue2668Mapper INSTANCE = Mappers.getMapper( Issue2668Mapper.class ); + + CollectionTarget map(CollectionSource source); + + class CollectionTarget { + MyArrayList list; + MyHashMap map; + MyCopyArrayList copyList; + MyCopyHashMap copyMap; + + public MyArrayList getList() { + return list; + } + + public MyHashMap getMap() { + return map; + } + + public void setList(MyArrayList list) { + this.list = list; + } + + public void setMap(MyHashMap map) { + this.map = map; + } + + public MyCopyArrayList getCopyList() { + return copyList; + } + + public MyCopyHashMap getCopyMap() { + return copyMap; + } + + public void setCopyList(MyCopyArrayList copyList) { + this.copyList = copyList; + } + + public void setCopyMap(MyCopyHashMap copyMap) { + this.copyMap = copyMap; + } + } + + class CollectionSource { + MyArrayList list; + MyHashMap map; + MyCopyArrayList copyList; + MyCopyHashMap copyMap; + + public MyArrayList getList() { + return list; + } + + public MyHashMap getMap() { + return map; + } + + public void setList(MyArrayList list) { + this.list = list; + } + + public void setMap(MyHashMap map) { + this.map = map; + } + + public MyCopyArrayList getCopyList() { + return copyList; + } + + public MyCopyHashMap getCopyMap() { + return copyMap; + } + + public void setCopyList(MyCopyArrayList copyList) { + this.copyList = copyList; + } + + public void setCopyMap(MyCopyHashMap copyMap) { + this.copyMap = copyMap; + } + } + + class MyArrayList extends ArrayList { + } + + class MyHashMap extends HashMap { + } + + class MyCopyArrayList extends ArrayList { + public MyCopyArrayList(Collection copy) { + super( copy ); + } + } + + class MyCopyHashMap extends HashMap { + public MyCopyHashMap(HashMap copy) { + super( copy ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Test.java new file mode 100644 index 0000000000..20b4e445fd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Test.java @@ -0,0 +1,59 @@ +/* + * 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._2668; + +import javax.tools.Diagnostic.Kind; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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; + +/** + * @author Ben Zegveld + */ +@IssueKey( "2668" ) +class Issue2668Test { + + @ProcessorTest + @WithClasses( Issue2668Mapper.class ) + void shouldCompileCorrectlyWithAvailableConstructors() { + } + + @ProcessorTest + @WithClasses( Erroneous2668ListMapper.class ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = Kind.ERROR, + line = 21, + message = "org.mapstruct.ap.test.bugs._2668.Erroneous2668ListMapper.MyArrayList" + + " does not have an accessible copy or no-args constructor." + ) + } + ) + void errorExpectedBecauseCollectionIsNotUsable() { + } + + @ProcessorTest + @WithClasses( Erroneous2668MapMapper.class ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = Kind.ERROR, + line = 21, + message = "org.mapstruct.ap.test.bugs._2668.Erroneous2668MapMapper.MyHashMap" + + " does not have an accessible copy or no-args constructor." + ) + } + ) + void errorExpectedBecauseMapIsNotUsable() { + } +} From ec30f5d2797670f053e6b2cc5eb96f62dcc3541c Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 29 Jan 2022 11:03:44 +0100 Subject: [PATCH 042/363] #2725 Update tools-gem to 1.0.0.Alpha3 and run GitHub Action with Java 19-ea Also update Spring to 5.3.15 to be able to run on Java 19 --- .github/workflows/java-ea.yml | 2 +- parent/pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index f2b2a99be4..76e24db3b1 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - java: [18-ea] + java: [18-ea, 19-ea] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: diff --git a/parent/pom.xml b/parent/pom.xml index 263aa73503..242d5215de 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -21,11 +21,11 @@ UTF-8 - 1.0.0.Alpha2 + 1.0.0.Alpha3 3.0.0-M3 3.0.0-M5 3.1.0 - 5.3.10 + 5.3.15 1.6.0 8.36.1 5.8.0-M1 From 20ff51ebb8782afe571e797b992e9660e3667e74 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 29 Jan 2022 11:13:16 +0100 Subject: [PATCH 043/363] #2728 Add new WithTestDependency annotation for our processor testing Adding this dependency allows us to dynamically pick the dependencies that we want to have on the test compilation classpath. It would allow us to more granularly test things with different dependencies, such as javax inject and jakarta inject --- .../ap/test/bugs/_1395/Issue1395Test.java | 2 + .../ap/test/bugs/_1425/Issue1425Test.java | 2 + .../ap/test/bugs/_1460/Issue1460Test.java | 2 + .../ap/test/bugs/_2145/Issue2145Test.java | 2 + .../ap/test/bugs/_880/Issue880Test.java | 2 + .../ap/test/builtin/BuiltInTest.java | 28 +++++++--- .../test/builtin/jodatime/JodaTimeTest.java | 4 ++ .../collection/wildcard/WildCardTest.java | 2 + .../erroneous/InvalidDateFormatTest.java | 2 + .../jodatime/JodaConversionTest.java | 2 + .../decorator/jsr330/Jsr330DecoratorTest.java | 2 + .../constructor/SpringDecoratorTest.java | 2 + .../spring/field/SpringDecoratorTest.java | 2 + .../destination/DestinationClassNameTest.java | 2 + .../imports/ConflictingTypesNamesTest.java | 2 + ...30DefaultCompileOptionFieldMapperTest.java | 2 + ...330CompileOptionConstructorMapperTest.java | 2 + .../Jsr330ConstructorMapperTest.java | 2 + .../jsr330/field/Jsr330FieldMapperTest.java | 2 + .../_default/SpringDefaultMapperTest.java | 2 + ...ingCompileOptionConstructorMapperTest.java | 2 + .../SpringConstructorMapperTest.java | 2 + .../spring/field/SpringFieldMapperTest.java | 2 + .../NestedMappingMethodInvocationTest.java | 4 ++ ...ferencedMappersWithSameSimpleNameTest.java | 2 + .../jaxb/JaxbFactoryMethodSelectionTest.java | 2 + .../jaxb/UnderscoreSelectionTest.java | 2 + .../ap/testutil/WithJavaxInject.java | 27 ++++++++++ .../mapstruct/ap/testutil/WithJavaxJaxb.java | 27 ++++++++++ .../org/mapstruct/ap/testutil/WithJoda.java | 27 ++++++++++ .../org/mapstruct/ap/testutil/WithSpring.java | 28 ++++++++++ .../ap/testutil/WithTestDependencies.java | 22 ++++++++ .../ap/testutil/WithTestDependency.java | 28 ++++++++++ .../testutil/runner/CompilationRequest.java | 11 +++- .../testutil/runner/CompilingExtension.java | 52 ++++++++++++------- .../runner/EclipseCompilingExtension.java | 25 +++++++-- .../runner/JdkCompilingExtension.java | 20 ++++++- 37 files changed, 319 insertions(+), 32 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxInject.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxJaxb.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithJoda.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependencies.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependency.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java index 757436a0f4..042d0288cd 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java @@ -8,6 +8,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; /** * @author Filip Hrisafov @@ -18,6 +19,7 @@ Source.class, Target.class } ) +@WithSpring @IssueKey( "1395" ) public class Issue1395Test { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Issue1425Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Issue1425Test.java index 8c5700772f..a71c33c396 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Issue1425Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Issue1425Test.java @@ -9,6 +9,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJoda; import static org.assertj.core.api.Assertions.assertThat; @@ -21,6 +22,7 @@ Target.class }) @IssueKey("1425") +@WithJoda public class Issue1425Test { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Test.java index 077e66df76..e9c97f39bc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Test.java @@ -12,6 +12,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJoda; import static org.assertj.core.api.Assertions.assertThat; @@ -24,6 +25,7 @@ Target.class }) @IssueKey("1460") +@WithJoda public class Issue1460Test { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2145/Issue2145Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2145/Issue2145Test.java index ec8bb4603c..38aed76003 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2145/Issue2145Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2145/Issue2145Test.java @@ -8,11 +8,13 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxJaxb; import static org.assertj.core.api.Assertions.assertThat; @IssueKey("2145") @WithClasses(Issue2145Mapper.class) +@WithJavaxJaxb public class Issue2145Test { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java index d05f01dc91..9d8540b409 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java @@ -11,6 +11,7 @@ import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.MappingConstants; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; @@ -30,6 +31,7 @@ @ProcessorOptions({ @ProcessorOption(name = "mapstruct.defaultComponentModel", value = MappingConstants.ComponentModel.SPRING), @ProcessorOption(name = "mapstruct.unmappedTargetPolicy", value = "ignore") }) +@WithSpring public class Issue880Test { @RegisterExtension final GeneratedSource generatedSource = new GeneratedSource(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java index 0f1983a851..2dc41b822d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java @@ -58,6 +58,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxJaxb; import static org.assertj.core.api.Assertions.assertThat; @@ -71,8 +72,6 @@ MapTarget.class, CalendarProperty.class, DateProperty.class, - JaxbElementListProperty.class, - JaxbElementProperty.class, StringListProperty.class, StringProperty.class, BigDecimalProperty.class, @@ -81,13 +80,16 @@ XmlGregorianCalendarProperty.class, ZonedDateTimeProperty.class, IterableSource.class, - MapSource.class }) @DefaultTimeZone("Europe/Berlin") public class BuiltInTest { @ProcessorTest - @WithClasses( JaxbMapper.class ) + @WithClasses( { + JaxbMapper.class, + JaxbElementProperty.class, + } ) + @WithJavaxJaxb public void shouldApplyBuiltInOnJAXBElement() { JaxbElementProperty source = new JaxbElementProperty(); source.setProp( createJaxb( "TEST" ) ); @@ -100,7 +102,11 @@ public void shouldApplyBuiltInOnJAXBElement() { } @ProcessorTest - @WithClasses( JaxbMapper.class ) + @WithClasses( { + JaxbMapper.class, + JaxbElementProperty.class, + } ) + @WithJavaxJaxb @IssueKey( "1698" ) public void shouldApplyBuiltInOnJAXBElementExtra() { JaxbElementProperty source = new JaxbElementProperty(); @@ -123,7 +129,11 @@ public void shouldApplyBuiltInOnJAXBElementExtra() { } @ProcessorTest - @WithClasses( JaxbListMapper.class ) + @WithClasses( { + JaxbListMapper.class, + JaxbElementListProperty.class, + } ) + @WithJavaxJaxb @IssueKey( "141" ) public void shouldApplyBuiltInOnJAXBElementList() { @@ -347,7 +357,11 @@ public void shouldApplyBuiltInOnIterable() throws DatatypeConfigurationException } @ProcessorTest - @WithClasses( MapSourceTargetMapper.class ) + @WithClasses( { + MapSourceTargetMapper.class, + MapSource.class, + } ) + @WithJavaxJaxb public void shouldApplyBuiltInOnMap() throws DatatypeConfigurationException { MapSource source = new MapSource(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/JodaTimeTest.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/JodaTimeTest.java index 77c62c9cac..cb0c3d2378 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/JodaTimeTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/JodaTimeTest.java @@ -30,6 +30,8 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxJaxb; +import org.mapstruct.ap.testutil.WithJoda; import static org.assertj.core.api.Assertions.assertThat; @@ -45,6 +47,8 @@ XmlGregorianCalendarBean.class }) @IssueKey( "689" ) +@WithJoda +@WithJavaxJaxb public class JodaTimeTest { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/WildCardTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/WildCardTest.java index 37ed4606f6..4da6f8648a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/WildCardTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/WildCardTest.java @@ -13,6 +13,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxJaxb; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; @@ -133,6 +134,7 @@ public void shouldFailOnTypeVarTarget() { @ProcessorTest @WithClasses( { BeanMapper.class, GoodIdea.class, CunningPlan.class } ) + @WithJavaxJaxb public void shouldMapBean() { GoodIdea aGoodIdea = new GoodIdea(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/InvalidDateFormatTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/InvalidDateFormatTest.java index 5eb67bee05..81bf3eb568 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/InvalidDateFormatTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/InvalidDateFormatTest.java @@ -8,6 +8,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJoda; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; @@ -19,6 +20,7 @@ Source.class, Target.class }) +@WithJoda @IssueKey("725") public class InvalidDateFormatTest { diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java index 001b6390e2..1ab73616d5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java @@ -20,6 +20,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJoda; import static org.assertj.core.api.Assertions.assertThat; @@ -31,6 +32,7 @@ @WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) @IssueKey("75") @DefaultLocale("de") +@WithJoda public class JodaConversionTest { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java index d00368dfd4..a3bad68b92 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java @@ -19,6 +19,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -44,6 +45,7 @@ @IssueKey("592") @ComponentScan(basePackageClasses = Jsr330DecoratorTest.class) @Configuration +@WithJavaxInject public class Jsr330DecoratorTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/SpringDecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/SpringDecoratorTest.java index dc834683e3..6d18555372 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/SpringDecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/SpringDecoratorTest.java @@ -16,6 +16,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -40,6 +41,7 @@ @IssueKey("592") @ComponentScan(basePackageClasses = SpringDecoratorTest.class) @Configuration +@WithSpring public class SpringDecoratorTest { @Autowired diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/SpringDecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/SpringDecoratorTest.java index 1b81ed8c48..ac0af86381 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/SpringDecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/SpringDecoratorTest.java @@ -16,6 +16,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -37,6 +38,7 @@ PersonMapper.class, PersonMapperDecorator.class }) +@WithSpring @IssueKey("592") @ComponentScan(basePackageClasses = SpringDecoratorTest.class) @Configuration diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java index e61bd1cb2c..30611a8d60 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java @@ -7,6 +7,7 @@ import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; import org.mapstruct.factory.Mappers; import static org.assertj.core.api.Assertions.assertThat; @@ -25,6 +26,7 @@ public void shouldGenerateRightName() { @ProcessorTest @WithClasses({ DestinationClassNameWithJsr330Mapper.class }) + @WithJavaxInject public void shouldNotGenerateSpi() throws Exception { Class clazz = DestinationClassNameWithJsr330Mapper.class; diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/ConflictingTypesNamesTest.java b/processor/src/test/java/org/mapstruct/ap/test/imports/ConflictingTypesNamesTest.java index d09ddf66a2..8d61eb8f8a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/imports/ConflictingTypesNamesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/ConflictingTypesNamesTest.java @@ -15,6 +15,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; import org.mapstruct.ap.testutil.runner.GeneratedSource; import static org.assertj.core.api.Assertions.assertThat; @@ -41,6 +42,7 @@ org.mapstruct.ap.test.imports.to.FooWrapper.class, SecondSourceTargetMapper.class }) +@WithJavaxInject public class ConflictingTypesNamesTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java index 65d8acfb2c..dd639684b8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java @@ -17,6 +17,7 @@ import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -41,6 +42,7 @@ GenderJsr330DefaultCompileOptionFieldMapper.class }) @ComponentScan(basePackageClasses = CustomerJsr330DefaultCompileOptionFieldMapper.class) +@WithJavaxInject @Configuration public class Jsr330DefaultCompileOptionFieldMapperTest { diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java index c564648ecf..24bfba5511 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java @@ -14,6 +14,7 @@ import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.beans.factory.annotation.Autowired; @@ -42,6 +43,7 @@ @ProcessorOption( name = "mapstruct.defaultInjectionStrategy", value = "constructor") @ComponentScan(basePackageClasses = CustomerJsr330CompileOptionConstructorMapper.class) @Configuration +@WithJavaxInject public class Jsr330CompileOptionConstructorMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java index 06f4180289..8543d57e38 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java @@ -15,6 +15,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; @@ -42,6 +43,7 @@ @IssueKey("571") @ComponentScan(basePackageClasses = CustomerJsr330ConstructorMapper.class) @Configuration +@WithJavaxInject public class Jsr330ConstructorMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java index fc3bd09561..d8cf0c3925 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java @@ -18,6 +18,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -44,6 +45,7 @@ @IssueKey("571") @ComponentScan(basePackageClasses = CustomerJsr330FieldMapper.class) @Configuration +@WithJavaxInject public class Jsr330FieldMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/SpringDefaultMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/SpringDefaultMapperTest.java index ef1b79b34b..2d9c374721 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/SpringDefaultMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/SpringDefaultMapperTest.java @@ -15,6 +15,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; @@ -41,6 +42,7 @@ @IssueKey("571") @ComponentScan(basePackageClasses = CustomerSpringDefaultMapper.class) @Configuration +@WithSpring public class SpringDefaultMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/SpringCompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/SpringCompileOptionConstructorMapperTest.java index b106a5d788..c3291a5f3b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/SpringCompileOptionConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/SpringCompileOptionConstructorMapperTest.java @@ -21,6 +21,7 @@ import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.beans.factory.annotation.Autowired; @@ -52,6 +53,7 @@ @ProcessorOption( name = "mapstruct.defaultInjectionStrategy", value = "constructor") @ComponentScan(basePackageClasses = CustomerSpringCompileOptionConstructorMapper.class) @Configuration +@WithSpring @DefaultTimeZone("Europe/Berlin") public class SpringCompileOptionConstructorMapperTest { diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java index 1ff3fc8428..2594d5c23d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java @@ -23,6 +23,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; @@ -53,6 +54,7 @@ @IssueKey( "571" ) @ComponentScan(basePackageClasses = CustomerSpringConstructorMapper.class) @Configuration +@WithSpring @DefaultTimeZone("Europe/Berlin") public class SpringConstructorMapperTest { diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java index 8ee03f4bfb..d464dd5b16 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java @@ -15,6 +15,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; @@ -42,6 +43,7 @@ @IssueKey("571") @ComponentScan(basePackageClasses = CustomerSpringFieldMapper.class) @Configuration +@WithSpring public class SpringFieldMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java index 1e9b52cf62..8d600d8604 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java @@ -20,6 +20,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxJaxb; import static org.assertj.core.api.Assertions.assertThat; @@ -42,6 +43,7 @@ public class NestedMappingMethodInvocationTest { OrderDetailsType.class, OrderType.class } ) + @WithJavaxJaxb public void shouldMapViaMethodAndMethod() throws DatatypeConfigurationException { OrderTypeToOrderDtoMapper instance = OrderTypeToOrderDtoMapper.INSTANCE; OrderDto target = instance.sourceToTarget( createOrderType() ); @@ -62,6 +64,7 @@ public void shouldMapViaMethodAndMethod() throws DatatypeConfigurationException ObjectFactory.class, TargetDto.class } ) + @WithJavaxJaxb public void shouldMapViaMethodAndConversion() { SourceTypeTargetDtoMapper instance = SourceTypeTargetDtoMapper.INSTANCE; @@ -78,6 +81,7 @@ public void shouldMapViaMethodAndConversion() { ObjectFactory.class, TargetDto.class } ) + @WithJavaxJaxb public void shouldMapViaConversionAndMethod() { SourceTypeTargetDtoMapper instance = SourceTypeTargetDtoMapper.INSTANCE; diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/samename/SeveralReferencedMappersWithSameSimpleNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/references/samename/SeveralReferencedMappersWithSameSimpleNameTest.java index 68ae703769..fe99685e9c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/references/samename/SeveralReferencedMappersWithSameSimpleNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/references/samename/SeveralReferencedMappersWithSameSimpleNameTest.java @@ -12,6 +12,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; import static org.assertj.core.api.Assertions.assertThat; @@ -30,6 +31,7 @@ Jsr330SourceTargetMapper.class, AnotherSourceTargetMapper.class }) +@WithJavaxInject public class SeveralReferencedMappersWithSameSimpleNameTest { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/jaxb/JaxbFactoryMethodSelectionTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/jaxb/JaxbFactoryMethodSelectionTest.java index 02705a097a..94fe8a1c10 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/jaxb/JaxbFactoryMethodSelectionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/jaxb/JaxbFactoryMethodSelectionTest.java @@ -13,6 +13,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxJaxb; import static org.assertj.core.api.Assertions.assertThat; @@ -28,6 +29,7 @@ OrderDto.class, OrderShippingDetailsDto.class, OrderType.class, OrderShippingDetailsType.class, OrderMapper.class }) +@WithJavaxJaxb public class JaxbFactoryMethodSelectionTest { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/jaxb/UnderscoreSelectionTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/jaxb/UnderscoreSelectionTest.java index f94dd7c94c..caa60eeea3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/jaxb/UnderscoreSelectionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/jaxb/UnderscoreSelectionTest.java @@ -13,6 +13,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxJaxb; import static org.assertj.core.api.Assertions.assertThat; @@ -23,6 +24,7 @@ */ @IssueKey( "726" ) @WithClasses( { UnderscoreType.class, ObjectFactory.class, SuperType.class, SubType.class, UnderscoreMapper.class } ) +@WithJavaxJaxb public class UnderscoreSelectionTest { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxInject.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxInject.java new file mode 100644 index 0000000000..de2ae231e7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxInject.java @@ -0,0 +1,27 @@ +/* + * 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.testutil; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "javax.inject", +}) +public @interface WithJavaxInject { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxJaxb.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxJaxb.java new file mode 100644 index 0000000000..a404187b52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxJaxb.java @@ -0,0 +1,27 @@ +/* + * 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.testutil; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "jaxb-api", +}) +public @interface WithJavaxJaxb { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJoda.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJoda.java new file mode 100644 index 0000000000..5b8bede20b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJoda.java @@ -0,0 +1,27 @@ +/* + * 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.testutil; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "joda-time" +}) +public @interface WithJoda { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java new file mode 100644 index 0000000000..40db5afd81 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java @@ -0,0 +1,28 @@ +/* + * 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.testutil; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "spring-beans", + "spring-context" +}) +public @interface WithSpring { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependencies.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependencies.java new file mode 100644 index 0000000000..c9e8d8c523 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependencies.java @@ -0,0 +1,22 @@ +/* + * 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.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Filip Hrisafov + * @see WithTestDependency + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +public @interface WithTestDependencies { + + WithTestDependency[] value(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependency.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependency.java new file mode 100644 index 0000000000..26d478b666 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependency.java @@ -0,0 +1,28 @@ +/* + * 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.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies the group id of the additional test dependencies that should be added to the test compile path + * + * @author Filip Hrisafov + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Repeatable( WithTestDependencies.class ) +public @interface WithTestDependency { + /** + * @return The group ids of the additional test dependencies for the test compile path + */ + String[] value(); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java index 3f1599aad0..60e9a30072 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.testutil.runner; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -17,13 +18,15 @@ public class CompilationRequest { private final Set> sourceClasses; private final Map, Class> services; private final List processorOptions; + private final Collection testDependencies; CompilationRequest(Compiler compiler, Set> sourceClasses, Map, Class> services, - List processorOptions) { + List processorOptions, Collection testDependencies) { this.compiler = compiler; this.sourceClasses = sourceClasses; this.services = services; this.processorOptions = processorOptions; + this.testDependencies = testDependencies; } @Override @@ -34,6 +37,7 @@ public int hashCode() { result = prime * result + ( ( processorOptions == null ) ? 0 : processorOptions.hashCode() ); result = prime * result + ( ( services == null ) ? 0 : services.hashCode() ); result = prime * result + ( ( sourceClasses == null ) ? 0 : sourceClasses.hashCode() ); + result = prime * result + ( ( testDependencies == null ) ? 0 : testDependencies.hashCode() ); return result; } @@ -53,6 +57,7 @@ public boolean equals(Object obj) { return compiler.equals( other.compiler ) && processorOptions.equals( other.processorOptions ) && services.equals( other.services ) + && testDependencies.equals( other.testDependencies ) && sourceClasses.equals( other.sourceClasses ); } @@ -67,4 +72,8 @@ public List getProcessorOptions() { public Map, Class> getServices() { return services; } + + public Collection getTestDependencies() { + return testDependencies; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index b372974461..7c47d2a2e8 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -22,7 +23,6 @@ import java.util.Map; import java.util.Properties; import java.util.Set; -import java.util.stream.Stream; import com.puppycrawl.tools.checkstyle.Checker; import com.puppycrawl.tools.checkstyle.ConfigurationLoader; @@ -34,6 +34,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.WithTestDependency; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.DisableCheckstyle; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; @@ -98,37 +99,38 @@ protected String getPathSuffix() { return "_" + compiler.name().toLowerCase(); } + /** + * Build the default test compilation classpath + * needed for compiling the generated sources once the processor has run. + */ private static List buildTestCompilationClasspath() { - String[] whitelist = - new String[] { + Collection whitelist = Arrays.asList( // MapStruct annotations in multi-module reactor build or IDE "core" + File.separator + "target", // MapStruct annotations in single module build "org" + File.separator + "mapstruct" + File.separator + "mapstruct" + File.separator, - "guava", - "javax.inject", - "spring-beans", - "spring-context", - "jaxb-api", - "joda-time" }; + "guava" + ); return filterBootClassPath( whitelist ); } + /** + * Build the annotation processor classpath. + * i.e. the classpath that is needed to run the annotation processor with its own dependencies only. + * The optional dependencies are not needed in this classpath. + */ private static List buildProcessorClasspath() { - String[] whitelist = - new String[] { + Collection whitelist = Arrays.asList( "processor" + File.separator + "target", // the processor itself, "freemarker", - "javax.inject", - "spring-context", - "gem-api", - "joda-time" }; + "gem-api" + ); return filterBootClassPath( whitelist ); } - protected static List filterBootClassPath(String[] whitelist) { + protected static List filterBootClassPath(Collection whitelist) { String[] bootClasspath = System.getProperty( "java.class.path" ).split( File.pathSeparator ); String testClasses = "target" + File.separator + "test-classes"; @@ -143,8 +145,8 @@ protected static List filterBootClassPath(String[] whitelist) { return classpath; } - private static boolean isWhitelisted(String path, String[] whitelist) { - return Stream.of( whitelist ).anyMatch( path::contains ); + private static boolean isWhitelisted(String path, Collection whitelist) { + return whitelist.stream().anyMatch( path::contains ); } @Override @@ -368,6 +370,17 @@ private Map, Class> getServices(Method testMethod, Class testClas return services; } + private Collection getAdditionalTestDependencies(Method testMethod, Class testClass) { + Collection testDependencies = new HashSet<>(); + findRepeatableAnnotations( testMethod, WithTestDependency.class ) + .forEach( annotation -> Collections.addAll( testDependencies, annotation.value() ) ); + + findRepeatableAnnotations( testClass, WithTestDependency.class ) + .forEach( annotation -> Collections.addAll( testDependencies, annotation.value() ) ); + + return testDependencies; + } + private void addServices(Map, Class> services, List withImplementations) { for ( WithServiceImplementation withImplementation : withImplementations ) { addService( services, withImplementation ); @@ -446,7 +459,8 @@ private CompilationOutcomeDescriptor compile(ExtensionContext context) { compiler, getTestClasses( testMethod, testClass ), getServices( testMethod, testClass ), - getProcessorOptions( testMethod, testClass ) + getProcessorOptions( testMethod, testClass ), + getAdditionalTestDependencies( testMethod, testClass ) ); ExtensionContext.Store rootStore = context.getRoot().getStore( NAMESPACE ); diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java index 6cab46cc7f..c356347664 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java @@ -6,6 +6,9 @@ package org.mapstruct.ap.testutil.runner; import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Set; @@ -65,13 +68,27 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe return clHelper.compileInOtherClassloader( compilationRequest, - TEST_COMPILATION_CLASSPATH, + getTestCompilationClasspath( compilationRequest ), getSourceFiles( compilationRequest.getSourceClasses() ), SOURCE_DIR, sourceOutputDir, classOutputDir ); } + private static List getTestCompilationClasspath(CompilationRequest request) { + Collection testDependencies = request.getTestDependencies(); + if ( testDependencies.isEmpty() ) { + return TEST_COMPILATION_CLASSPATH; + } + + List testCompilationPaths = new ArrayList<>( + TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() ); + + testCompilationPaths.addAll( TEST_COMPILATION_CLASSPATH ); + testCompilationPaths.addAll( filterBootClassPath( testDependencies ) ); + return testCompilationPaths; + } + private static FilteringParentClassLoader newFilteringClassLoaderForEclipse() { return new FilteringParentClassLoader( // reload eclipse compiler classes @@ -143,12 +160,12 @@ private static String getSourceVersion() { } private static List buildEclipseCompilerClasspath() { - String[] whitelist = - new String[] { + Collection whitelist = Arrays.asList( "tycho-compiler", "ecj", "plexus-compiler-api", - "plexus-component-annotations" }; + "plexus-component-annotations" + ); return filterBootClassPath( whitelist ); } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index 1761956678..2a0b6ba537 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Objects; import javax.annotation.processing.Processor; @@ -57,7 +58,7 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.getSourceClasses() ) ); try { - fileManager.setLocation( StandardLocation.CLASS_PATH, COMPILER_CLASSPATH_FILES ); + fileManager.setLocation( StandardLocation.CLASS_PATH, getCompilerClasspathFiles( compilationRequest ) ); fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( new File( classOutputDir ) ) ); fileManager.setLocation( StandardLocation.SOURCE_OUTPUT, Arrays.asList( new File( sourceOutputDir ) ) ); } @@ -97,6 +98,23 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe diagnostics.getDiagnostics() ); } + private static List getCompilerClasspathFiles(CompilationRequest request) { + Collection testDependencies = request.getTestDependencies(); + if ( testDependencies.isEmpty() ) { + return COMPILER_CLASSPATH_FILES; + } + + List compilerClasspathFiles = new ArrayList<>( + COMPILER_CLASSPATH_FILES.size() + testDependencies.size() ); + + compilerClasspathFiles.addAll( COMPILER_CLASSPATH_FILES ); + for ( String testDependencyPath : filterBootClassPath( testDependencies ) ) { + compilerClasspathFiles.add( new File( testDependencyPath ) ); + } + + return compilerClasspathFiles; + } + private static List asFiles(List paths) { List classpath = new ArrayList<>(); for ( String path : paths ) { From aade31f095f037a0ddf934de24f1638cb24aac6b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 23 Jan 2022 21:12:30 +0100 Subject: [PATCH 044/363] #2468 Update needed dependencies for running CDI tests on Java 16+ --- .../mapstruct/itest/tests/MavenIntegrationTest.java | 2 -- integrationtest/src/test/resources/cdiTest/pom.xml | 2 +- .../org/mapstruct/itest/cdi/CdiBasedMapperTest.java | 4 +++- parent/pom.xml | 11 ++++++----- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 59aa48b053..9696542f58 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -5,7 +5,6 @@ */ package org.mapstruct.itest.tests; -import org.junit.jupiter.api.condition.DisabledForJreRange; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.parallel.Execution; @@ -26,7 +25,6 @@ void autoValueBuilderTest() { } @ProcessorTest(baseDir = "cdiTest") - @DisabledForJreRange(min = JRE.JAVA_16) void cdiTest() { } diff --git a/integrationtest/src/test/resources/cdiTest/pom.xml b/integrationtest/src/test/resources/cdiTest/pom.xml index 30d35a421e..0b3c394e82 100644 --- a/integrationtest/src/test/resources/cdiTest/pom.xml +++ b/integrationtest/src/test/resources/cdiTest/pom.xml @@ -56,7 +56,7 @@ org.jboss.weld - weld-core + weld-core-impl test diff --git a/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java b/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java index 702ab6d255..0c343fce27 100644 --- a/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java +++ b/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java @@ -38,7 +38,9 @@ public static JavaArchive createDeployment() { .addPackage( SourceTargetMapper.class.getPackage() ) .addPackage( DateMapper.class.getPackage() ) .addAsManifestResource( - new StringAsset("org.mapstruct.itest.cdi.SourceTargetMapperDecorator"), + new StringAsset("" + + "org.mapstruct.itest.cdi.SourceTargetMapperDecorator" + + ""), "beans.xml" ); } diff --git a/parent/pom.xml b/parent/pom.xml index 242d5215de..21413dcef0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -152,7 +152,7 @@ javax.enterprise cdi-api - 1.2 + 2.0.SP1 javax.inject @@ -162,19 +162,20 @@ org.jboss.arquillian arquillian-bom - 1.0.2.Final + 1.6.0.Final import pom org.jboss.arquillian.container arquillian-weld-se-embedded-1.1 - 1.0.0.CR7 + 1.0.0.Final org.jboss.weld - weld-core - 2.3.2.Final + weld-core-impl + 3.1.8.Final + test org.glassfish From 5f4d355838adffe2691f4e2d3eb3ac7f6c4c7850 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 29 Jan 2022 11:46:34 +0100 Subject: [PATCH 045/363] #1661 Add support for globally disabling builders --- .../src/main/asciidoc/chapter-2-set-up.asciidoc | 5 +++++ .../chapter-3-defining-a-mapper.asciidoc | 2 +- .../java/org/mapstruct/ap/MappingProcessor.java | 4 ++++ .../mapstruct/ap/internal/option/Options.java | 10 +++++++++- .../util/AnnotationProcessorContext.java | 10 ++++++++-- .../off/SimpleNotRealyImmutableBuilderTest.java | 17 ++++++++++++++++- .../builder/off/SimpleWithBuilderMapper.java | 17 +++++++++++++++++ 7 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleWithBuilderMapper.java diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index 730433639d..54906f7893 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -260,6 +260,11 @@ Supported values are: If a policy is given for a specific mapper via `@Mapper#unmappedSourcePolicy()`, the value from the annotation takes precedence. If a policy is given for a specific bean mapping via `@BeanMapping#ignoreUnmappedSourceProperties()`, it takes precedence over both `@Mapper#unmappedSourcePolicy()` and the option. |`WARN` + +|`mapstruct. +disableBuilders` +|If set to `true`, then MapStruct will not use builder patterns when doing the mapping. This is equivalent to doing `@Mapper( builder = @Builder( disableBuilder = true ) )` for all of your mappers. +|`false` |=== === Using MapStruct with the Java Module System diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index 9d5124ee97..d1c63c25a2 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -532,7 +532,7 @@ Otherwise, you would need to write a custom `BuilderProvider` [TIP] ==== -In case you want to disable using builders then you can use the `NoOpBuilderProvider` by creating a `org.mapstruct.ap.spi.BuilderProvider` file in the `META-INF/services` directory with `org.mapstruct.ap.spi.NoOpBuilderProvider` as it's content. +In case you want to disable using builders then you can pass the MapStruct processor option `mapstruct.disableBuilders` to the compiler. e.g. `-Amapstruct.disableBuilders=true`. ==== [[mapping-with-constructors]] diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 952257753a..0cc6b7cce3 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -87,6 +87,7 @@ MappingProcessor.UNMAPPED_SOURCE_POLICY, MappingProcessor.DEFAULT_COMPONENT_MODEL, MappingProcessor.DEFAULT_INJECTION_STRATEGY, + MappingProcessor.DISABLE_BUILDERS, MappingProcessor.VERBOSE }) public class MappingProcessor extends AbstractProcessor { @@ -104,6 +105,7 @@ public class MappingProcessor extends AbstractProcessor { protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel"; protected static final String DEFAULT_INJECTION_STRATEGY = "mapstruct.defaultInjectionStrategy"; protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; + protected static final String DISABLE_BUILDERS = "mapstruct.disableBuilders"; protected static final String VERBOSE = "mapstruct.verbose"; private Options options; @@ -130,6 +132,7 @@ public synchronized void init(ProcessingEnvironment processingEnv) { processingEnv.getElementUtils(), processingEnv.getTypeUtils(), processingEnv.getMessager(), + options.isDisableBuilders(), options.isVerbose() ); } @@ -146,6 +149,7 @@ private Options createOptions() { processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ), processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ), Boolean.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), + Boolean.valueOf( processingEnv.getOptions().get( DISABLE_BUILDERS ) ), Boolean.valueOf( processingEnv.getOptions().get( VERBOSE ) ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index 46a90d4b43..65089cc139 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -21,13 +21,16 @@ public class Options { private final boolean alwaysGenerateSpi; private final String defaultComponentModel; private final String defaultInjectionStrategy; + private final boolean disableBuilders; private final boolean verbose; public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, ReportingPolicyGem unmappedTargetPolicy, ReportingPolicyGem unmappedSourcePolicy, String defaultComponentModel, String defaultInjectionStrategy, - boolean alwaysGenerateSpi, boolean verbose) { + boolean alwaysGenerateSpi, + boolean disableBuilders, + boolean verbose) { this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorVersionComment = suppressGeneratorVersionComment; this.unmappedTargetPolicy = unmappedTargetPolicy; @@ -35,6 +38,7 @@ public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVers this.defaultComponentModel = defaultComponentModel; this.defaultInjectionStrategy = defaultInjectionStrategy; this.alwaysGenerateSpi = alwaysGenerateSpi; + this.disableBuilders = disableBuilders; this.verbose = verbose; } @@ -66,6 +70,10 @@ public boolean isAlwaysGenerateSpi() { return alwaysGenerateSpi; } + public boolean isDisableBuilders() { + return disableBuilders; + } + public boolean isVerbose() { return verbose; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java index 86fb7e3092..7421dfde7a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java @@ -31,6 +31,7 @@ import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; import org.mapstruct.ap.spi.ImmutablesBuilderProvider; import org.mapstruct.ap.spi.MapStructProcessingEnvironment; +import org.mapstruct.ap.spi.NoOpBuilderProvider; /** * Keeps contextual data in the scope of the entire annotation processor ("application scope"). @@ -51,14 +52,17 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen private Elements elementUtils; private Types typeUtils; private Messager messager; + private boolean disableBuilder; private boolean verbose; - public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messager messager, boolean verbose) { + public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messager messager, boolean disableBuilder, + boolean verbose) { astModifyingAnnotationProcessors = java.util.Collections.unmodifiableList( findAstModifyingAnnotationProcessors( messager ) ); this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.messager = messager; + this.disableBuilder = disableBuilder; this.verbose = verbose; } @@ -103,7 +107,9 @@ else if ( elementUtils.getTypeElement( FreeBuilderConstants.FREE_BUILDER_FQN ) ! + this.accessorNamingStrategy.getClass().getCanonicalName() ); } - this.builderProvider = Services.get( BuilderProvider.class, defaultBuilderProvider ); + this.builderProvider = this.disableBuilder ? + new NoOpBuilderProvider() : + Services.get( BuilderProvider.class, defaultBuilderProvider ); this.builderProvider.init( this ); if ( verbose ) { messager.printMessage( diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleNotRealyImmutableBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleNotRealyImmutableBuilderTest.java index cc11a8fcdb..30cf5ff12d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleNotRealyImmutableBuilderTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleNotRealyImmutableBuilderTest.java @@ -9,12 +9,13 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.mapstruct.factory.Mappers; import static org.assertj.core.api.Assertions.assertThat; -@IssueKey("1743") +@IssueKey("1661") @WithClasses({ SimpleMutablePerson.class, SimpleNotRealyImmutablePerson.class @@ -36,4 +37,18 @@ public void testSimpleImmutableBuilderHappyPath() { assertThat( targetObject.getName() ).isEqualTo( "Bob" ); } + + @ProcessorTest + @WithClasses({ SimpleWithBuilderMapper.class }) + @ProcessorOption( name = "mapstruct.disableBuilders", value = "true") + public void builderGloballyDisabled() { + SimpleWithBuilderMapper mapper = Mappers.getMapper( SimpleWithBuilderMapper.class ); + SimpleMutablePerson source = new SimpleMutablePerson(); + source.setFullName( "Bob" ); + + SimpleNotRealyImmutablePerson targetObject = mapper.toNotRealyImmutable( source ); + + assertThat( targetObject.getName() ).isEqualTo( "Bob" ); + + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleWithBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleWithBuilderMapper.java new file mode 100644 index 0000000000..6df547db78 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleWithBuilderMapper.java @@ -0,0 +1,17 @@ +/* + * 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.builder.off; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface SimpleWithBuilderMapper { + + @Mapping(target = "name", source = "fullName") + SimpleNotRealyImmutablePerson toNotRealyImmutable(SimpleMutablePerson source); +} From 464adc914306ce32c5a5c89cd537009806c6ec34 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 29 Jan 2022 09:24:56 +0100 Subject: [PATCH 046/363] #2682 Change RetentionPolicy of `@DecoratedWith` to `CLASS` In some circumstances (used with other types of aggregating processors, e.g. Spring) the Gradle incremental compilation works correctly only for classes annotated with the `CLASS` or `RUNTIME` retention policy. The `@DecoratedWith` is the only annotation from MapStruct that was `SOURCE` retention. With this commit we are changing its retention policy in order for better compatibility with the Gradle Incremental compilation --- core/src/main/java/org/mapstruct/DecoratedWith.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/mapstruct/DecoratedWith.java b/core/src/main/java/org/mapstruct/DecoratedWith.java index 78b2c580ff..8a644f4b22 100644 --- a/core/src/main/java/org/mapstruct/DecoratedWith.java +++ b/core/src/main/java/org/mapstruct/DecoratedWith.java @@ -145,7 +145,7 @@ * @author Gunnar Morling */ @Target(ElementType.TYPE) -@Retention(RetentionPolicy.SOURCE) +@Retention(RetentionPolicy.CLASS) public @interface DecoratedWith { /** From aed3ff5295fd91f28622f41ea14cf0a4e9670efa Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 23 Jan 2022 22:04:12 +0100 Subject: [PATCH 047/363] #1997 Use builders to construct empty objects in update wrapper --- .../ap/internal/model/MethodReference.java | 53 +++++++++++++++++++ .../model/ObjectFactoryMethodResolver.java | 6 ++- .../ap/internal/model/PropertyMapping.java | 22 ++++++++ .../ap/internal/model/MethodReference.ftl | 2 + .../org/mapstruct/ap/test/bugs/_1997/Car.java | 44 +++++++++++++++ .../ap/test/bugs/_1997/CarDetail.java | 44 +++++++++++++++ .../ap/test/bugs/_1997/CarInsurance.java | 44 +++++++++++++++ .../test/bugs/_1997/CarInsuranceMapper.java | 23 ++++++++ .../ap/test/bugs/_1997/Issue1997Test.java | 35 ++++++++++++ 9 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Car.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarDetail.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsurance.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsuranceMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Issue1997Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java index 78a3830f45..e2e518c55b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; @@ -58,8 +59,10 @@ public class MethodReference extends ModelElement implements Assignment { private final Type definingType; private final List parameterBindings; private final Parameter providingParameter; + private final List methodsToChain; private final boolean isStatic; private final boolean isConstructor; + private final boolean isMethodChaining; /** * Creates a new reference to the given method. @@ -95,6 +98,8 @@ protected MethodReference(Method method, MapperReference declaringMapper, Parame this.isStatic = method.isStatic(); this.name = method.getName(); this.isConstructor = false; + this.methodsToChain = Collections.emptyList(); + this.isMethodChaining = false; } private MethodReference(BuiltInMethod method, ConversionContext contextParam) { @@ -111,6 +116,8 @@ private MethodReference(BuiltInMethod method, ConversionContext contextParam) { this.isStatic = method.isStatic(); this.name = method.getName(); this.isConstructor = false; + this.methodsToChain = Collections.emptyList(); + this.isMethodChaining = false; } private MethodReference(String name, Type definingType, boolean isStatic) { @@ -127,6 +134,8 @@ private MethodReference(String name, Type definingType, boolean isStatic) { this.providingParameter = null; this.isStatic = isStatic; this.isConstructor = false; + this.methodsToChain = Collections.emptyList(); + this.isMethodChaining = false; } private MethodReference(Type definingType, List parameterBindings) { @@ -142,6 +151,8 @@ private MethodReference(Type definingType, List parameterBindi this.providingParameter = null; this.isStatic = false; this.isConstructor = true; + this.methodsToChain = Collections.emptyList(); + this.isMethodChaining = false; if ( parameterBindings.isEmpty() ) { this.importTypes = Collections.emptySet(); @@ -159,6 +170,24 @@ private MethodReference(Type definingType, List parameterBindi } } + private MethodReference(MethodReference... references) { + this.name = null; + this.definingType = null; + this.sourceParameters = Collections.emptyList(); + this.returnType = null; + this.declaringMapper = null; + this.importTypes = Collections.emptySet(); + this.thrownTypes = Collections.emptyList(); + this.isUpdateMethod = false; + this.contextParam = null; + this.parameterBindings = null; + this.providingParameter = null; + this.isStatic = false; + this.isConstructor = false; + this.methodsToChain = Arrays.asList( references ); + this.isMethodChaining = true; + } + public MapperReference getDeclaringMapper() { return declaringMapper; } @@ -267,6 +296,12 @@ public Set getImportTypes() { if ( isStatic() ) { imported.add( definingType ); } + if ( isMethodChaining() ) { + for ( MethodReference methodToChain : methodsToChain ) { + imported.addAll( methodToChain.getImportTypes() ); + } + } + return imported; } @@ -276,6 +311,12 @@ public List getThrownTypes() { if ( assignment != null ) { exceptions.addAll( assignment.getThrownTypes() ); } + if ( isMethodChaining() ) { + for ( MethodReference methodToChain : methodsToChain ) { + exceptions.addAll( methodToChain.getThrownTypes() ); + } + + } return exceptions; } @@ -311,6 +352,14 @@ public boolean isConstructor() { return isConstructor; } + public boolean isMethodChaining() { + return isMethodChaining; + } + + public List getMethodsToChain() { + return methodsToChain; + } + public List getParameterBindings() { return parameterBindings; } @@ -385,6 +434,10 @@ public static MethodReference forConstructorInvocation(Type type, List> getMatchingFactoryMethods( Meth } public static MethodReference getBuilderFactoryMethod(Method method, BuilderType builder ) { + return getBuilderFactoryMethod( method.getReturnType(), builder ); + } + + public static MethodReference getBuilderFactoryMethod(Type typeToBuild, BuilderType builder ) { if ( builder == null ) { return null; } @@ -149,7 +153,7 @@ public static MethodReference getBuilderFactoryMethod(Method method, BuilderType return null; } - if ( !builder.getBuildingType().isAssignableTo( method.getReturnType() ) ) { + if ( !builder.getBuildingType().isAssignableTo( typeToBuild ) ) { //TODO print error message return null; } 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 f17ab9ddb1..ad788de43b 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 @@ -419,6 +419,28 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) { Assignment factory = ObjectFactoryMethodResolver .getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( rightHandSide ), ctx ); + + if ( factory == null && targetBuilderType != null) { + // If there is no dedicated factory method and the target has a builder we will try to use that + MethodReference builderFactoryMethod = ObjectFactoryMethodResolver.getBuilderFactoryMethod( + targetType, + targetBuilderType + ); + + if ( builderFactoryMethod != null ) { + + MethodReference finisherMethod = BuilderFinisherMethodResolver.getBuilderFinisherMethod( + method, + targetBuilderType, + ctx + ); + + if ( finisherMethod != null ) { + factory = MethodReference.forMethodChaining( builderFactoryMethod, finisherMethod ); + } + } + + } return new UpdateWrapper( rhs, method.getThrownTypes(), 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 d0ee7bef3b..271ba58f9b 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 @@ -18,6 +18,8 @@ <@includeModel object=definingType/>.<@methodCall/> <#elseif constructor> new <@includeModel object=definingType/><#if (parameterBindings?size > 0)>( <@arguments/> )<#else>() + <#elseif methodChaining> + <#list methodsToChain as methodToChain><@includeModel object=methodToChain /><#if methodToChain_has_next>. <#else> <@methodCall/> diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Car.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Car.java new file mode 100644 index 0000000000..87722cdb50 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Car.java @@ -0,0 +1,44 @@ +/* + * 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._1997; + +/** + * @author Filip Hrisafov + */ +public class Car { + private String model; + + private Car(Builder builder) { + this.model = builder.model; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String model; + + public Builder model(String model) { + this.model = model; + return this; + } + + public Car build() { + return new Car( this ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarDetail.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarDetail.java new file mode 100644 index 0000000000..84472d1263 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarDetail.java @@ -0,0 +1,44 @@ +/* + * 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._1997; + +/** + * @author Filip Hrisafov + */ +public class CarDetail { + private String model; + + private CarDetail(Builder builder) { + this.model = builder.model; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String model; + + public Builder model(String model) { + this.model = model; + return this; + } + + public CarDetail build() { + return new CarDetail( this ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsurance.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsurance.java new file mode 100644 index 0000000000..83fb4350dc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsurance.java @@ -0,0 +1,44 @@ +/* + * 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._1997; + +/** + * @author Filip Hrisafov + */ +public class CarInsurance { + private CarDetail detail; + + private CarInsurance(Builder builder) { + this.detail = builder.detail; + } + + public CarDetail getDetail() { + return detail; + } + + public void setDetail(CarDetail detail) { + this.detail = detail; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private CarDetail detail; + + public Builder detail(CarDetail detail) { + this.detail = detail; + return this; + } + + public CarInsurance build() { + return new CarInsurance( this ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsuranceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsuranceMapper.java new file mode 100644 index 0000000000..79369b9a79 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsuranceMapper.java @@ -0,0 +1,23 @@ +/* + * 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._1997; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CarInsuranceMapper { + + CarInsuranceMapper INSTANCE = Mappers.getMapper( CarInsuranceMapper.class ); + + @Mapping(source = "model", target = "detail.model") + void update(Car source, @MappingTarget CarInsurance target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Issue1997Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Issue1997Test.java new file mode 100644 index 0000000000..5645ca173b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Issue1997Test.java @@ -0,0 +1,35 @@ +/* + * 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._1997; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Car.class, + CarDetail.class, + CarInsurance.class, + CarInsuranceMapper.class +}) +class Issue1997Test { + + @ProcessorTest + void shouldCorrectCreateIntermediateObjectsWithBuilder() { + Car source = Car.builder().model( "Model S" ).build(); + CarInsurance target = CarInsurance.builder().build(); + assertThat( target.getDetail() ).isNull(); + + CarInsuranceMapper.INSTANCE.update( source, target ); + + assertThat( target.getDetail() ).isNotNull(); + assertThat( target.getDetail().getModel() ).isEqualTo( "Model S" ); + } +} From 12070186a43f9d4f03c0b441cbe5cd56c16089a9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 22 Jan 2022 18:57:41 +0100 Subject: [PATCH 048/363] #2567 Add support for Jakarta Injection Support for Jakarta is done in 2 ways: * current jsr330 component model - In this case Jakarta Inject will be used if javax.inject is not available * new jakarta component model - In this case Jakarta Inject will always be used --- .../java/org/mapstruct/DecoratedWith.java | 8 +- .../java/org/mapstruct/InjectionStrategy.java | 2 +- core/src/main/java/org/mapstruct/Mapper.java | 7 +- .../main/java/org/mapstruct/MapperConfig.java | 7 +- .../java/org/mapstruct/MappingConstants.java | 15 +++- .../main/asciidoc/chapter-2-set-up.asciidoc | 3 +- .../test/resources/fullFeatureTest/pom.xml | 4 + parent/pom.xml | 5 ++ processor/pom.xml | 5 ++ .../ap/internal/gem/MappingConstantsGem.java | 2 + .../processor/JakartaComponentProcessor.java | 78 +++++++++++++++++++ .../processor/Jsr330ComponentProcessor.java | 24 +++++- ...p.internal.processor.ModelElementProcessor | 1 + .../jsr330/JakartaJsr330DecoratorTest.java | 55 +++++++++++++ .../jakarta/jsr330/PersonMapper.java | 25 ++++++ .../jakarta/jsr330/PersonMapperDecorator.java | 27 +++++++ .../mapstruct/ap/test/gem/ConstantTest.java | 2 + ...akartaDefaultCompileOptionFieldMapper.java | 21 +++++ ...akartaDefaultCompileOptionFieldMapper.java | 26 +++++++ ...30DefaultCompileOptionFieldMapperTest.java | 55 +++++++++++++ ...taDefaultCompileOptionFieldMapperTest.java | 53 +++++++++++++ ...JakartaCompileOptionConstructorMapper.java | 24 ++++++ ...JakartaCompileOptionConstructorMapper.java | 26 +++++++ ...330CompileOptionConstructorMapperTest.java | 59 ++++++++++++++ ...rtaCompileOptionConstructorMapperTest.java | 57 ++++++++++++++ .../constructor/ConstructorJakartaConfig.java | 18 +++++ .../CustomerJakartaConstructorMapper.java | 26 +++++++ .../GenderJakartaConstructorMapper.java | 25 ++++++ ...JakartaAndJsr330ConstructorMapperTest.java | 58 ++++++++++++++ .../JakartaConstructorMapperTest.java | 56 +++++++++++++ .../jakarta/field/CustomerFieldMapper.java | 22 ++++++ .../jakarta/field/FieldJakartaConfig.java | 17 ++++ .../field/GenderJakartaFieldMapper.java | 25 ++++++ .../JakartaAndJsr330FieldMapperTest.java | 53 +++++++++++++ .../jakarta/field/JakartaFieldMapperTest.java | 51 ++++++++++++ ...30DefaultCompileOptionFieldMapperTest.java | 3 + ...330CompileOptionConstructorMapperTest.java | 3 + .../Jsr330ConstructorMapperTest.java | 3 + .../jsr330/field/Jsr330FieldMapperTest.java | 3 + ...Jsr330DefaultCompileOptionFieldMapper.java | 21 +++++ ...Jsr330DefaultCompileOptionFieldMapper.java | 26 +++++++ ...30DefaultCompileOptionFieldMapperTest.java | 55 +++++++++++++ ...30DefaultCompileOptionFieldMapperTest.java | 53 +++++++++++++ .../ap/testutil/WithJakartaInject.java | 27 +++++++ 44 files changed, 1123 insertions(+), 13 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/JakartaJsr330DecoratorTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/CustomerJakartaDefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/GenderJakartaDefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaDefaultCompileOptionFieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/CustomerJakartaCompileOptionConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/GenderJakartaCompileOptionConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaAndJsr330CompileOptionConstructorMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaCompileOptionConstructorMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/ConstructorJakartaConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/CustomerJakartaConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/GenderJakartaConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaAndJsr330ConstructorMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaConstructorMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/CustomerFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/FieldJakartaConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/GenderJakartaFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaAndJsr330FieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaFieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/CustomerJsr330DefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/GenderJsr330DefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaJsr330DefaultCompileOptionFieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaInject.java diff --git a/core/src/main/java/org/mapstruct/DecoratedWith.java b/core/src/main/java/org/mapstruct/DecoratedWith.java index 8a644f4b22..b4ca05f3de 100644 --- a/core/src/main/java/org/mapstruct/DecoratedWith.java +++ b/core/src/main/java/org/mapstruct/DecoratedWith.java @@ -103,12 +103,12 @@ * private PersonMapper personMapper; // injects the decorator, with the injected original mapper *
    * - *

    3. Component model 'jsr330'

    + *

    3. Component model 'jsr330' or 'jakarta'

    *

    Referencing the original mapper

    *

    - * JSR 330 doesn't specify qualifiers and only allows to specifically name the beans. Hence, the generated - * implementation of the original mapper is annotated with - * {@code @javax.inject.Named("fully-qualified-name-of-generated-impl")} and {@code @Singleton} (please note that when + * JSR 330 / Jakarta Inject doesn't specify qualifiers and only allows to specifically name the beans. Hence, + * the generated implementation of the original mapper is annotated with + * {@code @Named("fully-qualified-name-of-generated-impl")} and {@code @Singleton} (please note that when * using a decorator, the class name of the mapper implementation ends with an underscore). To inject that bean in your * decorator, add the same annotation to the delegate field (e.g. by copy/pasting it from the generated class): * diff --git a/core/src/main/java/org/mapstruct/InjectionStrategy.java b/core/src/main/java/org/mapstruct/InjectionStrategy.java index 84baa8afa9..b841667671 100644 --- a/core/src/main/java/org/mapstruct/InjectionStrategy.java +++ b/core/src/main/java/org/mapstruct/InjectionStrategy.java @@ -7,7 +7,7 @@ /** * Strategy for handling injection. This is only used on annotated based component models such as CDI, Spring and - * JSR330. + * JSR330 / Jakarta. * * @author Kevin Grüneberg */ diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index c5aed661f3..0436c0cf69 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -142,7 +142,12 @@ * can be retrieved via {@code @Autowired} *

  • * {@code jsr330}: the generated mapper is annotated with {@code @javax.inject.Named} and - * {@code @Singleton}, and can be retrieved via {@code @Inject}
  • + * {@code @Singleton}, and can be retrieved via {@code @Inject}. + * The annotations will either be from javax.inject or jakarta.inject, + * depending on which one is available, with javax.inject having precedence. + *
  • + * {@code jakarta}: the generated mapper is annotated with {@code @jakarta.inject.Named} and + * {@code @Singleton}, and can be retrieved via {@code @Inject}.
  • * * The method overrides a componentModel set in a central configuration set * by {@link #config() } diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index 757a4ab0af..893801cdaa 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -131,7 +131,12 @@ * can be retrieved via {@code @Autowired} *
  • * {@code jsr330}: the generated mapper is annotated with {@code @javax.inject.Named} and - * {@code @Singleton}, and can be retrieved via {@code @Inject}
  • + * {@code @Singleton}, and can be retrieved via {@code @Inject}. + * The annotations will either be from javax.inject or jakarta.inject, + * depending on which one is available, with javax.inject having precedence. + *
  • + * {@code jakarta}: the generated mapper is annotated with {@code @jakarta.inject.Named} and + * {@code @Singleton}, and can be retrieved via {@code @Inject}.
  • * * * @return The component model for the generated mapper. diff --git a/core/src/main/java/org/mapstruct/MappingConstants.java b/core/src/main/java/org/mapstruct/MappingConstants.java index 002f005299..ce2438298c 100644 --- a/core/src/main/java/org/mapstruct/MappingConstants.java +++ b/core/src/main/java/org/mapstruct/MappingConstants.java @@ -120,11 +120,24 @@ private ComponentModel() { public static final String SPRING = "spring"; /** - * The generated mapper is annotated with @javax.inject.Named and @Singleton, and can be retrieved via @Inject + * The generated mapper is annotated with @Named and @Singleton, and can be retrieved via @Inject. + * The annotations are either from {@code javax.inject} or {@code jakarta.inject}. + * Priority have the {@code javax.inject} annotations. + * In case you want to only use Jakarta then use {@link #JAKARTA}. * + * @see #JAKARTA */ public static final String JSR330 = "jsr330"; + /** + * The generated mapper is annotated with @Named and @Singleton, and can be retrieved via @Inject. + * The annotations are from {@code jakarta.inject}. + * In case you want to use {@code javax.inject} then use {@link #JSR330}. + * + * @see #JSR330 + */ + public static final String JAKARTA = "jakarta"; + } } diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index 54906f7893..d95983e840 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -217,7 +217,8 @@ Supported values are: * `default`: the mapper uses no component model, instances are typically retrieved via `Mappers#getMapper(Class)` * `cdi`: the generated mapper is an application-scoped CDI bean and can be retrieved via `@Inject` * `spring`: the generated mapper is a singleton-scoped Spring bean and can be retrieved via `@Autowired` -* `jsr330`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject`, e.g. using Spring +* `jsr330`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject` (from javax.inject or jakarta.inject, depending which one is available with javax.inject having priority), e.g. using Spring +* `jakarta`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject` (from jakarta.inject), e.g. using Spring If a component model is given for a specific mapper via `@Mapper#componentModel()`, the value from the annotation takes precedence. |`default` diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index ebf4590d30..ec26f45ce2 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -60,6 +60,10 @@ javax.inject javax.inject + + jakarta.inject + jakarta.inject-api + diff --git a/parent/pom.xml b/parent/pom.xml index 21413dcef0..1f75e9d6d5 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -159,6 +159,11 @@ javax.inject 1 + + jakarta.inject + jakarta.inject-api + 2.0.1 + org.jboss.arquillian arquillian-bom diff --git a/processor/pom.xml b/processor/pom.xml index 4f2476ceb0..2a3c38abfc 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -83,6 +83,11 @@ javax.inject test + + jakarta.inject + jakarta.inject-api + test + diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java index b49b9d17e5..cb6d49c0cb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java @@ -49,6 +49,8 @@ private ComponentModelGem() { public static final String SPRING = "spring"; public static final String JSR330 = "jsr330"; + + public static final String JAKARTA = "jakarta"; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java new file mode 100644 index 0000000000..5161ea689c --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java @@ -0,0 +1,78 @@ +/* + * 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.internal.processor; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.mapstruct.ap.internal.gem.MappingConstantsGem; +import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Mapper; + +/** + * A {@link ModelElementProcessor} which converts the given {@link Mapper} + * object into a Jakarta Inject style bean in case "jakarta" is configured as the + * target component model for this mapper. + * + * @author Filip Hrisafov + */ +public class JakartaComponentProcessor extends AnnotationBasedComponentModelProcessor { + @Override + protected String getComponentModelIdentifier() { + return MappingConstantsGem.ComponentModelGem.JAKARTA; + } + + @Override + protected List getTypeAnnotations(Mapper mapper) { + if ( mapper.getDecorator() == null ) { + return Arrays.asList( singleton(), named() ); + } + else { + return Arrays.asList( singleton(), namedDelegate( mapper ) ); + } + } + + @Override + protected List getDecoratorAnnotations() { + return Arrays.asList( singleton(), named() ); + } + + @Override + protected List getDelegatorReferenceAnnotations(Mapper mapper) { + return Arrays.asList( inject(), namedDelegate( mapper ) ); + } + + @Override + protected List getMapperReferenceAnnotations() { + return Collections.singletonList( inject() ); + } + + @Override + protected boolean requiresGenerationOfDecoratorClass() { + return true; + } + + private Annotation singleton() { + return new Annotation( getTypeFactory().getType( "jakarta.inject.Singleton" ) ); + } + + private Annotation named() { + return new Annotation( getTypeFactory().getType( "jakarta.inject.Named" ) ); + } + + private Annotation namedDelegate(Mapper mapper) { + return new Annotation( + getTypeFactory().getType( "jakarta.inject.Named" ), + Collections.singletonList( '"' + mapper.getPackageName() + "." + mapper.getName() + '"' ) + ); + } + + private Annotation inject() { + return new Annotation( getTypeFactory().getType( "jakarta.inject.Inject" ) ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java index 18a6e4f69d..6a6cc16580 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.AnnotationProcessingException; /** * A {@link ModelElementProcessor} which converts the given {@link Mapper} @@ -58,21 +60,35 @@ protected boolean requiresGenerationOfDecoratorClass() { } private Annotation singleton() { - return new Annotation( getTypeFactory().getType( "javax.inject.Singleton" ) ); + return new Annotation( getType( "Singleton" ) ); } private Annotation named() { - return new Annotation( getTypeFactory().getType( "javax.inject.Named" ) ); + return new Annotation( getType( "Named" ) ); } private Annotation namedDelegate(Mapper mapper) { return new Annotation( - getTypeFactory().getType( "javax.inject.Named" ), + getType( "Named" ), Collections.singletonList( '"' + mapper.getPackageName() + "." + mapper.getName() + '"' ) ); } private Annotation inject() { - return new Annotation( getTypeFactory().getType( "javax.inject.Inject" ) ); + return new Annotation( getType( "Inject" ) ); + } + + private Type getType(String simpleName) { + if ( getTypeFactory().isTypeAvailable( "javax.inject." + simpleName ) ) { + return getTypeFactory().getType( "javax.inject." + simpleName ); + } + + if ( getTypeFactory().isTypeAvailable( "jakarta.inject." + simpleName ) ) { + return getTypeFactory().getType( "jakarta.inject." + simpleName ); + } + + throw new AnnotationProcessingException( + "Couldn't find any of the JSR330 or Jakarta Dependency Inject types." + + " Are you missing a dependency on your classpath?" ); } } diff --git a/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor b/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor index 7b27e54faf..cab1d83ed7 100644 --- a/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor +++ b/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor @@ -4,6 +4,7 @@ org.mapstruct.ap.internal.processor.CdiComponentProcessor org.mapstruct.ap.internal.processor.Jsr330ComponentProcessor +org.mapstruct.ap.internal.processor.JakartaComponentProcessor org.mapstruct.ap.internal.processor.MapperCreationProcessor org.mapstruct.ap.internal.processor.MapperRenderingProcessor org.mapstruct.ap.internal.processor.MethodRetrievalProcessor diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/JakartaJsr330DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/JakartaJsr330DecoratorTest.java new file mode 100644 index 0000000000..2e0dfcec00 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/JakartaJsr330DecoratorTest.java @@ -0,0 +1,55 @@ +/* + * 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.decorator.jakarta.jsr330; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + PersonMapper.class, + PersonMapperDecorator.class +}) +@IssueKey("2567") +@WithJakartaInject +// We can't use Spring for testing since Spring 5 does not support Jakarta Inject +// However, we can test the generated source code +public class JakartaJsr330DecoratorTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void hasCorrectImports() { + // check the decorator + generatedSource.forMapper( PersonMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + // check the plain mapper + generatedSource.forDecoratedMapper( PersonMapper.class ).content() + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapper.java new file mode 100644 index 0000000000..74ef38439f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapper.java @@ -0,0 +1,25 @@ +/* + * 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.decorator.jakarta.jsr330; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +@DecoratedWith(PersonMapperDecorator.class) +public interface PersonMapper { + + @Mapping( target = "name", ignore = true ) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapperDecorator.java new file mode 100644 index 0000000000..f16a9f8c1e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapperDecorator.java @@ -0,0 +1,27 @@ +/* + * 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.decorator.jakarta.jsr330; + +import jakarta.inject.Inject; +import jakarta.inject.Named; + +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +public abstract class PersonMapperDecorator implements PersonMapper { + + @Inject + @Named("org.mapstruct.ap.test.decorator.jakarta.jsr330.PersonMapperImpl_") + private PersonMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/gem/ConstantTest.java b/processor/src/test/java/org/mapstruct/ap/test/gem/ConstantTest.java index e3a5b7c264..548ee61f2e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/gem/ConstantTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/gem/ConstantTest.java @@ -40,5 +40,7 @@ public void componentModelContantsShouldBeEqual() { assertThat( MappingConstants.ComponentModel.CDI ).isEqualTo( MappingConstantsGem.ComponentModelGem.CDI ); assertThat( MappingConstants.ComponentModel.SPRING ).isEqualTo( MappingConstantsGem.ComponentModelGem.SPRING ); assertThat( MappingConstants.ComponentModel.JSR330 ).isEqualTo( MappingConstantsGem.ComponentModelGem.JSR330 ); + assertThat( MappingConstants.ComponentModel.JAKARTA ) + .isEqualTo( MappingConstantsGem.ComponentModelGem.JAKARTA ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/CustomerJakartaDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/CustomerJakartaDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..2fb6ae0d07 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/CustomerJakartaDefaultCompileOptionFieldMapper.java @@ -0,0 +1,21 @@ +/* + * 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.injectionstrategy.jakarta._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA, + uses = GenderJakartaDefaultCompileOptionFieldMapper.class) +public interface CustomerJakartaDefaultCompileOptionFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/GenderJakartaDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/GenderJakartaDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..da3409ef41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/GenderJakartaDefaultCompileOptionFieldMapper.java @@ -0,0 +1,26 @@ +/* + * 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.injectionstrategy.jakarta._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA) +public interface GenderJakartaDefaultCompileOptionFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..c647605e66 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java @@ -0,0 +1,55 @@ +/* + * 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.injectionstrategy.jakarta._default; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jakarta. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaDefaultCompileOptionFieldMapper.class, + GenderJakartaDefaultCompileOptionFieldMapper.class +}) +@WithJakartaInject +@WithJavaxInject +public class JakartaAndJsr330DefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaDefaultCompileOptionFieldMapper" ) + .doesNotContain( "public CustomerJakartaDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..01b43c5b46 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaDefaultCompileOptionFieldMapperTest.java @@ -0,0 +1,53 @@ +/* + * 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.injectionstrategy.jakarta._default; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jakarta. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaDefaultCompileOptionFieldMapper.class, + GenderJakartaDefaultCompileOptionFieldMapper.class +}) +@WithJakartaInject +public class JakartaDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaDefaultCompileOptionFieldMapper" ) + .doesNotContain( "public CustomerJakartaDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/CustomerJakartaCompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/CustomerJakartaCompileOptionConstructorMapper.java new file mode 100644 index 0000000000..9775d05c87 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/CustomerJakartaCompileOptionConstructorMapper.java @@ -0,0 +1,24 @@ +/* + * 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.injectionstrategy.jakarta.compileoptionconstructor; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper( componentModel = MappingConstants.ComponentModel.JAKARTA, + uses = GenderJakartaCompileOptionConstructorMapper.class ) +public interface CustomerJakartaCompileOptionConstructorMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/GenderJakartaCompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/GenderJakartaCompileOptionConstructorMapper.java new file mode 100644 index 0000000000..4539bb1c69 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/GenderJakartaCompileOptionConstructorMapper.java @@ -0,0 +1,26 @@ +/* + * 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.injectionstrategy.jakarta.compileoptionconstructor; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA) +public interface GenderJakartaCompileOptionConstructorMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaAndJsr330CompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaAndJsr330CompileOptionConstructorMapperTest.java new file mode 100644 index 0000000000..c897891b14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaAndJsr330CompileOptionConstructorMapperTest.java @@ -0,0 +1,59 @@ +/* + * 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.injectionstrategy.jakarta.compileoptionconstructor; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test constructor injection for component model jakarta with compile option + * mapstruct.defaultInjectionStrategy=constructor + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaCompileOptionConstructorMapper.class, + GenderJakartaCompileOptionConstructorMapper.class +}) +@ProcessorOption(name = "mapstruct.defaultInjectionStrategy", value = "constructor") +@WithJakartaInject +@WithJavaxInject +public class JakartaAndJsr330CompileOptionConstructorMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaCompileOptionConstructorMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "private final GenderJakartaCompileOptionConstructorMapper" ) + .contains( "@Inject" + lineSeparator() + + " public CustomerJakartaCompileOptionConstructorMapperImpl" + + "(GenderJakartaCompileOptionConstructorMapper" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaCompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaCompileOptionConstructorMapperTest.java new file mode 100644 index 0000000000..cc36591bb7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaCompileOptionConstructorMapperTest.java @@ -0,0 +1,57 @@ +/* + * 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.injectionstrategy.jakarta.compileoptionconstructor; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test constructor injection for component model jakarta with compile option + * mapstruct.defaultInjectionStrategy=constructor + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaCompileOptionConstructorMapper.class, + GenderJakartaCompileOptionConstructorMapper.class +}) +@ProcessorOption(name = "mapstruct.defaultInjectionStrategy", value = "constructor") +@WithJakartaInject +public class JakartaCompileOptionConstructorMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaCompileOptionConstructorMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "private final GenderJakartaCompileOptionConstructorMapper" ) + .contains( "@Inject" + lineSeparator() + + " public CustomerJakartaCompileOptionConstructorMapperImpl" + + "(GenderJakartaCompileOptionConstructorMapper" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/ConstructorJakartaConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/ConstructorJakartaConfig.java new file mode 100644 index 0000000000..0bc9bb16e6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/ConstructorJakartaConfig.java @@ -0,0 +1,18 @@ +/* + * 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.injectionstrategy.jakarta.constructor; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = MappingConstants.ComponentModel.JAKARTA, + injectionStrategy = InjectionStrategy.CONSTRUCTOR) +public interface ConstructorJakartaConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/CustomerJakartaConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/CustomerJakartaConstructorMapper.java new file mode 100644 index 0000000000..73d201cbfb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/CustomerJakartaConstructorMapper.java @@ -0,0 +1,26 @@ +/* + * 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.injectionstrategy.jakarta.constructor; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper( componentModel = MappingConstants.ComponentModel.JAKARTA, + uses = GenderJakartaConstructorMapper.class, + injectionStrategy = InjectionStrategy.CONSTRUCTOR ) +public interface CustomerJakartaConstructorMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/GenderJakartaConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/GenderJakartaConstructorMapper.java new file mode 100644 index 0000000000..7cbd0bb3df --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/GenderJakartaConstructorMapper.java @@ -0,0 +1,25 @@ +/* + * 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.injectionstrategy.jakarta.constructor; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = ConstructorJakartaConfig.class) +public interface GenderJakartaConstructorMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaAndJsr330ConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaAndJsr330ConstructorMapperTest.java new file mode 100644 index 0000000000..03b5dcf2bb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaAndJsr330ConstructorMapperTest.java @@ -0,0 +1,58 @@ +/* + * 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.injectionstrategy.jakarta.constructor; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaConstructorMapper.class, + GenderJakartaConstructorMapper.class, + ConstructorJakartaConfig.class +}) +@ComponentScan(basePackageClasses = CustomerJakartaConstructorMapper.class) +@Configuration +@WithJakartaInject +@WithJavaxInject +public class JakartaAndJsr330ConstructorMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaConstructorMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "private final GenderJakartaConstructorMapper" ) + .contains( "@Inject" + lineSeparator() + + " public CustomerJakartaConstructorMapperImpl(GenderJakartaConstructorMapper" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaConstructorMapperTest.java new file mode 100644 index 0000000000..45ea7efca8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaConstructorMapperTest.java @@ -0,0 +1,56 @@ +/* + * 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.injectionstrategy.jakarta.constructor; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaConstructorMapper.class, + GenderJakartaConstructorMapper.class, + ConstructorJakartaConfig.class +}) +@ComponentScan(basePackageClasses = CustomerJakartaConstructorMapper.class) +@Configuration +@WithJakartaInject +public class JakartaConstructorMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaConstructorMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "private final GenderJakartaConstructorMapper" ) + .contains( "@Inject" + lineSeparator() + + " public CustomerJakartaConstructorMapperImpl(GenderJakartaConstructorMapper" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/CustomerFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/CustomerFieldMapper.java new file mode 100644 index 0000000000..550d4ee945 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/CustomerFieldMapper.java @@ -0,0 +1,22 @@ +/* + * 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.injectionstrategy.jakarta.field; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA, uses = GenderJakartaFieldMapper.class, + injectionStrategy = InjectionStrategy.FIELD) +public interface CustomerFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/FieldJakartaConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/FieldJakartaConfig.java new file mode 100644 index 0000000000..b3ad9474cf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/FieldJakartaConfig.java @@ -0,0 +1,17 @@ +/* + * 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.injectionstrategy.jakarta.field; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = MappingConstants.ComponentModel.JAKARTA, injectionStrategy = InjectionStrategy.FIELD) +public interface FieldJakartaConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/GenderJakartaFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/GenderJakartaFieldMapper.java new file mode 100644 index 0000000000..ee5b959307 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/GenderJakartaFieldMapper.java @@ -0,0 +1,25 @@ +/* + * 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.injectionstrategy.jakarta.field; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = FieldJakartaConfig.class) +public interface GenderJakartaFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaAndJsr330FieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaAndJsr330FieldMapperTest.java new file mode 100644 index 0000000000..56ea535571 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaAndJsr330FieldMapperTest.java @@ -0,0 +1,53 @@ +/* + * 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.injectionstrategy.jakarta.field; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerFieldMapper.class, + GenderJakartaFieldMapper.class, + FieldJakartaConfig.class +}) +@IssueKey("2567") +@WithJakartaInject +@WithJavaxInject +public class JakartaAndJsr330FieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerFieldMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaFieldMapper" ) + .doesNotContain( "public CustomerJakartaFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaFieldMapperTest.java new file mode 100644 index 0000000000..2257bcb7ba --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaFieldMapperTest.java @@ -0,0 +1,51 @@ +/* + * 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.injectionstrategy.jakarta.field; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerFieldMapper.class, + GenderJakartaFieldMapper.class, + FieldJakartaConfig.class +}) +@IssueKey("2567") +@WithJakartaInject +public class JakartaFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerFieldMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaFieldMapper" ) + .doesNotContain( "public CustomerJakartaFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java index dd639684b8..6b92d9815a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java @@ -87,6 +87,9 @@ public void shouldConvertToTarget() { public void shouldHaveFieldInjection() { generatedSource.forMapper( CustomerJsr330DefaultCompileOptionFieldMapper.class ) .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) .contains( "@Inject" + lineSeparator() + " private GenderJsr330DefaultCompileOptionFieldMapper" ) .doesNotContain( "public CustomerJsr330DefaultCompileOptionFieldMapperImpl(" ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java index 24bfba5511..fe9cf295f4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java @@ -86,6 +86,9 @@ public void shouldConvertToTarget() { public void shouldHaveConstructorInjectionFromCompileOption() { generatedSource.forMapper( CustomerJsr330CompileOptionConstructorMapper.class ) .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) .contains( "private final GenderJsr330CompileOptionConstructorMapper" ) .contains( "@Inject" + lineSeparator() + " public CustomerJsr330CompileOptionConstructorMapperImpl" + diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java index 8543d57e38..a5cfce5c2e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java @@ -86,6 +86,9 @@ public void shouldConvertToTarget() { public void shouldHaveConstructorInjection() { generatedSource.forMapper( CustomerJsr330ConstructorMapper.class ) .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) .contains( "private final GenderJsr330ConstructorMapper" ) .contains( "@Inject" + lineSeparator() + " public CustomerJsr330ConstructorMapperImpl(GenderJsr330ConstructorMapper" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java index d8cf0c3925..84e67f63a8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java @@ -89,6 +89,9 @@ public void shouldConvertToTarget() { public void shouldHaveFieldInjection() { generatedSource.forMapper( CustomerJsr330FieldMapper.class ) .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) .contains( "@Inject" + lineSeparator() + " private GenderJsr330FieldMapper" ) .doesNotContain( "public CustomerJsr330FieldMapperImpl(" ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/CustomerJsr330DefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/CustomerJsr330DefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..80f5c60ef0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/CustomerJsr330DefaultCompileOptionFieldMapper.java @@ -0,0 +1,21 @@ +/* + * 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.injectionstrategy.jsr330.jakarta; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Andrei Arlou + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330, + uses = GenderJsr330DefaultCompileOptionFieldMapper.class) +public interface CustomerJsr330DefaultCompileOptionFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/GenderJsr330DefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/GenderJsr330DefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..4a9b56c1b2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/GenderJsr330DefaultCompileOptionFieldMapper.java @@ -0,0 +1,26 @@ +/* + * 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.injectionstrategy.jsr330.jakarta; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Andrei Arlou + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +public interface GenderJsr330DefaultCompileOptionFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..da7176fc7a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java @@ -0,0 +1,55 @@ +/* + * 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.injectionstrategy.jsr330.jakarta; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jsr330. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJsr330DefaultCompileOptionFieldMapper.class, + GenderJsr330DefaultCompileOptionFieldMapper.class +}) +@WithJakartaInject +@WithJavaxInject +public class JakartaAndJsr330DefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJavaxInjection() { + generatedSource.forMapper( CustomerJsr330DefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJsr330DefaultCompileOptionFieldMapper" ) + .doesNotContain( "public CustomerJsr330DefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "jakarta.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaJsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaJsr330DefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..f78721fb1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaJsr330DefaultCompileOptionFieldMapperTest.java @@ -0,0 +1,53 @@ +/* + * 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.injectionstrategy.jsr330.jakarta; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jsr330. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJsr330DefaultCompileOptionFieldMapper.class, + GenderJsr330DefaultCompileOptionFieldMapper.class +}) +@WithJakartaInject +public class JakartaJsr330DefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJsr330DefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJsr330DefaultCompileOptionFieldMapper" ) + .doesNotContain( "public CustomerJsr330DefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaInject.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaInject.java new file mode 100644 index 0000000000..ccb0b030a1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaInject.java @@ -0,0 +1,27 @@ +/* + * 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.testutil; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "jakarta.inject-api", +}) +public @interface WithJakartaInject { + +} From 37835a560770c307b1955a11cb399985bdc6a7b4 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 30 Jan 2022 20:49:05 +0100 Subject: [PATCH 049/363] #2677 Use type without bounds when looking for read / presence accessor in a SourceReference --- .../model/beanmapping/SourceReference.java | 7 +- .../ap/test/bugs/_2677/Issue2677Mapper.java | 117 ++++++++++++++++++ .../ap/test/bugs/_2677/Issue2677Test.java | 92 ++++++++++++++ 3 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index d5c18a99f1..8824f95901 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -311,11 +311,12 @@ private List matchWithSourceAccessorTypes(Type type, String[] ent Type newType = type; for ( int i = 0; i < entryNames.length; i++ ) { boolean matchFound = false; - ReadAccessor readAccessor = newType.getReadAccessor( entryNames[i] ); + Type noBoundsType = newType.withoutBounds(); + ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i] ); if ( readAccessor != null ) { - PresenceCheckAccessor presenceChecker = newType.getPresenceChecker( entryNames[i] ); + PresenceCheckAccessor presenceChecker = noBoundsType.getPresenceChecker( entryNames[i] ); newType = typeFactory.getReturnType( - (DeclaredType) newType.getTypeMirror(), + (DeclaredType) noBoundsType.getTypeMirror(), readAccessor ); sourceEntries.add( forSourceReference( diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Mapper.java new file mode 100644 index 0000000000..e74ad1e770 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Mapper.java @@ -0,0 +1,117 @@ +/* + * 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._2677; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2677Mapper { + + Issue2677Mapper INSTANCE = Mappers.getMapper( Issue2677Mapper.class ); + + @Mapping(target = "id", source = "value.id") + Output map(Wrapper in); + + @Mapping(target = ".", source = "value") + Output mapImplicitly(Wrapper in); + + @Mapping(target = "id", source = "value.id") + Output mapFromParent(Wrapper in); + + @Mapping(target = "id", source = "value.id") + Output mapFromChild(Wrapper in); + + @Mapping( target = "value", source = "wrapperValue") + Wrapper mapToWrapper(String wrapperValue, Wrapper wrapper); + + @Mapping(target = "id", source = "value.id") + Output mapWithPresenceCheck(Wrapper in); + + class Wrapper { + private final T value; + private final String status; + + public Wrapper(T value, String status) { + this.value = value; + this.status = status; + } + + public String getStatus() { + return status; + } + + public T getValue() { + return value; + } + } + + class Parent { + private final int id; + + public Parent(int id) { + this.id = id; + } + + public int getId() { + return id; + } + } + + class Child extends Parent { + private final String whatever; + + public Child(int id, String whatever) { + super( id ); + this.whatever = whatever; + } + + public String getWhatever() { + return whatever; + } + } + + class ParentWithPresenceCheck { + private final int id; + + public ParentWithPresenceCheck(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public boolean hasId() { + return id > 10; + } + } + + class Output { + private int id; + private String status; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Test.java new file mode 100644 index 0000000000..0eb4d6d794 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Test.java @@ -0,0 +1,92 @@ +/* + * 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._2677; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2677") +@WithClasses({ + Issue2677Mapper.class +}) +class Issue2677Test { + + @ProcessorTest + void shouldCorrectlyUseGenericsWithExtends() { + Issue2677Mapper.Parent parent = new Issue2677Mapper.Parent( 10 ); + Issue2677Mapper.Child child = new Issue2677Mapper.Child( 15, "Test" ); + + Issue2677Mapper.Output output = Issue2677Mapper.INSTANCE.map( new Issue2677Mapper.Wrapper<>( + parent, + "extends" + ) ); + + assertThat( output.getStatus() ).isEqualTo( "extends" ); + assertThat( output.getId() ).isEqualTo( 10 ); + + output = Issue2677Mapper.INSTANCE.mapFromChild( new Issue2677Mapper.Wrapper<>( + child, + "child" + ) ); + + assertThat( output.getStatus() ).isEqualTo( "child" ); + assertThat( output.getId() ).isEqualTo( 15 ); + + output = Issue2677Mapper.INSTANCE.mapFromParent( new Issue2677Mapper.Wrapper<>( + parent, + "parent" + ) ); + + assertThat( output.getStatus() ).isEqualTo( "parent" ); + assertThat( output.getId() ).isEqualTo( 10 ); + + output = Issue2677Mapper.INSTANCE.mapImplicitly( new Issue2677Mapper.Wrapper<>( + child, + "implicit" + ) ); + + assertThat( output.getStatus() ).isEqualTo( "implicit" ); + assertThat( output.getId() ).isEqualTo( 15 ); + + Issue2677Mapper.Wrapper result = Issue2677Mapper.INSTANCE.mapToWrapper( + "test", + new Issue2677Mapper.Wrapper<>( + child, + "super" + ) + ); + + assertThat( result.getStatus() ).isEqualTo( "super" ); + assertThat( result.getValue() ).isEqualTo( "test" ); + + output = Issue2677Mapper.INSTANCE.mapWithPresenceCheck( + new Issue2677Mapper.Wrapper<>( + new Issue2677Mapper.ParentWithPresenceCheck( 8 ), + "presenceCheck" + ) + ); + + assertThat( output.getStatus() ).isEqualTo( "presenceCheck" ); + assertThat( output.getId() ).isEqualTo( 0 ); + + output = Issue2677Mapper.INSTANCE.mapWithPresenceCheck( + new Issue2677Mapper.Wrapper<>( + new Issue2677Mapper.ParentWithPresenceCheck( 15 ), + "presenceCheck" + ) + ); + + assertThat( output.getStatus() ).isEqualTo( "presenceCheck" ); + assertThat( output.getId() ).isEqualTo( 15 ); + + } +} From 2a2c11e871001909187cea173d442624e724c64c Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 30 Jan 2022 20:52:22 +0100 Subject: [PATCH 050/363] #2629 Use ModuleElement when getting type element Prior to this MapStruct would only use `Elements#getTypeElement`. With this PR if the mapper being generated is within a module MapStruct will use that module for the methods that are needed (getTypeElement and getPackageElement). Adapt the build to require a minimum Java 11 for building the processor module. Adapt the GitHub actions to properly run integration tests with Java 8 Ignore Java 9 usages for the animal-sniffer-plugin --- .github/workflows/main.yml | 36 ++++++-- .../itest/tests/MavenIntegrationTest.java | 8 ++ .../test/resources/lombokModuleTest/pom.xml | 85 +++++++++++++++++++ .../src/main/java/module-info.java | 9 ++ .../org/mapstruct/itest/lombok/Address.java | 18 ++++ .../mapstruct/itest/lombok/AddressDto.java | 19 +++++ .../org/mapstruct/itest/lombok/Person.java | 30 +++++++ .../org/mapstruct/itest/lombok/PersonDto.java | 30 +++++++ .../mapstruct/itest/lombok/PersonMapper.java | 21 +++++ .../itest/lombok/LombokMapperTest.java | 37 ++++++++ parent/pom.xml | 12 ++- processor/pom.xml | 1 + .../org/mapstruct/ap/MappingProcessor.java | 6 +- .../DefaultModelElementProcessorContext.java | 5 +- .../util/AbstractElementUtilsDecorator.java | 33 ++++++- .../util/EclipseElementUtilsDecorator.java | 4 +- .../ap/internal/util/ElementUtils.java | 7 +- .../internal/util/IgnoreJRERequirement.java | 16 ++++ .../util/JavacElementUtilsDecorator.java | 4 +- 19 files changed, 357 insertions(+), 24 deletions(-) create mode 100644 integrationtest/src/test/resources/lombokModuleTest/pom.xml create mode 100644 integrationtest/src/test/resources/lombokModuleTest/src/main/java/module-info.java create mode 100644 integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Address.java create mode 100644 integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/AddressDto.java create mode 100644 integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Person.java create mode 100644 integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonDto.java create mode 100644 integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonMapper.java create mode 100644 integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/IgnoreJRERequirement.java diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b023631628..6d80d2da89 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - java: [11, 13, 16, 17] + java: [13, 16, 17] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: @@ -25,15 +25,15 @@ jobs: - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true linux: - name: 'Linux JDK 8' + name: 'Linux JDK 11' runs-on: ubuntu-latest steps: - name: 'Checkout' uses: actions/checkout@v2 - - name: 'Set up JDK 8' + - name: 'Set up JDK 11' uses: actions/setup-java@v1 with: - java-version: 8 + java-version: 11 - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install - name: 'Generate coverage report' @@ -43,25 +43,43 @@ jobs: - name: 'Publish Snapshots' if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'mapstruct/mapstruct' run: ./mvnw -s etc/ci-settings.xml -DskipTests=true -DskipDistribution=true deploy + linux-jdk-8: + name: 'Linux JDK 8' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + - name: 'Set up JDK 11 for building everything' + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: 'Install Processor' + run: ./mvnw ${MAVEN_ARGS} -DskipTests install -pl processor -am + - name: 'Set up JDK 8 for running integration tests' + uses: actions/setup-java@v1 + with: + java-version: 8 + - name: 'Run integration tests' + run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest windows: name: 'Windows' runs-on: windows-latest steps: - uses: actions/checkout@v2 - - name: 'Set up JDK 8' + - name: 'Set up JDK 11' uses: actions/setup-java@v1 with: - java-version: 8 + java-version: 11 - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} install + run: ./mvnw %MAVEN_ARGS% install mac: name: 'Mac OS' runs-on: macos-latest steps: - uses: actions/checkout@v2 - - name: 'Set up JDK 8' + - name: 'Set up JDK 11' uses: actions/setup-java@v1 with: - java-version: 8 + java-version: 11 - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 9696542f58..4f4182d5c7 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -85,6 +85,14 @@ void jsr330Test() { void lombokBuilderTest() { } + @ProcessorTest(baseDir = "lombokModuleTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC, + ProcessorTest.ProcessorType.JAVAC_WITH_PATHS + }) + @EnabledForJreRange(min = JRE.JAVA_11) + void lombokModuleTest() { + } + @ProcessorTest(baseDir = "namingStrategyTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }) diff --git a/integrationtest/src/test/resources/lombokModuleTest/pom.xml b/integrationtest/src/test/resources/lombokModuleTest/pom.xml new file mode 100644 index 0000000000..16b32cfb4f --- /dev/null +++ b/integrationtest/src/test/resources/lombokModuleTest/pom.xml @@ -0,0 +1,85 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + lombokModuleIntegrationTest + jar + + + 11 + 11 + + + + + org.projectlombok + lombok + compile + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + compile + + + + + + generate-via-compiler-plugin-with-annotation-processor-paths + + false + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + \${compiler-id} + + + ${project.groupId} + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok + 1.18.22 + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + + + org.eclipse.tycho + tycho-compiler-jdt + ${org.eclipse.tycho.compiler-jdt.version} + + + + + + + + diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/module-info.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/module-info.java new file mode 100644 index 0000000000..9bc85bea86 --- /dev/null +++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/module-info.java @@ -0,0 +1,9 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +module org.example { + requires org.mapstruct; + requires lombok; +} diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Address.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Address.java new file mode 100644 index 0000000000..f83c6881ce --- /dev/null +++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Address.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.lombok; + +public class Address { + private final String addressLine; + + public Address(String addressLine) { + this.addressLine = addressLine; + } + + public String getAddressLine() { + return addressLine; + } +} diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/AddressDto.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/AddressDto.java new file mode 100644 index 0000000000..d6b4f30f88 --- /dev/null +++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/AddressDto.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.lombok; + +public class AddressDto { + + private final String addressLine; + + public AddressDto(String addressLine) { + this.addressLine = addressLine; + } + + public String getAddressLine() { + return addressLine; + } +} diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Person.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Person.java new file mode 100644 index 0000000000..b6ca47fca1 --- /dev/null +++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/Person.java @@ -0,0 +1,30 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.lombok; + +public class Person { + private final String name; + private final int age; + private final Address address; + + public Person(String name, int age, Address address) { + this.name = name; + this.age = age; + this.address = address; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public Address getAddress() { + return address; + } +} diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonDto.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonDto.java new file mode 100644 index 0000000000..3223778663 --- /dev/null +++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonDto.java @@ -0,0 +1,30 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.lombok; + +public class PersonDto { + private final String name; + private final int age; + private final AddressDto address; + + public PersonDto(String name, int age, AddressDto address) { + this.name = name; + this.age = age; + this.address = address; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public AddressDto getAddress() { + return address; + } +} diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonMapper.java b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonMapper.java new file mode 100644 index 0000000000..27184bad27 --- /dev/null +++ b/integrationtest/src/test/resources/lombokModuleTest/src/main/java/org/mapstruct/itest/lombok/PersonMapper.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.lombok; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface PersonMapper { + + PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class ); + + Person fromDto(PersonDto personDto); + PersonDto toDto(Person personDto); +} diff --git a/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java b/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java new file mode 100644 index 0000000000..f8b702896d --- /dev/null +++ b/integrationtest/src/test/resources/lombokModuleTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java @@ -0,0 +1,37 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.lombok; + +import org.junit.Test; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for generation of Lombok Builder Mapper implementations + * + * @author Eric Martineau + */ +public class LombokMapperTest { + + @Test + public void testSimpleImmutableBuilderHappyPath() { + PersonDto personDto = PersonMapper.INSTANCE.toDto( new Person( "Bob", 33, new Address( "Wild Drive" ) ) ); + assertThat( personDto.getAge() ).isEqualTo( 33 ); + assertThat( personDto.getName() ).isEqualTo( "Bob" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "Wild Drive" ); + } + + @Test + public void testLombokToImmutable() { + Person person = PersonMapper.INSTANCE.fromDto( new PersonDto( "Bob", 33, new AddressDto( "Wild Drive" ) ) ); + assertThat( person.getAge() ).isEqualTo( 33 ); + assertThat( person.getName() ).isEqualTo( "Bob" ); + assertThat( person.getAddress() ).isNotNull(); + assertThat( person.getAddress().getAddressLine() ).isEqualTo( "Wild Drive" ); + } +} diff --git a/parent/pom.xml b/parent/pom.xml index 1f75e9d6d5..4ca3d9b2c0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -36,6 +36,11 @@ jdt_apt + + 1.8 @@ -214,7 +219,7 @@ org.projectlombok lombok - 1.18.20 + 1.18.22 org.immutables @@ -666,6 +671,9 @@ java18 1.0 + + org.mapstruct.ap.internal.util.IgnoreJRERequirement + @@ -685,7 +693,7 @@ - [1.8,) + [${minimum.java.version},) diff --git a/processor/pom.xml b/processor/pom.xml index 2a3c38abfc..ec3f20a241 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -24,6 +24,7 @@ + 11 diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 0cc6b7cce3..4f9e0fa384 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -269,7 +269,11 @@ private void processMapperElements(Set mapperElements, RoundContext // of one outer interface List tst = mapperElement.getEnclosedElements(); ProcessorContext context = new DefaultModelElementProcessorContext( - processingEnv, options, roundContext, getDeclaredTypesNotToBeImported( mapperElement ) + processingEnv, + options, + roundContext, + getDeclaredTypesNotToBeImported( mapperElement ), + mapperElement ); processMapperTypeElement( context, mapperElement ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java index d6a05cec32..51ea5dd786 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java @@ -13,6 +13,7 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic.Kind; import org.mapstruct.ap.internal.model.common.TypeFactory; @@ -46,14 +47,14 @@ public class DefaultModelElementProcessorContext implements ProcessorContext { private final RoundContext roundContext; public DefaultModelElementProcessorContext(ProcessingEnvironment processingEnvironment, Options options, - RoundContext roundContext, Map notToBeImported) { + RoundContext roundContext, Map notToBeImported, TypeElement mapperElement) { this.processingEnvironment = processingEnvironment; this.messager = new DelegatingMessager( processingEnvironment.getMessager(), options.isVerbose() ); this.accessorNaming = roundContext.getAnnotationProcessorContext().getAccessorNaming(); this.versionInformation = DefaultVersionInformation.fromProcessingEnvironment( processingEnvironment ); this.delegatingTypes = TypeUtils.create( processingEnvironment, versionInformation ); - this.delegatingElements = ElementUtils.create( processingEnvironment, versionInformation ); + this.delegatingElements = ElementUtils.create( processingEnvironment, versionInformation, mapperElement ); this.roundContext = roundContext; this.typeFactory = new TypeFactory( delegatingElements, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java index 14734b9c8e..72a44d106f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java @@ -13,11 +13,13 @@ import java.util.ListIterator; import java.util.Map; import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.element.ModuleElement; import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; @@ -35,19 +37,44 @@ public abstract class AbstractElementUtilsDecorator implements ElementUtils { private final Elements delegate; + /** + * The module element when running with the module system, + * {@code null} otherwise. + */ + private final Element moduleElement; - AbstractElementUtilsDecorator(ProcessingEnvironment processingEnv) { + @IgnoreJRERequirement + AbstractElementUtilsDecorator(ProcessingEnvironment processingEnv, TypeElement mapperElement) { this.delegate = processingEnv.getElementUtils(); + if ( SourceVersion.RELEASE_8.compareTo( processingEnv.getSourceVersion() ) >= 0 ) { + // We are running with Java 8 or lower + this.moduleElement = null; + } + else { + this.moduleElement = this.delegate.getModuleOf( mapperElement ); + } } @Override + @IgnoreJRERequirement public PackageElement getPackageElement(CharSequence name) { - return delegate.getPackageElement( name ); + if ( this.moduleElement == null ) { + return this.delegate.getPackageElement( name ); + } + + // If the module element is not null then we must be running on Java 8+ + return this.delegate.getPackageElement( (ModuleElement) moduleElement, name ); } @Override + @IgnoreJRERequirement public TypeElement getTypeElement(CharSequence name) { - return delegate.getTypeElement( name ); + if ( this.moduleElement == null ) { + return this.delegate.getTypeElement( name ); + } + + // If the module element is not null then we must be running on Java 8+ + return this.delegate.getTypeElement( (ModuleElement) moduleElement, name ); } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/EclipseElementUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/EclipseElementUtilsDecorator.java index aaf1bb7e20..3c1dc84d36 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/EclipseElementUtilsDecorator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/EclipseElementUtilsDecorator.java @@ -13,8 +13,8 @@ public class EclipseElementUtilsDecorator extends AbstractElementUtilsDecorator private final Elements delegate; - EclipseElementUtilsDecorator(ProcessingEnvironment processingEnv) { - super( processingEnv ); + EclipseElementUtilsDecorator(ProcessingEnvironment processingEnv, TypeElement mapperElement) { + super( processingEnv, mapperElement ); this.delegate = processingEnv.getElementUtils(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/ElementUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/util/ElementUtils.java index fdd221c1a6..3e026cde80 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/ElementUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/ElementUtils.java @@ -16,12 +16,13 @@ public interface ElementUtils extends Elements { - static ElementUtils create(ProcessingEnvironment processingEnvironment, VersionInformation info ) { + static ElementUtils create(ProcessingEnvironment processingEnvironment, VersionInformation info, + TypeElement mapperElement) { if ( info.isEclipseJDTCompiler() ) { - return new EclipseElementUtilsDecorator( processingEnvironment ); + return new EclipseElementUtilsDecorator( processingEnvironment, mapperElement ); } else { - return new JavacElementUtilsDecorator( processingEnvironment ); + return new JavacElementUtilsDecorator( processingEnvironment, mapperElement ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/IgnoreJRERequirement.java b/processor/src/main/java/org/mapstruct/ap/internal/util/IgnoreJRERequirement.java new file mode 100644 index 0000000000..565ebcce12 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/IgnoreJRERequirement.java @@ -0,0 +1,16 @@ +/* + * 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.internal.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE }) +public @interface IgnoreJRERequirement { +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JavacElementUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JavacElementUtilsDecorator.java index ad035743cf..fa0e46171b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/JavacElementUtilsDecorator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JavacElementUtilsDecorator.java @@ -10,8 +10,8 @@ public class JavacElementUtilsDecorator extends AbstractElementUtilsDecorator { - JavacElementUtilsDecorator(ProcessingEnvironment processingEnv) { - super( processingEnv ); + JavacElementUtilsDecorator(ProcessingEnvironment processingEnv, TypeElement mapperElement) { + super( processingEnv, mapperElement ); } @Override From 7bb85d05c0a7f8dc755c271a37e8feb3b09a592e Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sun, 6 Feb 2022 20:03:23 +0100 Subject: [PATCH 051/363] 2696: Invert `@SubclassMappings` with `@InheritInverseConfiguration`. (#2708) * #2696: Added support for '@InheritInverseConfiguration' with '@SubclassMappings'. * #2696: Overriding of inverse inheritence implemented. New order has preference over inherited order. --- .../ap/internal/model/BeanMappingMethod.java | 1 + .../model/source/MappingMethodOptions.java | 50 ++++++++++--- .../internal/model/source/SourceMethod.java | 14 +++- .../model/source/SubclassMappingOptions.java | 72 ++++++++++++------ .../model/source/SubclassValidator.java | 19 +++-- .../processor/MapperCreationProcessor.java | 36 ++++++--- .../processor/MethodRetrievalProcessor.java | 22 ++++-- .../mapstruct/ap/internal/util/Message.java | 1 + .../ErroneousInverseSubclassMapper.java | 29 ++++++++ .../InverseOrderSubclassMapper.java | 40 ++++++++++ .../subclassmapping/SimpleSubclassMapper.java | 6 ++ .../subclassmapping/SubclassMappingTest.java | 74 +++++++++++++++---- .../fixture/SubSourceOverride.java | 18 +++++ .../fixture/SubSourceSeparate.java | 18 +++++ .../fixture/SubTargetSeparate.java | 18 +++++ .../fixture/SubclassAbstractMapper.java | 6 ++ .../fixture/SubclassFixtureTest.java | 4 + .../fixture/SubclassAbstractMapperImpl.java | 62 +++++++++++++++- 18 files changed, 417 insertions(+), 73 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousInverseSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InverseOrderSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceOverride.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceSeparate.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTargetSeparate.java 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 bdfd8cf54a..267702826b 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 @@ -157,6 +157,7 @@ public Builder forgedMethod(ForgedMethod forgedMethod) { return this; } + @Override public BeanMappingMethod build() { BeanMappingOptions beanMapping = method.getOptions().getBeanMapping(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java index 45b1175f8d..4a140c4fb9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.lang.model.element.AnnotationMirror; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.model.common.Type; @@ -35,7 +36,8 @@ public class MappingMethodOptions { null, null, Collections.emptyList(), - Collections.emptySet() + Collections.emptySet(), + null ); private MapperOptions mapper; @@ -46,14 +48,16 @@ public class MappingMethodOptions { private EnumMappingOptions enumMappingOptions; private List valueMappings; private boolean fullyInitialized; - private Set subclassMapping; + private Set subclassMappings; + + private SubclassValidator subclassValidator; public MappingMethodOptions(MapperOptions mapper, Set mappings, IterableMappingOptions iterableMapping, MapMappingOptions mapMapping, BeanMappingOptions beanMapping, EnumMappingOptions enumMappingOptions, List valueMappings, - Set subclassMapping) { + Set subclassMappings, SubclassValidator subclassValidator) { this.mapper = mapper; this.mappings = mappings; this.iterableMapping = iterableMapping; @@ -61,7 +65,8 @@ public MappingMethodOptions(MapperOptions mapper, Set mappings, this.beanMapping = beanMapping; this.enumMappingOptions = enumMappingOptions; this.valueMappings = valueMappings; - this.subclassMapping = subclassMapping; + this.subclassMappings = subclassMappings; + this.subclassValidator = subclassValidator; } /** @@ -102,7 +107,7 @@ public List getValueMappings() { } public Set getSubclassMappings() { - return subclassMapping; + return subclassMappings; } public void setIterableMapping(IterableMappingOptions iterableMapping) { @@ -144,10 +149,13 @@ public void markAsFullyInitialized() { /** * Merges in all the mapping options configured, giving the already defined options precedence. * + * @param sourceMethod the method which inherits the options. * @param templateMethod the template method with the options to inherit, may be {@code null} * @param isInverse if {@code true}, the specified options are from an inverse method + * @param annotationMirror the annotation on which the compile errors will be shown. */ - public void applyInheritedOptions(SourceMethod templateMethod, boolean isInverse) { + public void applyInheritedOptions(SourceMethod sourceMethod, SourceMethod templateMethod, boolean isInverse, + AnnotationMirror annotationMirror) { MappingMethodOptions templateOptions = templateMethod.getOptions(); if ( null != templateOptions ) { if ( !getIterableMapping().hasAnnotation() && templateOptions.getIterableMapping().hasAnnotation() ) { @@ -184,7 +192,7 @@ public void applyInheritedOptions(SourceMethod templateMethod, boolean isInverse } else { if ( templateOptions.getValueMappings() != null ) { - // iff there are also inherited mappings, we inverse and add them. + // if there are also inherited mappings, we inverse and add them. for ( ValueMappingOptions inheritedValueMapping : templateOptions.getValueMappings() ) { ValueMappingOptions valueMapping = isInverse ? inheritedValueMapping.inverse() : inheritedValueMapping; @@ -196,7 +204,13 @@ public void applyInheritedOptions(SourceMethod templateMethod, boolean isInverse } } - // Do NOT inherit subclass mapping options!!! + if ( isInverse ) { + // normal inheritence of subclass mappings will result runtime in infinite loops. + List inheritedMappings = SubclassMappingOptions.copyForInverseInheritance( + templateOptions.getSubclassMappings(), + getBeanMapping() ); + addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings ); + } Set newMappings = new LinkedHashSet<>(); for ( MappingOptions mapping : templateOptions.getMappings() ) { @@ -218,6 +232,21 @@ public void applyInheritedOptions(SourceMethod templateMethod, boolean isInverse } } + private void addAllNonRedefined(SourceMethod sourceMethod, AnnotationMirror annotationMirror, + List inheritedMappings) { + Set redefinedSubclassMappings = new HashSet<>( subclassMappings ); + for ( SubclassMappingOptions subclassMappingOption : inheritedMappings ) { + if ( !redefinedSubclassMappings.contains( subclassMappingOption ) ) { + if ( subclassValidator.isValidUsage( + sourceMethod.getExecutable(), + annotationMirror, + subclassMappingOption.getSource() ) ) { + subclassMappings.add( subclassMappingOption ); + } + } + } + } + private void addAllNonRedefined(Set inheritedMappings) { Set redefinedSources = new HashSet<>(); Set redefinedTargets = new HashSet<>(); @@ -328,7 +357,7 @@ private String getFirstTargetPropertyName(MappingOptions mapping) { /** * SubclassMappingOptions are not inherited to forged methods. They would result in an infinite loop if they were. * - * @return a MappingMethodOptions without SubclassMappingOptions. + * @return a MappingMethodOptions without SubclassMappingOptions or SubclassValidator. */ public static MappingMethodOptions getForgedMethodInheritedOptions(MappingMethodOptions options) { return new MappingMethodOptions( @@ -339,7 +368,8 @@ public static MappingMethodOptions getForgedMethodInheritedOptions(MappingMethod options.beanMapping, options.enumMappingOptions, options.valueMappings, - Collections.emptySet() ); + Collections.emptySet(), + null ); } } 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 5895ecba2a..92180bca8a 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 @@ -16,15 +16,14 @@ import javax.lang.model.element.Modifier; import org.mapstruct.ap.internal.gem.ConditionGem; -import org.mapstruct.ap.internal.util.TypeUtils; - +import org.mapstruct.ap.internal.gem.ObjectFactoryGem; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.gem.ObjectFactoryGem; import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.TypeUtils; import static org.mapstruct.ap.internal.model.source.MappingMethodUtils.isEnumMapping; import static org.mapstruct.ap.internal.util.Collections.first; @@ -98,6 +97,7 @@ public static class Builder { private Set subclassMappings; private boolean verboseLogging; + private SubclassValidator subclassValidator; public Builder setDeclaringMapper(Type declaringMapper) { this.declaringMapper = declaringMapper; @@ -159,6 +159,11 @@ public Builder setSubclassMappings(Set subclassMappings) return this; } + public Builder setSubclassValidator(SubclassValidator subclassValidator) { + this.subclassValidator = subclassValidator; + return this; + } + public Builder setTypeUtils(TypeUtils typeUtils) { this.typeUtils = typeUtils; return this; @@ -212,7 +217,8 @@ public SourceMethod build() { beanMapping, enumMappingOptions, valueMappings, - subclassMappings + subclassMappings, + subclassValidator ); this.typeParameters = this.executable.getTypeParameters() diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java index 54a0ba4565..08df8083bc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.model.source; +import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.lang.model.element.ExecutableElement; @@ -31,11 +32,13 @@ public class SubclassMappingOptions extends DelegatingOptions { private final TypeMirror source; private final TypeMirror target; + private final TypeUtils typeUtils; - public SubclassMappingOptions(TypeMirror source, TypeMirror target, DelegatingOptions next) { + public SubclassMappingOptions(TypeMirror source, TypeMirror target, TypeUtils typeUtils, DelegatingOptions next) { super( next ); this.source = source; this.target = target; + this.typeUtils = typeUtils; } @Override @@ -84,7 +87,9 @@ private static boolean isConsistent(SubclassMappingGem gem, ExecutableElement me targetSubclass.toString() ); isConsistent = false; } - subclassValidator.isInCorrectOrder( method, gem.mirror(), targetSubclass ); + if ( !subclassValidator.isValidUsage( method, gem.mirror(), sourceSubclass ) ) { + isConsistent = false; + } return isConsistent; } @@ -115,10 +120,10 @@ public TypeMirror getTarget() { public static void addInstances(SubclassMappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, FormattingMessager messager, TypeUtils typeUtils, Set mappings, - List sourceParameters, Type resultType) { - SubclassValidator subclassValidator = new SubclassValidator( messager, typeUtils ); + List sourceParameters, Type resultType, + SubclassValidator subclassValidator) { for ( SubclassMappingGem subclassMappingGem : gem.value().get() ) { - addAndValidateInstance( + addInstance( subclassMappingGem, method, beanMappingOptions, @@ -134,24 +139,8 @@ public static void addInstances(SubclassMappingsGem gem, ExecutableElement metho public static void addInstance(SubclassMappingGem subclassMapping, ExecutableElement method, BeanMappingOptions beanMappingOptions, FormattingMessager messager, TypeUtils typeUtils, Set mappings, - List sourceParameters, Type resultType) { - addAndValidateInstance( - subclassMapping, - method, - beanMappingOptions, - messager, - typeUtils, - mappings, - sourceParameters, - resultType, - new SubclassValidator( messager, typeUtils ) ); - } - - private static void addAndValidateInstance(SubclassMappingGem subclassMapping, ExecutableElement method, - BeanMappingOptions beanMappingOptions, FormattingMessager messager, - TypeUtils typeUtils, Set mappings, - List sourceParameters, Type resultType, - SubclassValidator subclassValidator) { + List sourceParameters, Type resultType, + SubclassValidator subclassValidator) { if ( !isConsistent( subclassMapping, method, @@ -166,6 +155,41 @@ private static void addAndValidateInstance(SubclassMappingGem subclassMapping, E TypeMirror sourceSubclass = subclassMapping.source().getValue(); TypeMirror targetSubclass = subclassMapping.target().getValue(); - mappings.add( new SubclassMappingOptions( sourceSubclass, targetSubclass, beanMappingOptions ) ); + mappings + .add( + new SubclassMappingOptions( + sourceSubclass, + targetSubclass, + typeUtils, + beanMappingOptions ) ); + } + + public static List copyForInverseInheritance(Set subclassMappings, + BeanMappingOptions beanMappingOptions) { + // we are not interested in keeping it unique at this point. + List mappings = new ArrayList<>(); + for ( SubclassMappingOptions subclassMapping : subclassMappings ) { + mappings.add( + new SubclassMappingOptions( + subclassMapping.target, + subclassMapping.source, + subclassMapping.typeUtils, + beanMappingOptions ) ); + } + return mappings; + } + + @Override + public boolean equals(Object obj) { + if ( obj == null || !( obj instanceof SubclassMappingOptions ) ) { + return false; + } + SubclassMappingOptions other = (SubclassMappingOptions) obj; + return typeUtils.isSameType( source, other.source ); + } + + @Override + public int hashCode() { + return 1; // use a stable value because TypeMirror is not safe to use for hashCode. } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java index dd79ba0c02..601b588e42 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java @@ -20,19 +20,28 @@ * * @author Ben Zegveld */ -class SubclassValidator { +public class SubclassValidator { private final FormattingMessager messager; private final List handledSubclasses = new ArrayList<>(); private final TypeUtils typeUtils; - SubclassValidator(FormattingMessager messager, TypeUtils typeUtils) { + public SubclassValidator(FormattingMessager messager, TypeUtils typeUtils) { this.messager = messager; this.typeUtils = typeUtils; } - public boolean isInCorrectOrder(Element e, AnnotationMirror annotation, TypeMirror sourceType) { + public boolean isValidUsage(Element e, AnnotationMirror annotation, TypeMirror sourceType) { for ( TypeMirror typeMirror : handledSubclasses ) { + if ( typeUtils.isSameType( sourceType, typeMirror ) ) { + messager + .printMessage( + e, + annotation, + Message.SUBCLASSMAPPING_DOUBLE_SOURCE_SUBCLASS, + sourceType ); + return false; + } if ( typeUtils.isAssignable( sourceType, typeMirror ) ) { messager .printMessage( @@ -43,11 +52,11 @@ public boolean isInCorrectOrder(Element e, AnnotationMirror annotation, TypeMirr typeMirror, sourceType, typeMirror ); - return true; + return false; } } handledSubclasses.add( sourceType ); - return false; + return true; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index bae0cda1ae..d8acb10398 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -14,6 +14,7 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -320,7 +321,7 @@ private List getMappingMethods(MapperOptions mapperAnnotation, Li continue; } - mergeInheritedOptions( method, mapperAnnotation, methods, new ArrayList<>() ); + mergeInheritedOptions( method, mapperAnnotation, methods, new ArrayList<>(), null ); MappingMethodOptions mappingOptions = method.getOptions(); @@ -462,7 +463,8 @@ private M createWithElementMappingMethod(Sour } private void mergeInheritedOptions(SourceMethod method, MapperOptions mapperConfig, - List availableMethods, List initializingMethods) { + List availableMethods, List initializingMethods, + AnnotationMirror annotationMirror) { if ( initializingMethods.contains( method ) ) { // cycle detected @@ -497,10 +499,10 @@ private void mergeInheritedOptions(SourceMethod method, MapperOptions mapperConf // apply defined (@InheritConfiguration, @InheritInverseConfiguration) mappings if ( forwardTemplateMethod != null ) { - mappingOptions.applyInheritedOptions( forwardTemplateMethod, false ); + mappingOptions.applyInheritedOptions( method, forwardTemplateMethod, false, annotationMirror ); } if ( inverseTemplateMethod != null ) { - mappingOptions.applyInheritedOptions( inverseTemplateMethod, true ); + mappingOptions.applyInheritedOptions( method, inverseTemplateMethod, true, annotationMirror ); } // apply auto inherited options @@ -510,7 +512,8 @@ private void mergeInheritedOptions(SourceMethod method, MapperOptions mapperConf // but.. there should not be an @InheritedConfiguration if ( forwardTemplateMethod == null && inheritanceStrategy.isApplyForward() ) { if ( applicablePrototypeMethods.size() == 1 ) { - mappingOptions.applyInheritedOptions( first( applicablePrototypeMethods ), false ); + mappingOptions.applyInheritedOptions( method, first( applicablePrototypeMethods ), false, + annotationMirror ); } else if ( applicablePrototypeMethods.size() > 1 ) { messager.printMessage( @@ -523,7 +526,8 @@ else if ( applicablePrototypeMethods.size() > 1 ) { // or no @InheritInverseConfiguration if ( inverseTemplateMethod == null && inheritanceStrategy.isApplyReverse() ) { if ( applicableReversePrototypeMethods.size() == 1 ) { - mappingOptions.applyInheritedOptions( first( applicableReversePrototypeMethods ), true ); + mappingOptions.applyInheritedOptions( method, first( applicableReversePrototypeMethods ), true, + annotationMirror ); } else if ( applicableReversePrototypeMethods.size() > 1 ) { messager.printMessage( @@ -607,16 +611,23 @@ else if ( nameFilteredcandidates.size() > 1 ) { } } - return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods ); + return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods, + getAnnotationMirror( inverseConfiguration ) ); + } + + private AnnotationMirror getAnnotationMirror(InheritInverseConfigurationGem inverseConfiguration) { + return inverseConfiguration == null ? null : inverseConfiguration.mirror(); } private SourceMethod extractInitializedOptions(SourceMethod resultMethod, List rawMethods, MapperOptions mapperConfig, - List initializingMethods) { + List initializingMethods, + AnnotationMirror annotationMirror) { if ( resultMethod != null ) { if ( !resultMethod.getOptions().isFullyInitialized() ) { - mergeInheritedOptions( resultMethod, mapperConfig, rawMethods, initializingMethods ); + mergeInheritedOptions( resultMethod, mapperConfig, rawMethods, initializingMethods, + annotationMirror ); } return resultMethod; @@ -684,7 +695,12 @@ else if ( nameFilteredcandidates.size() > 1 ) { } } - return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods ); + return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods, + getAnnotationMirror( inheritConfiguration ) ); + } + + private AnnotationMirror getAnnotationMirror(InheritConfigurationGem inheritConfiguration) { + return inheritConfiguration == null ? null : inheritConfiguration.mirror(); } private void reportErrorWhenAmbigousReverseMapping(List candidates, SourceMethod method, 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 a31fb41a7a..201f1a1f22 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 @@ -45,6 +45,7 @@ import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; import org.mapstruct.ap.internal.model.source.SourceMethod; import org.mapstruct.ap.internal.model.source.SubclassMappingOptions; +import org.mapstruct.ap.internal.model.source.SubclassValidator; import org.mapstruct.ap.internal.model.source.ValueMappingOptions; import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.util.AccessorNamingUtils; @@ -307,11 +308,13 @@ private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, // We want to get as much error reporting as possible. // If targetParameter is not null it means we have an update method + SubclassValidator subclassValidator = new SubclassValidator( messager, typeUtils ); Set subclassMappingOptions = getSubclassMappings( sourceParameters, targetParameter != null ? null : resultType, method, - beanMappingOptions + beanMappingOptions, + subclassValidator ); return new SourceMethod.Builder() @@ -327,6 +330,7 @@ private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, .setValueMappingOptionss( getValueMappings( method ) ) .setEnumMappingOptions( enumMappingOptions ) .setSubclassMappings( subclassMappingOptions ) + .setSubclassValidator( subclassValidator ) .setTypeUtils( typeUtils ) .setTypeFactory( typeFactory ) .setPrototypeMethods( prototypeMethods ) @@ -601,8 +605,10 @@ private Set getMappings(ExecutableElement method, BeanMappingOpt * @return The subclass mappings for the given method */ private Set getSubclassMappings(List sourceParameters, Type resultType, - ExecutableElement method, BeanMappingOptions beanMapping) { - return new RepeatableSubclassMappings( sourceParameters, resultType ).getMappings( method, beanMapping ); + ExecutableElement method, BeanMappingOptions beanMapping, + SubclassValidator validator) { + return new RepeatableSubclassMappings( sourceParameters, resultType, validator ) + .getMappings( method, beanMapping ); } private class RepeatableMappings extends RepeatableMappingAnnotations { @@ -637,11 +643,13 @@ private class RepeatableSubclassMappings extends RepeatableMappingAnnotations { private final List sourceParameters; private final Type resultType; + private SubclassValidator validator; - RepeatableSubclassMappings(List sourceParameters, Type resultType) { + RepeatableSubclassMappings(List sourceParameters, Type resultType, SubclassValidator validator) { super( SUB_CLASS_MAPPING_FQN, SUB_CLASS_MAPPINGS_FQN ); this.sourceParameters = sourceParameters; this.resultType = resultType; + this.validator = validator; } @Override @@ -666,7 +674,8 @@ void addInstance(SubclassMappingGem gem, ExecutableElement method, BeanMappingOp typeUtils, mappings, sourceParameters, - resultType ); + resultType, + validator ); } @Override @@ -681,7 +690,8 @@ void addInstances(SubclassMappingsGem gem, ExecutableElement method, BeanMapping typeUtils, mappings, sourceParameters, - resultType ); + resultType, + validator ); } } 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 8588ec183a..6d8fed1a95 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 @@ -117,6 +117,7 @@ public enum Message { ENUMMAPPING_NO_ELEMENTS( "'nameTransformationStrategy', 'configuration' and 'unexpectedValueMappingException' are undefined in @EnumMapping, define at least one of them." ), ENUMMAPPING_ILLEGAL_TRANSFORMATION( "Illegal transformation for '%s' EnumTransformationStrategy. Error: '%s'." ), + SUBCLASSMAPPING_DOUBLE_SOURCE_SUBCLASS( "Subclass '%s' is already defined as a source." ), SUBCLASSMAPPING_ILLEGAL_SUBCLASS( "Class '%s' is not a subclass of '%s'." ), SUBCLASSMAPPING_NO_VALID_SUPERCLASS( "Could not find a parameter that is a superclass for '%s'." ), SUBCLASSMAPPING_UPDATE_METHODS_NOT_SUPPORTED( "SubclassMapping annotation can not be used for update methods." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousInverseSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousInverseSubclassMapper.java new file mode 100644 index 0000000000..a6cbba2cbc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/ErroneousInverseSubclassMapper.java @@ -0,0 +1,29 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Bike; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ErroneousInverseSubclassMapper { + ErroneousInverseSubclassMapper INSTANCE = Mappers.getMapper( ErroneousInverseSubclassMapper.class ); + + @SubclassMapping( source = Bike.class, target = VehicleDto.class ) + @SubclassMapping( source = Car.class, target = VehicleDto.class ) + @Mapping( target = "maker", source = "vehicleManufacturingCompany" ) + VehicleDto map(Vehicle vehicle); + + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InverseOrderSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InverseOrderSubclassMapper.java new file mode 100644 index 0000000000..302c036488 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InverseOrderSubclassMapper.java @@ -0,0 +1,40 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Bike; +import org.mapstruct.ap.test.subclassmapping.mappables.BikeDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.CarDto; +import org.mapstruct.ap.test.subclassmapping.mappables.HatchBack; +import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollection; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollectionDto; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface InverseOrderSubclassMapper { + InverseOrderSubclassMapper INSTANCE = Mappers.getMapper( InverseOrderSubclassMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + @SubclassMapping( source = HatchBack.class, target = CarDto.class ) + @SubclassMapping( source = Car.class, target = CarDto.class ) + @SubclassMapping( source = Bike.class, target = BikeDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker") + VehicleDto map(Vehicle vehicle); + + VehicleCollection mapInverse(VehicleCollectionDto vehicles); + + @SubclassMapping( source = CarDto.class, target = Car.class ) + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SimpleSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SimpleSubclassMapper.java index eb71797864..6fedf2c57a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SimpleSubclassMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SimpleSubclassMapper.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.subclassmapping; +import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.SubclassMapping; @@ -28,4 +29,9 @@ public interface SimpleSubclassMapper { @SubclassMapping( source = Bike.class, target = BikeDto.class ) @Mapping( source = "vehicleManufacturingCompany", target = "maker") VehicleDto map(Vehicle vehicle); + + VehicleCollection mapInverse(VehicleCollectionDto vehicles); + + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java index e5a77b56d3..7df555ecc2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java @@ -34,12 +34,11 @@ VehicleCollectionDto.class, Vehicle.class, VehicleDto.class, - SimpleSubclassMapper.class, - SubclassMapperUsingExistingMappings.class, }) public class SubclassMappingTest { @ProcessorTest + @WithClasses( SimpleSubclassMapper.class ) void mappingIsDoneUsingSubclassMapping() { VehicleCollection vehicles = new VehicleCollection(); vehicles.getVehicles().add( new Car() ); @@ -54,6 +53,22 @@ void mappingIsDoneUsingSubclassMapping() { } @ProcessorTest + @WithClasses( SimpleSubclassMapper.class ) + void inverseMappingIsDoneUsingSubclassMapping() { + VehicleCollectionDto vehicles = new VehicleCollectionDto(); + vehicles.getVehicles().add( new CarDto() ); + vehicles.getVehicles().add( new BikeDto() ); + + VehicleCollection result = SimpleSubclassMapper.INSTANCE.mapInverse( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( Car.class, Bike.class ); + } + + @ProcessorTest + @WithClasses( SubclassMapperUsingExistingMappings.class ) void existingMappingsAreUsedWhenFound() { VehicleCollection vehicles = new VehicleCollection(); vehicles.getVehicles().add( new Car() ); @@ -66,19 +81,38 @@ void existingMappingsAreUsedWhenFound() { } @ProcessorTest - void subclassMappingInheritsMapping() { - VehicleCollection vehicles = new VehicleCollection(); - Car car = new Car(); - car.setVehicleManufacturingCompany( "BenZ" ); - vehicles.getVehicles().add( car ); + @WithClasses( SimpleSubclassMapper.class ) + void subclassMappingInheritsInverseMapping() { + VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); + CarDto carDto = new CarDto(); + carDto.setMaker( "BenZ" ); + vehiclesDto.getVehicles().add( carDto ); - VehicleCollectionDto result = SimpleSubclassMapper.INSTANCE.map( vehicles ); + VehicleCollection result = SimpleSubclassMapper.INSTANCE.mapInverse( vehiclesDto ); assertThat( result.getVehicles() ) - .extracting( VehicleDto::getMaker ) + .extracting( Vehicle::getVehicleManufacturingCompany ) .containsExactly( "BenZ" ); } + @ProcessorTest + @WithClasses( { + HatchBack.class, + InverseOrderSubclassMapper.class + } ) + void subclassMappingOverridesInverseInheritsMapping() { + VehicleCollectionDto vehicleDtos = new VehicleCollectionDto(); + CarDto carDto = new CarDto(); + carDto.setMaker( "BenZ" ); + vehicleDtos.getVehicles().add( carDto ); + + VehicleCollection result = InverseOrderSubclassMapper.INSTANCE.mapInverse( vehicleDtos ); + + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( Car.class ); + } + @ProcessorTest @WithClasses({ HatchBack.class, @@ -91,11 +125,11 @@ void subclassMappingInheritsMapping() { line = 28, alternativeLine = 30, message = "SubclassMapping annotation for " - + "'org.mapstruct.ap.test.subclassmapping.mappables.HatchBackDto' found after " - + "'org.mapstruct.ap.test.subclassmapping.mappables.CarDto', but all " - + "'org.mapstruct.ap.test.subclassmapping.mappables.HatchBackDto' " + + "'org.mapstruct.ap.test.subclassmapping.mappables.HatchBack' found after " + + "'org.mapstruct.ap.test.subclassmapping.mappables.Car', but all " + + "'org.mapstruct.ap.test.subclassmapping.mappables.HatchBack' " + "objects are also instances of " - + "'org.mapstruct.ap.test.subclassmapping.mappables.CarDto'.") + + "'org.mapstruct.ap.test.subclassmapping.mappables.Car'.") }) void subclassOrderWarning() { } @@ -136,4 +170,18 @@ void unsupportedUpdateMethod() { }) void erroneousMethodWithSourceTargetType() { } + + @ProcessorTest + @WithClasses({ ErroneousInverseSubclassMapper.class }) + @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { + @Diagnostic(type = ErroneousInverseSubclassMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 28, + message = "Subclass " + + "'org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto'" + + " is already defined as a source." + ) + }) + void inverseSubclassMappingNotPossible() { + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceOverride.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceOverride.java new file mode 100644 index 0000000000..97b9079e87 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceOverride.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.fixture; + +public class SubSourceOverride extends ImplementedParentSource implements InterfaceParentSource { + private final String finalValue; + + public SubSourceOverride(String finalValue) { + this.finalValue = finalValue; + } + + public String getFinalValue() { + return finalValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceSeparate.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceSeparate.java new file mode 100644 index 0000000000..ccae04efff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubSourceSeparate.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.fixture; + +public class SubSourceSeparate extends ImplementedParentSource implements InterfaceParentSource { + private final String separateValue; + + public SubSourceSeparate(String separateValue) { + this.separateValue = separateValue; + } + + public String getSeparateValue() { + return separateValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTargetSeparate.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTargetSeparate.java new file mode 100644 index 0000000000..6b98c24106 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubTargetSeparate.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.fixture; + +public class SubTargetSeparate extends ImplementedParentTarget implements InterfaceParentTarget { + private final String separateValue; + + public SubTargetSeparate(String separateValue) { + this.separateValue = separateValue; + } + + public String getSeparateValue() { + return separateValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java index 8ef7908c30..c50afb9e57 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapper.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.subclassmapping.fixture; import org.mapstruct.BeanMapping; +import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.SubclassMapping; @@ -18,4 +19,9 @@ public interface SubclassAbstractMapper { @SubclassMapping( source = SubSource.class, target = SubTarget.class ) @SubclassMapping( source = SubSourceOther.class, target = SubTargetOther.class ) AbstractParentTarget map(AbstractParentSource item); + + @SubclassMapping( source = SubTargetSeparate.class, target = SubSourceSeparate.class ) + @InheritInverseConfiguration + @SubclassMapping( source = SubTargetOther.class, target = SubSourceOverride.class ) + AbstractParentSource map(AbstractParentTarget item); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java index 14dc16d4ab..9bc72942e6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/SubclassFixtureTest.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.subclassmapping.fixture; import org.junit.jupiter.api.extension.RegisterExtension; + import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.runner.GeneratedSource; @@ -37,6 +38,9 @@ void subclassInterfaceParentFixture() { @ProcessorTest @WithClasses( { + SubSourceOverride.class, + SubSourceSeparate.class, + SubTargetSeparate.class, SubclassAbstractMapper.class, } ) void subclassAbstractParentFixture() { diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java index 6deed338c1..a66b646f1e 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java @@ -9,7 +9,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2021-09-12T14:37:10+0200", + date = "2022-01-31T19:35:15+0100", comments = "version: , compiler: Eclipse JDT (Batch) 3.20.0.v20191203-2131, environment: Java 11.0.12 (Azul Systems, Inc.)" ) public class SubclassAbstractMapperImpl implements SubclassAbstractMapper { @@ -31,6 +31,26 @@ else if (item instanceof SubSourceOther) { } } + @Override + public AbstractParentSource map(AbstractParentTarget item) { + if ( item == null ) { + return null; + } + + if (item instanceof SubTargetSeparate) { + return subTargetSeparateToSubSourceSeparate( (SubTargetSeparate) item ); + } + else if (item instanceof SubTargetOther) { + return subTargetOtherToSubSourceOverride( (SubTargetOther) item ); + } + else if (item instanceof SubTarget) { + return subTargetToSubSource( (SubTarget) item ); + } + else { + throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for " + item.getClass()); + } + } + protected SubTarget subSourceToSubTarget(SubSource subSource) { if ( subSource == null ) { return null; @@ -56,4 +76,44 @@ protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSource return subTargetOther; } + + protected SubSourceSeparate subTargetSeparateToSubSourceSeparate(SubTargetSeparate subTargetSeparate) { + if ( subTargetSeparate == null ) { + return null; + } + + String separateValue = null; + + separateValue = subTargetSeparate.getSeparateValue(); + + SubSourceSeparate subSourceSeparate = new SubSourceSeparate( separateValue ); + + return subSourceSeparate; + } + + protected SubSourceOverride subTargetOtherToSubSourceOverride(SubTargetOther subTargetOther) { + if ( subTargetOther == null ) { + return null; + } + + String finalValue = null; + + finalValue = subTargetOther.getFinalValue(); + + SubSourceOverride subSourceOverride = new SubSourceOverride( finalValue ); + + return subSourceOverride; + } + + protected SubSource subTargetToSubSource(SubTarget subTarget) { + if ( subTarget == null ) { + return null; + } + + SubSource subSource = new SubSource(); + + subSource.setValue( subTarget.getValue() ); + + return subSource; + } } From 9b434f80f8572e3f2cf03ce2a5bc932ff3f2eeae Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sun, 6 Feb 2022 20:05:42 +0100 Subject: [PATCH 052/363] #2715: Updated documentation to reflect impact of conditions on update mappers. (#2740) * #2715: added an example with an update mapper for Conditional behavior. --- ...apter-10-advanced-mapping-options.asciidoc | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 0bbec1fe57..1cc19fcc3e 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -354,6 +354,56 @@ public class CarMapperImpl implements CarMapper { ---- ==== +When using this in combination with an update mapping method it will replace the `null-check` there, for example: + +.Update mapper using custom condition check method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + CarDto carToCarDto(Car car, @MappingTarget CarDto carDto); + + @Condition + default boolean isNotEmpty(String value) { + return value != null && !value.isEmpty(); + } +} +---- +==== + +The generated update mapper will look like: + +.Custom condition check in generated implementation +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class CarMapperImpl implements CarMapper { + + @Override + public CarDto carToCarDto(Car car, CarDto carDto) { + if ( car == null ) { + return carDto; + } + + if ( isNotEmpty( car.getOwner() ) ) { + carDto.setOwner( car.getOwner() ); + } else { + carDto.setOwner( null ); + } + + // Mapping of other properties + + return carDto; + } +} +---- +==== + [IMPORTANT] ==== If there is a custom `@Condition` method applicable for the property it will have a precedence over a presence check method in the bean itself. From 0b2c7e58b264adb5674de51634509d70a4578da2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 23 Feb 2022 15:55:20 +0100 Subject: [PATCH 053/363] Add Christian Kosmowski to the copyright.txt --- copyright.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/copyright.txt b/copyright.txt index 01b6526c70..8008a66bb9 100644 --- a/copyright.txt +++ b/copyright.txt @@ -8,6 +8,7 @@ Andrei Arlou - https://github.com/Captain1653 Andres Jose Sebastian Rincon Gonzalez - https://github.com/stianrincon Arne Seime - https://github.com/seime Christian Bandowski - https://github.com/chris922 +Christian Kosmowski - https://github.com/ckosmowski Christian Schuster - https://github.com/chschu Christophe Labouisse - https://github.com/ggtools Ciaran Liedeman - https://github.com/cliedeman From b6a3aa151228c17183979da0fd1573bebd1e9ed3 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sat, 12 Mar 2022 18:01:15 +0100 Subject: [PATCH 054/363] #2758: fallback to param.variableName if ext.targetBeanName is not present in MethodReference handling. (#2759) --- .../ap/internal/model/MethodReference.ftl | 2 +- .../basic/ConditionalMappingTest.java | 17 ++++++++++- ...MethodWithMappingTargetInUpdateMapper.java | 29 +++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithMappingTargetInUpdateMapper.java 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 271ba58f9b..d8236140f5 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 @@ -41,7 +41,7 @@ <#-- a class is passed on for casting, see @TargetType --> <@includeModel object=inferTypeWhenEnum( ext.targetType ) raw=true/>.class<#t> <#elseif param.mappingTarget> - ${ext.targetBeanName}<#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}<#t> + <#if ext.targetBeanName??>${ext.targetBeanName}<#else>${param.variableName}<#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}<#t> <#elseif param.mappingContext> ${param.variableName}<#t> <#elseif param.sourceRHS??> diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java index 0fc791fb13..62fd79ae84 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java @@ -7,8 +7,8 @@ import java.util.Arrays; import java.util.Collections; - import org.junit.jupiter.api.extension.RegisterExtension; + import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -223,4 +223,19 @@ public void optionalLikeConditional() { } + @ProcessorTest + @WithClasses( { + ConditionalMethodWithMappingTargetInUpdateMapper.class + } ) + @IssueKey( "2758" ) + public void conditionalMethodWithMappingTarget() { + ConditionalMethodWithMappingTargetInUpdateMapper mapper = + ConditionalMethodWithMappingTargetInUpdateMapper.INSTANCE; + + BasicEmployee targetEmployee = new BasicEmployee(); + targetEmployee.setName( "CurrentName" ); + mapper.map( new BasicEmployeeDto( "ReplacementName" ), targetEmployee ); + + assertThat( targetEmployee.getName() ).isEqualTo( "CurrentName" ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithMappingTargetInUpdateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithMappingTargetInUpdateMapper.java new file mode 100644 index 0000000000..12f4a00212 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithMappingTargetInUpdateMapper.java @@ -0,0 +1,29 @@ +/* + * 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.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper( nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE ) +public interface ConditionalMethodWithMappingTargetInUpdateMapper { + + ConditionalMethodWithMappingTargetInUpdateMapper INSTANCE = + Mappers.getMapper( ConditionalMethodWithMappingTargetInUpdateMapper.class ); + + void map(BasicEmployeeDto employee, @MappingTarget BasicEmployee targetEmployee); + + @Condition + default boolean isNotBlankAndNotPresent(String value, @MappingTarget BasicEmployee targetEmployee) { + return value != null && !value.trim().isEmpty() && targetEmployee.getName() == null; + } +} From 0a69492983a34a5a41dd6968fa0c2f18f97484e8 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sat, 12 Mar 2022 18:02:01 +0100 Subject: [PATCH 055/363] #2755: use raw Type when calling a static method. (#2762) --- .../ap/internal/model/MethodReference.ftl | 2 +- .../selection/generics/GenericMapperBase.java | 48 +++++++++++++++++++ .../generics/GenericMapperBaseTest.java | 25 ++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericMapperBase.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericMapperBaseTest.java 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 d8236140f5..6ccdf26871 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 @@ -15,7 +15,7 @@ <#if static><@includeModel object=providingParameter.type/><#else>${providingParameter.name}.<@methodCall/> <#-- method is referenced java8 static method in the mapper to implement (interface) --> <#elseif static> - <@includeModel object=definingType/>.<@methodCall/> + <@includeModel object=definingType raw=true/>.<@methodCall/> <#elseif constructor> new <@includeModel object=definingType/><#if (parameterBindings?size > 0)>( <@arguments/> )<#else>() <#elseif methodChaining> diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericMapperBase.java b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericMapperBase.java new file mode 100644 index 0000000000..025acba4ae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericMapperBase.java @@ -0,0 +1,48 @@ +/* + * 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.selection.generics; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +public interface GenericMapperBase { + + T toDto(Map prop); + + @Named( "StringArrayToString" ) + static String stringArrayToString(Property property) { + return "converted"; + } +} + +class Property { + +} + +class ProjectDto { + private String productionSite; + + public void setProductionSite(String productionSite) { + this.productionSite = productionSite; + } + + public String getProductionSite() { + return productionSite; + } +} + +@Mapper +interface ProjectMapper extends GenericMapperBase { + ProjectMapper INSTANCE = Mappers.getMapper( ProjectMapper.class ); + + @Mapping( target = "productionSite", source = "productionSite", qualifiedByName = "StringArrayToString" ) + @Override + ProjectDto toDto(Map projectProp); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericMapperBaseTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericMapperBaseTest.java new file mode 100644 index 0000000000..1fe4930e2e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericMapperBaseTest.java @@ -0,0 +1,25 @@ +/* + * 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.selection.generics; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Ben Zegveld + */ +@WithClasses( { + GenericMapperBase.class +} ) +@IssueKey( "2755" ) +class GenericMapperBaseTest { + + @ProcessorTest + void generatesCompilableCode() { + } + +} From ad00adfa8659d22fca4fd725434e6d5971418692 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 12 Mar 2022 23:57:17 +0100 Subject: [PATCH 056/363] #2538 Allow using 2 step mappings with only one of the 2 methods being qualified --- .../source/selector/SelectionCriteria.java | 10 ++++- .../creation/MappingResolverImpl.java | 41 ++++++++++++++++-- .../mapstruct/ap/test/bugs/_2538/Group.java | 22 ++++++++++ .../ap/test/bugs/_2538/GroupDto.java | 22 ++++++++++ .../ap/test/bugs/_2538/Issue2538Test.java | 39 +++++++++++++++++ .../ap/test/bugs/_2538/TeamMapper.java | 42 +++++++++++++++++++ .../ap/test/bugs/_2538/TeamRole.java | 22 ++++++++++ .../ap/test/bugs/_2538/TeamRoleDto.java | 22 ++++++++++ 8 files changed, 215 insertions(+), 5 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Group.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/GroupDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Issue2538Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRole.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRoleDto.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java index b70e94b6d1..da8bae365a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.internal.model.source.selector; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.lang.model.type.TypeMirror; @@ -26,6 +27,7 @@ public class SelectionCriteria { private final String targetPropertyName; private final TypeMirror qualifyingResultType; private final SourceRHS sourceRHS; + private boolean ignoreQualifiers = false; private Type type; private final boolean allowDirect; private final boolean allowConversion; @@ -87,12 +89,16 @@ public boolean isPresenceCheckRequired() { return type == Type.PRESENCE_CHECK; } + public void setIgnoreQualifiers(boolean ignoreQualifiers) { + this.ignoreQualifiers = ignoreQualifiers; + } + public List getQualifiers() { - return qualifiers; + return ignoreQualifiers ? Collections.emptyList() : qualifiers; } public List getQualifiedByNames() { - return qualifiedByNames; + return ignoreQualifiers ? Collections.emptyList() : qualifiedByNames; } public String getTargetPropertyName() { 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 b971341fc9..2e2a760bf4 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 @@ -712,6 +712,11 @@ public int hashCode() { } } + private enum BestMatchType { + IGNORE_QUALIFIERS_BEFORE_Y_CANDIDATES, + IGNORE_QUALIFIERS_AFTER_Y_CANDIDATES, + } + /** * Suppose mapping required from A to C and: *
      @@ -743,6 +748,17 @@ static Assignment getBestMatch(ResolvingAttempt att, Type sourceType, Type targe if ( mmAttempt.hasResult ) { return mmAttempt.result; } + if ( att.hasQualfiers() ) { + mmAttempt = mmAttempt.getBestMatchIgnoringQualifiersBeforeY( sourceType, targetType ); + if ( mmAttempt.hasResult ) { + return mmAttempt.result; + } + + mmAttempt = mmAttempt.getBestMatchIgnoringQualifiersAfterY( sourceType, targetType ); + if ( mmAttempt.hasResult ) { + return mmAttempt.result; + } + } MethodMethod mbAttempt = new MethodMethod<>( att, att.methods, att.builtIns, att::toMethodRef, att::toBuildInRef ) .getBestMatch( sourceType, targetType ); @@ -772,6 +788,18 @@ static Assignment getBestMatch(ResolvingAttempt att, Type sourceType, Type targe } private MethodMethod getBestMatch(Type sourceType, Type targetType) { + return getBestMatch( sourceType, targetType, null ); + } + + private MethodMethod getBestMatchIgnoringQualifiersBeforeY(Type sourceType, Type targetType) { + return getBestMatch( sourceType, targetType, BestMatchType.IGNORE_QUALIFIERS_BEFORE_Y_CANDIDATES ); + } + + private MethodMethod getBestMatchIgnoringQualifiersAfterY(Type sourceType, Type targetType) { + return getBestMatch( sourceType, targetType, BestMatchType.IGNORE_QUALIFIERS_AFTER_Y_CANDIDATES ); + } + + private MethodMethod getBestMatch(Type sourceType, Type targetType, BestMatchType matchType) { Set yCandidates = new HashSet<>(); Map, List>> xCandidates = new LinkedHashMap<>(); @@ -784,6 +812,9 @@ private MethodMethod getBestMatch(Type sourceType, Type targetType) { // sourceMethod or builtIn that fits the signature B to C. Only then there is a match. If we have a match // a nested method call can be called. so C = methodY( methodX (A) ) attempt.selectionCriteria.setPreferUpdateMapping( false ); + attempt.selectionCriteria.setIgnoreQualifiers( + matchType == BestMatchType.IGNORE_QUALIFIERS_BEFORE_Y_CANDIDATES ); + for ( T2 yCandidate : yMethods ) { Type ySourceType = yCandidate.getMappingSourceType(); ySourceType = ySourceType.resolveParameterToType( targetType, yCandidate.getResultType() ).getMatch(); @@ -796,13 +827,16 @@ private MethodMethod getBestMatch(Type sourceType, Type targetType) { } List> xMatches = attempt.getBestMatch( xMethods, sourceType, ySourceType ); if ( !xMatches.isEmpty() ) { - xMatches.stream().forEach( x -> xCandidates.put( x, new ArrayList<>() ) ); - final Type typeInTheMiddle = ySourceType; - xMatches.stream().forEach( x -> typesInTheMiddle.put( x, typeInTheMiddle ) ); + for ( SelectedMethod x : xMatches ) { + xCandidates.put( x, new ArrayList<>() ); + typesInTheMiddle.put( x, ySourceType ); + } yCandidates.add( yCandidate ); } } attempt.selectionCriteria.setPreferUpdateMapping( true ); + attempt.selectionCriteria.setIgnoreQualifiers( + matchType == BestMatchType.IGNORE_QUALIFIERS_AFTER_Y_CANDIDATES ); // collect all results List yCandidatesList = new ArrayList<>( yCandidates ); @@ -816,6 +850,7 @@ private MethodMethod getBestMatch(Type sourceType, Type targetType) { } } + attempt.selectionCriteria.setIgnoreQualifiers( false ); // no results left if ( xCandidates.isEmpty() ) { return this; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Group.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Group.java new file mode 100644 index 0000000000..4451431fee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Group.java @@ -0,0 +1,22 @@ +/* + * 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._2538; + +/** + * @author Filip Hrisafov + */ +public class Group { + + private final String id; + + public Group(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/GroupDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/GroupDto.java new file mode 100644 index 0000000000..0d94c90815 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/GroupDto.java @@ -0,0 +1,22 @@ +/* + * 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._2538; + +/** + * @author Filip Hrisafov + */ +public class GroupDto { + + private final String id; + + public GroupDto(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Issue2538Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Issue2538Test.java new file mode 100644 index 0000000000..cf99e67f92 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Issue2538Test.java @@ -0,0 +1,39 @@ +/* + * 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._2538; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Group.class, + GroupDto.class, + TeamMapper.class, + TeamRole.class, + TeamRoleDto.class, +}) +class Issue2538Test { + + @ProcessorTest + void shouldCorrectlyUseQualifiedMethodIn2StepMapping() { + TeamRole role = TeamMapper.INSTANCE.mapUsingFirstLookup( new TeamRoleDto( "test" ) ); + + assertThat( role ).isNotNull(); + assertThat( role.getGroup() ).isNotNull(); + assertThat( role.getGroup().getId() ).isEqualTo( "lookup-test" ); + + role = TeamMapper.INSTANCE.mapUsingSecondLookup( new TeamRoleDto( "test" ) ); + + assertThat( role ).isNotNull(); + assertThat( role.getGroup() ).isNotNull(); + assertThat( role.getGroup().getId() ).isEqualTo( "second-test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamMapper.java new file mode 100644 index 0000000000..5615e7bc43 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamMapper.java @@ -0,0 +1,42 @@ +/* + * 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._2538; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface TeamMapper { + + TeamMapper INSTANCE = Mappers.getMapper( TeamMapper.class ); + + // This method is testing methodX(methodY(...)) where methodY is qualified + @Mapping(target = "group", source = "groupId", qualifiedByName = "firstLookup") + TeamRole mapUsingFirstLookup(TeamRoleDto in); + + // This method is testing methodX(methodY(...)) where methodX is qualified + @Mapping(target = "group", source = "groupId", qualifiedByName = "secondLookup") + TeamRole mapUsingSecondLookup(TeamRoleDto in); + + Group map(GroupDto in); + + @Named("firstLookup") + default GroupDto lookupGroup(String groupId) { + return groupId != null ? new GroupDto( "lookup-" + groupId ) : null; + } + + default GroupDto normalLookup(String groupId) { + return groupId != null ? new GroupDto( groupId ) : null; + } + + @Named("secondLookup") + default Group mapSecondLookup(GroupDto in) { + return in != null ? new Group( "second-" + in.getId() ) : null; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRole.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRole.java new file mode 100644 index 0000000000..f73700fa4d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRole.java @@ -0,0 +1,22 @@ +/* + * 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._2538; + +/** + * @author Filip Hrisafov + */ +public class TeamRole { + + private final Group group; + + public TeamRole(Group group) { + this.group = group; + } + + public Group getGroup() { + return group; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRoleDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRoleDto.java new file mode 100644 index 0000000000..fac5a1a9ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRoleDto.java @@ -0,0 +1,22 @@ +/* + * 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._2538; + +/** + * @author Filip Hrisafov + */ +public class TeamRoleDto { + + private final String groupId; + + public TeamRoleDto(String groupId) { + this.groupId = groupId; + } + + public String getGroupId() { + return groupId; + } +} From ab528678318cb0983f50767b8170ab4697ba4e75 Mon Sep 17 00:00:00 2001 From: Chris DeLashmutt Date: Sat, 19 Mar 2022 06:51:19 -0400 Subject: [PATCH 057/363] #2748 Support mapping map keys with invalid chars for methods --- .../mapstruct/ap/internal/util/Strings.java | 14 +++++- .../ap/internal/util/StringsTest.java | 3 ++ .../ap/test/bugs/_2748/Issue2748Mapper.java | 48 +++++++++++++++++++ .../ap/test/bugs/_2748/Issue2748Test.java | 36 ++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java index 42727ed816..88e0bdf6f6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java @@ -183,7 +183,19 @@ public static String sanitizeIdentifierName(String identifier) { if ( firstAlphabeticIndex < identifier.length()) { // If it is not consisted of only underscores - return identifier.substring( firstAlphabeticIndex ).replace( "[]", "Array" ); + String firstAlphaString = identifier.substring( firstAlphabeticIndex ).replace( "[]", "Array" ); + + StringBuilder sb = new StringBuilder( firstAlphaString.length() ); + for ( int i = 0; i < firstAlphaString.length(); i++ ) { + int codePoint = firstAlphaString.codePointAt( i ); + if ( Character.isJavaIdentifierPart( codePoint ) || codePoint == '.') { + sb.appendCodePoint( codePoint ); + } + else { + sb.append( '_' ); + } + } + return sb.toString(); } return identifier.replace( "[]", "Array" ); diff --git a/processor/src/test/java/org/mapstruct/ap/internal/util/StringsTest.java b/processor/src/test/java/org/mapstruct/ap/internal/util/StringsTest.java index 687b63f793..524fd193aa 100644 --- a/processor/src/test/java/org/mapstruct/ap/internal/util/StringsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/util/StringsTest.java @@ -72,6 +72,7 @@ public void testGetSaveVariableNameWithArrayExistingVariables() { assertThat( Strings.getSafeVariableName( "__Test" ) ).isEqualTo( "test" ); assertThat( Strings.getSafeVariableName( "_0Test" ) ).isEqualTo( "test" ); assertThat( Strings.getSafeVariableName( "_0123Test" ) ).isEqualTo( "test" ); + assertThat( Strings.getSafeVariableName( "bad/test" ) ).isEqualTo( "bad_test" ); } @Test @@ -94,6 +95,7 @@ public void testGetSaveVariableNameWithCollection() { assertThat( Strings.getSafeVariableName( "__0Test", Arrays.asList( "test" ) ) ).isEqualTo( "test1" ); assertThat( Strings.getSafeVariableName( "___0", new ArrayList<>() ) ).isEqualTo( "___0" ); assertThat( Strings.getSafeVariableName( "__0123456789Test", Arrays.asList( "test" ) ) ).isEqualTo( "test1" ); + assertThat( Strings.getSafeVariableName( "bad/test", Arrays.asList( "bad_test" ) ) ).isEqualTo( "bad_test1" ); } @Test @@ -110,6 +112,7 @@ public void testSanitizeIdentifierName() { assertThat( Strings.sanitizeIdentifierName( "_0int[]" ) ).isEqualTo( "intArray" ); assertThat( Strings.sanitizeIdentifierName( "__0int[]" ) ).isEqualTo( "intArray" ); assertThat( Strings.sanitizeIdentifierName( "___0" ) ).isEqualTo( "___0" ); + assertThat( Strings.sanitizeIdentifierName( "bad/test" ) ).isEqualTo( "bad_test" ); } @Test diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Mapper.java new file mode 100644 index 0000000000..0062667df4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Mapper.java @@ -0,0 +1,48 @@ +/* + * 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._2748; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2748Mapper { + + Issue2748Mapper INSTANCE = Mappers.getMapper( Issue2748Mapper.class ); + + @Mapping(target = "specificValue", source = "annotations.specific/value") + Target map(Source source); + + class Target { + private final String specificValue; + + public Target(String specificValue) { + this.specificValue = specificValue; + } + + public String getSpecificValue() { + return specificValue; + } + } + + class Source { + private final Map annotations; + + public Source(Map annotations) { + this.annotations = annotations; + } + + public Map getAnnotations() { + return annotations; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Test.java new file mode 100644 index 0000000000..4bda2cd43d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Test.java @@ -0,0 +1,36 @@ +/* + * 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._2748; + +import java.util.Collections; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2748") +@WithClasses( { + Issue2748Mapper.class +} ) +class Issue2748Test { + + @ProcessorTest + void shouldMapNonJavaIdentifier() { + Map annotations = Collections.singletonMap( "specific/value", "value" ); + Issue2748Mapper.Source source = new Issue2748Mapper.Source( annotations ); + + Issue2748Mapper.Target target = Issue2748Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getSpecificValue() ).isEqualTo( "value" ); + } +} From 190b486b7919a64e5fe4f80e6dc551554b0d6455 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 19 Mar 2022 12:09:30 +0100 Subject: [PATCH 058/363] Add users that have contributed post 1.5.0.Beta2 to copyright.txt --- copyright.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/copyright.txt b/copyright.txt index 8008a66bb9..1b23a0e84a 100644 --- a/copyright.txt +++ b/copyright.txt @@ -8,6 +8,7 @@ Andrei Arlou - https://github.com/Captain1653 Andres Jose Sebastian Rincon Gonzalez - https://github.com/stianrincon Arne Seime - https://github.com/seime Christian Bandowski - https://github.com/chris922 +Chris DeLashmutt - https://github.com/cdelashmutt-pivotal Christian Kosmowski - https://github.com/ckosmowski Christian Schuster - https://github.com/chschu Christophe Labouisse - https://github.com/ggtools @@ -35,6 +36,7 @@ João Paulo Bassinello - https://github.com/jpbassinello Jonathan Kraska - https://github.com/jakraska Joshua Spoerri - https://github.com/spoerri Jude Niroshan - https://github.com/JudeNiroshan +Justyna Kubica-Ledzion - https://github.com/JKLedzion Kemal Özcan - https://github.com/yekeoe Kevin Grüneberg - https://github.com/kevcodez Lukas Lazar - https://github.com/LukeLaz @@ -63,4 +65,5 @@ Tobias Meggendorfer - https://github.com/incaseoftrouble Tillmann Gaida - https://github.com/Tillerino Timo Eckhardt - https://github.com/timoe Tomek Gubala - https://github.com/vgtworld +Valentin Kulesh - https://github.com/unshare Vincent Alexander Beelte - https://github.com/grandmasterpixel From 7e00af6ff451b15d125242af91df2864a0a58aea Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 21 Mar 2022 08:14:08 +0100 Subject: [PATCH 059/363] [maven-release-plugin] prepare release 1.5.0.RC1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index aaae7b5404..6c34822f96 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.RC1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 293043e6f9..2a412327da 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.RC1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 10c95cf3b5..4d16fc611f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.RC1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 337ad35e51..4769c58d25 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.RC1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 8850da641c..a46ea17a53 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.RC1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 4b48c19496..a1ec01d3a6 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.RC1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 4ca3d9b2c0..958d4bcafb 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.RC1 pom MapStruct Parent @@ -69,7 +69,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.5.0.RC1 diff --git a/pom.xml b/pom.xml index 28c35aea74..b89fd7d12e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.RC1 parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.5.0.RC1 diff --git a/processor/pom.xml b/processor/pom.xml index ec3f20a241..369e8066d2 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.RC1 ../parent/pom.xml From 08a0313840307e96d17246509cbea99c4b048dec Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 21 Mar 2022 08:14:09 +0100 Subject: [PATCH 060/363] [maven-release-plugin] prepare for next development iteration --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 6c34822f96..aaae7b5404 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.RC1 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 2a412327da..293043e6f9 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.RC1 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 4d16fc611f..10c95cf3b5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.RC1 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 4769c58d25..337ad35e51 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.RC1 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index a46ea17a53..8850da641c 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.RC1 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index a1ec01d3a6..4b48c19496 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.RC1 + 1.5.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 958d4bcafb..4ca3d9b2c0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.5.0.RC1 + 1.5.0-SNAPSHOT pom MapStruct Parent @@ -69,7 +69,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.5.0.RC1 + HEAD diff --git a/pom.xml b/pom.xml index b89fd7d12e..28c35aea74 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.5.0.RC1 + 1.5.0-SNAPSHOT parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.5.0.RC1 + HEAD diff --git a/processor/pom.xml b/processor/pom.xml index 369e8066d2..ec3f20a241 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.RC1 + 1.5.0-SNAPSHOT ../parent/pom.xml From 07eeea6bc9563e6964e9e573795d1ccb5ad34c87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 18:41:52 +0000 Subject: [PATCH 061/363] Bump spring-beans from 5.3.15 to 5.3.18 in /parent Bumps [spring-beans](https://github.com/spring-projects/spring-framework) from 5.3.15 to 5.3.18. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.15...v5.3.18) --- updated-dependencies: - dependency-name: org.springframework:spring-beans dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index 4ca3d9b2c0..1a9fddff92 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -25,7 +25,7 @@ 3.0.0-M3 3.0.0-M5 3.1.0 - 5.3.15 + 5.3.18 1.6.0 8.36.1 5.8.0-M1 From 2473c3eaaa812490b23dc32287b90bd36d539ecf Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sat, 2 Apr 2022 11:59:25 +0200 Subject: [PATCH 062/363] #2797: Add the nested type import types in the NestedPropertyMappingMethod * #2797: Reproduction scenario * Add the nested type import types in the NestedPropertyMappingMethod Co-authored-by: Ben Zegveld Co-authored-by: Filip Hrisafov --- .../model/NestedPropertyMappingMethod.java | 2 +- .../ap/test/bugs/_2797/ExampleDto.java | 31 +++++++++++++ .../ap/test/bugs/_2797/ExampleMapper.java | 23 ++++++++++ .../ap/test/bugs/_2797/Issue2797Test.java | 24 ++++++++++ .../ap/test/bugs/_2797/model/BasePerson.java | 44 +++++++++++++++++++ .../ap/test/bugs/_2797/model/Example.java | 16 +++++++ 6 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/Issue2797Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/BasePerson.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/Example.java 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 96004cb474..da73e1783f 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 @@ -103,7 +103,7 @@ public List getPropertyEntries() { public Set getImportTypes() { Set types = super.getImportTypes(); for ( SafePropertyEntry propertyEntry : safePropertyEntries) { - types.add( propertyEntry.getType() ); + types.addAll( propertyEntry.getType().getImportTypes() ); if ( propertyEntry.getPresenceChecker() != null ) { types.addAll( propertyEntry.getPresenceChecker().getImportTypes() ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleDto.java new file mode 100644 index 0000000000..eff9f26271 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleDto.java @@ -0,0 +1,31 @@ +/* + * 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._2797; + +/** + * @author Ben Zegveld + */ +public class ExampleDto { + + private String personFirstName; + private String personLastName; + + public String getPersonFirstName() { + return personFirstName; + } + + public String getPersonLastName() { + return personLastName; + } + + public void setPersonFirstName(String personFirstName) { + this.personFirstName = personFirstName; + } + + public void setPersonLastName(String personLastName) { + this.personLastName = personLastName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleMapper.java new file mode 100644 index 0000000000..eccac27256 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleMapper.java @@ -0,0 +1,23 @@ +/* + * 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._2797; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.bugs._2797.model.Example.Person; + +import static org.mapstruct.ReportingPolicy.ERROR; + +/** + * @author Ben Zegveld + */ +@Mapper(unmappedTargetPolicy = ERROR) +public interface ExampleMapper { + + @Mapping(target = "personFirstName", source = "names.first") + @Mapping(target = "personLastName", source = "names.last") + ExampleDto map(Person person); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/Issue2797Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/Issue2797Test.java new file mode 100644 index 0000000000..7f3dd5f3de --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/Issue2797Test.java @@ -0,0 +1,24 @@ +/* + * 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._2797; + +import org.mapstruct.ap.test.bugs._2797.model.BasePerson; +import org.mapstruct.ap.test.bugs._2797.model.Example; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Ben Zegveld + */ +@IssueKey( "2797" ) +@WithClasses( { ExampleDto.class, ExampleMapper.class, Example.class, BasePerson.class } ) +public class Issue2797Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/BasePerson.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/BasePerson.java new file mode 100644 index 0000000000..6b54e6ee31 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/BasePerson.java @@ -0,0 +1,44 @@ +/* + * 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._2797.model; + +/** + * @author Ben Zegveld + */ +public class BasePerson { + + private Names names; + + public Names getNames() { + return names; + } + + public void setNames(Names names) { + this.names = names; + } + + public static class Names { + + private String first; + private String last; + + public String getFirst() { + return first; + } + + public String getLast() { + return last; + } + + public void setFirst(String first) { + this.first = first; + } + + public void setLast(String last) { + this.last = last; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/Example.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/Example.java new file mode 100644 index 0000000000..4dfa40f695 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/Example.java @@ -0,0 +1,16 @@ +/* + * 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._2797.model; + +/** + * @author Ben Zegveld + */ +public class Example { + + public static class Person extends BasePerson { + + } +} From 03d44b5a872f796b79336429d8cde2ee7918910a Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sat, 2 Apr 2022 18:55:06 +0200 Subject: [PATCH 063/363] #2795: use 'includeModel' for the 'sourcePresenceCheckerReference' in the 'UpdateWrapper'. (#2796) * #2795: use 'includeModel' for the 'sourcePresenceCheckerReference' in the 'UpdateWrapper'. * Simplify the tests Co-authored-by: Ben Zegveld Co-authored-by: Filip Hrisafov --- .../model/assignment/UpdateWrapper.ftl | 2 +- .../ap/test/bugs/_2795/Issue2795Mapper.java | 30 +++++++++++++++++++ .../ap/test/bugs/_2795/Issue2795Test.java | 25 ++++++++++++++++ .../mapstruct/ap/test/bugs/_2795/Nested.java | 20 +++++++++++++ .../ap/test/bugs/_2795/NestedDto.java | 20 +++++++++++++ .../mapstruct/ap/test/bugs/_2795/Source.java | 22 ++++++++++++++ .../mapstruct/ap/test/bugs/_2795/Target.java | 20 +++++++++++++ 7 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Nested.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/NestedDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Target.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index 58416fc63b..9cdd07c230 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -10,7 +10,7 @@ <@lib.handleExceptions> <#if includeSourceNullCheck> <@lib.sourceLocalVarAssignment/> - if ( <#if sourcePresenceCheckerReference?? >${sourcePresenceCheckerReference}<#else><#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} != null ) { + if ( <#if sourcePresenceCheckerReference?? ><@includeModel object=sourcePresenceCheckerReference /><#else><#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} != null ) { <@assignToExistingTarget/> <@lib.handleAssignment/>; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Mapper.java new file mode 100644 index 0000000000..41f29da920 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Mapper.java @@ -0,0 +1,30 @@ +/* + * 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._2795; + +import java.util.Optional; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; + +@Mapper +public interface Issue2795Mapper { + + void update(Source update, @MappingTarget Target destination); + + void update(NestedDto update, @MappingTarget Nested destination); + + static T unwrap(Optional optional) { + return optional.orElse( null ); + } + + @Condition + static boolean isNotEmpty(Optional field) { + return field != null && field.isPresent(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Test.java new file mode 100644 index 0000000000..4b7b599527 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Test.java @@ -0,0 +1,25 @@ +/* + * 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._2795; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@IssueKey("2795") +@WithClasses({ + Issue2795Mapper.class, + Nested.class, + NestedDto.class, + Target.class, + Source.class, +}) +public class Issue2795Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Nested.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Nested.java new file mode 100644 index 0000000000..5214c7d076 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Nested.java @@ -0,0 +1,20 @@ +/* + * 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._2795; + +public class Nested { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/NestedDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/NestedDto.java new file mode 100644 index 0000000000..1b27453435 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/NestedDto.java @@ -0,0 +1,20 @@ +/* + * 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._2795; + +public class NestedDto { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Source.java new file mode 100644 index 0000000000..927e2b09f5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Source.java @@ -0,0 +1,22 @@ +/* + * 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._2795; + +import java.util.Optional; + +public class Source { + + private Optional nested = Optional.empty(); + + public Optional getNested() { + return nested; + } + + public void setNested(Optional nested) { + this.nested = nested; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Target.java new file mode 100644 index 0000000000..ed69a6d48f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Target.java @@ -0,0 +1,20 @@ +/* + * 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._2795; + +public class Target { + + private Nested nested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + +} From 66046177300c9c89bbec6e841a61208e6d08140a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 2 Apr 2022 14:51:26 +0200 Subject: [PATCH 064/363] #2794 Compile error when condition expression used with constant or expression --- .../ap/internal/model/source/MappingOptions.java | 6 ++++++ .../org/mapstruct/ap/internal/util/Message.java | 2 ++ .../expression/ConditionalExpressionTest.java | 13 +++++++++++++ .../ErroneousConditionExpressionMapper.java | 6 ++++++ 4 files changed, 27 insertions(+) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index cb496a3a31..e788b51f6e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -222,9 +222,15 @@ else if ( gem.constant().hasValue() && gem.defaultValue().hasValue() ) { else if ( gem.expression().hasValue() && gem.defaultExpression().hasValue() ) { message = Message.PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_EXPRESSION_BOTH_DEFINED; } + else if ( gem.expression().hasValue() && gem.conditionExpression().hasValue() ) { + message = Message.PROPERTYMAPPING_EXPRESSION_AND_CONDITION_EXPRESSION_BOTH_DEFINED; + } else if ( gem.constant().hasValue() && gem.defaultExpression().hasValue() ) { message = Message.PROPERTYMAPPING_CONSTANT_AND_DEFAULT_EXPRESSION_BOTH_DEFINED; } + else if ( gem.constant().hasValue() && gem.conditionExpression().hasValue() ) { + message = Message.PROPERTYMAPPING_CONSTANT_AND_CONDITION_EXPRESSION_BOTH_DEFINED; + } else if ( gem.defaultValue().hasValue() && gem.defaultExpression().hasValue() ) { message = Message.PROPERTYMAPPING_DEFAULT_VALUE_AND_DEFAULT_EXPRESSION_BOTH_DEFINED; } 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 6d8fed1a95..4e1c0302fa 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 @@ -59,7 +59,9 @@ public enum Message { PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_VALUE_BOTH_DEFINED( "Expression and default value are both defined in @Mapping, either define a defaultValue or an expression." ), PROPERTYMAPPING_CONSTANT_AND_DEFAULT_VALUE_BOTH_DEFINED( "Constant and default value are both defined in @Mapping, either define a defaultValue or a constant." ), PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_EXPRESSION_BOTH_DEFINED( "Expression and default expression are both defined in @Mapping, either define an expression or a default expression." ), + PROPERTYMAPPING_EXPRESSION_AND_CONDITION_EXPRESSION_BOTH_DEFINED( "Expression and condition expression are both defined in @Mapping, either define an expression or a condition expression." ), PROPERTYMAPPING_CONSTANT_AND_DEFAULT_EXPRESSION_BOTH_DEFINED( "Constant and default expression are both defined in @Mapping, either define a constant or a default expression." ), + PROPERTYMAPPING_CONSTANT_AND_CONDITION_EXPRESSION_BOTH_DEFINED( "Constant and condition expression are both defined in @Mapping, either define a constant or a condition expression." ), PROPERTYMAPPING_DEFAULT_VALUE_AND_DEFAULT_EXPRESSION_BOTH_DEFINED( "Default value and default expression are both defined in @Mapping, either define a default value or a default expression." ), PROPERTYMAPPING_DEFAULT_VALUE_AND_NVPMS( "Default value and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a defaultValue or an nullValuePropertyMappingStrategy." ), PROPERTYMAPPING_EXPRESSION_VALUE_AND_NVPMS( "Expression and nullValuePropertyMappingStrategy are both defined in @Mapping, either define an expression or an nullValuePropertyMappingStrategy." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalExpressionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalExpressionTest.java index 42219cfb6a..1ad36f7de1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalExpressionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalExpressionTest.java @@ -111,6 +111,7 @@ public void conditionalExpressionForSourceToTarget() { } + @IssueKey("2794") @ProcessorTest @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -119,6 +120,18 @@ public void conditionalExpressionForSourceToTarget() { kind = javax.tools.Diagnostic.Kind.ERROR, line = 19, message = "Value for condition expression must be given in the form \"java()\"." + ), + @Diagnostic(type = ErroneousConditionExpressionMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 22, + message = "Constant and condition expression are both defined in @Mapping," + + " either define a constant or a condition expression." + ), + @Diagnostic(type = ErroneousConditionExpressionMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 25, + message = "Expression and condition expression are both defined in @Mapping," + + " either define an expression or a condition expression." ) } ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ErroneousConditionExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ErroneousConditionExpressionMapper.java index c74c552531..c8e5db243e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ErroneousConditionExpressionMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ErroneousConditionExpressionMapper.java @@ -18,4 +18,10 @@ public interface ErroneousConditionExpressionMapper { @Mapping(target = "name", conditionExpression = "!employee.getName().isEmpty()") BasicEmployee map(EmployeeDto employee); + + @Mapping(target = "name", conditionExpression = "java(true)", constant = "test") + BasicEmployee mapConstant(EmployeeDto employee); + + @Mapping(target = "name", conditionExpression = "java(true)", expression = "java(\"test\")") + BasicEmployee mapExpression(EmployeeDto employee); } From 437a70d6dfa238f3d0262d1bf90d88003152fbbf Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Fri, 8 Apr 2022 20:57:40 +0200 Subject: [PATCH 065/363] #2807: Include LifeCycleMethod importTypes in the list of importTypes. (#2808) Co-authored-by: Ben Zegveld --- .../ap/internal/model/MappingMethod.java | 21 +++++++++++---- .../ap/test/bugs/_2807/Issue2807Test.java | 27 +++++++++++++++++++ .../bugs/_2807/SpringLifeCycleMapper.java | 26 ++++++++++++++++++ .../ap/test/bugs/_2807/after/AfterMethod.java | 23 ++++++++++++++++ .../test/bugs/_2807/before/BeforeMethod.java | 21 +++++++++++++++ .../beforewithtarget/BeforeWithTarget.java | 23 ++++++++++++++++ 6 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java 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 c7d329916f..5b9eda644b 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.internal.model; -import static org.mapstruct.ap.internal.util.Strings.getSafeVariableName; -import static org.mapstruct.ap.internal.util.Strings.join; - import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -21,6 +19,9 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; +import static org.mapstruct.ap.internal.util.Strings.getSafeVariableName; +import static org.mapstruct.ap.internal.util.Strings.join; + /** * A method implemented or referenced by a {@link Mapper} class. * @@ -70,7 +71,7 @@ protected MappingMethod(Method method, List parameters, Collection parameters) { @@ -153,6 +154,16 @@ public Set getImportTypes() { types.addAll( type.getImportTypes() ); } + for ( LifecycleCallbackMethodReference reference : beforeMappingReferencesWithMappingTarget ) { + types.addAll( reference.getImportTypes() ); + } + for ( LifecycleCallbackMethodReference reference : beforeMappingReferencesWithoutMappingTarget ) { + types.addAll( reference.getImportTypes() ); + } + for ( LifecycleCallbackMethodReference reference : afterMappingReferences ) { + types.addAll( reference.getImportTypes() ); + } + return types; } @@ -178,7 +189,7 @@ public String toString() { private List filterMappingTarget(List methods, boolean mustHaveMappingTargetParameter) { if ( methods == null ) { - return null; + return Collections.emptyList(); } List result = diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java new file mode 100644 index 0000000000..2cb96ce37e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java @@ -0,0 +1,27 @@ +/* + * 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._2807; + +import org.mapstruct.ap.test.bugs._2807.after.AfterMethod; +import org.mapstruct.ap.test.bugs._2807.before.BeforeMethod; +import org.mapstruct.ap.test.bugs._2807.beforewithtarget.BeforeWithTarget; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; + +/** + * @author Ben Zegveld + */ +@IssueKey( "2807" ) +public class Issue2807Test { + + @ProcessorTest + @WithSpring + @WithClasses( { SpringLifeCycleMapper.class, BeforeMethod.class, BeforeWithTarget.class, AfterMethod.class } ) + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java new file mode 100644 index 0000000000..daabe7f52a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java @@ -0,0 +1,26 @@ +/* + * 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._2807; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.bugs._2807.after.AfterMethod; +import org.mapstruct.ap.test.bugs._2807.before.BeforeMethod; +import org.mapstruct.ap.test.bugs._2807.beforewithtarget.BeforeWithTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper( componentModel = "spring", uses = { BeforeMethod.class, AfterMethod.class, + BeforeWithTarget.class }, unmappedTargetPolicy = ReportingPolicy.IGNORE ) +public interface SpringLifeCycleMapper { + SpringLifeCycleMapper INSTANCE = Mappers.getMapper( SpringLifeCycleMapper.class ); + + List map(List list); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java new file mode 100644 index 0000000000..f7c7457348 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java @@ -0,0 +1,23 @@ +/* + * 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._2807.after; + +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.MappingTarget; + +/** + * @author Ben Zegveld + */ +public class AfterMethod { + private AfterMethod() { + } + + @AfterMapping + public static void doNothing(@MappingTarget List source) { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java new file mode 100644 index 0000000000..5252bee150 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java @@ -0,0 +1,21 @@ +/* + * 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._2807.before; + +import org.mapstruct.BeforeMapping; + +/** + * @author Ben Zegveld + */ +public class BeforeMethod { + private BeforeMethod() { + } + + @BeforeMapping + public static void doNothing(Iterable source) { + return; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java new file mode 100644 index 0000000000..69e62bdea1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java @@ -0,0 +1,23 @@ +/* + * 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._2807.beforewithtarget; + +import java.util.List; + +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; + +/** + * @author Ben Zegveld + */ +public class BeforeWithTarget { + private BeforeWithTarget() { + } + + @BeforeMapping + public static void doNothingBeforeWithTarget(Iterable source, @MappingTarget List target) { + } +} From a4162809a400f297e3c8c51e03f1cd6f8fe8a90d Mon Sep 17 00:00:00 2001 From: Hao Zhang <452227361@qq.com> Date: Sat, 28 May 2022 17:37:21 +0800 Subject: [PATCH 066/363] Doc: correct the annotation processor version (#2859) The lombok-mapstruct-binding anotation procossor version given by document will result a compile problem, correct it by the example repository so that work fine --- .../chapter-14-third-party-api-integration.asciidoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc b/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc index c31424a0de..69bef46ea6 100644 --- a/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc +++ b/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc @@ -58,7 +58,7 @@ This resolves the compilation issues of Lombok and MapStruct modules. org.projectlombok lombok-mapstruct-binding - 0.1.0 + 0.2.0 ---- ==== @@ -121,7 +121,7 @@ The set up using Maven or Gradle does not differ from what is described in < org.projectlombok lombok-mapstruct-binding - 0.1.0 + 0.2.0 @@ -141,7 +141,7 @@ dependencies { implementation "org.mapstruct:mapstruct:${mapstructVersion}" implementation "org.projectlombok:lombok:1.18.16" - annotationProcessor "org.projectlombok:lombok-mapstruct-binding:0.1.0" + annotationProcessor "org.projectlombok:lombok-mapstruct-binding:0.2.0" annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" annotationProcessor "org.projectlombok:lombok:1.18.16" } From 9769f51756e1e3d3173a1b9026635757909b90ad Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 30 May 2022 21:09:21 +0200 Subject: [PATCH 067/363] #2851 Fix typo in readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 645f3ee992..c7f237ad06 100644 --- a/readme.md +++ b/readme.md @@ -34,7 +34,7 @@ Compared to mapping frameworks working at runtime, MapStruct offers the followin * mappings are incorrect (cannot find a proper mapping method or type conversion) * **Easily debuggable mapping code** (or editable by hand—e.g. in case of a bug in the generator) -To create a mapping between two types, declare a mapper class like this: +To create a mapping between two types, declare a mapper interface like this: ```java @Mapper From c945ccd628a46cda862532f38acce5b2b070c3f8 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 30 May 2022 21:20:09 +0200 Subject: [PATCH 068/363] #2835 Upgrade jacoco-maven-plugin to latest 0.8.8 to support Java 17 --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index 1a9fddff92..4b5ea78486 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -545,7 +545,7 @@ org.jacoco jacoco-maven-plugin - 0.8.6 + 0.8.8 org.jvnet.jaxb2.maven2 From 0559c47c2174254a3a8fa8ec828ec9dd14d393c0 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Mon, 30 May 2022 21:51:57 +0200 Subject: [PATCH 069/363] #2739 Enhance documentation around SPI usage --- .../chapter-13-using-mapstruct-spi.asciidoc | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc index 51fec968bf..1095d41656 100644 --- a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc +++ b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc @@ -1,7 +1,18 @@ [[using-spi]] == Using the MapStruct SPI + +To use a custom SPI implementation, it must be located in a separate JAR file together with a file named after the SPI (e.g. `org.mapstruct.ap.spi.AccessorNamingStrategy`) in `META-INF/services/` with the fully qualified name of your custom implementation as content (e.g. `org.mapstruct.example.CustomAccessorNamingStrategy`). This JAR file needs to be added to the annotation processor classpath (i.e. add it next to the place where you added the mapstruct-processor jar). + + +[NOTE] +==== +It might also be necessary to add the jar to your IDE's annotation processor factory path. Otherwise you might get an error stating that it cannot be found, while a run using your build tool does succeed. +==== + === Custom Accessor Naming Strategy +SPI name: `org.mapstruct.ap.spi.AccessorNamingStrategy` + MapStruct offers the possibility to override the `AccessorNamingStrategy` via the Service Provider Interface (SPI). A nice example is the use of the fluent API on the source object `GolfPlayer` and `GolfPlayerDto` below. .Source object GolfPlayer with fluent API. @@ -121,14 +132,14 @@ public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy ==== The `CustomAccessorNamingStrategy` makes use of the `DefaultAccessorNamingStrategy` (also available in mapstruct-processor) and relies on that class to leave most of the default behaviour unchanged. -To use a custom SPI implementation, it must be located in a separate JAR file together with the file `META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy` with the fully qualified name of your custom implementation as content (e.g. `org.mapstruct.example.CustomAccessorNamingStrategy`). This JAR file needs to be added to the annotation processor classpath (i.e. add it next to the place where you added the mapstruct-processor jar). - [TIP] Fore more details: The example above is present in our examples repository (https://github.com/mapstruct/mapstruct-examples). -[mapping-exclusion-provider] +[[mapping-exclusion-provider]] === Mapping Exclusion Provider +SPI name: `org.mapstruct.ap.spi.MappingExclusionProvider` + MapStruct offers the possibility to override the `MappingExclusionProvider` via the Service Provider Interface (SPI). A nice example is to not allow MapStruct to create an automatic sub-mapping for a certain type, i.e. MapStruct will not try to generate an automatic sub-mapping method for an excluded type. @@ -177,16 +188,12 @@ include::{processor-ap-test}/nestedbeans/exclusions/custom/CustomMappingExclusio ---- ==== -To use a custom SPI implementation, it must be located in a separate JAR file -together with the file `META-INF/services/org.mapstruct.ap.spi.MappingExclusionProvider` with the fully qualified name of your custom implementation as content -(e.g. `org.mapstruct.example.CustomMappingExclusionProvider`). -This JAR file needs to be added to the annotation processor classpath -(i.e. add it next to the place where you added the mapstruct-processor jar). - [[custom-builder-provider]] === Custom Builder Provider +SPI name: org.mapstruct.ap.spi.BuilderProvider + MapStruct offers the possibility to override the `DefaultProvider` via the Service Provider Interface (SPI). A nice example is to provide support for a custom builder strategy. @@ -202,6 +209,8 @@ include::{processor-ap-main}/spi/NoOpBuilderProvider.java[tag=documentation] [[custom-enum-naming-strategy]] === Custom Enum Naming Strategy +SPI name: `org.mapstruct.ap.spi.EnumMappingStrategy` + MapStruct offers the possibility to override the `EnumMappingStrategy` via the Service Provider Interface (SPI). This can be used when you have certain enums that follow some conventions within your organization. For example all enums which implement an interface named `CustomEnumMarker` are prefixed with `CUSTOM_` @@ -349,6 +358,8 @@ public class CheeseTypeMapperImpl implements CheeseTypeMapper { [[custom-enum-transformation-strategy]] === Custom Enum Transformation Strategy +SPI name: `org.mapstruct.ap.spi.EnumTransformationStrategy` + MapStruct offers the possibility to other transformations strategies by implementing `EnumTransformationStrategy` via the Service Provider Interface (SPI). A nice example is to provide support for a custom transformation strategy. From a1a0786cf2c25651eae795f3c2649e75b4b641f9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 2 Jun 2022 22:14:42 +0200 Subject: [PATCH 070/363] #2846 Add test case showing that everything works as expected --- .../string/StringConversionTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/string/StringConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/StringConversionTest.java index 994b47957b..37d3c77b04 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/string/StringConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/StringConversionTest.java @@ -69,6 +69,33 @@ public void shouldApplyStringConversions() { assertThat( target.getSb() ).isEqualTo( "SB" ); } + @IssueKey("2846") + @ProcessorTest + public void shouldNotApplyStringConversionsWhenNull() { + Source source = new Source(); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( "0" ); + assertThat( target.getBb() ).isNull(); + assertThat( target.getS() ).isEqualTo( "0" ); + assertThat( target.getSs() ).isNull(); + assertThat( target.getI() ).isEqualTo( "0" ); + assertThat( target.getIi() ).isNull(); + assertThat( target.getL() ).isEqualTo( "0" ); + assertThat( target.getLl() ).isNull(); + assertThat( target.getF() ).isEqualTo( "0.0" ); + assertThat( target.getFf() ).isNull(); + assertThat( target.getD() ).isEqualTo( "0.0" ); + assertThat( target.getDd() ).isNull(); + assertThat( target.getBool() ).isEqualTo( "false" ); + assertThat( target.getBoolBool() ).isNull(); + assertThat( target.getC() ).isEqualTo( String.valueOf( '\u0000' ) ); + assertThat( target.getCc() ).isNull(); + assertThat( target.getSb() ).isNull(); + } + @ProcessorTest public void shouldApplyReverseStringConversions() { Target target = new Target(); From efa11ba312bf601179289369b219c97ed9307f07 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 2 Jun 2022 23:11:41 +0200 Subject: [PATCH 071/363] [maven-release-plugin] prepare release 1.5.0.Final --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index aaae7b5404..8cc783f39a 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Final ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 293043e6f9..903558558f 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Final ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 10c95cf3b5..bf4e4387ae 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Final ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 337ad35e51..404001b0c3 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Final ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 8850da641c..7ca09c75f4 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Final ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 4b48c19496..e103296690 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Final ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 4b5ea78486..da625ee0dc 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Final pom MapStruct Parent @@ -69,7 +69,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.5.0.Final diff --git a/pom.xml b/pom.xml index 28c35aea74..811dda0bfb 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Final parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.5.0.Final diff --git a/processor/pom.xml b/processor/pom.xml index ec3f20a241..6feeff0796 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0-SNAPSHOT + 1.5.0.Final ../parent/pom.xml From 5efe5e291c0433cafa53bd76cb5f170529f5a60a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 2 Jun 2022 23:11:41 +0200 Subject: [PATCH 072/363] [maven-release-plugin] prepare for next development iteration --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 8cc783f39a..0bdec734cb 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 903558558f..160f963adf 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index bf4e4387ae..ac993c73d6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 404001b0c3..9ceeb505ea 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 7ca09c75f4..0a62a3ef3c 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index e103296690..364faae643 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index da625ee0dc..8c6a55ebed 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Final + 1.6.0-SNAPSHOT pom MapStruct Parent @@ -69,7 +69,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.5.0.Final + HEAD diff --git a/pom.xml b/pom.xml index 811dda0bfb..25a5ead6bc 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Final + 1.6.0-SNAPSHOT parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.5.0.Final + HEAD diff --git a/processor/pom.xml b/processor/pom.xml index 6feeff0796..6f2bb6495c 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.0.Final + 1.6.0-SNAPSHOT ../parent/pom.xml From 07265630241fa87cb2ec443219c134aac56457c5 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 2 Jun 2022 23:30:17 +0200 Subject: [PATCH 073/363] Update readme with latest released 1.5.0.Final release --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index c7f237ad06..c56eefb771 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.4.2.Final-blue.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.0.Final-blue.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final) [![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/master/LICENSE.txt) @@ -68,7 +68,7 @@ For Maven-based projects, add the following to your POM file in order to use Map ```xml ... - 1.4.2.Final + 1.5.0.Final ... @@ -114,10 +114,10 @@ plugins { dependencies { ... - compile 'org.mapstruct:mapstruct:1.4.2.Final' + compile 'org.mapstruct:mapstruct:1.5.0.Final' - annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final' - testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final' // if you are using mapstruct in test code + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.0.Final' + testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.0.Final' // if you are using mapstruct in test code } ... ``` From 46b78bfe59c0473d3b92c08c73bbb782e03491bd Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 4 Jun 2022 21:52:59 +0200 Subject: [PATCH 074/363] #2867 Fix NPE when reporting message on parent mappers --- .../MapperAnnotatedFormattingMessenger.java | 11 +++++- .../test/bugs/_2867/Issue2867BaseMapper.java | 17 ++++++++ .../ap/test/bugs/_2867/Issue2867Mapper.java | 31 +++++++++++++++ .../ap/test/bugs/_2867/Issue2867Test.java | 39 +++++++++++++++++++ 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867BaseMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperAnnotatedFormattingMessenger.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperAnnotatedFormattingMessenger.java index eeb84570b0..d3384f0b4c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperAnnotatedFormattingMessenger.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperAnnotatedFormattingMessenger.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.processor; +import java.util.Objects; import java.util.stream.Collectors; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -12,6 +13,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; import org.mapstruct.ap.internal.util.FormattingMessager; @@ -97,7 +99,7 @@ private String constructMethod(Element e) { if ( e instanceof ExecutableElement ) { ExecutableElement ee = (ExecutableElement) e; StringBuilder method = new StringBuilder(); - method.append( typeUtils.asElement( ee.getReturnType() ).getSimpleName() ); + method.append( typeMirrorToString( ee.getReturnType() ) ); method.append( " " ); method.append( ee.getSimpleName() ); method.append( "(" ); @@ -112,7 +114,12 @@ private String constructMethod(Element e) { } private String parameterToString(VariableElement element) { - return typeUtils.asElement( element.asType() ).getSimpleName() + " " + element.getSimpleName(); + return typeMirrorToString( element.asType() ) + " " + element.getSimpleName(); + } + + private String typeMirrorToString(TypeMirror type) { + Element element = typeUtils.asElement( type ); + return element != null ? element.getSimpleName().toString() : Objects.toString( type ); } private Message determineDelegationMessage(Element e, Message msg) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867BaseMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867BaseMapper.java new file mode 100644 index 0000000000..8ebe1aa98d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867BaseMapper.java @@ -0,0 +1,17 @@ +/* + * 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._2867; + +import org.mapstruct.MappingTarget; + +/** + * @author Filip Hrisafov + */ +public interface Issue2867BaseMapper { + + void update(@MappingTarget T target, S source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Mapper.java new file mode 100644 index 0000000000..77dfda0fc9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Mapper.java @@ -0,0 +1,31 @@ +/* + * 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._2867; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2867Mapper extends Issue2867BaseMapper { + + class Target { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class Source { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Test.java new file mode 100644 index 0000000000..5f0e7ad763 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Test.java @@ -0,0 +1,39 @@ +/* + * 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._2867; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue2867BaseMapper.class, + Issue2867Mapper.class, +}) +@IssueKey("2867") +class Issue2867Test { + + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = @Diagnostic( + type = Issue2867Mapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 14, + message = "Unmapped target property: \"name\"." + + " Occured at 'void update(T target, S source)' in 'Issue2867BaseMapper'." + ) + ) + @ProcessorTest + void shouldCompile() { + + } +} From ec9288ce6693abc1d14687d315f10e7e7697dd5e Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 5 Jun 2022 08:42:55 +0200 Subject: [PATCH 075/363] [maven-release-plugin] prepare release 1.5.1.Final --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0bdec734cb..58ab41d726 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.1.Final ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 160f963adf..8ef899caaf 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.1.Final ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index ac993c73d6..527ace445d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.1.Final ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 9ceeb505ea..73a358a613 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.1.Final ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 0a62a3ef3c..d55ec5dae5 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.1.Final ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 364faae643..4881caebfd 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.1.Final ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 8c6a55ebed..48bb5c8cba 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.1.Final pom MapStruct Parent @@ -69,7 +69,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.5.1.Final diff --git a/pom.xml b/pom.xml index 25a5ead6bc..7a90e263b9 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.1.Final parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.5.1.Final diff --git a/processor/pom.xml b/processor/pom.xml index 6f2bb6495c..61fde05422 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.1.Final ../parent/pom.xml From 20e97714d4e2d357c96a5065b22223f2152a6978 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 5 Jun 2022 08:42:56 +0200 Subject: [PATCH 076/363] [maven-release-plugin] prepare for next development iteration --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 58ab41d726..0bdec734cb 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.1.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 8ef899caaf..160f963adf 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.1.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 527ace445d..ac993c73d6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.1.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 73a358a613..9ceeb505ea 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.1.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d55ec5dae5..0a62a3ef3c 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.1.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 4881caebfd..364faae643 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.1.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 48bb5c8cba..8c6a55ebed 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.5.1.Final + 1.6.0-SNAPSHOT pom MapStruct Parent @@ -69,7 +69,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.5.1.Final + HEAD diff --git a/pom.xml b/pom.xml index 7a90e263b9..25a5ead6bc 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.5.1.Final + 1.6.0-SNAPSHOT parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.5.1.Final + HEAD diff --git a/processor/pom.xml b/processor/pom.xml index 61fde05422..6f2bb6495c 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.1.Final + 1.6.0-SNAPSHOT ../parent/pom.xml From 4c9aa00369efbea00bc05b5631b5deea29baaced Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 5 Jun 2022 08:53:10 +0200 Subject: [PATCH 077/363] Update readme with latest released 1.5.1.Final release --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index c56eefb771..e9ccc8d890 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.0.Final-blue.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.1.Final-blue.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final) [![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/master/LICENSE.txt) @@ -68,7 +68,7 @@ For Maven-based projects, add the following to your POM file in order to use Map ```xml ... - 1.5.0.Final + 1.5.1.Final ... @@ -114,10 +114,10 @@ plugins { dependencies { ... - compile 'org.mapstruct:mapstruct:1.5.0.Final' + compile 'org.mapstruct:mapstruct:1.5.1.Final' - annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.0.Final' - testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.0.Final' // if you are using mapstruct in test code + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.1.Final' + testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.1.Final' // if you are using mapstruct in test code } ... ``` From 9247c5d7fbc5302061be1e5cc5cca8a747320b14 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 5 Jun 2022 12:15:26 +0200 Subject: [PATCH 078/363] #2870 Use codecov action v2 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6d80d2da89..387b004642 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,7 +39,7 @@ jobs: - name: 'Generate coverage report' run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 - name: 'Publish Snapshots' if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'mapstruct/mapstruct' run: ./mvnw -s etc/ci-settings.xml -DskipTests=true -DskipDistribution=true deploy From d7c0d15fe1ef3382471de8027f728f30fd456809 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 5 Jun 2022 15:58:19 +0200 Subject: [PATCH 079/363] Change required Java version for running MapStruct in the readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index e9ccc8d890..0d6a93c781 100644 --- a/readme.md +++ b/readme.md @@ -130,7 +130,7 @@ To learn more about MapStruct, refer to the [project homepage](http://mapstruct. ## Building from Source -MapStruct uses Maven for its build. Java 8 is required for building MapStruct from source. To build the complete project, run +MapStruct uses Maven for its build. Java 11 is required for building MapStruct from source. To build the complete project, run mvn clean install From 05ae9922ea2bd6a6db5aac9358e8016adb5e82bb Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 5 Jun 2022 17:24:49 +0200 Subject: [PATCH 080/363] Update GitHub actions Run tests with Java 18 Change actions/checkout to v3 Change actions/setup-java to v3 --- .github/workflows/java-ea.yml | 7 ++++--- .github/workflows/main.yml | 30 ++++++++++++++++++------------ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index 76e24db3b1..3cc488ca7e 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -10,15 +10,16 @@ jobs: strategy: fail-fast: false matrix: - java: [18-ea, 19-ea] + java: [19-ea] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Set up JDK' - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'zulu' java-version: ${{ matrix.java }} - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 387b004642..40999c680a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,15 +12,16 @@ jobs: strategy: fail-fast: false matrix: - java: [13, 16, 17] + java: [13, 17, 18] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Set up JDK' - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'zulu' java-version: ${{ matrix.java }} - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true @@ -29,10 +30,11 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Set up JDK 11' - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'zulu' java-version: 11 - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install @@ -48,16 +50,18 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Set up JDK 11 for building everything' - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'zulu' java-version: 11 - name: 'Install Processor' run: ./mvnw ${MAVEN_ARGS} -DskipTests install -pl processor -am - name: 'Set up JDK 8 for running integration tests' - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'zulu' java-version: 8 - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest @@ -65,10 +69,11 @@ jobs: name: 'Windows' runs-on: windows-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: 'Set up JDK 11' - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'zulu' java-version: 11 - name: 'Test' run: ./mvnw %MAVEN_ARGS% install @@ -76,10 +81,11 @@ jobs: name: 'Mac OS' runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: 'Set up JDK 11' - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: 'zulu' java-version: 11 - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install From 22ad9f636d9f786bc18febf1fb623a17145c33a5 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Tue, 14 Jun 2022 22:03:32 +0200 Subject: [PATCH 081/363] #2806 Try to stabilise some date conversion tests by locking them on reading the default timezone --- .../mapstruct/ap/test/conversion/date/DateConversionTest.java | 2 ++ .../java/org/mapstruct/ap/test/naming/VariableNamingTest.java | 2 ++ .../nestedmethodcall/NestedMappingMethodInvocationTest.java | 2 ++ 3 files changed, 6 insertions(+) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java index 83464f81a0..9ccf8e7e1f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.condition.JRE; import org.junitpioneer.jupiter.DefaultLocale; +import org.junitpioneer.jupiter.ReadsDefaultTimeZone; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -35,6 +36,7 @@ }) @IssueKey("43") @DefaultLocale("de") +@ReadsDefaultTimeZone public class DateConversionTest { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/test/naming/VariableNamingTest.java b/processor/src/test/java/org/mapstruct/ap/test/naming/VariableNamingTest.java index 4b5c7982ea..a8e833ee06 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/naming/VariableNamingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/naming/VariableNamingTest.java @@ -12,6 +12,7 @@ import java.util.HashMap; import java.util.Map; +import org.junitpioneer.jupiter.ReadsDefaultTimeZone; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -26,6 +27,7 @@ */ @WithClasses({ SourceTargetMapper.class, While.class, Break.class, Source.class }) @IssueKey("53") +@ReadsDefaultTimeZone public class VariableNamingTest { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java index 8d600d8604..03c47f7a49 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java @@ -17,6 +17,7 @@ import javax.xml.namespace.QName; import org.junitpioneer.jupiter.DefaultLocale; +import org.junitpioneer.jupiter.ReadsDefaultTimeZone; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -31,6 +32,7 @@ */ @IssueKey("134") @DefaultLocale("de") +@ReadsDefaultTimeZone public class NestedMappingMethodInvocationTest { public static final QName QNAME = new QName( "dont-care" ); From fa800926e7cbf6d1ed5b219ae79309036d2c0879 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 5 Jun 2022 18:16:00 +0200 Subject: [PATCH 082/363] #2837 Add support for text blocks in expressions --- .../itest/tests/MavenIntegrationTest.java | 7 +++ .../expressionTextBlocksTest/pom.xml | 22 ++++++++++ .../org/mapstruct/itest/textBlocks/Car.java | 22 ++++++++++ .../itest/textBlocks/CarAndWheelMapper.java | 41 +++++++++++++++++ .../mapstruct/itest/textBlocks/CarDto.java | 15 +++++++ .../itest/textBlocks/WheelPosition.java | 22 ++++++++++ .../itest/textBlocks/TextBlocksTest.java | 27 ++++++++++++ .../internal/model/source/MappingOptions.java | 2 +- .../java/JavaDefaultExpressionTest.java | 20 +++++++++ .../MultiLineDefaultExpressionMapper.java | 44 +++++++++++++++++++ .../expressions/java/JavaExpressionTest.java | 25 +++++++++++ .../java/MultiLineExpressionMapper.java | 37 ++++++++++++++++ 12 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 integrationtest/src/test/resources/expressionTextBlocksTest/pom.xml create mode 100644 integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/Car.java create mode 100644 integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarAndWheelMapper.java create mode 100644 integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarDto.java create mode 100644 integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/WheelPosition.java create mode 100644 integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/source/defaultExpressions/java/MultiLineDefaultExpressionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 4f4182d5c7..7d63ccf774 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -122,6 +122,13 @@ void recordsTest() { void recordsCrossModuleTest() { } + @ProcessorTest(baseDir = "expressionTextBlocksTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC + }) + @EnabledForJreRange(min = JRE.JAVA_17) + void expressionTextBlocksTest() { + } + @ProcessorTest(baseDir = "kotlinDataTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }, forkJvm = true) diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/pom.xml b/integrationtest/src/test/resources/expressionTextBlocksTest/pom.xml new file mode 100644 index 0000000000..b70c48f9d0 --- /dev/null +++ b/integrationtest/src/test/resources/expressionTextBlocksTest/pom.xml @@ -0,0 +1,22 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + expressionTextBlocksTest + jar + + diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/Car.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/Car.java new file mode 100644 index 0000000000..28d4fd3cd5 --- /dev/null +++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/Car.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.textBlocks; + +/** + * @author Filip Hrisafov + */ +public class Car { + + private WheelPosition wheelPosition; + + public WheelPosition getWheelPosition() { + return wheelPosition; + } + + public void setWheelPosition(WheelPosition wheelPosition) { + this.wheelPosition = wheelPosition; + } +} diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarAndWheelMapper.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarAndWheelMapper.java new file mode 100644 index 0000000000..23d665c6ff --- /dev/null +++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarAndWheelMapper.java @@ -0,0 +1,41 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.textBlocks; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface CarAndWheelMapper { + + CarAndWheelMapper INSTANCE = Mappers.getMapper( CarAndWheelMapper.class ); + + @Mapping(target = "wheelPosition", + expression = + """ + java( + source.getWheelPosition() == null ? + null : + source.getWheelPosition().getPosition() + ) + """) + CarDto carDtoFromCar(Car source); + + @Mapping(target = "wheelPosition", + expression = """ + java( + source.wheelPosition() == null ? + null : + new WheelPosition(source.wheelPosition()) + ) + """) + Car carFromCarDto(CarDto source); +} diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarDto.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarDto.java new file mode 100644 index 0000000000..f4baa4d044 --- /dev/null +++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/CarDto.java @@ -0,0 +1,15 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.textBlocks; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public record CarDto(String wheelPosition) { + +} diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/WheelPosition.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/WheelPosition.java new file mode 100644 index 0000000000..6effd485a3 --- /dev/null +++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/main/java/org/mapstruct/itest/textBlocks/WheelPosition.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.textBlocks; + +/** + * @author Filip Hrisafov + */ +public class WheelPosition { + + private final String position; + + public WheelPosition(String position) { + this.position = position; + } + + public String getPosition() { + return position; + } +} diff --git a/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java b/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java new file mode 100644 index 0000000000..42ed70b18e --- /dev/null +++ b/integrationtest/src/test/resources/expressionTextBlocksTest/src/test/java/org/mapstruct/itest/textBlocks/TextBlocksTest.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.textBlocks; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public class TextBlocksTest { + + @Test + public void textBlockExpressionShouldWork() { + Car car = new Car(); + car.setWheelPosition( new WheelPosition( "left" ) ); + + CarDto carDto = CarAndWheelMapper.INSTANCE.carDtoFromCar(car); + + assertThat( carDto ).isNotNull(); + assertThat( carDto.wheelPosition() ) + .isEqualTo( "left" ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index e788b51f6e..5046ce8b29 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -37,7 +37,7 @@ */ public class MappingOptions extends DelegatingOptions { - private static final Pattern JAVA_EXPRESSION = Pattern.compile( "^java\\((.*)\\)$" ); + private static final Pattern JAVA_EXPRESSION = Pattern.compile( "^\\s*java\\((.*)\\)\\s*$", Pattern.DOTALL ); private final String sourceName; private final String constant; diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/defaultExpressions/java/JavaDefaultExpressionTest.java b/processor/src/test/java/org/mapstruct/ap/test/source/defaultExpressions/java/JavaDefaultExpressionTest.java index 3df90421b3..8f3a745e84 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/defaultExpressions/java/JavaDefaultExpressionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/defaultExpressions/java/JavaDefaultExpressionTest.java @@ -5,6 +5,9 @@ */ package org.mapstruct.ap.test.source.defaultExpressions.java; +import java.time.LocalDate; +import java.time.Month; +import java.time.ZoneOffset; import java.util.Date; import org.mapstruct.ap.testutil.ProcessorTest; @@ -46,6 +49,23 @@ public void testJavaDefaultExpressionWithNoValues() { assertThat( target.getSourceDate() ).isEqualTo( new Date( 30L ) ); } + @ProcessorTest + @WithClasses({ Source.class, Target.class, MultiLineDefaultExpressionMapper.class }) + public void testMultiLineJavaDefaultExpression() { + Source source = new Source(); + + Target target = MultiLineDefaultExpressionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getSourceId() ).isEqualTo( "test" ); + assertThat( target.getSourceDate() ) + .isEqualTo( Date.from( + LocalDate.of( 2022, Month.JUNE, 5 ) + .atTime( 17, 10 ) + .toInstant( ZoneOffset.UTC ) + ) ); + } + @ProcessorTest @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/defaultExpressions/java/MultiLineDefaultExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/source/defaultExpressions/java/MultiLineDefaultExpressionMapper.java new file mode 100644 index 0000000000..ba27726377 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/source/defaultExpressions/java/MultiLineDefaultExpressionMapper.java @@ -0,0 +1,44 @@ +/* + * 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.source.defaultExpressions.java; + +import java.time.LocalDate; +import java.time.Month; +import java.time.ZoneOffset; +import java.util.Date; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(imports = { Date.class, LocalDate.class, ZoneOffset.class, Month.class }) +public interface MultiLineDefaultExpressionMapper { + + MultiLineDefaultExpressionMapper INSTANCE = Mappers.getMapper( MultiLineDefaultExpressionMapper.class ); + + @Mappings({ + @Mapping( + target = "sourceId", + source = "id", + defaultExpression = "java( new StringBuilder()\n.append( \"test\" )\n.toString() )" + ), + @Mapping( + target = "sourceDate", + source = "date", + defaultExpression = "java(" + + "Date.from(\n" + + "LocalDate.of( 2022, Month.JUNE, 5 )\n" + + ".atTime( 17, 10 )\n" + + ".toInstant( ZoneOffset.UTC )\n)" + + ")" + ) + }) + Target sourceToTarget(Source s); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/JavaExpressionTest.java b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/JavaExpressionTest.java index be80644606..9a20ddcdbd 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/JavaExpressionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/JavaExpressionTest.java @@ -132,6 +132,31 @@ public void testGetterOnly() throws ParseException { assertThat( target.getList() ).isEqualTo( Arrays.asList( "test2" ) ); } + @ProcessorTest + @WithClasses({ Source.class, Target.class, TimeAndFormat.class, MultiLineExpressionMapper.class }) + public void testMultiLineJavaExpressionInsertion() throws ParseException { + Source source = new Source(); + String format = "dd-MM-yyyy,hh:mm:ss"; + Date time = getTime( format, "09-01-2014,01:35:03" ); + + source.setFormat( format ); + source.setTime( time ); + + Target target = MultiLineExpressionMapper.INSTANCE.mapUsingMultiLineExpression( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getTimeAndFormat().getTime() ).isEqualTo( time ); + assertThat( target.getTimeAndFormat().getFormat() ).isEqualTo( format ); + assertThat( target.getAnotherProp() ).isNull(); + + target = MultiLineExpressionMapper.INSTANCE.mapUsingMultiLineExpressionWithLeadingSpaces( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getTimeAndFormat().getTime() ).isEqualTo( time ); + assertThat( target.getTimeAndFormat().getFormat() ).isEqualTo( format ); + assertThat( target.getAnotherProp() ).isNull(); + } + @IssueKey( "1851" ) @ProcessorTest @WithClasses({ diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java new file mode 100644 index 0000000000..50d0b4b2cd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java @@ -0,0 +1,37 @@ +/* + * 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.source.expressions.java; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.source.expressions.java.mapper.TimeAndFormat; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(imports = TimeAndFormat.class) +public interface MultiLineExpressionMapper { + + + MultiLineExpressionMapper INSTANCE = Mappers.getMapper( MultiLineExpressionMapper.class ); + + @Mappings({ + @Mapping(target = "timeAndFormat", expression = "java( new TimeAndFormat(\ns.getTime(),\ns.getFormat()\n ))"), + @Mapping(target = "anotherProp", ignore = true) + }) + Target mapUsingMultiLineExpression(Source s); + + @Mappings({ + @Mapping( + target = "timeAndFormat", + expression = " java( new TimeAndFormat(\ns.getTime(),\ns.getFormat()\n )) " + ), + @Mapping(target = "anotherProp", ignore = true) + }) + Target mapUsingMultiLineExpressionWithLeadingSpaces(Source s); +} From 98eb46aee9aec4135fed2bd0c2fbd0c621c22b08 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 18 Jun 2022 13:59:03 +0200 Subject: [PATCH 083/363] #2880 Fix missing import for array mapping methods Co-authored-by: Martin Kamp Jensen --- .../ap/internal/model/common/Type.java | 27 ++++++++++++++----- .../ap/test/bugs/_2880/Issue2880Mapper.java | 14 ++++++++++ .../ap/test/bugs/_2880/Issue2880Test.java | 25 +++++++++++++++++ .../mapstruct/ap/test/bugs/_2880/Outer.java | 16 +++++++++++ .../mapstruct/ap/test/bugs/_2880/Source.java | 21 +++++++++++++++ .../mapstruct/ap/test/bugs/_2880/Target.java | 21 +++++++++++++++ .../ap/test/bugs/_2880/TargetData.java | 13 +++++++++ 7 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Outer.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/TargetData.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index ba5f62254d..ce82fff621 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -181,8 +181,11 @@ public Type(TypeUtils typeUtils, ElementUtils elementUtils, TypeFactory typeFact this.loggingVerbose = loggingVerbose; - this.topLevelType = topLevelType( this.typeElement, this.typeFactory ); - this.nameWithTopLevelTypeName = nameWithTopLevelTypeName( this.typeElement ); + // The top level type for an array type is the top level type of the component type + TypeElement typeElementForTopLevel = + this.componentType == null ? this.typeElement : this.componentType.getTypeElement(); + this.topLevelType = topLevelType( typeElementForTopLevel, this.typeFactory ); + this.nameWithTopLevelTypeName = nameWithTopLevelTypeName( typeElementForTopLevel, this.name ); } //CHECKSTYLE:ON @@ -218,11 +221,21 @@ public String getName() { * (if the top level type is important, otherwise the fully-qualified name. */ public String createReferenceName() { - if ( isToBeImported() || shouldUseSimpleName() ) { + if ( isToBeImported() ) { + // isToBeImported() returns true for arrays. + // Therefore, we need to check the top level type when creating the reference + if ( isTopLevelTypeToBeImported() ) { + return nameWithTopLevelTypeName != null ? nameWithTopLevelTypeName : name; + } + + return name; + } + + if ( shouldUseSimpleName() ) { return name; } - if ( isTopLevelTypeToBeImported() && nameWithTopLevelTypeName != null ) { + if ( isTopLevelTypeToBeImported() && nameWithTopLevelTypeName != null) { return nameWithTopLevelTypeName; } @@ -1566,16 +1579,16 @@ private String trimSimpleClassName(String className) { return trimmedClassName; } - private static String nameWithTopLevelTypeName(TypeElement element) { + private static String nameWithTopLevelTypeName(TypeElement element, String name) { if ( element == null ) { return null; } if ( !element.getNestingKind().isNested() ) { - return element.getSimpleName().toString(); + return name; } Deque elements = new ArrayDeque<>(); - elements.addFirst( element.getSimpleName() ); + elements.addFirst( name ); Element parent = element.getEnclosingElement(); while ( parent != null && parent.getKind() != ElementKind.PACKAGE ) { elements.addFirst( parent.getSimpleName() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Mapper.java new file mode 100644 index 0000000000..218f6d384b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Mapper.java @@ -0,0 +1,14 @@ +/* + * 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._2880; + +import org.mapstruct.Mapper; + +@Mapper +public interface Issue2880Mapper { + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Test.java new file mode 100644 index 0000000000..e3d0f810c8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Test.java @@ -0,0 +1,25 @@ +/* + * 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._2880; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@IssueKey("2880") +@WithClasses({ + Issue2880Mapper.class, + Outer.class, + Source.class, + Target.class, + TargetData.class +}) +public class Issue2880Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Outer.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Outer.java new file mode 100644 index 0000000000..90b756137a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Outer.java @@ -0,0 +1,16 @@ +/* + * 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._2880; + +public class Outer { + + public static class SourceData { + + // CHECKSTYLE:OFF + public String value; + // CHECKSTYLE:ON + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Source.java new file mode 100644 index 0000000000..0ab3b81661 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Source.java @@ -0,0 +1,21 @@ +/* + * 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._2880; + +import java.util.List; + +public class Source { + + // CHECKSTYLE:OFF + public Outer.SourceData[] data1; + + public Outer.SourceData[] data2; + + public List data3; + + public List data4; + // CHECKSTYLE:ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Target.java new file mode 100644 index 0000000000..28c6b1cca1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Target.java @@ -0,0 +1,21 @@ +/* + * 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._2880; + +import java.util.List; + +public class Target { + + // CHECKSTYLE:OFF + public TargetData[] data1; + + public List data2; + + public TargetData[] data3; + + public List data4; + // CHECKSTYLE:ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/TargetData.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/TargetData.java new file mode 100644 index 0000000000..28cca96601 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/TargetData.java @@ -0,0 +1,13 @@ +/* + * 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._2880; + +public class TargetData { + + // CHECKSTYLE:OFF + public String value; + // CHECKSTYLE:ON +} From 406ae3fc13a665fd52144dd476af371623b73a88 Mon Sep 17 00:00:00 2001 From: Sergei Portnov <3230150+prtnv@users.noreply.github.com> Date: Sat, 18 Jun 2022 19:47:07 +0300 Subject: [PATCH 084/363] #2891 Fix subclass mapping while superclass has non-empty constructor Co-authored-by: Filip Hrisafov --- .../ap/internal/model/BeanMappingMethod.java | 10 ++- .../ap/test/bugs/_2891/Issue2891Mapper.java | 76 +++++++++++++++++++ .../ap/test/bugs/_2891/Issue2891Test.java | 30 ++++++++ .../test/bugs/_2891/Issue2891MapperImpl.java | 61 +++++++++++++++ 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Test.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2891/Issue2891MapperImpl.java 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 267702826b..d17718370d 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 @@ -704,6 +704,14 @@ else if ( matchingFactoryMethods.size() == 1 ) { } private ConstructorAccessor getConstructorAccessor(Type type) { + if ( type.isAbstract() ) { + // We cannot construct abstract classes. + // Usually we won't reach here, + // but if SubclassMapping is used with SubclassExhaustiveStrategy#RUNTIME_EXCEPTION + // then we will still generate the code. + // We shouldn't generate anything for those abstract types + return null; + } if ( type.isRecord() ) { // If the type is a record then just get the record components and use then @@ -1783,7 +1791,7 @@ public boolean hasSubclassMappings() { } public boolean isAbstractReturnType() { - return getFactoryMethod() == null && !hasConstructorMappings() && returnTypeToConstruct != null + return getFactoryMethod() == null && returnTypeToConstruct != null && returnTypeToConstruct.isAbstract(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Mapper.java new file mode 100644 index 0000000000..205978aeb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Mapper.java @@ -0,0 +1,76 @@ +/* + * 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._2891; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; + +/** + * @author Sergei Portnov + */ +@Mapper +public interface Issue2891Mapper { + + @BeanMapping(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) + @SubclassMapping(source = Source1.class, target = Target1.class) + @SubclassMapping(source = Source2.class, target = Target2.class) + AbstractTarget map(AbstractSource source); + + abstract class AbstractTarget { + + private final String name; + + protected AbstractTarget(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class Target1 extends AbstractTarget { + + protected Target1(String name) { + super( name ); + } + } + + class Target2 extends AbstractTarget { + + protected Target2(String name) { + super( name ); + } + } + + abstract class AbstractSource { + + private final String name; + + protected AbstractSource(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class Source1 extends AbstractSource { + protected Source1(String name) { + super( name ); + } + } + + class Source2 extends AbstractSource { + + protected Source2(String name) { + super( name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Test.java new file mode 100644 index 0000000000..c82d5aa63e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Test.java @@ -0,0 +1,30 @@ +/* + * 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._2891; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * @author Sergei Portnov + */ +@WithClasses({ + Issue2891Mapper.class +}) +@IssueKey("2891") +class Issue2891Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( Issue2891Mapper.class ); + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2891/Issue2891MapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2891/Issue2891MapperImpl.java new file mode 100644 index 0000000000..64ea2dc834 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2891/Issue2891MapperImpl.java @@ -0,0 +1,61 @@ +/* + * 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._2891; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2022-06-18T14:48:32+0300", + comments = "version: , compiler: Eclipse JDT (Batch) 3.20.0.v20191203-2131, environment: Java 11.0.15.1 (BellSoft)" +) +public class Issue2891MapperImpl implements Issue2891Mapper { + + @Override + public AbstractTarget map(AbstractSource source) { + if ( source == null ) { + return null; + } + + if (source instanceof Source1) { + return source1ToTarget1( (Source1) source ); + } + else if (source instanceof Source2) { + return source2ToTarget2( (Source2) source ); + } + else { + throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for " + source.getClass()); + } + } + + protected Target1 source1ToTarget1(Source1 source1) { + if ( source1 == null ) { + return null; + } + + String name = null; + + name = source1.getName(); + + Target1 target1 = new Target1( name ); + + return target1; + } + + protected Target2 source2ToTarget2(Source2 source2) { + if ( source2 == null ) { + return null; + } + + String name = null; + + name = source2.getName(); + + Target2 target2 = new Target2( name ); + + return target2; + } +} From 19973ff818e20531cbd81e5048404fd48c3c7508 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 18 Jun 2022 19:01:16 +0200 Subject: [PATCH 085/363] [maven-release-plugin] prepare release 1.5.2.Final --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0bdec734cb..2979ad289d 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.2.Final ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 160f963adf..9bce0a907a 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.2.Final ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index ac993c73d6..b2dec2c1ea 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.2.Final ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 9ceeb505ea..2c2351640c 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.2.Final ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 0a62a3ef3c..0691b8b88a 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.2.Final ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 364faae643..0c2f866da0 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.2.Final ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 8c6a55ebed..335d917363 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.2.Final pom MapStruct Parent @@ -69,7 +69,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.5.2.Final diff --git a/pom.xml b/pom.xml index 25a5ead6bc..66b25f8d89 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.2.Final parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.5.2.Final diff --git a/processor/pom.xml b/processor/pom.xml index 6f2bb6495c..20e8150f91 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.5.2.Final ../parent/pom.xml From 1459aabfc3f1867d8e6ebbae641f1e38b6a1c0e2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 18 Jun 2022 19:01:18 +0200 Subject: [PATCH 086/363] [maven-release-plugin] prepare for next development iteration --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 2979ad289d..0bdec734cb 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.2.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 9bce0a907a..160f963adf 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.2.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index b2dec2c1ea..ac993c73d6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.2.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 2c2351640c..9ceeb505ea 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.2.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 0691b8b88a..0a62a3ef3c 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.2.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 0c2f866da0..364faae643 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.2.Final + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 335d917363..8c6a55ebed 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.5.2.Final + 1.6.0-SNAPSHOT pom MapStruct Parent @@ -69,7 +69,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.5.2.Final + HEAD diff --git a/pom.xml b/pom.xml index 66b25f8d89..25a5ead6bc 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.5.2.Final + 1.6.0-SNAPSHOT parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.5.2.Final + HEAD diff --git a/processor/pom.xml b/processor/pom.xml index 20e8150f91..6f2bb6495c 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.5.2.Final + 1.6.0-SNAPSHOT ../parent/pom.xml From 07d144ebd1167fd4549835db0799413943091d4a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 18 Jun 2022 19:11:39 +0200 Subject: [PATCH 087/363] Update readme with latest released 1.5.2.Final release --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 0d6a93c781..e5811060a6 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.1.Final-blue.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.2.Final-blue.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final) [![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/master/LICENSE.txt) @@ -68,7 +68,7 @@ For Maven-based projects, add the following to your POM file in order to use Map ```xml ... - 1.5.1.Final + 1.5.2.Final ... @@ -114,10 +114,10 @@ plugins { dependencies { ... - compile 'org.mapstruct:mapstruct:1.5.1.Final' + compile 'org.mapstruct:mapstruct:1.5.2.Final' - annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.1.Final' - testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.1.Final' // if you are using mapstruct in test code + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final' + testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final' // if you are using mapstruct in test code } ... ``` From 88745d151ec60c7a0c2b8b840189c2db06fe053f Mon Sep 17 00:00:00 2001 From: Ben Zegveld Date: Thu, 9 Jun 2022 22:36:47 +0200 Subject: [PATCH 088/363] #2882: target type is now correctly passed on through the MethodReferencePresenceCheck to the MethodReference. --- .../model/MethodReferencePresenceCheck.ftl | 3 ++- .../ap/internal/model/macro/CommonMacros.ftl | 3 ++- .../basic/ConditionalMappingTest.java | 6 +++++ .../ConditionalMethodWithTargetType.java | 27 +++++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithTargetType.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl index 44ec296736..9a2837a02d 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl @@ -6,4 +6,5 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" --> -<@includeModel object=methodReference/> \ No newline at end of file +<@includeModel object=methodReference + targetType=ext.targetType/> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index 048402401b..6d05ddd131 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -15,7 +15,8 @@ --> <#macro handleSourceReferenceNullCheck> <#if sourcePresenceCheckerReference??> - if ( <@includeModel object=sourcePresenceCheckerReference /> ) { + if ( <@includeModel object=sourcePresenceCheckerReference + targetType=ext.targetType/> ) { <#nested> } <@elseDefaultAssignment/> diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java index 62fd79ae84..57a68a9526 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java @@ -50,6 +50,12 @@ public void conditionalMethodInMapper() { assertThat( employee.getName() ).isNull(); } + @IssueKey( "2882" ) + @ProcessorTest + @WithClasses( { ConditionalMethodWithTargetType.class } ) + public void conditionalMethodWithTargetTypeShouldCompile() { + } + @ProcessorTest @WithClasses({ ConditionalMethodAndBeanPresenceCheckMapper.class diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithTargetType.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithTargetType.java new file mode 100644 index 0000000000..5ec242d8ff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithTargetType.java @@ -0,0 +1,27 @@ +/* + * 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.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface ConditionalMethodWithTargetType { + + ConditionalMethodWithTargetType INSTANCE = Mappers.getMapper( ConditionalMethodWithTargetType.class ); + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @TargetType Class targetType) { + return value != null && !value.trim().isEmpty(); + } +} From de8c0c7070aabd10656c67088b5c7b52b92c954b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 18 Jun 2022 15:16:21 +0200 Subject: [PATCH 089/363] Use UTF-8 when compiling the tests The test infrastructure that we are using should use UTF-8 for generating the StandardJavaFileManager --- .../mapstruct/ap/testutil/runner/JdkCompilingExtension.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index 2a0b6ba537..7b44a4733b 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -7,6 +7,7 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -52,7 +53,7 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe String additionalCompilerClasspath) { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); DiagnosticCollector diagnostics = new DiagnosticCollector<>(); - StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); + StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, StandardCharsets.UTF_8 ); Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.getSourceClasses() ) ); From a2b4454a669759a9e423e2b85e803ef6d4ddf251 Mon Sep 17 00:00:00 2001 From: fml2 <534392+fml2@users.noreply.github.com> Date: Fri, 1 Jul 2022 17:18:46 +0200 Subject: [PATCH 090/363] fix(docs): No Lombok classes in the runtime --- .../asciidoc/chapter-14-third-party-api-integration.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc b/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc index 69bef46ea6..0f12d67e22 100644 --- a/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc +++ b/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc @@ -140,7 +140,7 @@ The set up using Maven or Gradle does not differ from what is described in < Date: Wed, 6 Jul 2022 14:39:21 +0200 Subject: [PATCH 091/363] #2922 Fix protobuf tests for M1 Macs --- .../src/test/resources/protobufBuilderTest/pom.xml | 6 +++--- parent/pom.xml | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/integrationtest/src/test/resources/protobufBuilderTest/pom.xml b/integrationtest/src/test/resources/protobufBuilderTest/pom.xml index ecbbf1d6d8..c0d8e2a81b 100644 --- a/integrationtest/src/test/resources/protobufBuilderTest/pom.xml +++ b/integrationtest/src/test/resources/protobufBuilderTest/pom.xml @@ -23,7 +23,7 @@ 1.6.0 - 0.5.1 + 0.6.1 @@ -55,10 +55,10 @@ compile-custom - com.google.protobuf:protoc:3.2.0:exe:${os.detected.classifier} + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.2.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.47.0:exe:${os.detected.classifier} diff --git a/parent/pom.xml b/parent/pom.xml index 8c6a55ebed..1c031a7b71 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -41,6 +41,7 @@ The processor module needs at least Java 11. --> 1.8 + 3.21.2 @@ -234,7 +235,7 @@ com.google.protobuf protobuf-java - 3.16.1 + ${protobuf.version} org.inferred From dd5ac3b637e20db014d89560e7467d09a65075e2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 8 Jul 2022 20:40:26 +0200 Subject: [PATCH 092/363] #2929 Improve documentation for `BeanMapping#ignoreByDefault` --- .../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index d1c63c25a2..ab33666435 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -40,6 +40,7 @@ The property name as defined in the http://www.oracle.com/technetwork/java/javas [TIP] ==== By means of the `@BeanMapping(ignoreByDefault = true)` the default behavior will be *explicit mapping*, meaning that all mappings have to be specified by means of the `@Mapping` and no warnings will be issued on missing target properties. +This allows to ignore all fields, except the ones that are explicitly defined through `@Mapping`. ==== [TIP] ==== From 691488951027b0b016f52928e746677a39201900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cassius=20Vinicius=20de=20Magalh=C3=A3es?= Date: Thu, 14 Jul 2022 14:38:18 -0300 Subject: [PATCH 093/363] Update Chapter 11.2 - Inverse Mappings Clarification of the inverse mapping usage. --- .../chapter-11-reusing-mapping-configurations.asciidoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-11-reusing-mapping-configurations.asciidoc b/documentation/src/main/asciidoc/chapter-11-reusing-mapping-configurations.asciidoc index fb8c71a622..efacaedbe5 100644 --- a/documentation/src/main/asciidoc/chapter-11-reusing-mapping-configurations.asciidoc +++ b/documentation/src/main/asciidoc/chapter-11-reusing-mapping-configurations.asciidoc @@ -46,6 +46,8 @@ In case of bi-directional mappings, e.g. from entity to DTO and from DTO to enti Use the annotation `@InheritInverseConfiguration` to indicate that a method shall inherit the inverse configuration of the corresponding reverse method. +In the example below, there is no need to write the inverse mapping manually. Think of a case where there are several mappings, so writing the inverse ones can be cumbersome and error prone. + .Inverse mapping method inheriting its configuration and ignoring some of them ==== [source, java, linenums] @@ -162,4 +164,4 @@ The attributes `@Mapper#mappingInheritanceStrategy()` / `@MapperConfig#mappingIn * `EXPLICIT` (default): the configuration will only be inherited, if the target mapping method is annotated with `@InheritConfiguration` and the source and target types are assignable to the corresponding types of the prototype method, all as described in <>. * `AUTO_INHERIT_FROM_CONFIG`: the configuration will be inherited automatically, if the source and target types of the target mapping method are assignable to the corresponding types of the prototype method. If multiple prototype methods match, the ambiguity must be resolved using `@InheritConfiguration(name = ...)` which will cause `AUTO_INHERIT_FROM_CONFIG` to be ignored. * `AUTO_INHERIT_REVERSE_FROM_CONFIG`: the inverse configuration will be inherited automatically, if the source and target types of the target mapping method are assignable to the corresponding types of the prototype method. If multiple prototype methods match, the ambiguity must be resolved using `@InheritInverseConfiguration(name = ...)` which will cause ``AUTO_INHERIT_REVERSE_FROM_CONFIG` to be ignored. -* `AUTO_INHERIT_ALL_FROM_CONFIG`: both the configuration and the inverse configuration will be inherited automatically. The same rules apply as for `AUTO_INHERIT_FROM_CONFIG` or `AUTO_INHERIT_REVERSE_FROM_CONFIG`. \ No newline at end of file +* `AUTO_INHERIT_ALL_FROM_CONFIG`: both the configuration and the inverse configuration will be inherited automatically. The same rules apply as for `AUTO_INHERIT_FROM_CONFIG` or `AUTO_INHERIT_REVERSE_FROM_CONFIG`. From 62e73464b2c3dd16c0c88a3d5bfebe7b710facec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Jul 2022 12:42:32 +0000 Subject: [PATCH 094/363] Bump kotlin-stdlib in /integrationtest/src/test/resources/kotlinDataTest Bumps [kotlin-stdlib](https://github.com/JetBrains/kotlin) from 1.3.70 to 1.6.0. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v1.6.0/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.3.70...v1.6.0) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-stdlib dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- integrationtest/src/test/resources/kotlinDataTest/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrationtest/src/test/resources/kotlinDataTest/pom.xml b/integrationtest/src/test/resources/kotlinDataTest/pom.xml index 8a43a04194..f86f92b9c4 100644 --- a/integrationtest/src/test/resources/kotlinDataTest/pom.xml +++ b/integrationtest/src/test/resources/kotlinDataTest/pom.xml @@ -22,7 +22,7 @@ jar - 1.3.70 + 1.6.0 From 8fa286fe4ce790cc2a65adff55c813bc9717437a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Iva=C4=8Di=C4=8D?= Date: Mon, 30 May 2022 12:33:47 +0200 Subject: [PATCH 095/363] #2688: Support accessing to the target property name --- .../main/java/org/mapstruct/Condition.java | 1 + .../org/mapstruct/TargetPropertyName.java | 25 ++ ...apter-10-advanced-mapping-options.asciidoc | 57 ++++ .../ap/internal/gem/GemGenerator.java | 2 + .../ap/internal/model/common/Parameter.java | 22 +- .../model/common/ParameterBinding.java | 28 +- .../internal/model/source/SourceMethod.java | 9 +- .../model/source/selector/TypeSelector.java | 16 +- .../ap/internal/model/MethodReference.ftl | 3 + .../model/MethodReferencePresenceCheck.ftl | 1 + .../ap/internal/model/PropertyMapping.ftl | 1 + .../ap/internal/model/TypeConversion.ftl | 1 + .../model/assignment/Java8FunctionWrapper.ftl | 1 + .../model/assignment/LocalVarWrapper.ftl | 1 + .../model/assignment/ReturnWrapper.ftl | 1 + .../ap/internal/model/macro/CommonMacros.ftl | 6 +- .../targetpropertyname/Address.java | 21 ++ .../targetpropertyname/AddressDto.java | 21 ++ ...ollectionMapperWithTargetPropertyName.java | 41 +++ ...onalMethodInMapperWithAllExceptTarget.java | 46 +++ ...nditionalMethodInMapperWithAllOptions.java | 52 ++++ ...lMethodInMapperWithTargetPropertyName.java | 32 ++ ...hodInUsesMapperWithTargetPropertyName.java | 36 +++ ...WithTargetPropertyNameInContextMapper.java | 89 ++++++ .../targetpropertyname/DomainModel.java | 15 + .../targetpropertyname/Employee.java | 99 ++++++ .../targetpropertyname/EmployeeDto.java | 98 ++++++ .../TargetPropertyNameTest.java | 281 ++++++++++++++++++ 28 files changed, 993 insertions(+), 13 deletions(-) create mode 100644 core/src/main/java/org/mapstruct/TargetPropertyName.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java diff --git a/core/src/main/java/org/mapstruct/Condition.java b/core/src/main/java/org/mapstruct/Condition.java index ec1bf06a88..148ec4879d 100644 --- a/core/src/main/java/org/mapstruct/Condition.java +++ b/core/src/main/java/org/mapstruct/Condition.java @@ -23,6 +23,7 @@ * e.g. the value given by calling {@code getName()} for the name property of the source bean *
    • The mapping source parameter
    • *
    • {@code @}{@link Context} parameter
    • + *
    • {@code @}{@link TargetPropertyName} parameter
    • *
    * * Note: The usage of this annotation is mandatory diff --git a/core/src/main/java/org/mapstruct/TargetPropertyName.java b/core/src/main/java/org/mapstruct/TargetPropertyName.java new file mode 100644 index 0000000000..17af966fa8 --- /dev/null +++ b/core/src/main/java/org/mapstruct/TargetPropertyName.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation marks a presence check method parameter as a property name parameter. + *

    + * This parameter enables conditional filtering based on target property name at run-time. + * Parameter must be of type {@link String} and can be present only in {@link Condition} method. + *

    + * @author Nikola Ivačič + * @since 1.6 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.CLASS) +public @interface TargetPropertyName { +} diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 1cc19fcc3e..decee792d0 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -404,6 +404,61 @@ public class CarMapperImpl implements CarMapper { ---- ==== +Additionally `@TargetPropertyName` of type `java.lang.String` can be used in custom condition check method: + +.Mapper using custom condition check method with `@TargetPropertyName` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + CarDto carToCarDto(Car car, @MappingTarget CarDto carDto); + + @Condition + default boolean isNotEmpty(String value, @TargetPropertyName String name) { + if ( name.equals( "owner" ) { + return value != null + && !value.isEmpty() + && !value.equals( value.toLowerCase() ); + } + return value != null && !value.isEmpty(); + } +} +---- +==== + +The generated mapper with `@TargetPropertyName` will look like: + +.Custom condition check in generated implementation +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class CarMapperImpl implements CarMapper { + + @Override + public CarDto carToCarDto(Car car, CarDto carDto) { + if ( car == null ) { + return carDto; + } + + if ( isNotEmpty( car.getOwner(), "owner" ) ) { + carDto.setOwner( car.getOwner() ); + } else { + carDto.setOwner( null ); + } + + // Mapping of other properties + + return carDto; + } +} +---- +==== + [IMPORTANT] ==== If there is a custom `@Condition` method applicable for the property it will have a precedence over a presence check method in the bean itself. @@ -412,6 +467,8 @@ If there is a custom `@Condition` method applicable for the property it will hav [NOTE] ==== Methods annotated with `@Condition` in addition to the value of the source property can also have the source parameter as an input. + +`@TargetPropertyName` parameter can only be used in `@Condition` methods. ==== <> is also valid for `@Condition` methods. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index e0491cbc4e..70098c952e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -30,6 +30,7 @@ import org.mapstruct.Qualifier; import org.mapstruct.SubclassMapping; import org.mapstruct.SubclassMappings; +import org.mapstruct.TargetPropertyName; import org.mapstruct.TargetType; import org.mapstruct.ValueMapping; import org.mapstruct.ValueMappings; @@ -52,6 +53,7 @@ @GemDefinition(SubclassMapping.class) @GemDefinition(SubclassMappings.class) @GemDefinition(TargetType.class) +@GemDefinition(TargetPropertyName.class) @GemDefinition(MappingTarget.class) @GemDefinition(DecoratedWith.class) @GemDefinition(MapperConfig.class) 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 dc62018be5..600c8ac337 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 @@ -15,6 +15,7 @@ import org.mapstruct.ap.internal.gem.ContextGem; import org.mapstruct.ap.internal.gem.MappingTargetGem; import org.mapstruct.ap.internal.gem.TargetTypeGem; +import org.mapstruct.ap.internal.gem.TargetPropertyNameGem; import org.mapstruct.ap.internal.util.Collections; /** @@ -31,6 +32,7 @@ public class Parameter extends ModelElement { private final boolean mappingTarget; private final boolean targetType; private final boolean mappingContext; + private final boolean targetPropertyName; private final boolean varArgs; @@ -42,10 +44,12 @@ private Parameter(Element element, Type type, boolean varArgs) { this.mappingTarget = MappingTargetGem.instanceOn( element ) != null; this.targetType = TargetTypeGem.instanceOn( element ) != null; this.mappingContext = ContextGem.instanceOn( element ) != null; + this.targetPropertyName = TargetPropertyNameGem.instanceOn( element ) != null; this.varArgs = varArgs; } private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext, + boolean targetPropertyName, boolean varArgs) { this.element = null; this.name = name; @@ -54,11 +58,12 @@ private Parameter(String name, Type type, boolean mappingTarget, boolean targetT this.mappingTarget = mappingTarget; this.targetType = targetType; this.mappingContext = mappingContext; + this.targetPropertyName = targetPropertyName; this.varArgs = varArgs; } public Parameter(String name, Type type) { - this( name, type, false, false, false, false ); + this( name, type, false, false, false, false, false ); } public Element getElement() { @@ -94,6 +99,7 @@ private String format() { return ( mappingTarget ? "@MappingTarget " : "" ) + ( targetType ? "@TargetType " : "" ) + ( mappingContext ? "@Context " : "" ) + + ( targetPropertyName ? "@TargetPropertyName " : "" ) + "%s " + name; } @@ -110,6 +116,10 @@ public boolean isMappingContext() { return mappingContext; } + public boolean isTargetPropertyName() { + return targetPropertyName; + } + public boolean isVarArgs() { return varArgs; } @@ -154,6 +164,7 @@ public static Parameter forForgedMappingTarget(Type parameterType) { true, false, false, + false, false ); } @@ -195,8 +206,15 @@ public static Parameter getTargetTypeParameter(List parameters) { return parameters.stream().filter( Parameter::isTargetType ).findAny().orElse( null ); } + public static Parameter getTargetPropertyNameParameter(List parameters) { + return parameters.stream().filter( Parameter::isTargetPropertyName ).findAny().orElse( null ); + } + private static boolean isSourceParameter( Parameter parameter ) { - return !parameter.isMappingTarget() && !parameter.isTargetType() && !parameter.isMappingContext(); + return !parameter.isMappingTarget() && + !parameter.isTargetType() && + !parameter.isMappingContext() && + !parameter.isTargetPropertyName(); } } 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 36e4d3e864..18274e5492 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 @@ -22,15 +22,17 @@ public class ParameterBinding { private final boolean targetType; private final boolean mappingTarget; private final boolean mappingContext; + private final boolean targetPropertyName; private final SourceRHS sourceRHS; private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType, - boolean mappingContext, SourceRHS sourceRHS) { + boolean mappingContext, boolean targetPropertyName, SourceRHS sourceRHS) { this.type = parameterType; this.variableName = variableName; this.targetType = targetType; this.mappingTarget = mappingTarget; this.mappingContext = mappingContext; + this.targetPropertyName = targetPropertyName; this.sourceRHS = sourceRHS; } @@ -62,6 +64,13 @@ public boolean isMappingContext() { return mappingContext; } + /** + * @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter. + */ + public boolean isTargetPropertyName() { + return targetPropertyName; + } + /** * @return the type of the parameter that is bound */ @@ -99,6 +108,7 @@ public static ParameterBinding fromParameter(Parameter parameter) { parameter.isMappingTarget(), parameter.isTargetType(), parameter.isMappingContext(), + parameter.isTargetPropertyName(), null ); } @@ -118,6 +128,7 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame false, false, false, + false, null ); } @@ -127,7 +138,14 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame * @return a parameter binding representing a target type parameter */ public static ParameterBinding forTargetTypeBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, true, false, null ); + return new ParameterBinding( classTypeOf, null, false, true, false, false, null ); + } + + /** + * @return a parameter binding representing a target property name parameter + */ + public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) { + return new ParameterBinding( classTypeOf, null, false, false, false, true, null ); } /** @@ -135,7 +153,7 @@ public static ParameterBinding forTargetTypeBinding(Type classTypeOf) { * @return a parameter binding representing a mapping target parameter */ public static ParameterBinding forMappingTargetBinding(Type resultType) { - return new ParameterBinding( resultType, null, true, false, false, null ); + return new ParameterBinding( resultType, null, true, false, false, false, null ); } /** @@ -143,10 +161,10 @@ public static ParameterBinding forMappingTargetBinding(Type resultType) { * @return a parameter binding representing a mapping source type */ public static ParameterBinding forSourceTypeBinding(Type sourceType) { - return new ParameterBinding( sourceType, null, false, false, false, null ); + return new ParameterBinding( sourceType, null, false, false, false, false, null ); } public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) { - return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, sourceRHS ); + return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, sourceRHS ); } } 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 92180bca8a..37b57d9025 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 @@ -47,6 +47,7 @@ public class SourceMethod implements Method { private final List parameters; private final Parameter mappingTargetParameter; private final Parameter targetTypeParameter; + private final Parameter targetPropertyNameParameter; private final boolean isObjectFactory; private final boolean isPresenceCheck; private final Type returnType; @@ -248,6 +249,7 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters ); + this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters ); this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null; this.isObjectFactory = determineIfIsObjectFactory(); this.isPresenceCheck = determineIfIsPresenceCheck(); @@ -263,8 +265,9 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) private boolean determineIfIsObjectFactory() { boolean hasNoSourceParameters = getSourceParameters().isEmpty(); boolean hasNoMappingTargetParam = getMappingTargetParameter() == null; + boolean hasNoTargetPropertyNameParam = getTargetPropertyNameParameter() == null; return !isLifecycleCallbackMethod() && !returnType.isVoid() - && hasNoMappingTargetParam + && hasNoMappingTargetParam && hasNoTargetPropertyNameParam && ( hasObjectFactoryAnnotation || hasNoSourceParameters ); } @@ -379,6 +382,10 @@ public Parameter getTargetTypeParameter() { return targetTypeParameter; } + public Parameter getTargetPropertyNameParameter() { + return targetPropertyNameParameter; + } + public boolean isIterableMapping() { if ( isIterableMapping == null ) { isIterableMapping = getSourceParameters().size() == 1 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 74e518c84d..8acdd327c4 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 @@ -96,7 +96,7 @@ private List getAvailableParameterBindingsFromMethod(Method me availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); } - addMappingTargetAndTargetTypeBindings( availableParams, targetType ); + addTargetRelevantBindings( availableParams, targetType ); return availableParams; } @@ -116,7 +116,7 @@ private List getAvailableParameterBindingsFromSourceTypes(List } } - addMappingTargetAndTargetTypeBindings( availableParams, targetType ); + addTargetRelevantBindings( availableParams, targetType ); return availableParams; } @@ -127,9 +127,10 @@ private List getAvailableParameterBindingsFromSourceTypes(List * @param availableParams Already available params, new entries will be added to this list * @param targetType Target type */ - private void addMappingTargetAndTargetTypeBindings(List availableParams, Type targetType) { + private void addTargetRelevantBindings(List availableParams, Type targetType) { boolean mappingTargetAvailable = false; boolean targetTypeAvailable = false; + boolean targetPropertyNameAvailable = false; // search available parameter bindings if mapping-target and/or target-type is available for ( ParameterBinding pb : availableParams ) { @@ -139,6 +140,9 @@ private void addMappingTargetAndTargetTypeBindings(List availa else if ( pb.isTargetType() ) { targetTypeAvailable = true; } + else if ( pb.isTargetPropertyName() ) { + targetPropertyNameAvailable = true; + } } if ( !mappingTargetAvailable ) { @@ -147,6 +151,9 @@ else if ( pb.isTargetType() ) { if ( !targetTypeAvailable ) { availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) ); } + if ( !targetPropertyNameAvailable ) { + availableParams.add( ParameterBinding.forTargetPropertyNameBinding( typeFactory.getType( String.class ) ) ); + } } private SelectedMethod getMatchingParameterBinding(Type returnType, @@ -301,7 +308,8 @@ private static List findCandidateBindingsForParameter(List${ext.targetBeanName}<#else>${param.variableName}<#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}<#t> <#elseif param.mappingContext> ${param.variableName}<#t> + <#elseif param.targetPropertyName> + "${ext.targetPropertyName}"<#t> <#elseif param.sourceRHS??> <@_assignment assignmentToUse=param.sourceRHS/><#t> <#elseif assignment??> @@ -66,5 +68,6 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName targetType=singleSourceParameterType/> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl index 9a2837a02d..24f871e214 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl @@ -7,4 +7,5 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" --> <@includeModel object=methodReference + targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl index 20e6f1734c..e6aef4cecd 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl @@ -11,5 +11,6 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=targetReadAccessorName targetWriteAccessorName=targetWriteAccessorName + targetPropertyName=name targetType=targetType defaultValueAssignment=defaultValueAssignment /> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl index 370fe7c978..4a9d356ce4 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl @@ -14,6 +14,7 @@ ${openExpression}<@_assignment/>${closeExpression} existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl index 2795dac7bc..ce1fcdcd6c 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl @@ -34,5 +34,6 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl index 53e987e476..97ea856ece 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl @@ -25,5 +25,6 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl index 9f46f3604f..9917dbacf8 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl @@ -13,5 +13,6 @@ return <@_assignment/>; existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index 6d05ddd131..e5fb970605 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -16,6 +16,7 @@ <#macro handleSourceReferenceNullCheck> <#if sourcePresenceCheckerReference??> if ( <@includeModel object=sourcePresenceCheckerReference + targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> ) { <#nested> } @@ -58,7 +59,8 @@ --> <#macro handleLocalVarNullCheck needs_explicit_local_var> <#if sourcePresenceCheckerReference??> - if ( <@includeModel object=sourcePresenceCheckerReference /> ) { + if ( <@includeModel object=sourcePresenceCheckerReference + targetPropertyName=ext.targetPropertyName/> ) { <#if needs_explicit_local_var> <@includeModel object=nullCheckLocalVarType/> ${nullCheckLocalVarName} = <@lib.handleAssignment/>; <#nested> @@ -113,6 +115,7 @@ Performs a standard assignment. existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> <#-- @@ -124,6 +127,7 @@ Performs a default assignment with a default value. existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName targetType=ext.targetType defaultValue=ext.defaultValue/> diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java new file mode 100644 index 0000000000..162ed118bb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java @@ -0,0 +1,21 @@ +/* + * 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.conditional.targetpropertyname; + +/** + * @author Nikola Ivačič + */ +public class Address implements DomainModel { + private String street; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java new file mode 100644 index 0000000000..f4cbc71915 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java @@ -0,0 +1,21 @@ +/* + * 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.conditional.targetpropertyname; + +/** + * @author Nikola Ivačič + */ +public class AddressDto implements DomainModel { + private final String street; + + public AddressDto(String street) { + this.street = street; + } + + public String getStreet() { + return street; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java new file mode 100644 index 0000000000..71baa0942b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java @@ -0,0 +1,41 @@ +/* + * 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.conditional.targetpropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.factory.Mappers; + +import java.util.Collection; + +/** + * @author Nikola Ivačič + */ +@Mapper +public interface ConditionalMethodForCollectionMapperWithTargetPropertyName { + + ConditionalMethodForCollectionMapperWithTargetPropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodForCollectionMapperWithTargetPropertyName.class ); + + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotEmpty(Collection collection, @TargetPropertyName String propName) { + if ( "addresses".equalsIgnoreCase( propName ) ) { + return false; + } + return collection != null && !collection.isEmpty(); + } + + @Condition + default boolean isNotBlank(String value, @TargetPropertyName String propName) { + if ( propName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java new file mode 100644 index 0000000000..430303d06d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java @@ -0,0 +1,46 @@ +/* + * 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.conditional.targetpropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.factory.Mappers; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + */ +@Mapper +public interface ConditionalMethodInMapperWithAllExceptTarget { + + ConditionalMethodInMapperWithAllExceptTarget INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllExceptTarget.class ); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + } + + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @TargetPropertyName String propName, + @Context PresenceUtils utils) { + utils.visited.add( propName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + if ( propName.equalsIgnoreCase( "firstName" ) ) { + return true; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java new file mode 100644 index 0000000000..a18ba0db73 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java @@ -0,0 +1,52 @@ +/* + * 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.conditional.targetpropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.factory.Mappers; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + */ +@Mapper +public interface ConditionalMethodInMapperWithAllOptions { + + ConditionalMethodInMapperWithAllOptions INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class ); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + Set visitedTargets = new LinkedHashSet<>(); + } + + void map(EmployeeDto employeeDto, + @MappingTarget Employee employee, + @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @MappingTarget DomainModel target, + @TargetPropertyName String propName, + @Context PresenceUtils utils) { + utils.visited.add( propName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + utils.visitedTargets.add( target.getClass().getSimpleName() ); + if ( propName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java new file mode 100644 index 0000000000..6d27bc9036 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java @@ -0,0 +1,32 @@ +/* + * 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.conditional.targetpropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + */ +@Mapper +public interface ConditionalMethodInMapperWithTargetPropertyName { + + ConditionalMethodInMapperWithTargetPropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithTargetPropertyName.class ); + + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @TargetPropertyName String propName) { + if ( propName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java new file mode 100644 index 0000000000..7c526884e9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java @@ -0,0 +1,36 @@ +/* + * 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.conditional.targetpropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + */ +@Mapper(uses = ConditionalMethodInUsesMapperWithTargetPropertyName.PresenceUtils.class) +public interface ConditionalMethodInUsesMapperWithTargetPropertyName { + + ConditionalMethodInUsesMapperWithTargetPropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInUsesMapperWithTargetPropertyName.class ); + + Employee map(EmployeeDto employee); + + class PresenceUtils { + + @Condition + public boolean isNotBlank(String value, @TargetPropertyName String propName) { + if ( propName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java new file mode 100644 index 0000000000..a7cde220c9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java @@ -0,0 +1,89 @@ +/* + * 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.conditional.targetpropertyname; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.factory.Mappers; + +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Set; + +/** + * @author Nikola Ivačič + */ +@Mapper +public interface ConditionalMethodWithTargetPropertyNameInContextMapper { + + ConditionalMethodWithTargetPropertyNameInContextMapper INSTANCE + = Mappers.getMapper( ConditionalMethodWithTargetPropertyNameInContextMapper.class ); + + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + Address map(AddressDto addressDto, @Context PresenceUtils utils); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean isNotBlank(String value, @TargetPropertyName String propName) { + visited.add( propName ); + return value != null && !value.trim().isEmpty(); + } + } + + Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils); + + class PresenceUtilsAllProps { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean collect(@TargetPropertyName String propName) { + visited.add( propName ); + return true; + } + } + + Employee map(EmployeeDto employee, @Context PresenceUtilsAllPropsWithSource utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllPropsWithSource utils); + + @BeforeMapping + default void before(DomainModel source, @Context PresenceUtilsAllPropsWithSource utils) { + String lastProp = utils.visitedSegments.peekLast(); + if ( lastProp != null && source != null ) { + utils.path.offerLast( lastProp ); + } + } + + @AfterMapping + default void after(@Context PresenceUtilsAllPropsWithSource utils) { + utils.path.pollLast(); + } + + class PresenceUtilsAllPropsWithSource { + Deque visitedSegments = new LinkedList<>(); + Deque visited = new LinkedList<>(); + Deque path = new LinkedList<>(); + + @Condition + public boolean collect(@TargetPropertyName String propName) { + visitedSegments.offerLast( propName ); + path.offerLast( propName ); + visited.offerLast( String.join( ".", path ) ); + path.pollLast(); + return true; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java new file mode 100644 index 0000000000..4d2c716a96 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java @@ -0,0 +1,15 @@ +/* + * 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.conditional.targetpropertyname; + +/** + * Target Property Name test entities + * + * @author Nikola Ivačič + */ +public interface DomainModel { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java new file mode 100644 index 0000000000..59ee3426e1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java @@ -0,0 +1,99 @@ +/* + * 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.conditional.targetpropertyname; + +import java.util.List; + +/** + * @author Nikola Ivačič + */ +public class Employee implements DomainModel { + + private String firstName; + private String lastName; + private String title; + private String country; + private boolean active; + private int age; + + private Employee boss; + + private Address primaryAddress; + + private List
    addresses; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public Employee getBoss() { + return boss; + } + + public void setBoss(Employee boss) { + this.boss = boss; + } + + public Address getPrimaryAddress() { + return primaryAddress; + } + + public void setPrimaryAddress(Address primaryAddress) { + this.primaryAddress = primaryAddress; + } + + public List
    getAddresses() { + return addresses; + } + + public void setAddresses(List
    addresses) { + this.addresses = addresses; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java new file mode 100644 index 0000000000..5d81a9334b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java @@ -0,0 +1,98 @@ +/* + * 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.conditional.targetpropertyname; + +import java.util.List; + +/** + * @author Nikola Ivačič + */ +public class EmployeeDto implements DomainModel { + + private String firstName; + private String lastName; + private String title; + private String country; + private boolean active; + private int age; + + private EmployeeDto boss; + + private AddressDto primaryAddress; + private List addresses; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public EmployeeDto getBoss() { + return boss; + } + + public void setBoss(EmployeeDto boss) { + this.boss = boss; + } + + public AddressDto getPrimaryAddress() { + return primaryAddress; + } + + public void setPrimaryAddress(AddressDto primaryAddress) { + this.primaryAddress = primaryAddress; + } + + public List getAddresses() { + return addresses; + } + + public void setAddresses(List addresses) { + this.addresses = addresses; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java new file mode 100644 index 0000000000..3d96e66664 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java @@ -0,0 +1,281 @@ +/* + * 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.conditional.targetpropertyname; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + */ +@IssueKey("2051") +@WithClasses({ + Address.class, + AddressDto.class, + Employee.class, + EmployeeDto.class, + DomainModel.class +}) +public class TargetPropertyNameTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithTargetPropertyName.class + }) + public void conditionalMethodInMapperWithTargetPropertyName() { + ConditionalMethodInMapperWithTargetPropertyName mapper + = ConditionalMethodInMapperWithTargetPropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setCountry( "US" ); + employeeDto.setAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodForCollectionMapperWithTargetPropertyName.class + }) + public void conditionalMethodForCollectionMapperWithTargetPropertyName() { + ConditionalMethodForCollectionMapperWithTargetPropertyName mapper + = ConditionalMethodForCollectionMapperWithTargetPropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setCountry( "US" ); + employeeDto.setAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInUsesMapperWithTargetPropertyName.class + }) + public void conditionalMethodInUsesMapperWithTargetPropertyName() { + ConditionalMethodInUsesMapperWithTargetPropertyName mapper + = ConditionalMethodInUsesMapperWithTargetPropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setCountry( "US" ); + employeeDto.setAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllOptions.class + }) + public void conditionalMethodInMapperWithAllOptions() { + ConditionalMethodInMapperWithAllOptions mapper + = ConditionalMethodInMapperWithAllOptions.INSTANCE; + + ConditionalMethodInMapperWithAllOptions.PresenceUtils utils = + new ConditionalMethodInMapperWithAllOptions.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setCountry( "US" ); + employeeDto.setAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = new Employee(); + mapper.map( employeeDto, employee, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country" ); + assertThat( utils.visitedSources ).containsExactly( "EmployeeDto" ); + assertThat( utils.visitedTargets ).containsExactly( "Employee" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllExceptTarget.class + }) + public void conditionalMethodInMapperWithAllExceptTarget() { + ConditionalMethodInMapperWithAllExceptTarget mapper + = ConditionalMethodInMapperWithAllExceptTarget.INSTANCE; + + ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils utils = + new ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setCountry( "US" ); + employeeDto.setAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isEqualTo( " " ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country", "street" ); + assertThat( utils.visitedSources ).containsExactlyInAnyOrder( "EmployeeDto", "AddressDto" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithTargetPropertyNameInContextMapper.class + }) + public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { + ConditionalMethodWithTargetPropertyNameInContextMapper mapper + = ConditionalMethodWithTargetPropertyNameInContextMapper.INSTANCE; + + ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtils utils = + new ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setLastName( " " ); + employeeDto.setCountry( "US" ); + employeeDto.setAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country", "street" ); + + ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllProps allPropsUtils = + new ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllProps(); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setCountry( "US" ); + employeeDto.setAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtils ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( allPropsUtils.visited ) + .containsExactlyInAnyOrder( + "firstName", + "lastName", + "title", + "country", + "active", + "age", + "boss", + "primaryAddress", + "addresses", + "street" + ); + + ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllPropsWithSource allPropsUtilsWithSource = + new ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllPropsWithSource(); + + EmployeeDto bossEmployeeDto = new EmployeeDto(); + bossEmployeeDto.setLastName( "Boss Tester" ); + bossEmployeeDto.setCountry( "US" ); + bossEmployeeDto.setAddresses( Collections.singletonList( new AddressDto( + "Testing St. 10" ) ) ); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setCountry( "US" ); + employeeDto.setBoss( bossEmployeeDto ); + employeeDto.setAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtilsWithSource ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ).isNotEmpty(); + assertThat( employee.getAddresses().get( 0 ).getStreet() ).isEqualTo( "Testing St. 6" ); + assertThat( employee.getBoss() ).isNotNull(); + assertThat( employee.getBoss().getCountry() ).isEqualTo( "US" ); + assertThat( employee.getBoss().getLastName() ).isEqualTo( "Boss Tester" ); + assertThat( employee.getBoss().getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 10" ); + assertThat( allPropsUtilsWithSource.visited ) + .containsExactly( + "firstName", + "lastName", + "title", + "country", + "active", + "age", + "boss", + "boss.firstName", + "boss.lastName", + "boss.title", + "boss.country", + "boss.active", + "boss.age", + "boss.boss", + "boss.primaryAddress", + "boss.addresses", + "boss.addresses.street", + "primaryAddress", + "addresses", + "addresses.street" + ); + } +} From 849085e02647d5d0cc63c9c2da98c8e959f4e6e2 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sat, 20 Aug 2022 12:59:38 +0200 Subject: [PATCH 096/363] #1574: Support for annotating the generated code with custom annotations Add new `@AnnotateWith` annotation. This annotation can be used to instruct the MapStruct processor to generate custom annotations in the generated code. --- .../main/java/org/mapstruct/AnnotateWith.java | 176 +++++ .../java/org/mapstruct/AnnotateWiths.java | 31 + .../src/main/java/org/mapstruct/NullEnum.java | 15 + .../chapter-3-defining-a-mapper.asciidoc | 40 ++ .../ap/internal/gem/GemGenerator.java | 5 + .../model/AdditionalAnnotationsBuilder.java | 602 ++++++++++++++++++ .../ap/internal/model/Annotation.java | 18 +- .../ap/internal/model/BeanMappingMethod.java | 15 + .../ap/internal/model/GeneratedType.java | 7 +- .../mapstruct/ap/internal/model/Mapper.java | 11 +- .../model/annotation/AnnotationElement.java | 128 ++++ .../EnumAnnotationElementHolder.java | 35 + .../processor/JakartaComponentProcessor.java | 7 +- .../processor/Jsr330ComponentProcessor.java | 8 +- .../processor/MapperCreationProcessor.java | 6 + .../processor/MethodRetrievalProcessor.java | 198 ++---- .../processor/SpringComponentProcessor.java | 8 +- .../mapstruct/ap/internal/util/Message.java | 14 + .../internal/util/RepeatableAnnotations.java | 120 ++++ .../ap/internal/model/Annotation.ftl | 2 +- .../ap/internal/model/BeanMappingMethod.ftl | 3 + .../model/annotation/AnnotationElement.ftl | 48 ++ .../EnumAnnotationElementHolder.ftl | 9 + .../test/annotatewith/AnnotateWithEnum.java | 10 + .../test/annotatewith/AnnotateWithTest.java | 548 ++++++++++++++++ .../AnnotationWithRequiredParameter.java | 19 + .../AnnotationWithoutElementNameMapper.java | 15 + .../annotatewith/ClassMetaAnnotation.java | 20 + .../test/annotatewith/CustomAnnotation.java | 19 + .../CustomAnnotationOnlyAnnotation.java | 18 + .../CustomAnnotationWithParams.java | 64 ++ .../CustomAnnotationWithParamsContainer.java | 19 + ...omAnnotationWithTwoAnnotationElements.java | 20 + .../CustomClassOnlyAnnotation.java | 18 + .../CustomMethodOnlyAnnotation.java | 18 + .../CustomNamedGenericClassMapper.java | 22 + .../test/annotatewith/CustomNamedMapper.java | 40 ++ .../CustomRepeatableAnnotation.java | 21 + .../CustomRepeatableAnnotationContainer.java | 19 + .../DeprecateAndCustomMapper.java | 19 + ...oneousMapperWithAnnotationOnlyOnClass.java | 18 + ...usMapperWithAnnotationOnlyOnInterface.java | 18 + .../ErroneousMapperWithClassOnMethod.java | 27 + .../ErroneousMapperWithMethodOnClass.java | 18 + .../ErroneousMapperWithMethodOnInterface.java | 18 + .../ErroneousMapperWithMissingEnumClass.java | 22 + .../ErroneousMapperWithMissingEnums.java | 21 + .../ErroneousMapperWithMissingParameter.java | 18 + .../ErroneousMapperWithNonExistantEnum.java | 22 + .../ErroneousMapperWithParameterRepeat.java | 22 + ...erWithRepeatOfNotRepeatableAnnotation.java | 19 + ...neousMapperWithTooManyParameterValues.java | 21 + .../ErroneousMapperWithUnknownParameter.java | 21 + .../ErroneousMapperWithWrongParameter.java | 45 ++ .../ErroneousMultipleArrayValuesMapper.java | 31 + ...MapperWithIdenticalAnnotationRepeated.java | 19 + ...apperWithMissingAnnotationElementName.java | 21 + .../MapperWithRepeatableAnnotation.java | 19 + .../annotatewith/MetaAnnotatedMapper.java | 14 + .../annotatewith/MethodMetaAnnotation.java | 20 + .../MultipleArrayValuesMapper.java | 31 + .../annotatewith/WrongAnnotateWithEnum.java | 10 + .../annotatewith/CustomNamedMapperImpl.java | 17 + .../MultipleArrayValuesMapperImpl.java | 18 + 64 files changed, 2774 insertions(+), 151 deletions(-) create mode 100644 core/src/main/java/org/mapstruct/AnnotateWith.java create mode 100644 core/src/main/java/org/mapstruct/AnnotateWiths.java create mode 100644 core/src/main/java/org/mapstruct/NullEnum.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/AnnotationElement.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.ftl create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithEnum.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithRequiredParameter.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithoutElementNameMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ClassMetaAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationOnlyAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParams.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParamsContainer.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithTwoAnnotationElements.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomClassOnlyAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomMethodOnlyAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedGenericClassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotationContainer.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/DeprecateAndCustomMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnClass.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnInterface.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnClass.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnInterface.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnumClass.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnums.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingParameter.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithNonExistantEnum.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithParameterRepeat.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithRepeatOfNotRepeatableAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithTooManyParameterValues.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithUnknownParameter.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithWrongParameter.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMultipleArrayValuesMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithIdenticalAnnotationRepeated.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithMissingAnnotationElementName.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithRepeatableAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MetaAnnotatedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MethodMetaAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/WrongAnnotateWithEnum.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java diff --git a/core/src/main/java/org/mapstruct/AnnotateWith.java b/core/src/main/java/org/mapstruct/AnnotateWith.java new file mode 100644 index 0000000000..090a7bc0d8 --- /dev/null +++ b/core/src/main/java/org/mapstruct/AnnotateWith.java @@ -0,0 +1,176 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * This can be used to have mapstruct generate additional annotations on classes/methods. + *

    + * Examples based on the spring framework annotations. + *

    + * Marking a class as `Lazy`: + * + *
    
    + * @AnnotateWith( value = Lazy.class )
    + * @Mapper
    + * public interface FooMapper {
    + *     // mapper code
    + * }
    + * 
    + * + * The following code would be generated: + * + *
    
    + * @Lazy
    + * public class FooMapperImpl implements FooMapper {
    + *     // mapper code
    + * }
    + * 
    + * Setting the profile on the generated implementation: + * + *
    
    + * @AnnotateWith( value = Profile.class, elements = @AnnotateWith.Element( strings = "prod" ) )
    + * @Mapper
    + * public interface FooMapper {
    + *     // mapper code
    + * }
    + * 
    + * + * The following code would be generated: + * + *
    
    + * @Profile( value = "prod" )
    + * public class FooMapperImpl implements FooMapper {
    + *     // mapper code
    + * }
    + * 
    + * + * @author Ben Zegveld + * @since 1.6 + */ +@Repeatable( AnnotateWiths.class ) +@Retention( CLASS ) +@Target( { TYPE, METHOD, ANNOTATION_TYPE } ) +public @interface AnnotateWith { + /** + * @return the annotation class that needs to be added. + */ + Class value(); + + /** + * @return the annotation elements that are to be applied to this annotation. + */ + Element[] elements() default {}; + + /** + * Used in combination with {@link AnnotateWith} to configure the annotation elements. Only 1 value type may be used + * within the same annotation at a time. For example mixing shorts and ints is not allowed. + * + * @author Ben Zegveld + * @since 1.6 + */ + @interface Element { + /** + * @return name of the annotation element. + */ + String name() default "value"; + + /** + * cannot be used in conjunction with other value fields. + * + * @return short value(s) for the annotation element. + */ + short[] shorts() default {}; + + /** + * cannot be used in conjunction with other value fields. + * + * @return byte value(s) for the annotation element. + */ + byte[] bytes() default {}; + + /** + * cannot be used in conjunction with other value fields. + * + * @return int value(s) for the annotation element. + */ + int[] ints() default {}; + + /** + * cannot be used in conjunction with other value fields. + * + * @return long value(s) for the annotation element. + */ + long[] longs() default {}; + + /** + * cannot be used in conjunction with other value fields. + * + * @return float value(s) for the annotation element. + */ + float[] floats() default {}; + + /** + * cannot be used in conjunction with other value fields. + * + * @return double value(s) for the annotation element. + */ + double[] doubles() default {}; + + /** + * cannot be used in conjunction with other value fields. + * + * @return char value(s) for the annotation element. + */ + char[] chars() default {}; + + /** + * cannot be used in conjunction with other value fields. + * + * @return boolean value(s) for the annotation element. + */ + boolean[] booleans() default {}; + + /** + * cannot be used in conjunction with other value fields. + * + * @return string value(s) for the annotation element. + */ + String[] strings() default {}; + + /** + * cannot be used in conjunction with other value fields. + * + * @return class value(s) for the annotation element. + */ + Class[] classes() default {}; + + /** + * only used in conjunction with the {@link #enums()} annotation element. + * + * @return the class of the enum. + */ + Class> enumClass() default NullEnum.class; + + /** + * cannot be used in conjunction with other value fields. {@link #enumClass()} is also required when using + * {@link #enums()} + * + * @return enum value(s) for the annotation element. + */ + String[] enums() default {}; + + } +} diff --git a/core/src/main/java/org/mapstruct/AnnotateWiths.java b/core/src/main/java/org/mapstruct/AnnotateWiths.java new file mode 100644 index 0000000000..5e86ad9a19 --- /dev/null +++ b/core/src/main/java/org/mapstruct/AnnotateWiths.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * This can be used to have mapstruct generate additional annotations on classes/methods. + * + * @author Ben Zegveld + * @since 1.6 + */ +@Retention( CLASS ) +@Target( { TYPE, METHOD } ) +public @interface AnnotateWiths { + + /** + * The configuration of the additional annotations. + * + * @return The configuration of the additional annotations. + */ + AnnotateWith[] value(); +} diff --git a/core/src/main/java/org/mapstruct/NullEnum.java b/core/src/main/java/org/mapstruct/NullEnum.java new file mode 100644 index 0000000000..ac39b3485d --- /dev/null +++ b/core/src/main/java/org/mapstruct/NullEnum.java @@ -0,0 +1,15 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +/** + * To be used as a default value for enum class annotation elements. + * + * @author Ben Zegveld + * @since 1.6 + */ +enum NullEnum { +} diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index ab33666435..e4a77f5391 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -721,3 +721,43 @@ i.e. You can map from `Map` where for each property a conversio When a raw map or a map that does not have a String as a key is used, then a warning will be generated. The warning is not generated if the map itself is mapped into some other target property directly as is. ==== + +[[adding-annotations]] +=== Adding annotations + +Other frameworks sometimes requires you to add annotations to certain classes so that they can easily detect the mappers. +Using the `@AnnotateWith` annotation you can generate an annotation at the specified location. + +For example Apache Camel has a `@Converter` annotation which you can apply to generated mappers using the `@AnnotateWith` annotation. + +.AnnotateWith source example +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +@AnnotateWith( + value = Converter.class, + elements = @AnnotateWith.Element( name = "generateBulkLoader", booleans = true ) +) +public interface MyConverter { + @AnnotateWith( Converter.class ) + DomainObject map( DtoObject dto ); +} +---- +==== + +.AnnotateWith generated mapper +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Converter( generateBulkLoader = true ) +public class MyConverterImpl implements MyConverter { + @Converter + public DomainObject map( DtoObject dto ) { + // default mapping behaviour + } +} +---- +==== diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index 70098c952e..cbb59f4e57 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -9,6 +9,8 @@ import javax.xml.bind.annotation.XmlElementRef; import org.mapstruct.AfterMapping; +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWiths; import org.mapstruct.BeanMapping; import org.mapstruct.BeforeMapping; import org.mapstruct.Builder; @@ -43,6 +45,9 @@ * * @author Gunnar Morling */ +@GemDefinition(AnnotateWith.class) +@GemDefinition(AnnotateWith.Element.class) +@GemDefinition(AnnotateWiths.class) @GemDefinition(Mapper.class) @GemDefinition(Mapping.class) @GemDefinition(Mappings.class) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java new file mode 100644 index 0000000000..f28cdfef9c --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java @@ -0,0 +1,602 @@ +/* + * 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.internal.model; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.AnnotateWithGem; +import org.mapstruct.ap.internal.gem.AnnotateWithsGem; +import org.mapstruct.ap.internal.gem.ElementGem; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; +import org.mapstruct.ap.internal.model.annotation.EnumAnnotationElementHolder; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.RepeatableAnnotations; +import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; +import org.mapstruct.tools.gem.GemValue; + +import static javax.lang.model.util.ElementFilter.methodsIn; + +/** + * @author Ben Zegveld + * @since 1.5 + */ +public class AdditionalAnnotationsBuilder + extends RepeatableAnnotations { + private static final String ANNOTATE_WITH_FQN = "org.mapstruct.AnnotateWith"; + private static final String ANNOTATE_WITHS_FQN = "org.mapstruct.AnnotateWiths"; + + private TypeFactory typeFactory; + private FormattingMessager messager; + + public AdditionalAnnotationsBuilder(ElementUtils elementUtils, TypeFactory typeFactory, + FormattingMessager messager) { + super( elementUtils, ANNOTATE_WITH_FQN, ANNOTATE_WITHS_FQN ); + this.typeFactory = typeFactory; + this.messager = messager; + } + + @Override + protected AnnotateWithGem singularInstanceOn(Element element) { + return AnnotateWithGem.instanceOn( element ); + } + + @Override + protected AnnotateWithsGem multipleInstanceOn(Element element) { + return AnnotateWithsGem.instanceOn( element ); + } + + @Override + protected void addInstance(AnnotateWithGem gem, Element source, Set mappings) { + buildAnnotation( gem, source ).ifPresent( t -> addAndValidateMapping( mappings, source, gem, t ) ); + } + + @Override + protected void addInstances(AnnotateWithsGem gem, Element source, Set mappings) { + for ( AnnotateWithGem annotateWithGem : gem.value().get() ) { + buildAnnotation( + annotateWithGem, + source ).ifPresent( t -> addAndValidateMapping( mappings, source, annotateWithGem, t ) ); + } + } + + private void addAndValidateMapping(Set mappings, Element source, AnnotateWithGem gem, Annotation anno) { + if ( anno.getType().getTypeElement().getAnnotation( Repeatable.class ) == null ) { + if ( mappings.stream().anyMatch( existing -> existing.getType().equals( anno.getType() ) ) ) { + messager.printMessage( + source, + gem.mirror(), + Message.ANNOTATE_WITH_ANNOTATION_IS_NOT_REPEATABLE, + anno.getType().describe() ); + return; + } + } + if ( mappings.stream().anyMatch( existing -> { + return existing.getType().equals( anno.getType() ) + && existing.getProperties().equals( anno.getProperties() ); + } ) ) { + messager.printMessage( + source, + gem.mirror(), + Message.ANNOTATE_WITH_DUPLICATE, + anno.getType().describe() ); + return; + } + mappings.add( anno ); + } + + private Optional buildAnnotation(AnnotateWithGem annotationGem, Element element) { + Type annotationType = typeFactory.getType( getTypeMirror( annotationGem.value() ) ); + List eleGems = annotationGem.elements().get(); + if ( isValid( annotationType, eleGems, element, annotationGem.mirror() ) ) { + return Optional.of( new Annotation( annotationType, convertToProperties( eleGems ) ) ); + } + return Optional.empty(); + } + + private List convertToProperties(List eleGems) { + return eleGems.stream().map( gem -> convertToProperty( gem, typeFactory ) ).collect( Collectors.toList() ); + } + + private enum ConvertToProperty { + BOOLEAN( + AnnotationElementType.BOOLEAN, + (eleGem, typeFactory) -> eleGem.booleans().get(), + eleGem -> eleGem.booleans().hasValue() + ), + BYTE( + AnnotationElementType.BYTE, + (eleGem, typeFactory) -> eleGem.bytes().get(), + eleGem -> eleGem.bytes().hasValue() + ), + CHARACTER( + AnnotationElementType.CHARACTER, + (eleGem, typeFactory) -> eleGem.chars().get(), + eleGem -> eleGem.chars().hasValue() + ), + CLASSES( + AnnotationElementType.CLASS, + (eleGem, typeFactory) -> { + return eleGem.classes().get().stream().map( typeFactory::getType ).collect( Collectors.toList() ); + }, + eleGem -> eleGem.classes().hasValue() + ), + DOUBLE( + AnnotationElementType.DOUBLE, + (eleGem, typeFactory) -> eleGem.doubles().get(), + eleGem -> eleGem.doubles().hasValue() + ), + ENUM( + AnnotationElementType.ENUM, + (eleGem, typeFactory) -> { + List values = new ArrayList<>(); + for ( String enumName : eleGem.enums().get() ) { + Type type = typeFactory.getType( eleGem.enumClass().get() ); + values.add( new EnumAnnotationElementHolder( type, enumName ) ); + } + return values; + }, + eleGem -> eleGem.enums().hasValue() && eleGem.enumClass().hasValue() + ), + FLOAT( + AnnotationElementType.FLOAT, + (eleGem, typeFactory) -> eleGem.floats().get(), + eleGem -> eleGem.floats().hasValue() + ), + INT( + AnnotationElementType.INTEGER, + (eleGem, typeFactory) -> eleGem.ints().get(), + eleGem -> eleGem.ints().hasValue() + ), + LONG( + AnnotationElementType.LONG, + (eleGem, typeFactory) -> eleGem.longs().get(), + eleGem -> eleGem.longs().hasValue() + ), + SHORT( + AnnotationElementType.SHORT, + (eleGem, typeFactory) -> eleGem.shorts().get(), + eleGem -> eleGem.shorts().hasValue() + ), + STRING( + AnnotationElementType.STRING, + (eleGem, typeFactory) -> eleGem.strings().get(), + eleGem -> eleGem.strings().hasValue() + ); + + private final AnnotationElementType type; + private final BiFunction> factory; + private final Predicate usabilityChecker; + + ConvertToProperty(AnnotationElementType type, + BiFunction> factory, + Predicate usabilityChecker) { + this.type = type; + this.factory = factory; + this.usabilityChecker = usabilityChecker; + } + + AnnotationElement toProperty(ElementGem eleGem, TypeFactory typeFactory) { + return new AnnotationElement( + type, + eleGem.name().get(), + factory.apply( eleGem, typeFactory ) + ); + } + + boolean isUsable(ElementGem eleGem) { + return usabilityChecker.test( eleGem ); + } + } + + private AnnotationElement convertToProperty(ElementGem eleGem, TypeFactory typeFactory) { + for ( ConvertToProperty convertToJava : ConvertToProperty.values() ) { + if ( convertToJava.isUsable( eleGem ) ) { + return convertToJava.toProperty( eleGem, typeFactory ); + } + } + return null; + } + + private boolean isValid(Type annotationType, List eleGems, Element element, + AnnotationMirror annotationMirror) { + boolean isValid = true; + if ( !annotationIsAllowed( annotationType, element, annotationMirror ) ) { + isValid = false; + } + + List annotationElements = methodsIn( annotationType.getTypeElement() + .getEnclosedElements() ); + if ( !allRequiredElementsArePresent( annotationType, annotationElements, eleGems, element, + annotationMirror ) ) { + isValid = false; + } + if ( !allElementsAreKnownInAnnotation( annotationType, annotationElements, eleGems, element ) ) { + isValid = false; + } + if ( !allElementsAreOfCorrectType( annotationType, annotationElements, eleGems, element ) ) { + isValid = false; + } + if ( !enumConstructionIsCorrectlyUsed( eleGems, element ) ) { + isValid = false; + } + if ( !allElementsAreUnique( eleGems, element ) ) { + isValid = false; + } + return isValid; + } + + private boolean allElementsAreUnique(List eleGems, Element element) { + boolean isValid = true; + List checkedElements = new ArrayList<>(); + for ( ElementGem elementGem : eleGems ) { + String elementName = elementGem.name().get(); + if ( checkedElements.contains( elementName ) ) { + isValid = false; + messager + .printMessage( + element, + elementGem.mirror(), + Message.ANNOTATE_WITH_DUPLICATE_PARAMETER, + elementName ); + } + else { + checkedElements.add( elementName ); + } + } + return isValid; + } + + private boolean enumConstructionIsCorrectlyUsed(List eleGems, Element element) { + boolean isValid = true; + for ( ElementGem elementGem : eleGems ) { + if ( elementGem.enums().hasValue() ) { + if ( elementGem.enumClass().getValue() == null ) { + isValid = false; + messager + .printMessage( + element, + elementGem.mirror(), + Message.ANNOTATE_WITH_ENUM_CLASS_NOT_DEFINED ); + } + else { + Type type = typeFactory.getType( getTypeMirror( elementGem.enumClass() ) ); + if ( type.isEnumType() ) { + List enumConstants = type.getEnumConstants(); + for ( String enumName : elementGem.enums().get() ) { + if ( !enumConstants.contains( enumName ) ) { + isValid = false; + messager + .printMessage( + element, + elementGem.mirror(), + elementGem.enums().getAnnotationValue(), + Message.ANNOTATE_WITH_ENUM_VALUE_DOES_NOT_EXIST, + type.describe(), + enumName ); + } + } + } + } + } + else if ( elementGem.enumClass().getValue() != null ) { + isValid = false; + messager.printMessage( element, elementGem.mirror(), Message.ANNOTATE_WITH_ENUMS_NOT_DEFINED ); + } + } + return isValid; + } + + private boolean annotationIsAllowed(Type annotationType, Element element, AnnotationMirror annotationMirror) { + Target target = annotationType.getTypeElement().getAnnotation( Target.class ); + if ( target == null ) { + return true; + } + + Set annotationTargets = Stream.of( target.value() ) + // The eclipse compiler returns null for some values + // Therefore, we filter out null values + .filter( Objects::nonNull ) + .collect( Collectors.toCollection( () -> EnumSet.noneOf( ElementType.class ) ) ); + + boolean isValid = true; + if ( isTypeTarget( element ) && !annotationTargets.contains( ElementType.TYPE ) ) { + isValid = false; + messager.printMessage( + element, + annotationMirror, + Message.ANNOTATE_WITH_NOT_ALLOWED_ON_CLASS, + annotationType.describe() + ); + } + if ( isMethodTarget( element ) && !annotationTargets.contains( ElementType.METHOD ) ) { + isValid = false; + messager.printMessage( + element, + annotationMirror, + Message.ANNOTATE_WITH_NOT_ALLOWED_ON_METHODS, + annotationType.describe() + ); + } + return isValid; + } + + private boolean isTypeTarget(Element element) { + return element.getKind().isInterface() || element.getKind().isClass(); + } + + private boolean isMethodTarget(Element element) { + return element.getKind() == ElementKind.METHOD; + } + + private boolean allElementsAreKnownInAnnotation(Type annotationType, List annotationParameters, + List eleGems, Element element) { + Set allowedAnnotationParameters = annotationParameters.stream() + .map( ee -> ee.getSimpleName().toString() ) + .collect( Collectors.toSet() ); + boolean isValid = true; + for ( ElementGem eleGem : eleGems ) { + if ( eleGem.name().isValid() + && !allowedAnnotationParameters.contains( eleGem.name().get() ) ) { + isValid = false; + messager + .printMessage( + element, + eleGem.mirror(), + eleGem.name().getAnnotationValue(), + Message.ANNOTATE_WITH_UNKNOWN_PARAMETER, + eleGem.name().get(), + annotationType.describe(), + Strings.getMostSimilarWord( eleGem.name().get(), allowedAnnotationParameters ) + ); + } + } + return isValid; + } + + private boolean allRequiredElementsArePresent(Type annotationType, List annotationParameters, + List elements, Element element, + AnnotationMirror annotationMirror) { + + boolean valid = true; + for ( ExecutableElement annotationParameter : annotationParameters ) { + if ( annotationParameter.getDefaultValue() == null ) { + // Mandatory parameter, must be present in the elements + String parameterName = annotationParameter.getSimpleName().toString(); + boolean elementGemDefined = false; + for ( ElementGem elementGem : elements ) { + if ( elementGem.isValid() && elementGem.name().get().equals( parameterName ) ) { + elementGemDefined = true; + break; + } + } + + if ( !elementGemDefined ) { + valid = false; + messager + .printMessage( + element, + annotationMirror, + Message.ANNOTATE_WITH_MISSING_REQUIRED_PARAMETER, + parameterName, + annotationType.describe() + ); + } + } + } + + + return valid; + } + + private boolean allElementsAreOfCorrectType(Type annotationType, List annotationParameters, + List elements, + Element element) { + Map annotationParametersByName = + annotationParameters.stream() + .collect( Collectors.toMap( ee -> ee.getSimpleName().toString(), Function.identity() ) ); + boolean isValid = true; + for ( ElementGem eleGem : elements ) { + Type annotationParameterType = getAnnotationParameterType( annotationParametersByName, eleGem ); + Type annotationParameterTypeSingular = getNonArrayType( annotationParameterType ); + if ( annotationParameterTypeSingular == null ) { + continue; + } + if ( hasTooManyDifferentTypes( eleGem ) ) { + isValid = false; + messager.printMessage( + element, + eleGem.mirror(), + eleGem.name().getAnnotationValue(), + Message.ANNOTATE_WITH_TOO_MANY_VALUE_TYPES, + eleGem.name().get(), + annotationParameterType.describe(), + annotationType.describe() + ); + } + else { + Map elementTypes = getParameterTypes( eleGem ); + Set reportedSizeError = new HashSet<>(); + for ( Type eleGemType : elementTypes.keySet() ) { + if ( !sameTypeOrAssignableClass( annotationParameterTypeSingular, eleGemType ) ) { + isValid = false; + messager.printMessage( + element, + eleGem.mirror(), + eleGem.name().getAnnotationValue(), + Message.ANNOTATE_WITH_WRONG_PARAMETER, + eleGem.name().get(), + eleGemType.describe(), + annotationParameterType.describe(), + annotationType.describe() + ); + } + else if ( !annotationParameterType.isArrayType() + && elementTypes.get( eleGemType ) > 1 + && !reportedSizeError.contains( eleGem ) ) { + isValid = false; + messager.printMessage( + element, + eleGem.mirror(), + Message.ANNOTATE_WITH_PARAMETER_ARRAY_NOT_EXPECTED, + eleGem.name().get(), + annotationType.describe() + ); + reportedSizeError.add( eleGem ); + } + } + } + } + return isValid; + } + + private boolean hasTooManyDifferentTypes( ElementGem eleGem ) { + return Arrays.stream( ConvertToProperty.values() ) + .filter( anotationElement -> anotationElement.isUsable( eleGem ) ) + .count() > 1; + } + + private Type getNonArrayType(Type annotationParameterType) { + if ( annotationParameterType == null ) { + return null; + } + if ( annotationParameterType.isArrayType() ) { + return annotationParameterType.getComponentType(); + } + + return annotationParameterType; + } + + private boolean sameTypeOrAssignableClass(Type annotationParameterType, Type eleGemType) { + return annotationParameterType.equals( eleGemType ) + || eleGemType.isAssignableTo( getTypeBound( annotationParameterType ) ); + } + + private Type getTypeBound(Type annotationParameterType) { + List typeParameters = annotationParameterType.getTypeParameters(); + if ( typeParameters.size() != 1 ) { + return annotationParameterType; + } + return typeParameters.get( 0 ).getTypeBound(); + } + + private Map getParameterTypes(ElementGem eleGem) { + Map suppliedParameterTypes = new HashMap<>(); + if ( eleGem.booleans().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( boolean.class ), + eleGem.booleans().get().size() ); + } + if ( eleGem.bytes().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( byte.class ), + eleGem.bytes().get().size() ); + } + if ( eleGem.chars().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( char.class ), + eleGem.chars().get().size() ); + } + if ( eleGem.classes().hasValue() ) { + for ( TypeMirror mirror : eleGem.classes().get() ) { + suppliedParameterTypes.put( + typeFactory.getType( typeMirrorFromAnnotation( mirror ) ), + eleGem.classes().get().size() + ); + } + } + if ( eleGem.doubles().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( double.class ), + eleGem.doubles().get().size() ); + } + if ( eleGem.floats().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( float.class ), + eleGem.floats().get().size() ); + } + if ( eleGem.ints().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( int.class ), + eleGem.ints().get().size() ); + } + if ( eleGem.longs().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( long.class ), + eleGem.longs().get().size() ); + } + if ( eleGem.shorts().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( short.class ), + eleGem.shorts().get().size() ); + } + if ( eleGem.strings().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( String.class ), + eleGem.strings().get().size() ); + } + if ( eleGem.enums().hasValue() && eleGem.enumClass().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( getTypeMirror( eleGem.enumClass() ) ), + eleGem.enums().get().size() ); + } + return suppliedParameterTypes; + } + + private Type getAnnotationParameterType(Map annotationParameters, + ElementGem element) { + if ( annotationParameters.containsKey( element.name().get() ) ) { + return typeFactory.getType( annotationParameters.get( element.name().get() ).getReturnType() ); + } + else { + return null; + } + } + + private TypeMirror getTypeMirror(GemValue gemValue) { + return typeMirrorFromAnnotation( gemValue.getValue() ); + } + + private TypeMirror typeMirrorFromAnnotation(TypeMirror typeMirror) { + if ( typeMirror == null ) { + // When a class used in an annotation is created by another annotation processor + // then javac will not return correct TypeMirror with TypeKind#ERROR, but rather a string "" + // the gem tools would return a null TypeMirror in that case. + // Therefore, throw TypeHierarchyErroneousException so we can postpone the generation of the mapper + throw new TypeHierarchyErroneousException( typeMirror ); + } + + return typeMirror; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java index 405958ccdf..866012c51a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java @@ -6,9 +6,11 @@ package org.mapstruct.ap.internal.model; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.Type; @@ -21,16 +23,13 @@ public class Annotation extends ModelElement { private final Type type; - /** - * List of annotation attributes. Quite simplistic, but it's sufficient for now. - */ - private List properties; + private List properties; public Annotation(Type type) { this( type, Collections.emptyList() ); } - public Annotation(Type type, List properties) { + public Annotation(Type type, List properties) { this.type = type; this.properties = properties; } @@ -41,10 +40,15 @@ public Type getType() { @Override public Set getImportTypes() { - return Collections.singleton( type ); + Set types = new HashSet<>(); + for ( AnnotationElement prop : properties ) { + types.addAll( prop.getImportTypes() ); + } + types.add( type ); + return types; } - public List getProperties() { + public List getProperties() { return properties; } } 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 d17718370d..e748c173af 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 @@ -85,6 +85,7 @@ */ public class BeanMappingMethod extends NormalTypeMappingMethod { + private final List annotations; private final List propertyMappings; private final Map> mappingsByParameter; private final Map> constructorMappingsByParameter; @@ -112,6 +113,7 @@ public static class Builder extends AbstractMappingMethodBuilder unprocessedSourceParameters = new HashSet<>(); private final Set existingVariableNames = new HashSet<>(); private final Map> unprocessedDefinedTargets = new LinkedHashMap<>(); + private final List annotations = new ArrayList<>(); private MappingReferences mappingReferences; private MethodReference factoryMethod; @@ -214,6 +216,12 @@ else if ( !method.isUpdateMethod() ) { // If the return type cannot be constructed then no need to try to create mappings return null; } + AdditionalAnnotationsBuilder additionalAnnotationsBuilder = + new AdditionalAnnotationsBuilder( + ctx.getElementUtils(), + ctx.getTypeFactory(), + ctx.getMessager() ); + annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); /* the type that needs to be used in the mapping process as target */ Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct; @@ -362,6 +370,7 @@ else if ( !method.isUpdateMethod() ) { return new BeanMappingMethod( method, + annotations, existingVariableNames, propertyMappings, factoryMethod, @@ -1701,6 +1710,7 @@ private ConstructorAccessor( //CHECKSTYLE:OFF private BeanMappingMethod(Method method, + List annotations, Collection existingVariableNames, List propertyMappings, MethodReference factoryMethod, @@ -1722,6 +1732,7 @@ private BeanMappingMethod(Method method, ); //CHECKSTYLE:ON + this.annotations = annotations; this.propertyMappings = propertyMappings; this.returnTypeBuilder = returnTypeBuilder; this.finalizerMethod = finalizerMethod; @@ -1760,6 +1771,10 @@ else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { this.subclassMappings = subclassMappings; } + public List getAnnotations() { + return annotations; + } + public List getConstantMappings() { return constantMappings; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index 134ab081dc..f8f65c927f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -10,15 +10,14 @@ import java.util.List; import java.util.SortedSet; import java.util.TreeSet; - import javax.lang.model.type.TypeKind; -import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.option.Options; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.version.VersionInformation; @@ -220,7 +219,9 @@ public SortedSet getImportTypes() { } for ( Annotation annotation : annotations ) { - addIfImportRequired( importedTypes, annotation.getType() ); + for ( Type type : annotation.getImportTypes() ) { + addIfImportRequired( importedTypes, type ); + } } for ( Type extraImport : extraImportedTypes ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java index fa15cd8ed0..9b7729e8fa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java @@ -8,7 +8,6 @@ import java.util.List; import java.util.Set; import java.util.SortedSet; - import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; @@ -43,6 +42,7 @@ public static class Builder extends GeneratedTypeBuilder { private String implPackage; private boolean customPackage; private boolean suppressGeneratorTimestamp; + private Set customAnnotations; public Builder() { super( Builder.class ); @@ -63,6 +63,11 @@ public Builder constructorFragments(Set fragment return this; } + public Builder additionalAnnotations(Set customAnnotations) { + this.customAnnotations = customAnnotations; + return this; + } + public Builder decorator(Decorator decorator) { this.decorator = decorator; return this; @@ -105,6 +110,7 @@ public Mapper build() { definitionType, customPackage, customName, + customAnnotations, methods, options, versionInformation, @@ -126,7 +132,7 @@ public Mapper build() { @SuppressWarnings( "checkstyle:parameternumber" ) private Mapper(TypeFactory typeFactory, String packageName, String name, Type mapperDefinitionType, - boolean customPackage, boolean customImplName, + boolean customPackage, boolean customImplName, Set customAnnotations, List methods, Options options, VersionInformation versionInformation, boolean suppressGeneratorTimestamp, Accessibility accessibility, List fields, Constructor constructor, @@ -148,6 +154,7 @@ private Mapper(TypeFactory typeFactory, String packageName, String name, ); this.customPackage = customPackage; this.customImplName = customImplName; + customAnnotations.forEach( this::addAnnotation ); this.decorator = decorator; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java new file mode 100644 index 0000000000..6ecde886bf --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java @@ -0,0 +1,128 @@ +/* + * 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.internal.model.annotation; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * @author Ben Zegveld + */ +public class AnnotationElement extends ModelElement { + public enum AnnotationElementType { + BOOLEAN, BYTE, CHARACTER, CLASS, DOUBLE, ENUM, FLOAT, INTEGER, LONG, SHORT, STRING + } + + private final String elementName; + private final List values; + private final AnnotationElementType type; + + public AnnotationElement(AnnotationElementType type, List values) { + this( type, null, values ); + } + + public AnnotationElement(AnnotationElementType type, String elementName, List values) { + this.type = type; + this.elementName = elementName; + this.values = values; + } + + public String getElementName() { + return elementName; + } + + public List getValues() { + return values; + } + + @Override + public Set getImportTypes() { + Set importTypes = null; + for ( Object value : values ) { + if ( value instanceof ModelElement ) { + if ( importTypes == null ) { + importTypes = new HashSet<>(); + } + importTypes.addAll( ( (ModelElement) value ).getImportTypes() ); + } + } + + return importTypes == null ? Collections.emptySet() : importTypes; + } + + public boolean isBoolean() { + return type == AnnotationElementType.BOOLEAN; + } + + public boolean isByte() { + return type == AnnotationElementType.BYTE; + } + + public boolean isCharacter() { + return type == AnnotationElementType.CHARACTER; + } + + public boolean isClass() { + return type == AnnotationElementType.CLASS; + } + + public boolean isDouble() { + return type == AnnotationElementType.DOUBLE; + } + + public boolean isEnum() { + return type == AnnotationElementType.ENUM; + } + + public boolean isFloat() { + return type == AnnotationElementType.FLOAT; + } + + public boolean isInteger() { + return type == AnnotationElementType.INTEGER; + } + + public boolean isLong() { + return type == AnnotationElementType.LONG; + } + + public boolean isShort() { + return type == AnnotationElementType.SHORT; + } + + public boolean isString() { + return type == AnnotationElementType.STRING; + } + + @Override + public int hashCode() { + return Objects.hash( elementName, type, values ); + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + AnnotationElement other = (AnnotationElement) obj; + return Objects.equals( elementName, other.elementName ) + && type == other.type + && Objects.equals( values, other.values ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.java new file mode 100644 index 0000000000..de87f32525 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.java @@ -0,0 +1,35 @@ +/* + * 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.internal.model.annotation; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +public class EnumAnnotationElementHolder extends ModelElement { + + private final Type enumClass; + private final String name; + + public EnumAnnotationElementHolder(Type enumClass, String name) { + this.enumClass = enumClass; + this.name = name; + } + + public Type getEnumClass() { + return enumClass; + } + + public String getName() { + return name; + } + + @Override + public Set getImportTypes() { + return enumClass.getImportTypes(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java index 5161ea689c..999c0a51df 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; /** * A {@link ModelElementProcessor} which converts the given {@link Mapper} @@ -67,7 +69,10 @@ private Annotation named() { private Annotation namedDelegate(Mapper mapper) { return new Annotation( getTypeFactory().getType( "jakarta.inject.Named" ), - Collections.singletonList( '"' + mapper.getPackageName() + "." + mapper.getName() + '"' ) + Collections.singletonList( + new AnnotationElement( AnnotationElementType.STRING, + Collections.singletonList( mapper.getPackageName() + "." + mapper.getName() ) + ) ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java index 6a6cc16580..00ba72a079 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.util.AnnotationProcessingException; @@ -70,7 +72,11 @@ private Annotation named() { private Annotation namedDelegate(Mapper mapper) { return new Annotation( getType( "Named" ), - Collections.singletonList( '"' + mapper.getPackageName() + "." + mapper.getName() + '"' ) + Collections.singletonList( + new AnnotationElement( + AnnotationElementType.STRING, + Collections.singletonList( mapper.getPackageName() + "." + mapper.getName() ) + ) ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index d8acb10398..c05d2d18bd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -31,6 +31,7 @@ import org.mapstruct.ap.internal.gem.MapperGem; import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.model.AdditionalAnnotationsBuilder; import org.mapstruct.ap.internal.model.BeanMappingMethod; import org.mapstruct.ap.internal.model.ContainerMappingMethod; import org.mapstruct.ap.internal.model.ContainerMappingMethodBuilder; @@ -93,6 +94,8 @@ public class MapperCreationProcessor implements ModelElementProcessor sourceModel) { this.elementUtils = context.getElementUtils(); @@ -103,6 +106,8 @@ public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, L this.versionInformation = context.getVersionInformation(); this.typeFactory = context.getTypeFactory(); this.accessorNaming = context.getAccessorNaming(); + additionalAnnotationsBuilder = + new AdditionalAnnotationsBuilder( elementUtils, typeFactory, messager ); MapperOptions mapperOptions = MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() ); List mapperReferences = initReferencedMappers( mapperTypeElement, mapperOptions ); @@ -206,6 +211,7 @@ private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List< .implName( mapperOptions.implementationName() ) .implPackage( mapperOptions.implementationPackage() ) .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) + .additionalAnnotations( additionalAnnotationsBuilder.getProcessedAnnotations( element ) ) .build(); if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) { 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 201f1a1f22..bda33b6a92 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 @@ -8,13 +8,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -54,9 +51,9 @@ import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.RepeatableAnnotations; import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.spi.EnumTransformationStrategy; -import org.mapstruct.tools.gem.Gem; /** * A {@link ModelElementProcessor} which retrieves a list of {@link SourceMethod}s @@ -68,8 +65,6 @@ */ public class MethodRetrievalProcessor implements ModelElementProcessor> { - private static final String JAVA_LANG_ANNOTATION_PGK = "java.lang.annotation"; - private static final String ORG_MAPSTRUCT_PKG = "org.mapstruct"; private static final String MAPPING_FQN = "org.mapstruct.Mapping"; private static final String MAPPINGS_FQN = "org.mapstruct.Mappings"; private static final String SUB_CLASS_MAPPING_FQN = "org.mapstruct.SubclassMapping"; @@ -280,8 +275,7 @@ private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, typeUtils, typeFactory ); - RepeatableMappings repeatableMappings = new RepeatableMappings(); - Set mappingOptions = repeatableMappings.getMappings( method, beanMappingOptions ); + Set mappingOptions = getMappings( method, beanMappingOptions ); IterableMappingOptions iterableMappingOptions = IterableMappingOptions.fromGem( IterableMappingGem.instanceOn( method ), @@ -593,7 +587,7 @@ private boolean isStreamTypeOrIterableFromJavaStdLib(Type type) { * @return The mappings for the given method, keyed by target property name */ private Set getMappings(ExecutableElement method, BeanMappingOptions beanMapping) { - return new RepeatableMappings().getMappings( method, beanMapping ); + return new RepeatableMappings( beanMapping ).getProcessedAnnotations( method ); } /** @@ -607,173 +601,107 @@ private Set getMappings(ExecutableElement method, BeanMappingOpt private Set getSubclassMappings(List sourceParameters, Type resultType, ExecutableElement method, BeanMappingOptions beanMapping, SubclassValidator validator) { - return new RepeatableSubclassMappings( sourceParameters, resultType, validator ) - .getMappings( method, beanMapping ); + return new RepeatableSubclassMappings( beanMapping, sourceParameters, resultType, validator ) + .getProcessedAnnotations( method ); } - private class RepeatableMappings extends RepeatableMappingAnnotations { - RepeatableMappings() { - super( MAPPING_FQN, MAPPINGS_FQN ); + private class RepeatableMappings extends RepeatableAnnotations { + private BeanMappingOptions beanMappingOptions; + + RepeatableMappings(BeanMappingOptions beanMappingOptions) { + super( elementUtils, MAPPING_FQN, MAPPINGS_FQN ); + this.beanMappingOptions = beanMappingOptions; } @Override - MappingGem singularInstanceOn(Element element) { + protected MappingGem singularInstanceOn(Element element) { return MappingGem.instanceOn( element ); } @Override - MappingsGem multipleInstanceOn(Element element) { + protected MappingsGem multipleInstanceOn(Element element) { return MappingsGem.instanceOn( element ); } @Override - void addInstance(MappingGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, - Set mappings) { - MappingOptions.addInstance( gem, method, beanMappingOptions, messager, typeUtils, mappings ); + protected void addInstance(MappingGem gem, Element method, Set mappings) { + MappingOptions.addInstance( + gem, + (ExecutableElement) method, + beanMappingOptions, + messager, + typeUtils, + mappings ); } @Override - void addInstances(MappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, - Set mappings) { - MappingOptions.addInstances( gem, method, beanMappingOptions, messager, typeUtils, mappings ); + protected void addInstances(MappingsGem gem, Element method, Set mappings) { + MappingOptions.addInstances( + gem, + (ExecutableElement) method, + beanMappingOptions, + messager, + typeUtils, + mappings ); } } private class RepeatableSubclassMappings - extends RepeatableMappingAnnotations { + extends RepeatableAnnotations { private final List sourceParameters; private final Type resultType; private SubclassValidator validator; + private BeanMappingOptions beanMappingOptions; - RepeatableSubclassMappings(List sourceParameters, Type resultType, SubclassValidator validator) { - super( SUB_CLASS_MAPPING_FQN, SUB_CLASS_MAPPINGS_FQN ); + RepeatableSubclassMappings(BeanMappingOptions beanMappingOptions, List sourceParameters, + Type resultType, SubclassValidator validator) { + super( elementUtils, SUB_CLASS_MAPPING_FQN, SUB_CLASS_MAPPINGS_FQN ); + this.beanMappingOptions = beanMappingOptions; this.sourceParameters = sourceParameters; this.resultType = resultType; this.validator = validator; } @Override - SubclassMappingGem singularInstanceOn(Element element) { + protected SubclassMappingGem singularInstanceOn(Element element) { return SubclassMappingGem.instanceOn( element ); } @Override - SubclassMappingsGem multipleInstanceOn(Element element) { + protected SubclassMappingsGem multipleInstanceOn(Element element) { return SubclassMappingsGem.instanceOn( element ); } @Override - void addInstance(SubclassMappingGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, - Set mappings) { - SubclassMappingOptions - .addInstance( - gem, - method, - beanMappingOptions, - messager, - typeUtils, - mappings, - sourceParameters, - resultType, - validator ); + protected void addInstance(SubclassMappingGem gem, + Element method, + Set mappings) { + SubclassMappingOptions.addInstance( + gem, + (ExecutableElement) method, + beanMappingOptions, + messager, + typeUtils, + mappings, + sourceParameters, + resultType, + validator ); } @Override - void addInstances(SubclassMappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, - Set mappings) { - SubclassMappingOptions - .addInstances( - gem, - method, - beanMappingOptions, - messager, - typeUtils, - mappings, - sourceParameters, - resultType, - validator ); - } - } - - private abstract class RepeatableMappingAnnotations { - - private final String singularFqn; - private final String multipleFqn; - - RepeatableMappingAnnotations(String singularFqn, String multipleFqn) { - this.singularFqn = singularFqn; - this.multipleFqn = multipleFqn; - } - - abstract SINGULAR singularInstanceOn(Element element); - - abstract MULTIPLE multipleInstanceOn(Element element); - - abstract void addInstance(SINGULAR gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, - Set mappings); - - abstract void addInstances(MULTIPLE gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, - Set mappings); - - /** - * Retrieves the mappings configured via {@code @Mapping} from the given method. - * - * @param method The method of interest - * @param beanMapping options coming from bean mapping method - * @return The mappings for the given method, keyed by target property name - */ - public Set getMappings(ExecutableElement method, BeanMappingOptions beanMapping) { - return getMappings( method, method, beanMapping, new LinkedHashSet<>(), new HashSet<>() ); - } - - /** - * Retrieves the mappings configured via {@code @Mapping} from the given method. - * - * @param method The method of interest - * @param element Element of interest: method, or (meta) annotation - * @param beanMapping options coming from bean mapping method - * @param mappingOptions LinkedSet of mappings found so far - * @return The mappings for the given method, keyed by target property name - */ - private Set getMappings(ExecutableElement method, Element element, - BeanMappingOptions beanMapping, LinkedHashSet mappingOptions, - Set handledElements) { - - for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { - Element lElement = annotationMirror.getAnnotationType().asElement(); - if ( isAnnotation( lElement, singularFqn ) ) { - // although getInstanceOn does a search on annotation mirrors, the order is preserved - SINGULAR mapping = singularInstanceOn( element ); - addInstance( mapping, method, beanMapping, mappingOptions ); - } - else if ( isAnnotation( lElement, multipleFqn ) ) { - // although getInstanceOn does a search on annotation mirrors, the order is preserved - MULTIPLE mappings = multipleInstanceOn( element ); - addInstances( mappings, method, beanMapping, mappingOptions ); - } - else if ( !isAnnotationInPackage( lElement, JAVA_LANG_ANNOTATION_PGK ) - && !isAnnotationInPackage( lElement, ORG_MAPSTRUCT_PKG ) - && !handledElements.contains( lElement ) ) { - // recur over annotation mirrors - handledElements.add( lElement ); - getMappings( method, lElement, beanMapping, mappingOptions, handledElements ); - } - } - return mappingOptions; - } - - private boolean isAnnotationInPackage(Element element, String packageFQN) { - if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { - return packageFQN.equals( elementUtils.getPackageOf( element ).getQualifiedName().toString() ); - } - return false; - } - - private boolean isAnnotation(Element element, String annotationFQN) { - if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { - return annotationFQN.equals( ( (TypeElement) element ).getQualifiedName().toString() ); - } - return false; + protected void addInstances(SubclassMappingsGem gem, + Element method, + Set mappings) { + SubclassMappingOptions.addInstances( + gem, + (ExecutableElement) method, + beanMappingOptions, + messager, + typeUtils, + mappings, + sourceParameters, + resultType, + validator ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java index f7aa9b4fea..4efc537c3d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java @@ -13,6 +13,8 @@ import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; /** * A {@link ModelElementProcessor} which converts the given {@link Mapper} @@ -75,7 +77,11 @@ private Annotation autowired() { private Annotation qualifierDelegate() { return new Annotation( getTypeFactory().getType( "org.springframework.beans.factory.annotation.Qualifier" ), - Collections.singletonList( "\"delegate\"" ) ); + Collections.singletonList( + new AnnotationElement( + AnnotationElementType.STRING, + Collections.singletonList( "delegate" ) + ) ) ); } private Annotation primary() { 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 4e1c0302fa..92951ad521 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 @@ -198,6 +198,20 @@ public enum Message { MAPTOBEANMAPPING_WRONG_KEY_TYPE( "The Map parameter \"%s\" cannot be used for property mapping. It must be typed with Map but it was typed with %s.", Diagnostic.Kind.WARNING ), MAPTOBEANMAPPING_RAW_MAP( "The Map parameter \"%s\" cannot be used for property mapping. It must be typed with Map but it was raw.", Diagnostic.Kind.WARNING ), + + ANNOTATE_WITH_MISSING_REQUIRED_PARAMETER( "Parameter \"%s\" is required for annotation \"%s\"." ), + ANNOTATE_WITH_UNKNOWN_PARAMETER( "Unknown parameter \"%s\" for annotation \"%s\". Did you mean \"%s\"?" ), + ANNOTATE_WITH_DUPLICATE_PARAMETER( "Parameter \"%s\" must not be defined more than once." ), + ANNOTATE_WITH_WRONG_PARAMETER( "Parameter \"%s\" is not of type \"%s\" but of type \"%s\" for annotation \"%s\"." ), + ANNOTATE_WITH_TOO_MANY_VALUE_TYPES( "Parameter \"%s\" has too many value types supplied, type \"%s\" is expected for annotation \"%s\"." ), + ANNOTATE_WITH_PARAMETER_ARRAY_NOT_EXPECTED( "Parameter \"%s\" does not accept multiple values for annotation \"%s\"." ), + ANNOTATE_WITH_NOT_ALLOWED_ON_CLASS( "Annotation \"%s\" is not allowed on classes." ), + ANNOTATE_WITH_NOT_ALLOWED_ON_METHODS( "Annotation \"%s\" is not allowed on methods." ), + ANNOTATE_WITH_ENUM_VALUE_DOES_NOT_EXIST( "Enum \"%s\" does not have value \"%s\"." ), + ANNOTATE_WITH_ENUM_CLASS_NOT_DEFINED( "enumClass needs to be defined when using enums." ), + ANNOTATE_WITH_ENUMS_NOT_DEFINED( "enums needs to be defined when using enumClass." ), + ANNOTATE_WITH_ANNOTATION_IS_NOT_REPEATABLE( "Annotation \"%s\" is not repeatable." ), + ANNOTATE_WITH_DUPLICATE( "Annotation \"%s\" is already present with the same elements configuration.", Diagnostic.Kind.WARNING ), ; // CHECKSTYLE:ON diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java new file mode 100644 index 0000000000..accae77718 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java @@ -0,0 +1,120 @@ +/* + * 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.internal.util; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.tools.gem.Gem; + +/** + * @author Ben Zegveld + */ +public abstract class RepeatableAnnotations { + private static final String JAVA_LANG_ANNOTATION_PGK = "java.lang.annotation"; + private static final String ORG_MAPSTRUCT_PKG = "org.mapstruct"; + + private ElementUtils elementUtils; + private final String singularFqn; + private final String multipleFqn; + + protected RepeatableAnnotations(ElementUtils elementUtils, String singularFqn, String multipleFqn) { + this.elementUtils = elementUtils; + this.singularFqn = singularFqn; + this.multipleFqn = multipleFqn; + } + + /** + * @param element the element on which the Gem needs to be found + * @return the Gem found on the element. + */ + protected abstract SINGULAR singularInstanceOn(Element element); + + /** + * @param element the element on which the Gems needs to be found + * @return the Gems found on the element. + */ + protected abstract MULTIPLE multipleInstanceOn(Element element); + + /** + * @param gem the annotation gem to be processed + * @param source the source element where the request originated from + * @param mappings the collection of completed processing + */ + protected abstract void addInstance(SINGULAR gem, Element source, Set mappings); + + /** + * @param gems the annotation gems to be processed + * @param source the source element where the request originated from + * @param mappings the collection of completed processing + */ + protected abstract void addInstances(MULTIPLE gems, Element source, Set mappings); + + /** + * Retrieves the processed annotations. + * + * @param source The source element of interest + * @return The processed annotations for the given element + */ + public Set getProcessedAnnotations(Element source) { + return getMappings( source, source, new LinkedHashSet<>(), new HashSet<>() ); + } + + /** + * Retrieves the processed annotations. + * + * @param source The source element of interest + * @param element Element of interest: method, or (meta) annotation + * @param mappingOptions LinkedSet of mappings found so far + * @param handledElements The collection of already handled elements to handle recursion correctly. + * @return The processed annotations for the given element + */ + private Set getMappings(Element source, Element element, + LinkedHashSet mappingOptions, + Set handledElements) { + + for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { + Element lElement = annotationMirror.getAnnotationType().asElement(); + if ( isAnnotation( lElement, singularFqn ) ) { + // although getInstanceOn does a search on annotation mirrors, the order is preserved + SINGULAR mapping = singularInstanceOn( element ); + addInstance( mapping, source, mappingOptions ); + } + else if ( isAnnotation( lElement, multipleFqn ) ) { + // although getInstanceOn does a search on annotation mirrors, the order is preserved + MULTIPLE mappings = multipleInstanceOn( element ); + addInstances( mappings, source, mappingOptions ); + } + else if ( !isAnnotationInPackage( lElement, JAVA_LANG_ANNOTATION_PGK ) + && !isAnnotationInPackage( lElement, ORG_MAPSTRUCT_PKG ) + && !handledElements.contains( lElement ) ) { + // recur over annotation mirrors + handledElements.add( lElement ); + getMappings( source, lElement, mappingOptions, handledElements ); + } + } + return mappingOptions; + } + + private boolean isAnnotationInPackage(Element element, String packageFQN) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return packageFQN.equals( elementUtils.getPackageOf( element ).getQualifiedName().toString() ); + } + return false; + } + + private boolean isAnnotation(Element element, String annotationFQN) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return annotationFQN.equals( ( (TypeElement) element ).getQualifiedName().toString() ); + } + return false; + } +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl index 4391bcb975..eb67d732fc 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl @@ -6,4 +6,4 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Annotation" --> -@<@includeModel object=type/><#if (properties?size > 0) >(<#list properties as property>${property}<#if property_has_next>, ) \ No newline at end of file +@<@includeModel object=type/><#if (properties?size > 0) >(<#list properties as property><@includeModel object=property/><#if property_has_next>, ) \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 81e3b61fd7..1b402a57f0 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -6,6 +6,9 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.BeanMappingMethod" --> +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + <#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#assign targetType = resultType /> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/AnnotationElement.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/AnnotationElement.ftl new file mode 100644 index 0000000000..21c1977f58 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/AnnotationElement.ftl @@ -0,0 +1,48 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.annotation.AnnotationElement" --> +<@compress single_line=true> + <#if elementName??> + ${elementName} = + + <#if (values?size > 1) > + { + + <#-- rt and lt tags below are for formatting the arrays so that there are no spaces before ',' --> + <#list values as value> + <#if boolean> + ${value?c}<#rt> + <#elseif byte> + ${value}<#rt> + <#elseif character> + '${value}'<#rt> + <#elseif class> + <@includeModel object=value raw=true/>.class<#rt> + <#elseif double> + ${value?c}<#rt> + <#elseif enum> + <@includeModel object=value/><#rt> + <#elseif float> + ${value?c}f<#rt> + <#elseif integer> + ${value?c}<#rt> + <#elseif long> + ${value?c}L<#rt> + <#elseif short> + ${value?c}<#rt> + <#elseif string> + "${value}"<#rt> + + <#if value_has_next> + , <#lt> + + + <#if (values?size > 1) > + } + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.ftl new file mode 100644 index 0000000000..145b933156 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.ftl @@ -0,0 +1,9 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.annotation.EnumAnnotationElementHolder" --> +<@includeModel object=enumClass raw=true/>.${name}<#rt> \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithEnum.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithEnum.java new file mode 100644 index 0000000000..b2627fd8ce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithEnum.java @@ -0,0 +1,10 @@ +/* + * 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.annotatewith; + +public enum AnnotateWithEnum { + EXISTING, OTHER_EXISTING +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java new file mode 100644 index 0000000000..c2055b4c5c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java @@ -0,0 +1,548 @@ +/* + * 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.annotatewith; + +import org.junit.jupiter.api.extension.RegisterExtension; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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.GeneratedSource; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ben Zegveld + */ +@IssueKey("1574") +@WithClasses(AnnotateWithEnum.class) +public class AnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ DeprecateAndCustomMapper.class, CustomAnnotation.class }) + public void mapperBecomesDeprecatedAndGetsCustomAnnotation() { + DeprecateAndCustomMapper mapper = Mappers.getMapper( DeprecateAndCustomMapper.class ); + + assertThat( mapper.getClass() ).hasAnnotations( Deprecated.class, CustomAnnotation.class ); + } + + @ProcessorTest + @WithClasses( { CustomNamedMapper.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void annotationWithValue() { + generatedSource.addComparisonToFixtureFor( CustomNamedMapper.class ); + } + + @ProcessorTest + @WithClasses( { MultipleArrayValuesMapper.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void annotationWithMultipleValues() { + generatedSource.addComparisonToFixtureFor( MultipleArrayValuesMapper.class ); + } + + @ProcessorTest + @WithClasses( { CustomNamedGenericClassMapper.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void annotationWithCorrectGenericClassValue() { + CustomNamedGenericClassMapper mapper = Mappers.getMapper( CustomNamedGenericClassMapper.class ); + + CustomAnnotationWithParams annotation = mapper.getClass().getAnnotation( CustomAnnotationWithParams.class ); + assertThat( annotation ).isNotNull(); + assertThat( annotation.stringParam() ).isEqualTo( "test" ); + assertThat( annotation.genericTypedClass() ).isEqualTo( Mapper.class ); + } + + @ProcessorTest + @WithClasses( { AnnotationWithoutElementNameMapper.class, CustomAnnotation.class } ) + public void annotateWithoutElementName() { + generatedSource + .forMapper( AnnotationWithoutElementNameMapper.class ) + .content() + .contains( "@CustomAnnotation(value = \"value\")" ); + } + + @ProcessorTest + @WithClasses({ MetaAnnotatedMapper.class, ClassMetaAnnotation.class, CustomClassOnlyAnnotation.class }) + public void metaAnnotationWorks() { + MetaAnnotatedMapper mapper = Mappers.getMapper( MetaAnnotatedMapper.class ); + + assertThat( mapper.getClass() ).hasAnnotation( CustomClassOnlyAnnotation.class ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithMissingParameter.class, + line = 15, + message = "Parameter \"required\" is required for annotation \"AnnotationWithRequiredParameter\"." + ) + } + ) + @WithClasses({ ErroneousMapperWithMissingParameter.class, AnnotationWithRequiredParameter.class }) + public void erroneousMapperWithMissingParameter() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithMethodOnInterface.class, + line = 15, + message = "Annotation \"CustomMethodOnlyAnnotation\" is not allowed on classes." + ) + } + ) + @WithClasses({ ErroneousMapperWithMethodOnInterface.class, CustomMethodOnlyAnnotation.class }) + public void erroneousMapperWithMethodOnInterface() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithMethodOnClass.class, + line = 15, + message = "Annotation \"CustomMethodOnlyAnnotation\" is not allowed on classes." + ) + } + ) + @WithClasses({ ErroneousMapperWithMethodOnClass.class, CustomMethodOnlyAnnotation.class }) + public void erroneousMapperWithMethodOnClass() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithAnnotationOnlyOnInterface.class, + line = 15, + message = "Annotation \"CustomAnnotationOnlyAnnotation\" is not allowed on classes." + ) + } + ) + @WithClasses({ ErroneousMapperWithAnnotationOnlyOnInterface.class, CustomAnnotationOnlyAnnotation.class }) + public void erroneousMapperWithAnnotationOnlyOnInterface() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithAnnotationOnlyOnClass.class, + line = 15, + message = "Annotation \"CustomAnnotationOnlyAnnotation\" is not allowed on classes." + ) + } + ) + @WithClasses({ ErroneousMapperWithAnnotationOnlyOnClass.class, CustomAnnotationOnlyAnnotation.class }) + public void erroneousMapperWithAnnotationOnlyOnClass() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithClassOnMethod.class, + line = 17, + message = "Annotation \"CustomClassOnlyAnnotation\" is not allowed on methods." + ) + } + ) + @WithClasses({ ErroneousMapperWithClassOnMethod.class, CustomClassOnlyAnnotation.class }) + public void erroneousMapperWithClassOnMethod() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithUnknownParameter.class, + line = 17, + message = "Unknown parameter \"unknownParameter\" for annotation \"CustomAnnotation\"." + + " Did you mean \"value\"?" + ) + } + ) + @WithClasses({ ErroneousMapperWithUnknownParameter.class, CustomAnnotation.class }) + public void erroneousMapperWithUnknownParameter() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithNonExistantEnum.class, + line = 17, + message = "Enum \"AnnotateWithEnum\" does not have value \"NON_EXISTANT\"." + ) + } + ) + @WithClasses( { ErroneousMapperWithNonExistantEnum.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperWithNonExistantEnum() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithTooManyParameterValues.class, + line = 17, + message = "Parameter \"stringParam\" has too many value types supplied, type \"String\" is expected" + + " for annotation \"CustomAnnotationWithParams\"." + ) + } + ) + @WithClasses( { ErroneousMapperWithTooManyParameterValues.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperWithTooManyParameterValues() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 16, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"boolean\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 18, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"byte\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 20, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"char\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 22, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"CustomAnnotationWithParams\"" + + " but of type \"String\" for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 24, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"double\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 26, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"float\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 28, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"int\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 30, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"long\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 32, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"short\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 35, + alternativeLine = 43, + message = "Parameter \"genericTypedClass\" is not of type \"String\" " + + "but of type \"Class\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 36, + alternativeLine = 43, + message = "Parameter \"enumParam\" is not of type \"WrongAnnotateWithEnum\" " + + "but of type \"AnnotateWithEnum\" for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 40, + alternativeLine = 43, + message = "Parameter \"genericTypedClass\" is not of type \"ErroneousMapperWithWrongParameter\" " + + "but of type \"Class\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 42, + alternativeLine = 43, + message = "Parameter \"value\" is not of type \"boolean\" " + + "but of type \"String\" for annotation \"CustomAnnotation\"." + ) + } + ) + @WithClasses({ + ErroneousMapperWithWrongParameter.class, CustomAnnotationWithParams.class, + CustomAnnotationWithParamsContainer.class, WrongAnnotateWithEnum.class, CustomAnnotation.class + }) + public void erroneousMapperWithWrongParameter() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 17, + alternativeLine = 43, + message = "Parameter \"stringParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 18, + alternativeLine = 43, + message = "Parameter \"booleanParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 19, + alternativeLine = 32, + message = "Parameter \"byteParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 20, + alternativeLine = 32, + message = "Parameter \"charParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 21, + alternativeLine = 32, + message = "Parameter \"doubleParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 22, + alternativeLine = 32, + message = "Parameter \"floatParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 23, + alternativeLine = 32, + message = "Parameter \"intParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 24, + alternativeLine = 32, + message = "Parameter \"longParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 25, + alternativeLine = 32, + message = "Parameter \"shortParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 26, + alternativeLine = 32, + message = "Parameter \"genericTypedClass\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 27, + alternativeLine = 32, + message = "Parameter \"enumParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ) + } + ) + @WithClasses( { ErroneousMultipleArrayValuesMapper.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperUsingMultipleValuesInsteadOfSingle() { + } + + @ProcessorTest + @WithClasses( { MapperWithMissingAnnotationElementName.class, + CustomAnnotationWithTwoAnnotationElements.class } ) + public void mapperWithMissingAnnotationElementNameShouldCompile() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithMissingEnumClass.class, + line = 17, + message = "enumClass needs to be defined when using enums." + ) + } + ) + @WithClasses( { ErroneousMapperWithMissingEnumClass.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperWithMissingEnumClass() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithMissingEnums.class, + line = 17, + message = "enums needs to be defined when using enumClass." + ) + } + ) + @WithClasses( { ErroneousMapperWithMissingEnums.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperWithMissingEnums() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithRepeatOfNotRepeatableAnnotation.class, + line = 16, + alternativeLine = 17, + message = "Annotation \"CustomAnnotation\" is not repeatable." + ) + } + ) + @WithClasses( { ErroneousMapperWithRepeatOfNotRepeatableAnnotation.class, CustomAnnotation.class } ) + public void erroneousMapperWithRepeatOfNotRepeatableAnnotation() { + } + + @ProcessorTest + @WithClasses( { MapperWithRepeatableAnnotation.class, CustomRepeatableAnnotation.class, + CustomRepeatableAnnotationContainer.class } ) + public void mapperWithRepeatableAnnotationShouldCompile() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithParameterRepeat.class, + line = 18, + message = "Parameter \"stringParam\" must not be defined more than once." + ) + } + ) + @WithClasses( { ErroneousMapperWithParameterRepeat.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperWithParameterRepeat() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = MapperWithIdenticalAnnotationRepeated.class, + line = 16, + alternativeLine = 17, + message = "Annotation \"CustomRepeatableAnnotation\" is already present " + + "with the same elements configuration." + ) + } + ) + @WithClasses( { MapperWithIdenticalAnnotationRepeated.class, CustomRepeatableAnnotation.class, + CustomRepeatableAnnotationContainer.class } ) + public void mapperWithIdenticalAnnotationRepeated() { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithRequiredParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithRequiredParameter.java new file mode 100644 index 0000000000..183b865928 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithRequiredParameter.java @@ -0,0 +1,19 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +public @interface AnnotationWithRequiredParameter { + String required(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithoutElementNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithoutElementNameMapper.java new file mode 100644 index 0000000000..8bb83ba562 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithoutElementNameMapper.java @@ -0,0 +1,15 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +@Mapper +@AnnotateWith( value = CustomAnnotation.class, elements = @AnnotateWith.Element( strings = "value" ) ) +public interface AnnotationWithoutElementNameMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ClassMetaAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ClassMetaAnnotation.java new file mode 100644 index 0000000000..057cb384fb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ClassMetaAnnotation.java @@ -0,0 +1,20 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.mapstruct.AnnotateWith; + +@Retention( RetentionPolicy.RUNTIME ) +@Target( { ElementType.TYPE } ) +@AnnotateWith( CustomClassOnlyAnnotation.class ) +public @interface ClassMetaAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotation.java new file mode 100644 index 0000000000..71ee6f12e3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotation.java @@ -0,0 +1,19 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +public @interface CustomAnnotation { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationOnlyAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationOnlyAnnotation.java new file mode 100644 index 0000000000..52d3e386a0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationOnlyAnnotation.java @@ -0,0 +1,18 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention(RUNTIME) +@Target({ ANNOTATION_TYPE }) +public @interface CustomAnnotationOnlyAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParams.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParams.java new file mode 100644 index 0000000000..bc25302fc1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParams.java @@ -0,0 +1,64 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { METHOD, TYPE } ) +@Repeatable( CustomAnnotationWithParamsContainer.class ) +public @interface CustomAnnotationWithParams { + String stringParam(); + + Class genericTypedClass() default CustomAnnotationWithParams.class; + + AnnotateWithEnum enumParam() default AnnotateWithEnum.EXISTING; + + byte byteParam() default 0x00; + + char charParam() default 'a'; + + double doubleParam() default 0.0; + + float floatParam() default 0.0f; + + int intParam() default 0; + + long longParam() default 0L; + + short shortParam() default 0; + + boolean booleanParam() default false; + + short[] shortArray() default {}; + + byte[] byteArray() default {}; + + int[] intArray() default {}; + + long[] longArray() default {}; + + float[] floatArray() default {}; + + double[] doubleArray() default {}; + + char[] charArray() default {}; + + boolean[] booleanArray() default {}; + + String[] stringArray() default {}; + + Class[] classArray() default {}; + + AnnotateWithEnum[] enumArray() default {}; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParamsContainer.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParamsContainer.java new file mode 100644 index 0000000000..51ed62885c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParamsContainer.java @@ -0,0 +1,19 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +public @interface CustomAnnotationWithParamsContainer { + CustomAnnotationWithParams[] value() default {}; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithTwoAnnotationElements.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithTwoAnnotationElements.java new file mode 100644 index 0000000000..fb8c5c69f8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithTwoAnnotationElements.java @@ -0,0 +1,20 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +public @interface CustomAnnotationWithTwoAnnotationElements { + String value() default ""; + boolean namedAnnotationElement() default false; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomClassOnlyAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomClassOnlyAnnotation.java new file mode 100644 index 0000000000..858c8fb52f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomClassOnlyAnnotation.java @@ -0,0 +1,18 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE } ) +public @interface CustomClassOnlyAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomMethodOnlyAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomMethodOnlyAnnotation.java new file mode 100644 index 0000000000..1dfe8e734e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomMethodOnlyAnnotation.java @@ -0,0 +1,18 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { METHOD } ) +public @interface CustomMethodOnlyAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedGenericClassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedGenericClassMapper.java new file mode 100644 index 0000000000..a320d56bba --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedGenericClassMapper.java @@ -0,0 +1,22 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element( name = "stringParam", strings = "test" ), + @Element( name = "genericTypedClass", classes = Mapper.class ) +} ) +public interface CustomNamedGenericClassMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java new file mode 100644 index 0000000000..32241ab4ab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java @@ -0,0 +1,40 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element( name = "stringArray", strings = "test" ), + @Element( name = "stringParam", strings = "test" ), + @Element( name = "booleanArray", booleans = true ), + @Element( name = "booleanParam", booleans = true ), + @Element( name = "byteArray", bytes = 0x10 ), + @Element( name = "byteParam", bytes = 0x13 ), + @Element( name = "charArray", chars = 'd' ), + @Element( name = "charParam", chars = 'a' ), + @Element( name = "enumArray", enumClass = AnnotateWithEnum.class, enums = "EXISTING" ), + @Element( name = "enumParam", enumClass = AnnotateWithEnum.class, enums = "EXISTING" ), + @Element( name = "doubleArray", doubles = 0.3 ), + @Element( name = "doubleParam", doubles = 1.2 ), + @Element( name = "floatArray", floats = 0.3f ), + @Element( name = "floatParam", floats = 1.2f ), + @Element( name = "intArray", ints = 3 ), + @Element( name = "intParam", ints = 1 ), + @Element( name = "longArray", longs = 3L ), + @Element( name = "longParam", longs = 1L ), + @Element( name = "shortArray", shorts = 3 ), + @Element( name = "shortParam", shorts = 1 ) +} ) +public interface CustomNamedMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotation.java new file mode 100644 index 0000000000..d7c0c85107 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotation.java @@ -0,0 +1,21 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +@Repeatable( CustomRepeatableAnnotationContainer.class ) +public @interface CustomRepeatableAnnotation { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotationContainer.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotationContainer.java new file mode 100644 index 0000000000..8de3a8bf0a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotationContainer.java @@ -0,0 +1,19 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +public @interface CustomRepeatableAnnotationContainer { + CustomRepeatableAnnotation[] value() default {}; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/DeprecateAndCustomMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/DeprecateAndCustomMapper.java new file mode 100644 index 0000000000..809ad5702a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/DeprecateAndCustomMapper.java @@ -0,0 +1,19 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( Deprecated.class ) +@AnnotateWith( CustomAnnotation.class ) +public interface DeprecateAndCustomMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnClass.java new file mode 100644 index 0000000000..d617e8d0fc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnClass.java @@ -0,0 +1,18 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +@AnnotateWith( value = CustomAnnotationOnlyAnnotation.class ) +public abstract class ErroneousMapperWithAnnotationOnlyOnClass { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnInterface.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnInterface.java new file mode 100644 index 0000000000..4e914b30a3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnInterface.java @@ -0,0 +1,18 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +@AnnotateWith( value = CustomAnnotationOnlyAnnotation.class ) +public interface ErroneousMapperWithAnnotationOnlyOnInterface { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java new file mode 100644 index 0000000000..d3d53c910a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java @@ -0,0 +1,27 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface ErroneousMapperWithClassOnMethod { + + @AnnotateWith( value = CustomClassOnlyAnnotation.class ) + Target toString(Source value); + + class Source { + + } + + class Target { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnClass.java new file mode 100644 index 0000000000..262880d6f5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnClass.java @@ -0,0 +1,18 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomMethodOnlyAnnotation.class ) +public abstract class ErroneousMapperWithMethodOnClass { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnInterface.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnInterface.java new file mode 100644 index 0000000000..75f9039093 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnInterface.java @@ -0,0 +1,18 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomMethodOnlyAnnotation.class ) +public interface ErroneousMapperWithMethodOnInterface { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnumClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnumClass.java new file mode 100644 index 0000000000..6905a363f2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnumClass.java @@ -0,0 +1,22 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = { @Element( name = "enumParam", enums = "EXISTING" ), + @Element( name = "stringParam", strings = "required" ) } +) +public interface ErroneousMapperWithMissingEnumClass { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnums.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnums.java new file mode 100644 index 0000000000..fc7096d290 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnums.java @@ -0,0 +1,21 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @Element( name = "stringParam", strings = "required", enumClass = AnnotateWithEnum.class ) +) +public interface ErroneousMapperWithMissingEnums { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingParameter.java new file mode 100644 index 0000000000..40ffa53094 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingParameter.java @@ -0,0 +1,18 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( AnnotationWithRequiredParameter.class ) +public interface ErroneousMapperWithMissingParameter { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithNonExistantEnum.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithNonExistantEnum.java new file mode 100644 index 0000000000..1ed85b990d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithNonExistantEnum.java @@ -0,0 +1,22 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = { @Element( name = "enumParam", enumClass = AnnotateWithEnum.class, enums = "NON_EXISTANT" ), + @Element( name = "stringParam", strings = "required" ) } +) +public interface ErroneousMapperWithNonExistantEnum { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithParameterRepeat.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithParameterRepeat.java new file mode 100644 index 0000000000..d75c7a4ee4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithParameterRepeat.java @@ -0,0 +1,22 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element( name = "stringParam", strings = "test" ), + @Element( name = "stringParam", strings = "otherValue" ) +} ) +public interface ErroneousMapperWithParameterRepeat { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithRepeatOfNotRepeatableAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithRepeatOfNotRepeatableAnnotation.java new file mode 100644 index 0000000000..be9596ea46 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithRepeatOfNotRepeatableAnnotation.java @@ -0,0 +1,19 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotation.class ) +@AnnotateWith( value = CustomAnnotation.class ) +public interface ErroneousMapperWithRepeatOfNotRepeatableAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithTooManyParameterValues.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithTooManyParameterValues.java new file mode 100644 index 0000000000..4b6c20c39b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithTooManyParameterValues.java @@ -0,0 +1,21 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @Element( name = "stringParam", booleans = true, strings = "test" ) +) +public interface ErroneousMapperWithTooManyParameterValues { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithUnknownParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithUnknownParameter.java new file mode 100644 index 0000000000..c705dc88f7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithUnknownParameter.java @@ -0,0 +1,21 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotation.class, + elements = @Element( name = "unknownParameter", strings = "unknown" ) +) +public interface ErroneousMapperWithUnknownParameter { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithWrongParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithWrongParameter.java new file mode 100644 index 0000000000..3251122b22 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithWrongParameter.java @@ -0,0 +1,45 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", booleans = true ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", bytes = 0x12 ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", chars = 'a' ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", classes = CustomAnnotationWithParams.class ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", doubles = 12.34 ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", floats = 12.34f ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", ints = 1234 ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", longs = 1234L ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", shorts = 12 ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @AnnotateWith.Element( name = "stringParam", strings = "correctValue" ), + @AnnotateWith.Element( name = "genericTypedClass", strings = "wrong" ), + @AnnotateWith.Element( name = "enumParam", enumClass = WrongAnnotateWithEnum.class, enums = "EXISTING" ) +} ) +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @AnnotateWith.Element( name = "stringParam", strings = "correctValue" ), + @AnnotateWith.Element( name = "genericTypedClass", classes = ErroneousMapperWithWrongParameter.class ) +} ) +@AnnotateWith( value = CustomAnnotation.class, elements = @AnnotateWith.Element( booleans = true ) ) +public interface ErroneousMapperWithWrongParameter { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMultipleArrayValuesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMultipleArrayValuesMapper.java new file mode 100644 index 0000000000..f6bf7629cd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMultipleArrayValuesMapper.java @@ -0,0 +1,31 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element( name = "stringParam", strings = { "test1", "test2" } ), + @Element( name = "booleanParam", booleans = { false, true } ), + @Element( name = "byteParam", bytes = { 0x08, 0x1f } ), + @Element( name = "charParam", chars = { 'b', 'c' } ), + @Element( name = "doubleParam", doubles = { 1.2, 3.4 } ), + @Element( name = "floatParam", floats = { 1.2f, 3.4f } ), + @Element( name = "intParam", ints = { 12, 34 } ), + @Element( name = "longParam", longs = { 12L, 34L } ), + @Element( name = "shortParam", shorts = { 12, 34 } ), + @Element( name = "genericTypedClass", classes = { Mapper.class, CustomAnnotationWithParams.class } ), + @Element( name = "enumParam", enumClass = AnnotateWithEnum.class, enums = { "EXISTING", "OTHER_EXISTING" } ) +} ) +public interface ErroneousMultipleArrayValuesMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithIdenticalAnnotationRepeated.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithIdenticalAnnotationRepeated.java new file mode 100644 index 0000000000..0959ee305a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithIdenticalAnnotationRepeated.java @@ -0,0 +1,19 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomRepeatableAnnotation.class, elements = @AnnotateWith.Element( strings = "identical" ) ) +@AnnotateWith( value = CustomRepeatableAnnotation.class, elements = @AnnotateWith.Element( strings = "identical" ) ) +public interface MapperWithIdenticalAnnotationRepeated { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithMissingAnnotationElementName.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithMissingAnnotationElementName.java new file mode 100644 index 0000000000..ed9523c545 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithMissingAnnotationElementName.java @@ -0,0 +1,21 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithTwoAnnotationElements.class, elements = { + @AnnotateWith.Element( strings = "unnamed annotation element" ), + @AnnotateWith.Element( name = "namedAnnotationElement", booleans = false ) +} ) +public abstract class MapperWithMissingAnnotationElementName { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithRepeatableAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithRepeatableAnnotation.java new file mode 100644 index 0000000000..b66ec68261 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithRepeatableAnnotation.java @@ -0,0 +1,19 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomRepeatableAnnotation.class ) +@AnnotateWith( value = CustomRepeatableAnnotation.class, elements = @AnnotateWith.Element( strings = "different" ) ) +public interface MapperWithRepeatableAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MetaAnnotatedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MetaAnnotatedMapper.java new file mode 100644 index 0000000000..2dddb17f83 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MetaAnnotatedMapper.java @@ -0,0 +1,14 @@ +/* + * 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.annotatewith; + +import org.mapstruct.Mapper; + +@ClassMetaAnnotation +@Mapper +public interface MetaAnnotatedMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MethodMetaAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MethodMetaAnnotation.java new file mode 100644 index 0000000000..a93d54c283 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MethodMetaAnnotation.java @@ -0,0 +1,20 @@ +/* + * 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.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.mapstruct.AnnotateWith; + +@Retention( RetentionPolicy.RUNTIME ) +@Target( { ElementType.METHOD } ) +@AnnotateWith( CustomMethodOnlyAnnotation.class ) +public @interface MethodMetaAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapper.java new file mode 100644 index 0000000000..2ccdbb0ea2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapper.java @@ -0,0 +1,31 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element( name = "stringArray", strings = { "test1", "test2" } ), + @Element( name = "booleanArray", booleans = { false, true } ), + @Element( name = "byteArray", bytes = { 0x08, 0x1f } ), + @Element( name = "charArray", chars = { 'b', 'c' } ), + @Element( name = "doubleArray", doubles = { 1.2, 3.4 } ), + @Element( name = "floatArray", floats = { 1.2f, 3.4f } ), + @Element( name = "intArray", ints = { 12, 34 } ), + @Element( name = "longArray", longs = { 12L, 34L } ), + @Element( name = "shortArray", shorts = { 12, 34 } ), + @Element( name = "classArray", classes = { Mapper.class, CustomAnnotationWithParams.class } ), + @Element( name = "stringParam", strings = "required parameter" ) +} ) +public interface MultipleArrayValuesMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/WrongAnnotateWithEnum.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/WrongAnnotateWithEnum.java new file mode 100644 index 0000000000..677a6cc6d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/WrongAnnotateWithEnum.java @@ -0,0 +1,10 @@ +/* + * 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.annotatewith; + +public enum WrongAnnotateWithEnum { + EXISTING +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java new file mode 100644 index 0000000000..cb01cfec85 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java @@ -0,0 +1,17 @@ +/* + * 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.annotatewith; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2022-06-06T16:48:18+0200", + comments = "version: , compiler: javac, environment: Java 11.0.12 (Azul Systems, Inc.)" +) +@CustomAnnotationWithParams(stringArray = "test", stringParam = "test", booleanArray = true, booleanParam = true, byteArray = 16, byteParam = 19, charArray = 'd', charParam = 'a', enumArray = AnnotateWithEnum.EXISTING, enumParam = AnnotateWithEnum.EXISTING, doubleArray = 0.3, doubleParam = 1.2, floatArray = 0.300000011920929f, floatParam = 1.2000000476837158f, intArray = 3, intParam = 1, longArray = 3L, longParam = 1L, shortArray = 3, shortParam = 1) +public class CustomNamedMapperImpl implements CustomNamedMapper { +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java new file mode 100644 index 0000000000..fe9bb93f13 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java @@ -0,0 +1,18 @@ +/* + * 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.annotatewith; + +import javax.annotation.processing.Generated; +import org.mapstruct.Mapper; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2022-06-06T16:48:23+0200", + comments = "version: , compiler: javac, environment: Java 11.0.12 (Azul Systems, Inc.)" +) +@CustomAnnotationWithParams(stringArray = { "test1", "test2" }, booleanArray = { false, true }, byteArray = { 8, 31 }, charArray = { 'b', 'c' }, doubleArray = { 1.2, 3.4 }, floatArray = { 1.2000000476837158f, 3.4000000953674316f }, intArray = { 12, 34 }, longArray = { 12L, 34L }, shortArray = { 12, 34 }, classArray = { Mapper.class, CustomAnnotationWithParams.class }, stringParam = "required parameter") +public class MultipleArrayValuesMapperImpl implements MultipleArrayValuesMapper { +} From 17997ef617e6ba5d9b9c70e7e5e8484cd82dbc53 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sat, 20 Aug 2022 13:01:47 +0200 Subject: [PATCH 097/363] #2901: Fix `@TargetType` annotation on a `@Condition` annotated method for a Collection value --- .../ap/internal/model/macro/CommonMacros.ftl | 3 ++- ...itionWithTargetTypeOnCollectionMapper.java | 23 +++++++++++++++++++ .../ap/test/bugs/_2901/Issue2901Test.java | 22 ++++++++++++++++++ .../mapstruct/ap/test/bugs/_2901/Source.java | 21 +++++++++++++++++ .../mapstruct/ap/test/bugs/_2901/Target.java | 21 +++++++++++++++++ 5 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/ConditionWithTargetTypeOnCollectionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Issue2901Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Target.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index e5fb970605..bce28ebe19 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -60,7 +60,8 @@ <#macro handleLocalVarNullCheck needs_explicit_local_var> <#if sourcePresenceCheckerReference??> if ( <@includeModel object=sourcePresenceCheckerReference - targetPropertyName=ext.targetPropertyName/> ) { + targetType=ext.targetType + targetPropertyName=ext.targetPropertyName /> ) { <#if needs_explicit_local_var> <@includeModel object=nullCheckLocalVarType/> ${nullCheckLocalVarName} = <@lib.handleAssignment/>; <#nested> diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/ConditionWithTargetTypeOnCollectionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/ConditionWithTargetTypeOnCollectionMapper.java new file mode 100644 index 0000000000..7e833cfc53 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/ConditionWithTargetTypeOnCollectionMapper.java @@ -0,0 +1,23 @@ +/* + * 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._2901; + +import java.util.List; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.TargetType; + +@Mapper +public interface ConditionWithTargetTypeOnCollectionMapper { + + Target map(Source source); + + @Condition + default boolean check(List test, @TargetType Class type) { + return type.isInstance( test ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Issue2901Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Issue2901Test.java new file mode 100644 index 0000000000..3ae2c3db86 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Issue2901Test.java @@ -0,0 +1,22 @@ +/* + * 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._2901; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Ben Zegveld + */ +@IssueKey( "2901" ) +class Issue2901Test { + + @ProcessorTest + @WithClasses( { Source.class, Target.class, ConditionWithTargetTypeOnCollectionMapper.class } ) + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Source.java new file mode 100644 index 0000000000..4eb50d58fb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Source.java @@ -0,0 +1,21 @@ +/* + * 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._2901; + +import java.util.List; + +public class Source { + + private List field; + + public List getField() { + return field; + } + + public void setField(List field) { + this.field = field; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Target.java new file mode 100644 index 0000000000..b4c5299f35 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Target.java @@ -0,0 +1,21 @@ +/* + * 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._2901; + +import java.util.List; + +public class Target { + + private List field; + + public List getField() { + return field; + } + + public void setField(List field) { + this.field = field; + } +} From 46900cabde211f885521b9b57d1d3de3f07a849a Mon Sep 17 00:00:00 2001 From: Prasanth Omanakuttan Date: Sat, 20 Aug 2022 17:07:43 +0530 Subject: [PATCH 098/363] Update Typos in javadoc (#2958) --- .../main/java/org/mapstruct/InheritInverseConfiguration.java | 4 ++-- .../org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java | 2 +- .../mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java | 4 ++-- .../mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java b/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java index ac582a5dfa..b659b7f37a 100644 --- a/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java +++ b/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java @@ -81,8 +81,8 @@ public @interface InheritInverseConfiguration { /** - * The name of the inverse mapping method to inherit the mappings from. Needs only to be specified in case more than - * one inverse method with matching source and target type exists. + * The name of the inverse mapping method to inherit the mappings from. Needs to be specified only in case more than + * one inverse method exists with a matching source and target type exists. * * @return The name of the inverse mapping method to inherit the mappings from. */ diff --git a/processor/src/main/java/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java b/processor/src/main/java/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java index 8ac41395bf..6b9e079523 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java @@ -15,7 +15,7 @@ *

    * This contract will be queried by MapStruct when examining types referenced by mappers to be generated, most notably * the source and target types of mapping methods. If at least one AST-modifying processor announces further changes to - * such type, the generation of the affected mapper(s) will be deferred to a future round in the annnotation processing + * such type, the generation of the affected mapper(s) will be deferred to a future round in the annotation processing * cycle. *

    * Implementations are discovered via the service loader, i.e. a JAR providing an AST-modifying processor needs to diff --git a/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java index 92fef3a1be..dc14e20de6 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java @@ -20,8 +20,8 @@ *

  • {@code mergeFrom(Target.Builder)}
  • * *

    - * When the JavaBean convention is not used with FreeBuilder then the getters are non standard and MapStruct - * won't recognize them. Therefore one needs to use the JavaBean convention in which the fluent setters + * When the JavaBean convention is not used with FreeBuilder then the getters are non-standard and MapStruct + * won't recognize them. Therefore, one needs to use the JavaBean convention in which the fluent setters * start with {@code set}. * * @author Filip Hrisafov diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java index 69c66cdd28..100798e063 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java @@ -10,9 +10,9 @@ import org.mapstruct.util.Experimental; /** - * Accesor naming strategy for Immutables. + * Accessor naming strategy for Immutables. * The generated Immutables also have a from that works as a copy. Our default strategy considers this method - * as a setter with a name {@code from}. Therefore we are ignoring it. + * as a setter with a name {@code from}. Therefore, we are ignoring it. * * @author Filip Hrisafov */ From 54321d6e663d656a0d33bbddb3a7e030f8faa74c Mon Sep 17 00:00:00 2001 From: Hakan Date: Sat, 20 Aug 2022 15:23:32 +0200 Subject: [PATCH 099/363] #2839 Keep thrown types when creating a new ForgedMethod with the same arguments This fixes a compilation error when mapping fields with the same type due to not wrapping in a `try-catch` block --- .../ap/internal/model/ForgedMethod.java | 2 +- .../org/mapstruct/ap/test/bugs/_2839/Car.java | 36 ++++++++++++ .../mapstruct/ap/test/bugs/_2839/CarDto.java | 36 ++++++++++++ .../ap/test/bugs/_2839/CarMapper.java | 25 +++++++++ .../org/mapstruct/ap/test/bugs/_2839/Id.java | 28 ++++++++++ .../test/bugs/_2839/Issue2839Exception.java | 16 ++++++ .../ap/test/bugs/_2839/Issue2839Test.java | 56 +++++++++++++++++++ 7 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Car.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Id.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Exception.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java index a1f5b091ce..a33bc7520e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java @@ -196,7 +196,7 @@ private ForgedMethod(String name, Type sourceType, Type returnType, List(); + this.thrownTypes = forgedMethod.thrownTypes; this.history = forgedMethod.history; this.sourceParameters = Parameter.getSourceParameters( parameters ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Car.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Car.java new file mode 100644 index 0000000000..9419223a42 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Car.java @@ -0,0 +1,36 @@ +/* + * 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._2839; + +import java.util.List; + +/** + * @author Hakan Özkan + */ +public final class Car { + + private final Id id; + private final List seatIds; + private final List tireIds; + + public Car(Id id, List seatIds, List tireIds) { + this.id = id; + this.seatIds = seatIds; + this.tireIds = tireIds; + } + + public Id getId() { + return id; + } + + public List getSeatIds() { + return seatIds; + } + + public List getTireIds() { + return tireIds; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarDto.java new file mode 100644 index 0000000000..68741ebc92 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarDto.java @@ -0,0 +1,36 @@ +/* + * 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._2839; + +import java.util.List; + +/** + * @author Hakan Özkan + */ +public final class CarDto { + + private final String id; + private final List seatIds; + private final List tireIds; + + public CarDto(String id, List seatIds, List tireIds) { + this.id = id; + this.seatIds = seatIds; + this.tireIds = tireIds; + } + + public String getId() { + return id; + } + + public List getSeatIds() { + return seatIds; + } + + public List getTireIds() { + return tireIds; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarMapper.java new file mode 100644 index 0000000000..e535935d21 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarMapper.java @@ -0,0 +1,25 @@ +/* + * 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._2839; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Hakan Özkan + */ +@Mapper +public abstract class CarMapper { + + public static final CarMapper MAPPER = Mappers.getMapper( CarMapper.class ); + + public abstract Car toEntity(CarDto dto); + + protected Id mapId(String id) throws Issue2839Exception { + throw new Issue2839Exception("For id " + id); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Id.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Id.java new file mode 100644 index 0000000000..5bb9a29dd7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Id.java @@ -0,0 +1,28 @@ +/* + * 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._2839; + +import java.util.UUID; + +/** + * @author Hakan Özkan + */ +public class Id { + + private final UUID id; + + public Id() { + this.id = UUID.randomUUID(); + } + + public Id(UUID id) { + this.id = id; + } + + public UUID getId() { + return id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Exception.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Exception.java new file mode 100644 index 0000000000..91f02014d6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Exception.java @@ -0,0 +1,16 @@ +/* + * 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._2839; + +/** + * @author Hakan Özkan + */ +public class Issue2839Exception extends Exception { + + public Issue2839Exception(String message) { + super( message ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Test.java new file mode 100644 index 0000000000..ed61c39a63 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Test.java @@ -0,0 +1,56 @@ +/* + * 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._2839; + +import java.util.Collections; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Hakan Özkan + */ +@IssueKey("2839") +@WithClasses({ + Car.class, + CarDto.class, + CarMapper.class, + Id.class, + Issue2839Exception.class, +}) +public class Issue2839Test { + + @ProcessorTest + void shouldCompile() { + CarDto car1 = new CarDto( + "carId", + Collections.singletonList( "seatId" ), + Collections.singletonList( "tireId" ) + ); + assertThatThrownBy( () -> CarMapper.MAPPER.toEntity( car1 ) ) + .isExactlyInstanceOf( RuntimeException.class ) + .getCause() + .isInstanceOf( Issue2839Exception.class ) + .hasMessage( "For id seatId" ); + + CarDto car2 = new CarDto( "carId", Collections.emptyList(), Collections.singletonList( "tireId" ) ); + assertThatThrownBy( () -> CarMapper.MAPPER.toEntity( car2 ) ) + .isExactlyInstanceOf( RuntimeException.class ) + .getCause() + .isInstanceOf( Issue2839Exception.class ) + .hasMessage( "For id tireId" ); + + CarDto car3 = new CarDto( "carId", Collections.emptyList(), Collections.emptyList() ); + assertThatThrownBy( () -> CarMapper.MAPPER.toEntity( car3 ) ) + .isExactlyInstanceOf( RuntimeException.class ) + .getCause() + .isInstanceOf( Issue2839Exception.class ) + .hasMessage( "For id carId" ); + } +} From 4118a446306ab3d413285ccf1e0c709aa55de4c2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 21 Aug 2022 10:56:16 +0200 Subject: [PATCH 100/363] #2974 Fix typos in documentation Closes #2974 --- .../main/asciidoc/chapter-10-advanced-mapping-options.asciidoc | 2 +- documentation/src/main/asciidoc/chapter-2-set-up.asciidoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index decee792d0..9c1b2243eb 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -292,7 +292,7 @@ The source presence checker name can be changed in the MapStruct service provide [NOTE] ==== -Some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder as target accessor see `CollectionMappingStrategy`, MapStruct will always generate a source property +Some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder as target accessor (see `CollectionMappingStrategy`), MapStruct will always generate a source property null check, regardless the value of the `NullValueCheckStrategy` to avoid addition of `null` to the target collection or map. ==== diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index d95983e840..48b352bd94 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -130,7 +130,7 @@ You can find a complete example in the https://github.com/mapstruct/mapstruct-ex The MapStruct code generator can be configured using _annotation processor options_. -When invoking javac directly, these options are passed to the compiler in the form _-Akey=value_. When using MapStruct via Maven, any processor options can be passed using an `options` element within the configuration of the Maven processor plug-in like this: +When invoking javac directly, these options are passed to the compiler in the form _-Akey=value_. When using MapStruct via Maven, any processor options can be passed using `compilerArgs` within the configuration of the Maven processor plug-in like this: .Maven configuration ==== From 3e0c62ac36e6adbf689031a62be3b96457348740 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 21 Aug 2022 11:26:42 +0200 Subject: [PATCH 101/363] Publish snapshots when on main --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 40999c680a..54cf0b1076 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,7 +43,7 @@ jobs: - name: 'Upload coverage to Codecov' uses: codecov/codecov-action@v2 - name: 'Publish Snapshots' - if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'mapstruct/mapstruct' + if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' run: ./mvnw -s etc/ci-settings.xml -DskipTests=true -DskipDistribution=true deploy linux-jdk-8: name: 'Linux JDK 8' From ef4c26b075896dd564e63235ae98dbaedb7d512f Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 24 Aug 2022 18:36:43 +0200 Subject: [PATCH 102/363] #2949 Do not inverse inherit BeanMapping#ignoreUnmappedSourceProperties --- .../model/source/BeanMappingOptions.java | 11 ++-- .../ap/test/bugs/_2949/Issue2949Mapper.java | 59 +++++++++++++++++++ .../ap/test/bugs/_2949/Issue2949Test.java | 35 +++++++++++ 3 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index cc03878362..60aac32e54 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -34,6 +34,7 @@ public class BeanMappingOptions extends DelegatingOptions { private final SelectionParameters selectionParameters; + private final List ignoreUnmappedSourceProperties; private final BeanMappingGem beanMapping; /** @@ -46,6 +47,7 @@ public class BeanMappingOptions extends DelegatingOptions { public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping) { BeanMappingOptions options = new BeanMappingOptions( SelectionParameters.forInheritance( beanMapping.selectionParameters ), + Collections.emptyList(), beanMapping.beanMapping, beanMapping ); @@ -57,7 +59,7 @@ public static BeanMappingOptions getInstanceOn(BeanMappingGem beanMapping, Mappe TypeUtils typeUtils, TypeFactory typeFactory ) { if ( beanMapping == null || !isConsistent( beanMapping, method, messager ) ) { - BeanMappingOptions options = new BeanMappingOptions( null, null, mapperOptions ); + BeanMappingOptions options = new BeanMappingOptions( null, Collections.emptyList(), null, mapperOptions ); return options; } @@ -77,6 +79,7 @@ public static BeanMappingOptions getInstanceOn(BeanMappingGem beanMapping, Mappe //TODO Do we want to add the reporting policy to the BeanMapping as well? To give more granular support? BeanMappingOptions options = new BeanMappingOptions( selectionParameters, + beanMapping.ignoreUnmappedSourceProperties().get(), beanMapping, mapperOptions ); @@ -104,10 +107,12 @@ private static boolean isConsistent(BeanMappingGem gem, ExecutableElement method } private BeanMappingOptions(SelectionParameters selectionParameters, + List ignoreUnmappedSourceProperties, BeanMappingGem beanMapping, DelegatingOptions next) { super( next ); this.selectionParameters = selectionParameters; + this.ignoreUnmappedSourceProperties = ignoreUnmappedSourceProperties; this.beanMapping = beanMapping; } @@ -188,9 +193,7 @@ public boolean isignoreByDefault() { } public List getIgnoreUnmappedSourceProperties() { - return Optional.ofNullable( beanMapping ).map( BeanMappingGem::ignoreUnmappedSourceProperties ) - .map( GemValue::get ) - .orElse( Collections.emptyList() ); + return ignoreUnmappedSourceProperties; } public AnnotationMirror getMirror() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Mapper.java new file mode 100644 index 0000000000..553563066e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Mapper.java @@ -0,0 +1,59 @@ +/* + * 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._2949; + +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface Issue2949Mapper { + + Issue2949Mapper INSTANCE = Mappers.getMapper( Issue2949Mapper.class ); + + @Mapping( target = "property1", ignore = true) + @InheritInverseConfiguration + Source toSource(Target target); + + @BeanMapping(ignoreUnmappedSourceProperties = { "property1" }) + Target toTarget(Source source); + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + private final String value; + private final String property1; + + public Source(String value, String property1) { + this.value = value; + this.property1 = property1; + } + + public String getValue() { + return value; + } + + public String getProperty1() { + return property1; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Test.java new file mode 100644 index 0000000000..2a277a4bfd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Test.java @@ -0,0 +1,35 @@ +/* + * 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._2949; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue2949Mapper.class +}) +class Issue2949Test { + + @ProcessorTest + void shouldCorrectlyInheritInverseBeanMappingWithIgnoreUnmappedSourceProeprties() { + Issue2949Mapper.Target target = Issue2949Mapper.INSTANCE.toTarget( new Issue2949Mapper.Source( + "test", + "first" + ) ); + + assertThat( target.getValue() ).isEqualTo( "test" ); + + Issue2949Mapper.Source source = Issue2949Mapper.INSTANCE.toSource( target ); + + assertThat( source.getValue() ).isEqualTo( "test" ); + assertThat( source.getProperty1() ).isNull(); + } +} From 874bf1fd2c44065376483c2a9a700455aa0c31ce Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 24 Aug 2022 18:38:44 +0200 Subject: [PATCH 103/363] #2950 Add support for Jakarta CDI --- .../java/org/mapstruct/MappingConstants.java | 13 ++++- .../test/resources/fullFeatureTest/pom.xml | 4 ++ parent/pom.xml | 5 ++ processor/pom.xml | 5 ++ .../ap/internal/gem/MappingConstantsGem.java | 2 + .../processor/CdiComponentProcessor.java | 26 ++++++++- .../JakartaCdiComponentProcessor.java | 52 +++++++++++++++++ ...p.internal.processor.ModelElementProcessor | 1 + .../mapstruct/ap/test/gem/ConstantTest.java | 4 +- ...diDefaultCompileOptionFieldMapperTest.java | 54 ++++++++++++++++++ ...merCdiDefaultCompileOptionFieldMapper.java | 21 +++++++ ...derCdiDefaultCompileOptionFieldMapper.java | 26 +++++++++ ...merCdiDefaultCompileOptionFieldMapper.java | 21 +++++++ ...derCdiDefaultCompileOptionFieldMapper.java | 26 +++++++++ ...diDefaultCompileOptionFieldMapperTest.java | 56 +++++++++++++++++++ ...diDefaultCompileOptionFieldMapperTest.java | 54 ++++++++++++++++++ ...rtaCdiDefaultCompileOptionFieldMapper.java | 21 +++++++ ...rtaCdiDefaultCompileOptionFieldMapper.java | 26 +++++++++ ...diDefaultCompileOptionFieldMapperTest.java | 55 ++++++++++++++++++ ...diDefaultCompileOptionFieldMapperTest.java | 54 ++++++++++++++++++ .../org/mapstruct/ap/testutil/WithCdi.java | 28 ++++++++++ .../mapstruct/ap/testutil/WithJakartaCdi.java | 28 ++++++++++ 22 files changed, 578 insertions(+), 4 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaCdiComponentProcessor.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CdiDefaultCompileOptionFieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CustomerCdiDefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/GenderCdiDefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/CustomerCdiDefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/GenderCdiDefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiCdiDefaultCompileOptionFieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/CustomerJakartaCdiDefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/GenderJakartaCdiDefaultCompileOptionFieldMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiDefaultCompileOptionFieldMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithCdi.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaCdi.java diff --git a/core/src/main/java/org/mapstruct/MappingConstants.java b/core/src/main/java/org/mapstruct/MappingConstants.java index ce2438298c..3d3d8a4c77 100644 --- a/core/src/main/java/org/mapstruct/MappingConstants.java +++ b/core/src/main/java/org/mapstruct/MappingConstants.java @@ -109,7 +109,12 @@ private ComponentModel() { public static final String DEFAULT = "default"; /** - * The generated mapper is an application-scoped CDI bean and can be retrieved via @Inject + * The generated mapper is an application-scoped CDI bean and can be retrieved via @Inject. + * The annotations are either from {@code javax} or {@code jakarta}. + * Priority have the {@code javax} annotations. + * In case you want to only use Jakarta then use {@link #JAKARTA_CDI}. + * + * @see #JAKARTA_CDI */ public static final String CDI = "cdi"; @@ -138,6 +143,12 @@ private ComponentModel() { */ public static final String JAKARTA = "jakarta"; + /** + * The generated mapper is an application-scoped Jakarta CDI bean and can be retrieved via @Inject. + * @see #CDI + */ + public static final String JAKARTA_CDI = "jakarta-cdi"; + } } diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index ec26f45ce2..62f7986e9c 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -64,6 +64,10 @@ jakarta.inject jakarta.inject-api + + jakarta.enterprise + jakarta.enterprise.cdi-api + diff --git a/parent/pom.xml b/parent/pom.xml index 1c031a7b71..426405b4b7 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -160,6 +160,11 @@ cdi-api 2.0.SP1 + + jakarta.enterprise + jakarta.enterprise.cdi-api + 4.0.1 + javax.inject javax.inject diff --git a/processor/pom.xml b/processor/pom.xml index 6f2bb6495c..ff7864f32e 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -89,6 +89,11 @@ jakarta.inject-api test + + jakarta.enterprise + jakarta.enterprise.cdi-api + test + diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java index cb6d49c0cb..bc58024ca3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java @@ -51,6 +51,8 @@ private ComponentModelGem() { public static final String JSR330 = "jsr330"; public static final String JAKARTA = "jakarta"; + + public static final String JAKARTA_CDI = "jakarta-cdi"; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java index 74ff2b118b..d8f136034b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.AnnotationProcessingException; /** * A {@link ModelElementProcessor} which converts the given {@link Mapper} @@ -30,13 +32,13 @@ protected String getComponentModelIdentifier() { @Override protected List getTypeAnnotations(Mapper mapper) { return Collections.singletonList( - new Annotation( getTypeFactory().getType( "javax.enterprise.context.ApplicationScoped" ) ) + new Annotation( getType( "ApplicationScoped" ) ) ); } @Override protected List getMapperReferenceAnnotations() { - return Arrays.asList( new Annotation( getTypeFactory().getType( "javax.inject.Inject" ) ) ); + return Arrays.asList( new Annotation( getType( "Inject" ) ) ); } @Override @@ -48,4 +50,24 @@ protected boolean requiresGenerationOfDecoratorClass() { protected boolean additionalPublicEmptyConstructor() { return true; } + + private Type getType(String simpleName) { + String javaxPrefix = "javax.inject."; + String jakartaPrefix = "jakarta.inject."; + if ( "ApplicationScoped".equals( simpleName ) ) { + javaxPrefix = "javax.enterprise.context."; + jakartaPrefix = "jakarta.enterprise.context."; + } + if ( getTypeFactory().isTypeAvailable( javaxPrefix + simpleName ) ) { + return getTypeFactory().getType( javaxPrefix + simpleName ); + } + + if ( getTypeFactory().isTypeAvailable( jakartaPrefix + simpleName ) ) { + return getTypeFactory().getType( jakartaPrefix + simpleName ); + } + + throw new AnnotationProcessingException( + "Couldn't find any of the CDI or Jakarta CDI Dependency types." + + " Are you missing a dependency on your classpath?" ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaCdiComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaCdiComponentProcessor.java new file mode 100644 index 0000000000..11c765668e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaCdiComponentProcessor.java @@ -0,0 +1,52 @@ +/* + * 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.internal.processor; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.mapstruct.ap.internal.gem.MappingConstantsGem; +import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Mapper; + +/** + * A {@link ModelElementProcessor} which converts the given {@link Mapper} + * object into an application-scoped Jakarta CDI bean in case Jakarta CDI + * is configured as the target component model for this mapper. + * + * @author Filip Hrisafov + */ +public class JakartaCdiComponentProcessor extends AnnotationBasedComponentModelProcessor { + + @Override + protected String getComponentModelIdentifier() { + return MappingConstantsGem.ComponentModelGem.JAKARTA_CDI; + } + + @Override + protected List getTypeAnnotations(Mapper mapper) { + return Collections.singletonList( + new Annotation( getTypeFactory().getType( "jakarta.enterprise.context.ApplicationScoped" ) ) + ); + } + + @Override + protected List getMapperReferenceAnnotations() { + return Arrays.asList( new Annotation( getTypeFactory().getType( "jakarta.inject.Inject" ) ) ); + } + + @Override + protected boolean requiresGenerationOfDecoratorClass() { + return false; + } + + @Override + protected boolean additionalPublicEmptyConstructor() { + return true; + } + +} diff --git a/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor b/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor index cab1d83ed7..d5234fca5b 100644 --- a/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor +++ b/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor @@ -3,6 +3,7 @@ # Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 org.mapstruct.ap.internal.processor.CdiComponentProcessor +org.mapstruct.ap.internal.processor.JakartaCdiComponentProcessor org.mapstruct.ap.internal.processor.Jsr330ComponentProcessor org.mapstruct.ap.internal.processor.JakartaComponentProcessor org.mapstruct.ap.internal.processor.MapperCreationProcessor diff --git a/processor/src/test/java/org/mapstruct/ap/test/gem/ConstantTest.java b/processor/src/test/java/org/mapstruct/ap/test/gem/ConstantTest.java index 548ee61f2e..25489a5b6b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/gem/ConstantTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/gem/ConstantTest.java @@ -34,7 +34,7 @@ public void constantsShouldBeEqual() { } @Test - public void componentModelContantsShouldBeEqual() { + public void componentModelConstantsShouldBeEqual() { assertThat( MappingConstants.ComponentModel.DEFAULT ) .isEqualTo( MappingConstantsGem.ComponentModelGem.DEFAULT ); assertThat( MappingConstants.ComponentModel.CDI ).isEqualTo( MappingConstantsGem.ComponentModelGem.CDI ); @@ -42,5 +42,7 @@ public void componentModelContantsShouldBeEqual() { assertThat( MappingConstants.ComponentModel.JSR330 ).isEqualTo( MappingConstantsGem.ComponentModelGem.JSR330 ); assertThat( MappingConstants.ComponentModel.JAKARTA ) .isEqualTo( MappingConstantsGem.ComponentModelGem.JAKARTA ); + assertThat( MappingConstants.ComponentModel.JAKARTA_CDI ) + .isEqualTo( MappingConstantsGem.ComponentModelGem.JAKARTA_CDI ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CdiDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CdiDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..bf8b292ec7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CdiDefaultCompileOptionFieldMapperTest.java @@ -0,0 +1,54 @@ +/* + * 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.injectionstrategy.cdi._default; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithCdi; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jakarta-cdi. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2950") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerCdiDefaultCompileOptionFieldMapper.class, + GenderCdiDefaultCompileOptionFieldMapper.class +}) +@WithCdi +public class CdiDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveFieldInjection() { + generatedSource.forMapper( CustomerCdiDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import javax.enterprise.context.ApplicationScoped;" ) + .contains( "import javax.inject.Inject;" ) + .contains( "@Inject" + lineSeparator() + " private GenderCdiDefaultCompileOptionFieldMapper" ) + .contains( "@ApplicationScoped" + lineSeparator() + "public class" ) + .doesNotContain( "public CustomerCdiDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "jakarta.inject" ) + .doesNotContain( "jakarta.enterprise" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CustomerCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CustomerCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..ed0ed0a76e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CustomerCdiDefaultCompileOptionFieldMapper.java @@ -0,0 +1,21 @@ +/* + * 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.injectionstrategy.cdi._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.CDI, + uses = GenderCdiDefaultCompileOptionFieldMapper.class) +public interface CustomerCdiDefaultCompileOptionFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/GenderCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/GenderCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..14d6a2e0d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/GenderCdiDefaultCompileOptionFieldMapper.java @@ -0,0 +1,26 @@ +/* + * 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.injectionstrategy.cdi._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.CDI) +public interface GenderCdiDefaultCompileOptionFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/CustomerCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/CustomerCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..4c893b7486 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/CustomerCdiDefaultCompileOptionFieldMapper.java @@ -0,0 +1,21 @@ +/* + * 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.injectionstrategy.cdi.jakarta; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.CDI, + uses = GenderCdiDefaultCompileOptionFieldMapper.class) +public interface CustomerCdiDefaultCompileOptionFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/GenderCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/GenderCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..8664b0b433 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/GenderCdiDefaultCompileOptionFieldMapper.java @@ -0,0 +1,26 @@ +/* + * 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.injectionstrategy.cdi.jakarta; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.CDI) +public interface GenderCdiDefaultCompileOptionFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..55480399a3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java @@ -0,0 +1,56 @@ +/* + * 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.injectionstrategy.cdi.jakarta; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithCdi; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaCdi; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jsr330. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2950") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerCdiDefaultCompileOptionFieldMapper.class, + GenderCdiDefaultCompileOptionFieldMapper.class +}) +@WithJakartaCdi +@WithCdi +public class JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveCdiInjection() { + generatedSource.forMapper( CustomerCdiDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import javax.enterprise.context.ApplicationScoped;" ) + .contains( "import javax.inject.Inject;" ) + .contains( "@Inject" + lineSeparator() + " private GenderCdiDefaultCompileOptionFieldMapper" ) + .contains( "@ApplicationScoped" + lineSeparator() + "public class" ) + .doesNotContain( "public CustomerCdiDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "jakarta.inject" ) + .doesNotContain( "jakarta.enterprise" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiCdiDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiCdiDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..953747e404 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiCdiDefaultCompileOptionFieldMapperTest.java @@ -0,0 +1,54 @@ +/* + * 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.injectionstrategy.cdi.jakarta; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaCdi; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jsr330. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2950") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerCdiDefaultCompileOptionFieldMapper.class, + GenderCdiDefaultCompileOptionFieldMapper.class +}) +@WithJakartaCdi +public class JakartaCdiCdiDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaCdiInjection() { + generatedSource.forMapper( CustomerCdiDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.enterprise.context.ApplicationScoped;" ) + .contains( "import jakarta.inject.Inject;" ) + .contains( "@Inject" + lineSeparator() + " private GenderCdiDefaultCompileOptionFieldMapper" ) + .contains( "@ApplicationScoped" + lineSeparator() + "public class" ) + .doesNotContain( "public CustomerCdiDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ) + .doesNotContain( "javax.enterprise" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/CustomerJakartaCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/CustomerJakartaCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..9ec0709f52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/CustomerJakartaCdiDefaultCompileOptionFieldMapper.java @@ -0,0 +1,21 @@ +/* + * 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.injectionstrategy.jakarta_cdi._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA_CDI, + uses = GenderJakartaCdiDefaultCompileOptionFieldMapper.class) +public interface CustomerJakartaCdiDefaultCompileOptionFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/GenderJakartaCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/GenderJakartaCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..0c1aef6ec6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/GenderJakartaCdiDefaultCompileOptionFieldMapper.java @@ -0,0 +1,26 @@ +/* + * 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.injectionstrategy.jakarta_cdi._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +public interface GenderJakartaCdiDefaultCompileOptionFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..b542c99c2a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java @@ -0,0 +1,55 @@ +/* + * 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.injectionstrategy.jakarta_cdi._default; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithCdi; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaCdi; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jakarta. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2950") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaCdiDefaultCompileOptionFieldMapper.class, + GenderJakartaCdiDefaultCompileOptionFieldMapper.class +}) +@WithJakartaCdi +@WithCdi +public class JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaCdiDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.enterprise.context.ApplicationScoped;" ) + .contains( "import jakarta.inject.Inject;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaCdiDefaultCompileOptionFieldMapper" ) + .contains( "@ApplicationScoped" + lineSeparator() + "public class" ) + .doesNotContain( "javax.inject" ) + .doesNotContain( "javax.enterprise" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..57714cdb01 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiDefaultCompileOptionFieldMapperTest.java @@ -0,0 +1,54 @@ +/* + * 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.injectionstrategy.jakarta_cdi._default; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaCdi; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jakarta-cdi. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2950") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaCdiDefaultCompileOptionFieldMapper.class, + GenderJakartaCdiDefaultCompileOptionFieldMapper.class +}) +@WithJakartaCdi +public class JakartaCdiDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveFieldInjection() { + generatedSource.forMapper( CustomerJakartaCdiDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.enterprise.context.ApplicationScoped;" ) + .contains( "import jakarta.inject.Inject;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaCdiDefaultCompileOptionFieldMapper" ) + .contains( "@ApplicationScoped" + lineSeparator() + "public class" ) + .doesNotContain( "public CustomerJakartaCdiDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ) + .doesNotContain( "javax.enterprise" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithCdi.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithCdi.java new file mode 100644 index 0000000000..6da78fbfe0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithCdi.java @@ -0,0 +1,28 @@ +/* + * 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.testutil; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "javax.inject", + "cdi-api", +}) +public @interface WithCdi { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaCdi.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaCdi.java new file mode 100644 index 0000000000..f22a1d26e1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaCdi.java @@ -0,0 +1,28 @@ +/* + * 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.testutil; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "jakarta.inject-api", + "jakarta.enterprise.cdi-api", +}) +public @interface WithJakartaCdi { + +} From fd4a2548b3a1317cf4c419ef022889680fc6b056 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 21 Aug 2022 11:16:48 +0200 Subject: [PATCH 104/363] #2928 Add IntelliJ and Eclipse plugin information --- .../main/asciidoc/chapter-2-set-up.asciidoc | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index 48b352bd94..601bc42a99 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -273,3 +273,28 @@ disableBuilders` MapStruct can be used with Java 9 and higher versions. To allow usage of the `@Generated` annotation `java.annotation.processing.Generated` (part of the `java.compiler` module) can be enabled. + +=== IDE Integration + +There are optional MapStruct plugins for IntelliJ and Eclipse that allow you to have additional completion support (and more) in the annotations. + +==== IntelliJ + +The https://plugins.jetbrains.com/plugin/10036-mapstruct-support[MapStruct IntelliJ] plugin offers assistance in projects that use MapStruct. + +Some features include: + +* Code completion in `target`, `source`, `expression` +* Go To Declaration for properties in `target` and `source` +* Find Usages of properties in `target` and `source` +* Refactoring support +* Errors and Quick Fixes + +==== Eclipse + +The https://marketplace.eclipse.org/content/mapstruct-eclipse-plugin[MapStruct Eclipse] Plugin offers assistance in projects that use MapStruct. + +Some features include: + +* Code completion in `target` and `source` +* Quick Fixes From 237543c47cb1827189e8c8d81b5b5b9c2eabadf1 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 24 Aug 2022 18:59:31 +0200 Subject: [PATCH 105/363] #2897 Always import types defined in `Mapper#imports` --- .../ap/internal/model/common/TypeFactory.java | 19 ++++++++++- .../processor/MapperCreationProcessor.java | 2 +- .../ap/test/bugs/_2897/Issue2897Mapper.java | 23 +++++++++++++ .../ap/test/bugs/_2897/Issue2897Test.java | 33 +++++++++++++++++++ .../mapstruct/ap/test/bugs/_2897/Source.java | 22 +++++++++++++ .../mapstruct/ap/test/bugs/_2897/Target.java | 22 +++++++++++++ .../ap/test/bugs/_2897/util/Util.java | 21 ++++++++++++ 7 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/util/Util.java 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 b209626725..f28e0c687e 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 @@ -193,7 +193,24 @@ public Type getType(TypeMirror mirror) { return getType( mirror, false ); } + /** + * Return a type that is always going to be imported. + * This is useful when using it in {@code Mapper#imports} + * for types that should be used in expressions. + * + * @param mirror the type mirror for which we need a type + * + * @return the type + */ + public Type getAlwaysImportedType(TypeMirror mirror) { + return getType( mirror, false, true ); + } + private Type getType(TypeMirror mirror, boolean isLiteral) { + return getType( mirror, isLiteral, null ); + } + + private Type getType(TypeMirror mirror, boolean isLiteral, Boolean alwaysImport) { if ( !canBeProcessed( mirror ) ) { throw new TypeHierarchyErroneousException( mirror ); } @@ -212,7 +229,7 @@ private Type getType(TypeMirror mirror, boolean isLiteral) { String qualifiedName; TypeElement typeElement; Type componentType; - Boolean toBeImported = null; + Boolean toBeImported = alwaysImport; if ( mirror.getKind() == TypeKind.DECLARED ) { DeclaredType declaredType = (DeclaredType) mirror; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index c05d2d18bd..b69ba388b0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -307,7 +307,7 @@ private SortedSet getExtraImports(TypeElement element, MapperOptions mapp for ( TypeMirror extraImport : mapperOptions.imports() ) { - Type type = typeFactory.getType( extraImport ); + Type type = typeFactory.getAlwaysImportedType( extraImport ); extraImports.add( type ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Mapper.java new file mode 100644 index 0000000000..e13f2083ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Mapper.java @@ -0,0 +1,23 @@ +/* + * 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._2897; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.bugs._2897.util.Util; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(imports = Util.Factory.class) +public interface Issue2897Mapper { + + Issue2897Mapper INSTANCE = Mappers.getMapper( Issue2897Mapper.class ); + + @Mapping( target = "value", expression = "java(Factory.parse( source ))") + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Test.java new file mode 100644 index 0000000000..718ece4a90 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Test.java @@ -0,0 +1,33 @@ +/* + * 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._2897; + +import org.mapstruct.ap.test.bugs._2897.util.Util; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2897") +@WithClasses({ + Util.class, + Issue2897Mapper.class, + Source.class, + Target.class, +}) +class Issue2897Test { + + @ProcessorTest + void shouldImportNestedClassInMapperImports() { + Target target = Issue2897Mapper.INSTANCE.map( new Source( "test" ) ); + + assertThat( target.getValue() ).isEqualTo( "parsed(test)" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Source.java new file mode 100644 index 0000000000..5d46ad1ba3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Source.java @@ -0,0 +1,22 @@ +/* + * 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._2897; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Target.java new file mode 100644 index 0000000000..71fbabbe57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Target.java @@ -0,0 +1,22 @@ +/* + * 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._2897; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/util/Util.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/util/Util.java new file mode 100644 index 0000000000..5ef0c7e254 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/util/Util.java @@ -0,0 +1,21 @@ +/* + * 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._2897.util; + +import org.mapstruct.ap.test.bugs._2897.Source; + +/** + * @author Filip Hrisafov + */ +public class Util { + + public static class Factory { + + public static String parse(Source source) { + return source == null ? null : "parsed(" + source.getValue() + ")"; + } + } +} From b24e831cf0960cc25a4967fe6e1f11c536c0b118 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 24 Aug 2022 19:11:52 +0200 Subject: [PATCH 106/363] #2937 Fix conditional check for collections with adders --- .../ap/internal/model/MethodReference.ftl | 1 + .../model/MethodReferencePresenceCheck.ftl | 1 + .../ap/internal/model/common/SourceRHS.ftl | 2 +- .../ap/test/bugs/_2937/Issue2937Mapper.java | 59 +++++++++++++++++++ .../ap/test/bugs/_2937/Issue2937Test.java | 42 +++++++++++++ 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Test.java 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 80f893f1a2..4b45643dc8 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 @@ -64,6 +64,7 @@ --> <#macro _assignment assignmentToUse> <@includeModel object=assignmentToUse + presenceCheck=ext.presenceCheck targetBeanName=ext.targetBeanName existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl index 24f871e214..9a1c7b24ae 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl @@ -7,5 +7,6 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" --> <@includeModel object=methodReference + presenceCheck=true targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/SourceRHS.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/SourceRHS.ftl index 0b60bd39ae..aaf1eb8df4 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/SourceRHS.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/SourceRHS.ftl @@ -6,4 +6,4 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.SourceRHS" --> -<#if sourceLoopVarName??>${sourceLoopVarName}<#elseif sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} \ No newline at end of file +<#if sourceLoopVarName?? && !ext.presenceCheck??>${sourceLoopVarName}<#elseif sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Mapper.java new file mode 100644 index 0000000000..95d767acb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Mapper.java @@ -0,0 +1,59 @@ +/* + * 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._2937; + +import java.util.ArrayList; +import java.util.Collection; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface Issue2937Mapper { + + Issue2937Mapper INSTANCE = Mappers.getMapper( Issue2937Mapper.class ); + + Target map(Source source); + + @Condition + default boolean isApplicable(Collection collection) { + return collection == null || collection.size() > 1; + } + + class Source { + private final Collection names; + + public Source(Collection names) { + this.names = names; + } + + public Collection getNames() { + return names; + } + + } + + class Target { + private final Collection names; + + public Target() { + this.names = new ArrayList<>(); + } + + public Collection getNames() { + return names; + } + + public void addName(String name) { + this.names.add( name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Test.java new file mode 100644 index 0000000000..140575263c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Test.java @@ -0,0 +1,42 @@ +/* + * 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._2937; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2937") +@WithClasses({ + Issue2937Mapper.class, +}) +class Issue2937Test { + + @ProcessorTest + void shouldCorrectlyUseConditionalForAdder() { + List sourceNames = new ArrayList<>(); + sourceNames.add( "Tester 1" ); + Issue2937Mapper.Source source = new Issue2937Mapper.Source( sourceNames ); + Issue2937Mapper.Target target = Issue2937Mapper.INSTANCE.map( source ); + + assertThat( target.getNames() ).isEmpty(); + + sourceNames.add( "Tester 2" ); + + target = Issue2937Mapper.INSTANCE.map( source ); + + assertThat( target.getNames() ) + .containsExactly( "Tester 1", "Tester 2" ); + } +} From 71b1a7b8a2f33016d38f01590f641e0e20d424ae Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 21 Aug 2022 09:50:06 +0200 Subject: [PATCH 107/363] #2945 Stabilise top level imports Make sure that GeneratedType always gets the imported types from a Type before adding them --- .../ap/internal/model/GeneratedType.java | 6 ++-- .../ap/test/bugs/_2945/Issue2945Mapper.java | 22 ++++++++++++ .../ap/test/bugs/_2945/Issue2945Test.java | 35 +++++++++++++++++++ .../test/bugs/_2945/_target/EnumHolder.java | 13 +++++++ .../ap/test/bugs/_2945/_target/Target.java | 18 ++++++++++ .../ap/test/bugs/_2945/source/Source.java | 22 ++++++++++++ 6 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/EnumHolder.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/source/Source.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index f8f65c927f..df6ed9b2b1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -258,8 +258,10 @@ protected void addIfImportRequired(Collection collection, Type typeToAdd) return; } - if ( needsImportDeclaration( typeToAdd ) ) { - collection.add( typeToAdd ); + for ( Type type : typeToAdd.getImportTypes() ) { + if ( needsImportDeclaration( type ) ) { + collection.add( type ); + } } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Mapper.java new file mode 100644 index 0000000000..604d546273 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Mapper.java @@ -0,0 +1,22 @@ +/* + * 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._2945; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._2945._target.Target; +import org.mapstruct.ap.test.bugs._2945.source.Source; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2945Mapper { + + Issue2945Mapper INSTANCE = Mappers.getMapper( Issue2945Mapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Test.java new file mode 100644 index 0000000000..11dcf752c0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Test.java @@ -0,0 +1,35 @@ +/* + * 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._2945; + +import org.mapstruct.ap.test.bugs._2945._target.EnumHolder; +import org.mapstruct.ap.test.bugs._2945._target.Target; +import org.mapstruct.ap.test.bugs._2945.source.Source; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2945") +@WithClasses({ + EnumHolder.class, + Issue2945Mapper.class, + Source.class, + Target.class, +}) +class Issue2945Test { + + @ProcessorTest + void shouldCompile() { + Target target = Issue2945Mapper.INSTANCE.map( new Source( "VALUE_1" ) ); + + assertThat( target.getProperty() ).isEqualTo( EnumHolder.Property.VALUE_1 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/EnumHolder.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/EnumHolder.java new file mode 100644 index 0000000000..0b75e9ce36 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/EnumHolder.java @@ -0,0 +1,13 @@ +/* + * 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._2945._target; + +public class EnumHolder { + public enum Property { + VALUE_1, + VALUE_2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/Target.java new file mode 100644 index 0000000000..25b69eed29 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/Target.java @@ -0,0 +1,18 @@ +/* + * 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._2945._target; + +public class Target { + private EnumHolder.Property property; + + public EnumHolder.Property getProperty() { + return property; + } + + public void setProperty(EnumHolder.Property property) { + this.property = property; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/source/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/source/Source.java new file mode 100644 index 0000000000..8364941ad1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/source/Source.java @@ -0,0 +1,22 @@ +/* + * 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._2945.source; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final String property; + + public Source(String property) { + this.property = property; + } + + public String getProperty() { + return property; + } +} From 42500ca755a68eef115261adea79ff80892ddd49 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 21 Aug 2022 18:54:33 +0200 Subject: [PATCH 108/363] #2907 Add test case for nested import of array --- .../ap/test/bugs/_2907/Issue2907Test.java | 35 +++++++++++++++++++ .../mapstruct/ap/test/bugs/_2907/Source.java | 21 +++++++++++ .../ap/test/bugs/_2907/SourceNested.java | 19 ++++++++++ .../mapstruct/ap/test/bugs/_2907/Target.java | 31 ++++++++++++++++ .../bugs/_2907/mapper/Issue2907Mapper.java | 17 +++++++++ 5 files changed, 123 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Issue2907Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/SourceNested.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/mapper/Issue2907Mapper.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Issue2907Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Issue2907Test.java new file mode 100644 index 0000000000..a005b6a1ad --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Issue2907Test.java @@ -0,0 +1,35 @@ +/* + * 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._2907; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.bugs._2907.mapper.Issue2907Mapper; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2907") +@WithClasses({ + Issue2907Mapper.class, + Source.class, + SourceNested.class, + Target.class, +}) +class Issue2907Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void shouldNotGeneratedImportForNestedClass() { + generatedSource.forMapper( Issue2907Mapper.class ) + .containsNoImportFor( Target.TargetNested.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Source.java new file mode 100644 index 0000000000..42d015fde7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Source.java @@ -0,0 +1,21 @@ +/* + * 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._2907; + +import java.util.Set; + +public class Source { + + private Set nested; + + public Set getNested() { + return nested; + } + + public void setNested(Set nested) { + this.nested = nested; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/SourceNested.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/SourceNested.java new file mode 100644 index 0000000000..a58a94f0f3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/SourceNested.java @@ -0,0 +1,19 @@ +/* + * 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._2907; + +public class SourceNested { + + private String prop; + + public String getProp() { + return prop; + } + + public void setProp(String prop) { + this.prop = prop; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Target.java new file mode 100644 index 0000000000..80a796d20d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Target.java @@ -0,0 +1,31 @@ +/* + * 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._2907; + +public class Target { + + private TargetNested[] nested; + + public TargetNested[] getNested() { + return nested; + } + + public void setNested(TargetNested[] nested) { + this.nested = nested; + } + + public static class TargetNested { + private String prop; + + public String getProp() { + return prop; + } + + public void setProp(String prop) { + this.prop = prop; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/mapper/Issue2907Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/mapper/Issue2907Mapper.java new file mode 100644 index 0000000000..5244f95bb4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/mapper/Issue2907Mapper.java @@ -0,0 +1,17 @@ +/* + * 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._2907.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._2907.Source; +import org.mapstruct.ap.test.bugs._2907.Target; + +@Mapper +public interface Issue2907Mapper { + + Target map(Source source); + +} From 853e7b27df3f7a053bec28846a646588557fcfca Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 24 Aug 2022 19:39:09 +0200 Subject: [PATCH 109/363] #2925 Fix IllegalArgumentException when resolving generic parameters When resolving the parameter for a method like: ``` Optional from(T value) ``` There was an exception in javac because getting a DeclaredType from an Optional with a primitive type argument throws an exception. Therefore, when assigning the type arguments we get the boxed equivalent. This problem does not happen in the Eclipse compiler --- .../internal/model/source/MethodMatcher.java | 4 ++- .../ap/test/bugs/_2925/Issue2925Mapper.java | 23 +++++++++++++ .../ap/test/bugs/_2925/Issue2925Test.java | 32 +++++++++++++++++++ .../mapstruct/ap/test/bugs/_2925/Source.java | 19 +++++++++++ .../mapstruct/ap/test/bugs/_2925/Target.java | 22 +++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Target.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java index f0d9536799..baa3d7eac5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java @@ -374,7 +374,9 @@ else if ( typeFromCandidateMethodTypeParameter.isWildCardBoundByTypeVar() // something went wrong return null; } - typeArgs[i] = matchingType.getTypeMirror(); + // Use the boxed equivalent for the type arguments, + // because a primitive type cannot be a type argument + typeArgs[i] = matchingType.getBoxedEquivalent().getTypeMirror(); } else { // it is not a type var (e.g. Map ), String is not a type var diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Mapper.java new file mode 100644 index 0000000000..d231413820 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Mapper.java @@ -0,0 +1,23 @@ +/* + * 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._2925; + +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2925Mapper { + + Issue2925Mapper INSTANCE = Mappers.getMapper( Issue2925Mapper.class ); + + Target map(Source source); + + static Optional toOptional(T value) { + return Optional.ofNullable( value ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Test.java new file mode 100644 index 0000000000..73d009d093 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Test.java @@ -0,0 +1,32 @@ +/* + * 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._2925; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2925") +@WithClasses({ + Issue2925Mapper.class, + Source.class, + Target.class, +}) +class Issue2925Test { + + @ProcessorTest + void shouldUseOptionalWrappingMethod() { + Target target = Issue2925Mapper.INSTANCE.map( new Source( 10L ) ); + + assertThat( target.getValue() ) + .hasValue( 10L ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Source.java new file mode 100644 index 0000000000..c21ce67775 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Source.java @@ -0,0 +1,19 @@ +/* + * 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._2925; + +public class Source { + + private final long value; + + public Source(long value) { + this.value = value; + } + + public long getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Target.java new file mode 100644 index 0000000000..4693a83fdb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Target.java @@ -0,0 +1,22 @@ +/* + * 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._2925; + +import java.util.Optional; + +public class Target { + + private Long value; + + public Optional getValue() { + return Optional.ofNullable( value ); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public void setValue(Optional value) { + this.value = value.orElse( null ); + } +} From 3f798744ac347fbddaa7e9f79383c0798e479f18 Mon Sep 17 00:00:00 2001 From: Taihao Zhang <46465678+zhtaihao@users.noreply.github.com> Date: Wed, 24 Aug 2022 19:40:08 +0200 Subject: [PATCH 110/363] Fix typo in docs (#2982) --- .../main/asciidoc/chapter-10-advanced-mapping-options.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 9c1b2243eb..a6e42b6d48 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -304,7 +304,7 @@ The difference is that it allows users to write custom condition methods that wi A custom condition method is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`. -e.g. if you only want to map a String property when it is not `null, and it is not empty then you can do something like: +e.g. if you only want to map a String property when it is not `null`, and it is not empty then you can do something like: .Mapper using custom condition check method ==== From 4fa66229d9e3949f451fe8b79b62dcbbd138b669 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 26 Aug 2022 19:35:54 +0200 Subject: [PATCH 111/363] #2990 Stabilise top level imports --- .../mapstruct/ap/internal/model/common/Type.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index ce82fff621..fd7f3d6904 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -181,9 +181,16 @@ public Type(TypeUtils typeUtils, ElementUtils elementUtils, TypeFactory typeFact this.loggingVerbose = loggingVerbose; - // The top level type for an array type is the top level type of the component type - TypeElement typeElementForTopLevel = - this.componentType == null ? this.typeElement : this.componentType.getTypeElement(); + TypeElement typeElementForTopLevel; + if ( Boolean.TRUE.equals( isToBeImported ) ) { + // If the is to be imported is explicitly set to true then we shouldn't look for the top level type + typeElementForTopLevel = null; + } + else { + // The top level type for an array type is the top level type of the component type + typeElementForTopLevel = + this.componentType == null ? this.typeElement : this.componentType.getTypeElement(); + } this.topLevelType = topLevelType( typeElementForTopLevel, this.typeFactory ); this.nameWithTopLevelTypeName = nameWithTopLevelTypeName( typeElementForTopLevel, this.name ); } From 4708f4b2aa1d165b17f8d42586085c2c8d8aaf2a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 25 Aug 2022 08:40:50 +0200 Subject: [PATCH 112/363] #2950 Disable CDI in the full features tests on Java 8 --- .../tests/FullFeatureCompilationExclusionCliEnhancer.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index c8bac6208c..0f261d6568 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -28,6 +28,10 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1801/*.java" ); switch ( currentJreVersion ) { + case JAVA_8: + additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/cdi/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" ); + break; case JAVA_9: // TODO find out why this fails: additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" ); From ac356cab2529e1ab71205e2f9389686fe3b303c3 Mon Sep 17 00:00:00 2001 From: Orange Add <48479242+chenzijia12300@users.noreply.github.com> Date: Sun, 28 Aug 2022 18:33:46 +0800 Subject: [PATCH 113/363] #2983 Add `@AnnotateWith` support to non bean mapping methods --- .../model/AbstractMappingMethodBuilder.java | 14 ++++++ .../ap/internal/model/BeanMappingMethod.java | 16 +------ .../model/ContainerMappingMethod.java | 5 +- .../internal/model/IterableMappingMethod.java | 5 +- .../ap/internal/model/MapMappingMethod.java | 6 ++- .../model/NormalTypeMappingMethod.java | 13 +++++- .../internal/model/StreamMappingMethod.java | 21 +++++---- .../ap/internal/model/ValueMappingMethod.java | 20 +++++++- .../internal/model/IterableMappingMethod.ftl | 3 ++ .../ap/internal/model/MapMappingMethod.ftl | 3 ++ .../ap/internal/model/StreamMappingMethod.ftl | 3 ++ .../ap/internal/model/ValueMappingMethod.ftl | 3 ++ .../AnnotateBeanMappingMethodMapper.java | 46 +++++++++++++++++++ .../AnnotateIterableMappingMethodMapper.java | 21 +++++++++ .../AnnotateMapMappingMethodMapper.java | 24 ++++++++++ .../AnnotateStreamMappingMethodMapper.java | 21 +++++++++ .../AnnotateValueMappingMethodMapper.java | 26 +++++++++++ .../test/annotatewith/AnnotateWithTest.java | 46 ++++++++++++++++++- 18 files changed, 264 insertions(+), 32 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateBeanMappingMethodMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateIterableMappingMethodMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateMapMappingMethodMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateStreamMappingMethodMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateValueMappingMethodMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index efc7a71469..6efb67ba9c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -13,6 +13,9 @@ import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.util.Strings; +import java.util.ArrayList; +import java.util.List; + /** * An abstract builder that can be reused for building {@link MappingMethod}(s). * @@ -117,4 +120,15 @@ public ForgedMethodHistory getDescription() { return description; } + public List getMethodAnnotations() { + AdditionalAnnotationsBuilder additionalAnnotationsBuilder = + new AdditionalAnnotationsBuilder( + ctx.getElementUtils(), + ctx.getTypeFactory(), + ctx.getMessager() ); + List annotations = new ArrayList<>(); + annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); + return annotations; + } + } 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 e748c173af..4f2dc4e5f1 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 @@ -85,7 +85,6 @@ */ public class BeanMappingMethod extends NormalTypeMappingMethod { - private final List annotations; private final List propertyMappings; private final Map> mappingsByParameter; private final Map> constructorMappingsByParameter; @@ -113,7 +112,6 @@ public static class Builder extends AbstractMappingMethodBuilder unprocessedSourceParameters = new HashSet<>(); private final Set existingVariableNames = new HashSet<>(); private final Map> unprocessedDefinedTargets = new LinkedHashMap<>(); - private final List annotations = new ArrayList<>(); private MappingReferences mappingReferences; private MethodReference factoryMethod; @@ -216,12 +214,6 @@ else if ( !method.isUpdateMethod() ) { // If the return type cannot be constructed then no need to try to create mappings return null; } - AdditionalAnnotationsBuilder additionalAnnotationsBuilder = - new AdditionalAnnotationsBuilder( - ctx.getElementUtils(), - ctx.getTypeFactory(), - ctx.getMessager() ); - annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); /* the type that needs to be used in the mapping process as target */ Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct; @@ -370,7 +362,7 @@ else if ( !method.isUpdateMethod() ) { return new BeanMappingMethod( method, - annotations, + getMethodAnnotations(), existingVariableNames, propertyMappings, factoryMethod, @@ -1724,6 +1716,7 @@ private BeanMappingMethod(Method method, List subclassMappings) { super( method, + annotations, existingVariableNames, factoryMethod, mapNullToDefault, @@ -1732,7 +1725,6 @@ private BeanMappingMethod(Method method, ); //CHECKSTYLE:ON - this.annotations = annotations; this.propertyMappings = propertyMappings; this.returnTypeBuilder = returnTypeBuilder; this.finalizerMethod = finalizerMethod; @@ -1771,10 +1763,6 @@ else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { this.subclassMappings = subclassMappings; } - public List getAnnotations() { - return annotations; - } - public List getConstantMappings() { return constantMappings; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java index 382aff9737..21e547eea0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java @@ -32,12 +32,13 @@ public abstract class ContainerMappingMethod extends NormalTypeMappingMethod { private final String index2Name; private IterableCreation iterableCreation; - ContainerMappingMethod(Method method, Collection existingVariables, Assignment parameterAssignment, + ContainerMappingMethod(Method method, List annotations, + Collection existingVariables, Assignment parameterAssignment, MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName, List beforeMappingReferences, List afterMappingReferences, SelectionParameters selectionParameters) { - super( method, existingVariables, factoryMethod, mapNullToDefault, beforeMappingReferences, + super( method, annotations, existingVariables, factoryMethod, mapNullToDefault, beforeMappingReferences, afterMappingReferences ); this.elementAssignment = parameterAssignment; this.loopVariableName = loopVariableName; 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 38ec44f1e1..10e64b7008 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 @@ -57,6 +57,7 @@ protected IterableMappingMethod instantiateMappingMethod(Method method, Collecti List afterMappingMethods, SelectionParameters selectionParameters) { return new IterableMappingMethod( method, + getMethodAnnotations(), existingVariables, assignment, factoryMethod, @@ -69,13 +70,15 @@ protected IterableMappingMethod instantiateMappingMethod(Method method, Collecti } } - private IterableMappingMethod(Method method, Collection existingVariables, Assignment parameterAssignment, + private IterableMappingMethod(Method method, List annotations, + Collection existingVariables, Assignment parameterAssignment, MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName, List beforeMappingReferences, List afterMappingReferences, SelectionParameters selectionParameters) { super( method, + annotations, existingVariables, parameterAssignment, factoryMethod, 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 a8b68b435f..85766e2eca 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 MapMappingMethod build() { return new MapMappingMethod( method, + getMethodAnnotations(), existingVariables, keyAssignment, valueAssignment, @@ -224,11 +225,12 @@ protected boolean shouldUsePropertyNamesInHistory() { } - private MapMappingMethod(Method method, Collection existingVariableNames, Assignment keyAssignment, + private MapMappingMethod(Method method, List annotations, + Collection existingVariableNames, Assignment keyAssignment, Assignment valueAssignment, MethodReference factoryMethod, boolean mapNullToDefault, List beforeMappingReferences, List afterMappingReferences) { - super( method, existingVariableNames, factoryMethod, mapNullToDefault, beforeMappingReferences, + super( method, annotations, existingVariableNames, factoryMethod, mapNullToDefault, beforeMappingReferences, afterMappingReferences ); this.keyAssignment = keyAssignment; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java index fcd8d70d52..79cd0e0566 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java @@ -24,7 +24,10 @@ public abstract class NormalTypeMappingMethod extends MappingMethod { private final boolean overridden; private final boolean mapNullToDefault; - NormalTypeMappingMethod(Method method, Collection existingVariableNames, MethodReference factoryMethod, + private final List annotations; + + NormalTypeMappingMethod(Method method, List annotations, + Collection existingVariableNames, MethodReference factoryMethod, boolean mapNullToDefault, List beforeMappingReferences, List afterMappingReferences) { @@ -32,6 +35,7 @@ public abstract class NormalTypeMappingMethod extends MappingMethod { this.factoryMethod = factoryMethod; this.overridden = method.overridesMethod(); this.mapNullToDefault = mapNullToDefault; + this.annotations = annotations; } @Override @@ -45,6 +49,9 @@ public Set getImportTypes() { else if ( factoryMethod != null ) { types.addAll( factoryMethod.getImportTypes() ); } + for ( Annotation annotation : annotations ) { + types.addAll( annotation.getImportTypes() ); + } return types; } @@ -60,6 +67,10 @@ public MethodReference getFactoryMethod() { return this.factoryMethod; } + public List getAnnotations() { + return annotations; + } + @Override public int hashCode() { final int prime = 31; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/StreamMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/StreamMappingMethod.java index 552040d8ce..57ee9ccdf4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/StreamMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/StreamMappingMethod.java @@ -5,6 +5,12 @@ */ package org.mapstruct.ap.internal.model; +import org.mapstruct.ap.internal.model.assignment.Java8FunctionWrapper; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; + import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -13,12 +19,6 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import org.mapstruct.ap.internal.model.assignment.Java8FunctionWrapper; -import org.mapstruct.ap.internal.model.common.Assignment; -import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.model.source.SelectionParameters; - import static org.mapstruct.ap.internal.util.Collections.first; /** @@ -63,9 +63,9 @@ protected StreamMappingMethod instantiateMappingMethod(Method method, Collection sourceParameterType.isIterableType() ) { helperImports.add( ctx.getTypeFactory().getType( StreamSupport.class ) ); } - return new StreamMappingMethod( method, + getMethodAnnotations(), existingVariables, assignment, factoryMethod, @@ -78,14 +78,16 @@ protected StreamMappingMethod instantiateMappingMethod(Method method, Collection ); } } - - private StreamMappingMethod(Method method, Collection existingVariables, Assignment parameterAssignment, + //CHECKSTYLE:OFF + private StreamMappingMethod(Method method, List annotations, + Collection existingVariables, Assignment parameterAssignment, MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName, List beforeMappingReferences, List afterMappingReferences, SelectionParameters selectionParameters, Set helperImports) { super( method, + annotations, existingVariables, parameterAssignment, factoryMethod, @@ -95,6 +97,7 @@ private StreamMappingMethod(Method method, Collection existingVariables, afterMappingReferences, selectionParameters ); + //CHECKSTYLE:ON this.helperImports = helperImports; } 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 0d71ed5506..f61e80c2d4 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 @@ -41,6 +41,7 @@ */ public class ValueMappingMethod extends MappingMethod { + private final List annotations; private final List valueMappings; private final MappingEntry defaultTarget; private final MappingEntry nullTarget; @@ -116,9 +117,16 @@ else if ( sourceType.isString() && targetType.isEnumType() ) { LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables ); List afterMappingMethods = LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables ); - + AdditionalAnnotationsBuilder additionalAnnotationsBuilder = + new AdditionalAnnotationsBuilder( + ctx.getElementUtils(), + ctx.getTypeFactory(), + ctx.getMessager() ); + List annotations = new ArrayList<>(); + annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); // finally return a mapping return new ValueMappingMethod( method, + annotations, mappingEntries, valueMappings.nullValueTarget, valueMappings.defaultTargetValue, @@ -532,6 +540,7 @@ String getValue(ValueMappingOptions valueMapping) { } private ValueMappingMethod(Method method, + List annotations, List enumMappings, String nullTarget, String defaultTarget, @@ -544,6 +553,7 @@ private ValueMappingMethod(Method method, this.defaultTarget = new MappingEntry( null, defaultTarget != null ? defaultTarget : THROW_EXCEPTION); this.unexpectedValueMappingException = unexpectedValueMappingException; this.overridden = method.overridesMethod(); + this.annotations = annotations; } @Override @@ -556,7 +566,9 @@ public Set getImportTypes() { importTypes.addAll( unexpectedValueMappingException.getImportTypes() ); } } - + for ( Annotation annotation : annotations ) { + importTypes.addAll( annotation.getImportTypes() ); + } return importTypes; } @@ -590,6 +602,10 @@ public boolean isOverridden() { return overridden; } + public List getAnnotations() { + return annotations; + } + public static class MappingEntry { private final String source; private final String target; diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl index 495111b7a1..9af6e762bf 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl @@ -6,6 +6,9 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.IterableMappingMethod" --> +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + <#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list beforeMappingReferencesWithoutMappingTarget as callback> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl index 443d87cba7..0e388da103 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl @@ -6,6 +6,9 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MapMappingMethod" --> +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + <#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType /> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list beforeMappingReferencesWithoutMappingTarget as callback> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl index 860859fc3a..7b5b44bd56 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl @@ -6,6 +6,9 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.StreamMappingMethod" --> +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + <#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#--TODO does it even make sense to do a callback if the result is a Stream, as they are immutable--> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl index 4d961c40af..2c3e2b6410 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl @@ -6,6 +6,9 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.ValueMappingMethod" --> +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + <#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, ) { <#list beforeMappingReferencesWithoutMappingTarget as callback> diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateBeanMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateBeanMappingMethodMapper.java new file mode 100644 index 0000000000..cc19c2d282 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateBeanMappingMethodMapper.java @@ -0,0 +1,46 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author orange add + */ +@Mapper +public interface AnnotateBeanMappingMethodMapper { + + @AnnotateWith(CustomMethodOnlyAnnotation.class) + Target map(Source source); + + class Source { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateIterableMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateIterableMappingMethodMapper.java new file mode 100644 index 0000000000..40c6e60fe6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateIterableMappingMethodMapper.java @@ -0,0 +1,21 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +import java.util.List; + +/** + * @author orange add + */ +@Mapper +public interface AnnotateIterableMappingMethodMapper { + + @AnnotateWith(CustomMethodOnlyAnnotation.class) + List toStringList(List integers); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateMapMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateMapMappingMethodMapper.java new file mode 100644 index 0000000000..458cb68d4d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateMapMappingMethodMapper.java @@ -0,0 +1,24 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; + +import java.util.Date; +import java.util.Map; + +/** + * @author orange add + */ +@Mapper +public interface AnnotateMapMappingMethodMapper { + + @MapMapping(valueDateFormat = "dd.MM.yyyy") + @AnnotateWith(CustomMethodOnlyAnnotation.class) + Map longDateMapToStringStringMap(Map source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateStreamMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateStreamMappingMethodMapper.java new file mode 100644 index 0000000000..c574b59697 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateStreamMappingMethodMapper.java @@ -0,0 +1,21 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +import java.util.stream.Stream; + +/** + * @author orange add + */ +@Mapper +public interface AnnotateStreamMappingMethodMapper { + + @AnnotateWith(CustomMethodOnlyAnnotation.class) + Stream toStringStream(Stream integerStream); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateValueMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateValueMappingMethodMapper.java new file mode 100644 index 0000000000..a9e7c7b519 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateValueMappingMethodMapper.java @@ -0,0 +1,26 @@ +/* + * 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.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; + +/** + * @author orange add + */ +@Mapper +public interface AnnotateValueMappingMethodMapper { + + @ValueMappings({ + @ValueMapping(target = "EXISTING", source = "EXISTING"), + @ValueMapping( source = MappingConstants.ANY_REMAINING, target = "OTHER_EXISTING" ) + }) + @AnnotateWith(CustomMethodOnlyAnnotation.class) + AnnotateWithEnum map(String str); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java index c2055b4c5c..31b8d8fad9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java @@ -6,7 +6,6 @@ package org.mapstruct.ap.test.annotatewith; import org.junit.jupiter.api.extension.RegisterExtension; - import org.mapstruct.Mapper; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; @@ -17,6 +16,11 @@ import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.mapstruct.factory.Mappers; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -545,4 +549,44 @@ public void erroneousMapperWithParameterRepeat() { public void mapperWithIdenticalAnnotationRepeated() { } + @ProcessorTest + @WithClasses( {AnnotateBeanMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} ) + public void beanMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException { + AnnotateBeanMappingMethodMapper mapper = Mappers.getMapper( AnnotateBeanMappingMethodMapper.class ); + Method method = mapper.getClass().getMethod( "map", AnnotateBeanMappingMethodMapper.Source.class ); + assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull(); + } + + @ProcessorTest + @WithClasses( {AnnotateIterableMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} ) + public void iterableMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException { + AnnotateIterableMappingMethodMapper mapper = Mappers.getMapper( AnnotateIterableMappingMethodMapper.class ); + Method method = mapper.getClass().getMethod( "toStringList", List.class ); + assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull(); + } + + @ProcessorTest + @WithClasses( {AnnotateMapMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} ) + public void mapMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException { + AnnotateMapMappingMethodMapper mapper = Mappers.getMapper( AnnotateMapMappingMethodMapper.class ); + Method method = mapper.getClass().getMethod( "longDateMapToStringStringMap", Map.class ); + assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull(); + } + + @ProcessorTest + @WithClasses( {AnnotateStreamMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} ) + public void streamMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException { + AnnotateStreamMappingMethodMapper mapper = Mappers.getMapper( AnnotateStreamMappingMethodMapper.class ); + Method method = mapper.getClass().getMethod( "toStringStream", Stream.class ); + assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull(); + } + + @ProcessorTest + @WithClasses( {AnnotateValueMappingMethodMapper.class, AnnotateWithEnum.class, CustomMethodOnlyAnnotation.class} ) + public void valueMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException { + AnnotateValueMappingMethodMapper mapper = Mappers.getMapper( AnnotateValueMappingMethodMapper.class ); + Method method = mapper.getClass().getMethod( "map", String.class ); + assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull(); + } + } From 3cc2aa76751b8a2f11f0577303df90737e783e1b Mon Sep 17 00:00:00 2001 From: Orange Add <48479242+chenzijia12300@users.noreply.github.com> Date: Sun, 28 Aug 2022 18:41:25 +0800 Subject: [PATCH 114/363] #2825 Fix SubclassMapping stackoverflow exception --- .../creation/MappingResolverImpl.java | 6 +-- .../mapstruct/ap/test/bugs/_2825/Animal.java | 21 +++++++++++ .../org/mapstruct/ap/test/bugs/_2825/Cat.java | 21 +++++++++++ .../org/mapstruct/ap/test/bugs/_2825/Dog.java | 21 +++++++++++ .../ap/test/bugs/_2825/Issue2825Mapper.java | 24 ++++++++++++ .../ap/test/bugs/_2825/Issue2825Test.java | 37 +++++++++++++++++++ .../ap/test/bugs/_2825/TargetAnimal.java | 31 ++++++++++++++++ 7 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Animal.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Cat.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Dog.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/TargetAnimal.java 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 2e2a760bf4..5a2af176f6 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 @@ -196,7 +196,7 @@ private ResolvingAttempt(List sourceModel, Method mappingMethod, ForgedM this.mappingMethod = mappingMethod; this.description = description; - this.methods = filterPossibleCandidateMethods( sourceModel ); + this.methods = filterPossibleCandidateMethods( sourceModel, mappingMethod ); this.formattingParameters = formattingParameters == null ? FormattingParameters.EMPTY : formattingParameters; this.sourceRHS = sourceRHS; @@ -210,10 +210,10 @@ private ResolvingAttempt(List sourceModel, Method mappingMethod, ForgedM } // CHECKSTYLE:ON - private List filterPossibleCandidateMethods(List candidateMethods) { + private List filterPossibleCandidateMethods(List candidateMethods, T mappingMethod) { List result = new ArrayList<>( candidateMethods.size() ); for ( T candidate : candidateMethods ) { - if ( isCandidateForMapping( candidate ) ) { + if ( isCandidateForMapping( candidate ) && !candidate.equals( mappingMethod )) { result.add( candidate ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Animal.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Animal.java new file mode 100644 index 0000000000..3d60eac9fa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Animal.java @@ -0,0 +1,21 @@ +/* + * 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._2825; + +/** + * @author orange add + */ +public class Animal { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Cat.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Cat.java new file mode 100644 index 0000000000..ec826c0ffb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Cat.java @@ -0,0 +1,21 @@ +/* + * 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._2825; + +/** + * @author orange add + */ +public class Cat extends Animal { + private String race; + + public String getRace() { + return race; + } + + public void setRace(String race) { + this.race = race; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Dog.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Dog.java new file mode 100644 index 0000000000..53b41a98c9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Dog.java @@ -0,0 +1,21 @@ +/* + * 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._2825; + +/** + * @author orange add + */ +public class Dog extends Animal { + private String race; + + public String getRace() { + return race; + } + + public void setRace(String race) { + this.race = race; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Mapper.java new file mode 100644 index 0000000000..c515011b0b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Mapper.java @@ -0,0 +1,24 @@ +/* + * 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._2825; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +/** + * @author orange add + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface Issue2825Mapper { + + Issue2825Mapper INSTANCE = Mappers.getMapper( Issue2825Mapper.class ); + + @SubclassMapping(target = TargetAnimal.class, source = Dog.class) + @SubclassMapping(target = TargetAnimal.class, source = Cat.class) + TargetAnimal map(Animal source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Test.java new file mode 100644 index 0000000000..9c0609b754 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Test.java @@ -0,0 +1,37 @@ +/* + * 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._2825; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author orange add + */ +@IssueKey("2825") +@WithClasses({ + Animal.class, + Cat.class, + Dog.class, + Issue2825Mapper.class, + TargetAnimal.class, +}) +public class Issue2825Test { + + @ProcessorTest + public void mappingMethodShouldNotBeReusedForSubclassMappings() { + Dog dog = new Dog(); + dog.setName( "Lucky" ); + dog.setRace( "Shepherd" ); + TargetAnimal target = Issue2825Mapper.INSTANCE.map( dog ); + assertThat( target.getName() ).isEqualTo( "Lucky" ); + assertThat( target.getRace() ).isEqualTo( "Shepherd" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/TargetAnimal.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/TargetAnimal.java new file mode 100644 index 0000000000..479741099a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/TargetAnimal.java @@ -0,0 +1,31 @@ +/* + * 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._2825; + +/** + * @author orange add + */ +public class TargetAnimal { + private String name; + + private String race; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getRace() { + return race; + } + + public void setRace(String race) { + this.race = race; + } +} From 68571de01bd9b06cef647a3d9ad073f0f0cd2456 Mon Sep 17 00:00:00 2001 From: Prasanth Omanakuttan Date: Fri, 26 Aug 2022 19:25:58 +0530 Subject: [PATCH 115/363] Update Typos in java-doc Closes #2989 --- .../conversion/AbstractJavaTimeToStringConversion.java | 8 ++++---- ...xistingInstanceSetterWrapperForCollectionsAndMaps.java | 2 +- .../assignment/GetterWrapperForCollectionsAndMaps.java | 2 +- .../ap/internal/model/assignment/UpdateWrapper.java | 2 +- .../ap/internal/model/beanmapping/PropertyEntry.java | 2 +- .../ap/internal/model/beanmapping/SourceReference.java | 4 ++-- .../java/org/mapstruct/ap/internal/util/RoundContext.java | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJavaTimeToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJavaTimeToStringConversion.java index 2f530618c9..3503a0e274 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJavaTimeToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractJavaTimeToStringConversion.java @@ -21,15 +21,15 @@ *

    *

    * In general each type comes with a "parse" method to convert a string to this particular type. - * For formatting a dedicated instance of {@link java.time.format.DateTimeFormatter} is used. + * For formatting a dedicated instance of {@link DateTimeFormatter} is used. *

    *

    * If no date format for mapping is specified predefined ISO* formatters from - * {@link java.time.format.DateTimeFormatter} are used. + * {@link DateTimeFormatter} are used. *

    *

    - * An overview of date and time types shipped with Java 8 can be found at - * http://docs.oracle.com/javase/tutorial/datetime/iso/index.html. + * An overview of date and time types shipped with Java 8 can be found at the + * Standard Calendar Tutorial *

    */ public abstract class AbstractJavaTimeToStringConversion extends SimpleConversion { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java index 06a2712f00..2dd7f81a0b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java @@ -22,7 +22,7 @@ /** * This wrapper handles the situation where an assignment is done for an update method. * - * In case of a pre-existing target the wrapper checks if there is an collection or map initialized on the target bean + * In case of a pre-existing target the wrapper checks if there is a collection or map initialized on the target bean * (not null). If so it uses the addAll (for collections) or putAll (for maps). The collection / map is cleared in case * of a pre-existing target {@link org.mapstruct.MappingTarget }before adding the source entries. * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java index 40e195dcd0..d29a80b420 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java @@ -16,7 +16,7 @@ * This wrapper handles the situation were an assignment must be done via a target getter method because there * is no setter available. * - * The wrapper checks if there is an collection or map initialized on the target bean (not null). If so it uses the + * The wrapper checks if there is a collection or map initialized on the target bean (not null). If so it uses the * addAll (for collections) or putAll (for maps). The collection / map is cleared in case of a pre-existing target * {@link org.mapstruct.MappingTarget }before adding the source entries. The goal is that the same collection / map * is used as target. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java index cf0140e8ce..ff5089d6c2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java @@ -54,7 +54,7 @@ private static Type determineImplType(Assignment factoryMethod, Type targetType) return targetType.getImplementationType(); } - // no factory method means we create a new instance ourself and thus need to import the type + // no factory method means we create a new instance ourselves and thus need to import the type return targetType; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java index cb6cd8af79..a3c8a74e3f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java @@ -14,7 +14,7 @@ /** * A PropertyEntry contains information on the name, readAccessor and presenceCheck (for source) - * and return type of a property. + * and return type of property. */ public class PropertyEntry { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 8824f95901..4e7dad220c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -143,7 +143,7 @@ public SourceReference build() { * the parameter name to avoid ambiguity * * consider: {@code Target map( Source1 source1 )} - * entries in an @Mapping#source can be "source1.propx" or just "propx" to be valid + * entries in a @Mapping#source can be "source1.propx" or just "propx" to be valid * * @param segments the segments of @Mapping#source * @param parameter the one and only parameter @@ -213,7 +213,7 @@ private SourceReference buildFromMultipleSourceParameters(String[] segments, Par * needs to match the parameter name to avoid ambiguity * * consider: {@code Target map( Source1 source1, Source2 source2 )} - * entries in an @Mapping#source need to be "source1.propx" or "source2.propy.propz" to be valid + * entries in a @Mapping#source need to be "source1.propx" or "source2.propy.propz" to be valid * * @param segments the segments of @Mapping#source * @return parameter that matches with first segment of @Mapping#source diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/RoundContext.java b/processor/src/main/java/org/mapstruct/ap/internal/util/RoundContext.java index 78e61fbf29..e34d013bc5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/RoundContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/RoundContext.java @@ -41,7 +41,7 @@ public void addTypeReadyForProcessing(TypeMirror type) { /** * Whether the given type has been found to be ready for further processing or not. This is the case if the type's - * hierarchy is complete (no super-types need to be generated by other processors) an no processors have signaled + * hierarchy is complete (no super-types need to be generated by other processors) and no processors have signaled * the intention to amend the given type. * * @param type the typed to be checked for its readiness From 21069e5a2ece79d3f4a7f7d247b2f96060ccedbb Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 29 Aug 2022 21:15:01 +0200 Subject: [PATCH 116/363] #2895 Generate more readable annotations --- .../ap/internal/model/Annotation.ftl | 15 +++++- .../test/annotatewith/AnnotateWithTest.java | 9 +++- .../test/annotatewith/CustomNamedMapper.java | 38 +++++++++++++++ .../annotatewith/CustomNamedMapperImpl.java | 46 ++++++++++++++++++- .../MultipleArrayValuesMapperImpl.java | 14 +++++- 5 files changed, 117 insertions(+), 5 deletions(-) diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl index eb67d732fc..81f3d2ba8b 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl @@ -6,4 +6,17 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Annotation" --> -@<@includeModel object=type/><#if (properties?size > 0) >(<#list properties as property><@includeModel object=property/><#if property_has_next>, ) \ No newline at end of file +<#switch properties?size> + <#case 0> + @<@includeModel object=type/><#rt> + <#break> + <#case 1> + @<@includeModel object=type/>(<@includeModel object=properties[0]/>)<#rt> + <#break> + <#default> + @<@includeModel object=type/>( + <#list properties as property> + <#nt><@includeModel object=property/><#if property_has_next>, + + )<#rt> + \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java index 31b8d8fad9..e3b20f547e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java @@ -42,8 +42,13 @@ public void mapperBecomesDeprecatedAndGetsCustomAnnotation() { } @ProcessorTest - @WithClasses( { CustomNamedMapper.class, CustomAnnotationWithParamsContainer.class, - CustomAnnotationWithParams.class } ) + @WithClasses( { + CustomNamedMapper.class, + CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class, + CustomClassOnlyAnnotation.class, + CustomMethodOnlyAnnotation.class, + } ) public void annotationWithValue() { generatedSource.addComparisonToFixtureFor( CustomNamedMapper.class ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java index 32241ab4ab..9f48d8c764 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java @@ -13,6 +13,7 @@ * @author Ben Zegveld */ @Mapper +@AnnotateWith( CustomClassOnlyAnnotation.class ) @AnnotateWith( value = CustomAnnotationWithParams.class, elements = { @Element( name = "stringArray", strings = "test" ), @Element( name = "stringParam", strings = "test" ), @@ -35,6 +36,43 @@ @Element( name = "shortArray", shorts = 3 ), @Element( name = "shortParam", shorts = 1 ) } ) +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element(name = "stringParam", strings = "single value") +}) public interface CustomNamedMapper { + @AnnotateWith(value = CustomAnnotationWithParams.class, elements = { + @Element(name = "stringParam", strings = "double method value"), + @Element(name = "stringArray", strings = { "first", "second" }), + }) + @AnnotateWith(value = CustomAnnotationWithParams.class, elements = { + @Element(name = "stringParam", strings = "single method value") + }) + @AnnotateWith( CustomMethodOnlyAnnotation.class ) + Target map(Source source); + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java index cb01cfec85..a485d6676f 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/CustomNamedMapperImpl.java @@ -12,6 +12,50 @@ date = "2022-06-06T16:48:18+0200", comments = "version: , compiler: javac, environment: Java 11.0.12 (Azul Systems, Inc.)" ) -@CustomAnnotationWithParams(stringArray = "test", stringParam = "test", booleanArray = true, booleanParam = true, byteArray = 16, byteParam = 19, charArray = 'd', charParam = 'a', enumArray = AnnotateWithEnum.EXISTING, enumParam = AnnotateWithEnum.EXISTING, doubleArray = 0.3, doubleParam = 1.2, floatArray = 0.300000011920929f, floatParam = 1.2000000476837158f, intArray = 3, intParam = 1, longArray = 3L, longParam = 1L, shortArray = 3, shortParam = 1) +@CustomClassOnlyAnnotation +@CustomAnnotationWithParams( + stringArray = "test", + stringParam = "test", + booleanArray = true, + booleanParam = true, + byteArray = 16, + byteParam = 19, + charArray = 'd', + charParam = 'a', + enumArray = AnnotateWithEnum.EXISTING, + enumParam = AnnotateWithEnum.EXISTING, + doubleArray = 0.3, + doubleParam = 1.2, + floatArray = 0.300000011920929f, + floatParam = 1.2000000476837158f, + intArray = 3, + intParam = 1, + longArray = 3L, + longParam = 1L, + shortArray = 3, + shortParam = 1 +) +@CustomAnnotationWithParams(stringParam = "single value") public class CustomNamedMapperImpl implements CustomNamedMapper { + + @CustomAnnotationWithParams( + stringParam = "double method value", + stringArray = { "first", "second" } + ) + @CustomAnnotationWithParams(stringParam = "single method value") + @CustomMethodOnlyAnnotation + @Override + public Target map(Source source) { + if ( source == null ) { + return null; + } + + String value = null; + + value = source.getValue(); + + Target target = new Target( value ); + + return target; + } } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java index fe9bb93f13..ea15207c66 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapperImpl.java @@ -13,6 +13,18 @@ date = "2022-06-06T16:48:23+0200", comments = "version: , compiler: javac, environment: Java 11.0.12 (Azul Systems, Inc.)" ) -@CustomAnnotationWithParams(stringArray = { "test1", "test2" }, booleanArray = { false, true }, byteArray = { 8, 31 }, charArray = { 'b', 'c' }, doubleArray = { 1.2, 3.4 }, floatArray = { 1.2000000476837158f, 3.4000000953674316f }, intArray = { 12, 34 }, longArray = { 12L, 34L }, shortArray = { 12, 34 }, classArray = { Mapper.class, CustomAnnotationWithParams.class }, stringParam = "required parameter") +@CustomAnnotationWithParams( + stringArray = { "test1", "test2" }, + booleanArray = { false, true }, + byteArray = { 8, 31 }, + charArray = { 'b', 'c' }, + doubleArray = { 1.2, 3.4 }, + floatArray = { 1.2000000476837158f, 3.4000000953674316f }, + intArray = { 12, 34 }, + longArray = { 12L, 34L }, + shortArray = { 12, 34 }, + classArray = { Mapper.class, CustomAnnotationWithParams.class }, + stringParam = "required parameter" +) public class MultipleArrayValuesMapperImpl implements MultipleArrayValuesMapper { } From bbf63ae1777981d1d89693d6f9d980064997cf28 Mon Sep 17 00:00:00 2001 From: Iaroslav Bogdanchikov <6546969+ibogdanchikov@users.noreply.github.com> Date: Fri, 2 Sep 2022 22:04:01 +0200 Subject: [PATCH 117/363] #2730 Add support for Jakarta XML Binding --- distribution/pom.xml | 8 +- integrationtest/pom.xml | 2 +- .../itest/tests/MavenIntegrationTest.java | 4 + .../test/resources/fullFeatureTest/pom.xml | 17 ++- .../test/resources/jakartaJaxbTest/pom.xml | 64 ++++++++++ .../itest/jakarta/jaxb/OrderDetailsDto.java | 42 +++++++ .../itest/jakarta/jaxb/OrderDto.java | 52 ++++++++ .../itest/jakarta/jaxb/OrderStatusDto.java | 35 ++++++ .../jakarta/jaxb/ShippingAddressDto.java | 50 ++++++++ .../jakarta/jaxb/SourceTargetMapper.java | 50 ++++++++ .../itest/jakarta/jaxb/SubTypeDto.java | 27 ++++ .../itest/jakarta/jaxb/SuperTypeDto.java | 27 ++++ .../src/main/resources/binding/binding.xjb | 28 +++++ .../src/main/resources/schema/test1.xsd | 36 ++++++ .../src/main/resources/schema/test2.xsd | 33 +++++ .../src/main/resources/schema/underscores.xsd | 34 +++++ .../jaxb/JakartaJaxbBasedMapperTest.java | 118 ++++++++++++++++++ .../src/test/resources/jaxbTest/pom.xml | 12 +- parent/pom.xml | 29 ++++- processor/pom.xml | 8 +- .../gem/jakarta/JakartaGemGenerator.java | 26 ++++ .../source/builtin/BuiltInMappingMethods.java | 21 +++- .../model/source/builtin/JaxbElemToValue.java | 11 +- .../JakartaXmlElementDeclSelector.java | 48 +++++++ .../selector/JavaxXmlElementDeclSelector.java | 48 +++++++ .../source/selector/MethodSelectors.java | 3 +- .../selector/XmlElementDeclSelector.java | 65 +++++++--- .../ap/internal/util/JaxbConstants.java | 3 +- .../ap/test/builtin/BuiltInTest.java | 77 ++++++++++++ .../bean/JakartaJaxbElementListProperty.java | 28 +++++ .../bean/JakartaJaxbElementProperty.java | 25 ++++ .../builtin/mapper/JakartaJaxbListMapper.java | 19 +++ .../builtin/mapper/JakartaJaxbMapper.java | 31 +++++ .../ap/testutil/WithJakartaJaxb.java | 27 ++++ 34 files changed, 1062 insertions(+), 46 deletions(-) create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/pom.xml create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDetailsDto.java create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDto.java create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderStatusDto.java create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/ShippingAddressDto.java create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SourceTargetMapper.java create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SubTypeDto.java create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SuperTypeDto.java create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/binding/binding.xjb create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test1.xsd create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test2.xsd create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/underscores.xsd create mode 100644 integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/gem/jakarta/JakartaGemGenerator.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementListProperty.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementProperty.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbListMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaJaxb.java diff --git a/distribution/pom.xml b/distribution/pom.xml index 9ceeb505ea..11fb7c9f37 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -39,6 +39,13 @@ org.mapstruct.tools.gem gem-api + + + jakarta.xml.bind + jakarta.xml.bind-api + provided + true + @@ -187,7 +194,6 @@ javax.xml.bind jaxb-api - 2.3.1 provided true diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 364faae643..f888e226ff 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -133,8 +133,8 @@ javax.xml.bind jaxb-api - 2.3.1 provided + true diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 7d63ccf774..75513dd6c6 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -75,6 +75,10 @@ void java8Test() { void jaxbTest() { } + @ProcessorTest(baseDir = "jakartaJaxbTest") + void jakartaJaxbTest() { + } + @ProcessorTest(baseDir = "jsr330Test") void jsr330Test() { } diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index 62f7986e9c..ac69114bd6 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -83,6 +83,13 @@ joda-time joda-time + + + jakarta.xml.bind + jakarta.xml.bind-api + provided + true + @@ -93,14 +100,16 @@ - jakarta.xml.bind - jakarta.xml.bind-api - 2.3.2 + javax.xml.bind + jaxb-api + provided + true org.glassfish.jaxb jaxb-runtime - 2.3.2 + provided + true diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/pom.xml b/integrationtest/src/test/resources/jakartaJaxbTest/pom.xml new file mode 100644 index 0000000000..3aabc1c7ae --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/pom.xml @@ -0,0 +1,64 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + jakartaJaxbTest + jar + + + + jakarta.xml.bind + jakarta.xml.bind-api + provided + true + + + com.sun.xml.bind + jaxb-impl + provided + true + + + + + + + org.codehaus.mojo + jaxb2-maven-plugin + 3.1.0 + + + xjc + initialize + + xjc + + + + + + ${project.build.resources[0].directory}/binding + + + ${project.build.resources[0].directory}/schema + + + + + + diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDetailsDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDetailsDto.java new file mode 100644 index 0000000000..e8500633e4 --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDetailsDto.java @@ -0,0 +1,42 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.jakarta.jaxb; + +import java.util.List; + +/** + * @author Sjaak Derksen + */ +public class OrderDetailsDto { + + private String name; + private List description; + private OrderStatusDto status; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getDescription() { + return description; + } + + public void setDescription(List description) { + this.description = description; + } + + public OrderStatusDto getStatus() { + return status; + } + + public void setStatus(OrderStatusDto status) { + this.status = status; + } +} diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDto.java new file mode 100644 index 0000000000..f94d5362ef --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderDto.java @@ -0,0 +1,52 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.jakarta.jaxb; + +import java.util.Date; + +/** + * @author Sjaak Derksen + */ +public class OrderDto { + + private Long orderNumber; + private Date orderDate; + private OrderDetailsDto orderDetails; + private ShippingAddressDto shippingAddress; + + public Long getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(Long orderNumber) { + this.orderNumber = orderNumber; + } + + public Date getOrderDate() { + return orderDate; + } + + public void setOrderDate(Date orderDate) { + this.orderDate = orderDate; + } + + public OrderDetailsDto getOrderDetails() { + return orderDetails; + } + + public void setOrderDetails(OrderDetailsDto orderDetails) { + this.orderDetails = orderDetails; + } + + public ShippingAddressDto getShippingAddress() { + return shippingAddress; + } + + public void setShippingAddress(ShippingAddressDto shippingAddress) { + this.shippingAddress = shippingAddress; + } + +} diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderStatusDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderStatusDto.java new file mode 100644 index 0000000000..5da5d45c99 --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/OrderStatusDto.java @@ -0,0 +1,35 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.jakarta.jaxb; + +/** + * @author Sjaak Derksen + */ +public enum OrderStatusDto { + + ORDERED( "small" ), + PROCESSED( "medium" ), + DELIVERED( "large" ); + private final String value; + + OrderStatusDto(String v) { + value = v; + } + + public String value() { + return value; + } + + public static OrderStatusDto fromValue(String v) { + for ( OrderStatusDto c : OrderStatusDto.values() ) { + if ( c.value.equals( v ) ) { + return c; + } + } + throw new IllegalArgumentException( v ); + } + +} diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/ShippingAddressDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/ShippingAddressDto.java new file mode 100644 index 0000000000..6bc40a19b2 --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/ShippingAddressDto.java @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.jakarta.jaxb; + +/** + * @author Sjaak Derksen + */ +public class ShippingAddressDto { + + private String street; + private String houseNumber; + private String city; + private String country; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getHouseNumber() { + return houseNumber; + } + + public void setHouseNumber(String houseNumber) { + this.houseNumber = houseNumber; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + +} diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SourceTargetMapper.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SourceTargetMapper.java new file mode 100644 index 0000000000..3b76aad437 --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SourceTargetMapper.java @@ -0,0 +1,50 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.jakarta.jaxb; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; +import org.mapstruct.itest.jakarta.jaxb.xsd.test1.OrderDetailsType; +import org.mapstruct.itest.jakarta.jaxb.xsd.test1.OrderType; +import org.mapstruct.itest.jakarta.jaxb.xsd.test2.OrderStatusType; +import org.mapstruct.itest.jakarta.jaxb.xsd.test2.ShippingAddressType; +import org.mapstruct.itest.jakarta.jaxb.xsd.underscores.SubType; + + +/** + * @author Sjaak Derksen + */ +@Mapper(uses = { + org.mapstruct.itest.jakarta.jaxb.xsd.test1.ObjectFactory.class, + org.mapstruct.itest.jakarta.jaxb.xsd.test2.ObjectFactory.class, + org.mapstruct.itest.jakarta.jaxb.xsd.underscores.ObjectFactory.class +}) +public interface SourceTargetMapper { + + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + // source 2 target methods + OrderDto sourceToTarget(OrderType source); + + OrderDetailsDto detailsToDto(OrderDetailsType source); + + OrderStatusDto statusToDto(OrderStatusType source); + + ShippingAddressDto shippingAddressToDto(ShippingAddressType source); + + SubTypeDto subTypeToDto(SubType source); + + // target 2 source methods + OrderType targetToSource(OrderDto target); + + OrderDetailsType dtoToDetails(OrderDetailsDto target); + + OrderStatusType dtoToStatus(OrderStatusDto target); + + ShippingAddressType dtoToShippingAddress(ShippingAddressDto source); + + SubType dtoToSubType(SubTypeDto source); +} diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SubTypeDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SubTypeDto.java new file mode 100644 index 0000000000..88218c2771 --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SubTypeDto.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.jakarta.jaxb; + +public class SubTypeDto extends SuperTypeDto { + private String declaredCamelCase; + private String declaredUnderscore; + + public String getDeclaredCamelCase() { + return declaredCamelCase; + } + + public void setDeclaredCamelCase(String declaredCamelCase) { + this.declaredCamelCase = declaredCamelCase; + } + + public String getDeclaredUnderscore() { + return declaredUnderscore; + } + + public void setDeclaredUnderscore(String declaredUnderscore) { + this.declaredUnderscore = declaredUnderscore; + } +} diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SuperTypeDto.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SuperTypeDto.java new file mode 100644 index 0000000000..cd0c6e22e7 --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SuperTypeDto.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.jakarta.jaxb; + +public class SuperTypeDto { + private String inheritedCamelCase; + private String inheritedUnderscore; + + public String getInheritedCamelCase() { + return inheritedCamelCase; + } + + public void setInheritedCamelCase(String inheritedCamelCase) { + this.inheritedCamelCase = inheritedCamelCase; + } + + public String getInheritedUnderscore() { + return inheritedUnderscore; + } + + public void setInheritedUnderscore(String inheritedUnderscore) { + this.inheritedUnderscore = inheritedUnderscore; + } +} diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/binding/binding.xjb b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/binding/binding.xjb new file mode 100644 index 0000000000..8f26b1a1ea --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/binding/binding.xjb @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test1.xsd b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test1.xsd new file mode 100644 index 0000000000..3433b01465 --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test1.xsd @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test2.xsd b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test2.xsd new file mode 100644 index 0000000000..f3b564a48e --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/test2.xsd @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/underscores.xsd b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/underscores.xsd new file mode 100644 index 0000000000..b7f5904656 --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/resources/schema/underscores.xsd @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java b/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java new file mode 100644 index 0000000000..b81c946d9c --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.java @@ -0,0 +1,118 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.jakarta.jaxb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayOutputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; + +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBElement; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Marshaller; + +import org.junit.Test; +import org.mapstruct.itest.jakarta.jaxb.xsd.test1.ObjectFactory; +import org.mapstruct.itest.jakarta.jaxb.xsd.test1.OrderType; +import org.mapstruct.itest.jakarta.jaxb.xsd.underscores.SubType; + +/** + * Test for generation of Jakarta JAXB based mapper implementations. + * + * @author Iaroslav Bogdanchikov + */ +public class JakartaJaxbBasedMapperTest { + + @Test + public void shouldMapJakartaJaxb() throws ParseException, JAXBException { + + SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; + + OrderDto source1 = new OrderDto(); + source1.setOrderDetails( new OrderDetailsDto() ); + source1.setOrderNumber( 11L ); + source1.setOrderDate( createDate( "31-08-1982 10:20:56" ) ); + source1.setShippingAddress( new ShippingAddressDto() ); + source1.getShippingAddress().setCity( "SmallTown" ); + source1.getShippingAddress().setHouseNumber( "11a" ); + source1.getShippingAddress().setStreet( "Awesome rd" ); + source1.getShippingAddress().setCountry( "USA" ); + source1.getOrderDetails().setDescription( new ArrayList() ); + source1.getOrderDetails().setName( "Shopping list for a Mapper" ); + source1.getOrderDetails().getDescription().add( "1 MapStruct" ); + source1.getOrderDetails().getDescription().add( "3 Lines of Code" ); + source1.getOrderDetails().getDescription().add( "1 Dose of Luck" ); + source1.getOrderDetails().setStatus( OrderStatusDto.ORDERED ); + + // map to JAXB + OrderType target = mapper.targetToSource( source1 ); + + // do a pretty print + ObjectFactory of = new ObjectFactory(); + System.out.println( toXml( of.createOrder( target ) ) ); + + // map back from JAXB + OrderDto source2 = mapper.sourceToTarget( target ); + + // verify that source1 and source 2 are equal + assertThat( source2.getOrderNumber() ).isEqualTo( source1.getOrderNumber() ); + assertThat( source2.getOrderDate() ).isEqualTo( source1.getOrderDate() ); + assertThat( source2.getOrderDetails().getDescription().size() ).isEqualTo( + source1.getOrderDetails().getDescription().size() + ); + assertThat( source2.getOrderDetails().getDescription().get( 0 ) ).isEqualTo( + source1.getOrderDetails().getDescription().get( 0 ) + ); + assertThat( source2.getOrderDetails().getDescription().get( 1 ) ).isEqualTo( + source1.getOrderDetails().getDescription().get( 1 ) + ); + assertThat( source2.getOrderDetails().getDescription().get( 2 ) ).isEqualTo( + source1.getOrderDetails().getDescription().get( 2 ) + ); + assertThat( source2.getOrderDetails().getName() ).isEqualTo( source1.getOrderDetails().getName() ); + assertThat( source2.getOrderDetails().getStatus() ).isEqualTo( source1.getOrderDetails().getStatus() ); + } + + @Test + public void underscores() throws ParseException, JAXBException { + + SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; + + SubTypeDto source1 = new SubTypeDto(); + source1.setInheritedCamelCase("InheritedCamelCase"); + source1.setInheritedUnderscore("InheritedUnderscore"); + source1.setDeclaredCamelCase("DeclaredCamelCase"); + source1.setDeclaredUnderscore("DeclaredUnderscore"); + + SubType target = mapper.dtoToSubType( source1 ); + + SubTypeDto source2 = mapper.subTypeToDto( target ); + + assertThat( source2.getInheritedCamelCase() ).isEqualTo( source1.getInheritedCamelCase() ); + assertThat( source2.getInheritedUnderscore() ).isEqualTo( source1.getInheritedUnderscore() ); + assertThat( source2.getDeclaredCamelCase() ).isEqualTo( source1.getDeclaredCamelCase() ); + assertThat( source2.getDeclaredUnderscore() ).isEqualTo( source1.getDeclaredUnderscore() ); + } + + private Date createDate(String date) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" ); + return sdf.parse( date ); + } + + private String toXml(JAXBElement element) throws JAXBException { + JAXBContext jc = JAXBContext.newInstance( element.getValue().getClass() ); + Marshaller marshaller = jc.createMarshaller(); + marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE ); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + marshaller.marshal( element, baos ); + return baos.toString(); + } +} diff --git a/integrationtest/src/test/resources/jaxbTest/pom.xml b/integrationtest/src/test/resources/jaxbTest/pom.xml index 000e7cf598..0e69e23e01 100644 --- a/integrationtest/src/test/resources/jaxbTest/pom.xml +++ b/integrationtest/src/test/resources/jaxbTest/pom.xml @@ -51,7 +51,7 @@ org.glassfish.jaxb jaxb-runtime - 2.3.2 + ${jaxb-runtime.version} @@ -66,14 +66,16 @@ - jakarta.xml.bind - jakarta.xml.bind-api - 2.3.2 + javax.xml.bind + jaxb-api + provided + true org.glassfish.jaxb jaxb-runtime - 2.3.2 + provided + true diff --git a/parent/pom.xml b/parent/pom.xml index 426405b4b7..3f536034c7 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -42,6 +42,7 @@ --> 1.8 3.21.2 + 2.3.2 @@ -255,6 +256,30 @@ 2.9 + + + + javax.xml.bind + jaxb-api + 2.3.1 + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb-runtime.version} + + + + jakarta.xml.bind + jakarta.xml.bind-api + 3.0.1 + + + com.sun.xml.bind + jaxb-impl + 3.0.2 + + org.eclipse.tycho @@ -488,12 +513,12 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.17 + 1.20 org.ow2.asm asm - 6.2.1 + 7.0 diff --git a/processor/pom.xml b/processor/pom.xml index ff7864f32e..12fc615f5f 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -137,6 +137,13 @@ joda-time test + + + jakarta.xml.bind + jakarta.xml.bind-api + provided + true + @@ -375,7 +382,6 @@ javax.xml.bind jaxb-api - 2.3.1 provided true diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/jakarta/JakartaGemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/jakarta/JakartaGemGenerator.java new file mode 100644 index 0000000000..93bdebeaef --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/jakarta/JakartaGemGenerator.java @@ -0,0 +1,26 @@ +/* + * 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.internal.gem.jakarta; + +import jakarta.xml.bind.annotation.XmlElementDecl; +import jakarta.xml.bind.annotation.XmlElementRef; +import org.mapstruct.tools.gem.GemDefinition; + +/** + * This class is a temporary solution to an issue in the Gem Tools library. + * + *

    + * This class can be merged with {@link org.mapstruct.ap.internal.gem.GemGenerator} + * after the mentioned issue is resolved. + *

    + * + * @see Gem Tools issue #10 + * @author Iaroslav Bogdanchikov + */ +@GemDefinition(XmlElementDecl.class) +@GemDefinition(XmlElementRef.class) +class JakartaGemGenerator { +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java index 0edae7f10f..6cd1605b23 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.util.JaxbConstants; import org.mapstruct.ap.internal.util.JodaTimeConstants; @@ -24,7 +25,7 @@ public class BuiltInMappingMethods { public BuiltInMappingMethods(TypeFactory typeFactory) { boolean isXmlGregorianCalendarPresent = isXmlGregorianCalendarAvailable( typeFactory ); - builtInMethods = new ArrayList<>( 20 ); + builtInMethods = new ArrayList<>( 21 ); if ( isXmlGregorianCalendarPresent ) { builtInMethods.add( new DateToXmlGregorianCalendar( typeFactory ) ); builtInMethods.add( new XmlGregorianCalendarToDate( typeFactory ) ); @@ -39,8 +40,14 @@ public BuiltInMappingMethods(TypeFactory typeFactory) { builtInMethods.add( new XmlGregorianCalendarToLocalDateTime( typeFactory ) ); } - if ( isJaxbAvailable( typeFactory ) ) { - builtInMethods.add( new JaxbElemToValue( typeFactory ) ); + if ( isJavaxJaxbAvailable( typeFactory ) ) { + Type type = typeFactory.getType( JaxbConstants.JAVAX_JAXB_ELEMENT_FQN ); + builtInMethods.add( new JaxbElemToValue( type ) ); + } + + if ( isJakartaJaxbAvailable( typeFactory ) ) { + Type type = typeFactory.getType( JaxbConstants.JAKARTA_JAXB_ELEMENT_FQN ); + builtInMethods.add( new JaxbElemToValue( type ) ); } builtInMethods.add( new ZonedDateTimeToCalendar( typeFactory ) ); @@ -58,8 +65,12 @@ public BuiltInMappingMethods(TypeFactory typeFactory) { } } - private static boolean isJaxbAvailable(TypeFactory typeFactory) { - return typeFactory.isTypeAvailable( JaxbConstants.JAXB_ELEMENT_FQN ); + private static boolean isJavaxJaxbAvailable(TypeFactory typeFactory) { + return typeFactory.isTypeAvailable( JaxbConstants.JAVAX_JAXB_ELEMENT_FQN ); + } + + private static boolean isJakartaJaxbAvailable(TypeFactory typeFactory) { + return typeFactory.isTypeAvailable( JaxbConstants.JAKARTA_JAXB_ELEMENT_FQN ); } private static boolean isXmlGregorianCalendarAvailable(TypeFactory typeFactory) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.java index 0d3d4c30af..2a5d1639e6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.java @@ -5,26 +5,23 @@ */ package org.mapstruct.ap.internal.model.source.builtin; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.util.Set; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.util.JaxbConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * @author Sjaak Derksen */ -public class JaxbElemToValue extends BuiltInMethod { +class JaxbElemToValue extends BuiltInMethod { private final Parameter parameter; private final Type returnType; private final Set importTypes; - public JaxbElemToValue(TypeFactory typeFactory) { - Type type = typeFactory.getType( JaxbConstants.JAXB_ELEMENT_FQN ); + JaxbElemToValue(Type type) { this.parameter = new Parameter( "element", type ); this.returnType = type.getTypeParameters().get( 0 ); this.importTypes = asSet( parameter.getType() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java new file mode 100644 index 0000000000..df5cd848a5 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java @@ -0,0 +1,48 @@ +/* + * 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.internal.model.source.selector; + +import javax.lang.model.element.Element; + +import org.mapstruct.ap.internal.gem.jakarta.XmlElementDeclGem; +import org.mapstruct.ap.internal.gem.jakarta.XmlElementRefGem; +import org.mapstruct.ap.internal.util.TypeUtils; + +/** + * The concrete implementation of the {@link XmlElementDeclSelector} that + * works with {@link jakarta.xml.bind.annotation.XmlElementRef} and + * {@link jakarta.xml.bind.annotation.XmlElementDecl}. + * + * @author Iaroslav Bogdanchikov + */ +class JakartaXmlElementDeclSelector extends XmlElementDeclSelector { + + JakartaXmlElementDeclSelector(TypeUtils typeUtils) { + super( typeUtils ); + } + + @Override + XmlElementDeclInfo getXmlElementDeclInfo(Element element) { + XmlElementDeclGem gem = XmlElementDeclGem.instanceOn( element ); + + if (gem == null) { + return null; + } + + return new XmlElementDeclInfo( gem.name().get(), gem.scope().get() ); + } + + @Override + XmlElementRefInfo getXmlElementRefInfo(Element element) { + XmlElementRefGem gem = XmlElementRefGem.instanceOn( element ); + + if (gem == null) { + return null; + } + + return new XmlElementRefInfo( gem.name().get(), gem.type().get() ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java new file mode 100644 index 0000000000..1d02e97e90 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java @@ -0,0 +1,48 @@ +/* + * 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.internal.model.source.selector; + +import javax.lang.model.element.Element; + +import org.mapstruct.ap.internal.gem.XmlElementDeclGem; +import org.mapstruct.ap.internal.gem.XmlElementRefGem; +import org.mapstruct.ap.internal.util.TypeUtils; + +/** + * The concrete implementation of the {@link XmlElementDeclSelector} that + * works with {@link javax.xml.bind.annotation.XmlElementRef} and + * {@link javax.xml.bind.annotation.XmlElementDecl}. + * + * @author Iaroslav Bogdanchikov + */ +class JavaxXmlElementDeclSelector extends XmlElementDeclSelector { + + JavaxXmlElementDeclSelector(TypeUtils typeUtils) { + super( typeUtils ); + } + + @Override + XmlElementDeclInfo getXmlElementDeclInfo(Element element) { + XmlElementDeclGem gem = XmlElementDeclGem.instanceOn( element ); + + if (gem == null) { + return null; + } + + return new XmlElementDeclInfo( gem.name().get(), gem.scope().get() ); + } + + @Override + XmlElementRefInfo getXmlElementRefInfo(Element element) { + XmlElementRefGem gem = XmlElementRefGem.instanceOn( element ); + + if (gem == null) { + return null; + } + + return new XmlElementRefInfo( gem.name().get(), gem.type().get() ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java index de429174da..519e1c3d6d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java @@ -32,7 +32,8 @@ public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, TypeFacto new TypeSelector( typeFactory, messager ), new QualifierSelector( typeUtils, elementUtils ), new TargetTypeSelector( typeUtils ), - new XmlElementDeclSelector( typeUtils ), + new JavaxXmlElementDeclSelector( typeUtils ), + new JakartaXmlElementDeclSelector( typeUtils ), new InheritanceSelector(), new CreateOrUpdateSelector(), new SourceRhsSelector(), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java index 7bdae0b771..91b4b5ca10 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java @@ -11,19 +11,17 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import org.mapstruct.ap.internal.util.TypeUtils; -import org.mapstruct.ap.internal.gem.XmlElementRefGem; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SourceMethod; -import org.mapstruct.ap.internal.gem.XmlElementDeclGem; +import org.mapstruct.ap.internal.util.TypeUtils; /** - * Finds the {@link javax.xml.bind.annotation.XmlElementRef} annotation on a field (of the mapping result type or its + * Finds the {@code XmlElementRef} annotation on a field (of the mapping result type or its * super types) matching the * target property name. Then selects those methods with matching {@code name} and {@code scope} attributes of the - * {@link javax.xml.bind.annotation.XmlElementDecl} annotation, if that is present. Matching happens in the following + * {@code XmlElementDecl} annotation, if that is present. Matching happens in the following * order: *
      *
    1. Name and Scope matches
    2. @@ -34,12 +32,15 @@ * the given method is not annotated with {@code XmlElementDecl} it will be considered as matching. * * @author Sjaak Derksen + * + * @see JavaxXmlElementDeclSelector + * @see JakartaXmlElementDeclSelector */ -public class XmlElementDeclSelector implements MethodSelector { +abstract class XmlElementDeclSelector implements MethodSelector { private final TypeUtils typeUtils; - public XmlElementDeclSelector(TypeUtils typeUtils) { + XmlElementDeclSelector(TypeUtils typeUtils) { this.typeUtils = typeUtils; } @@ -63,15 +64,14 @@ public List> getMatchingMethods(Method mapp } SourceMethod candidateMethod = (SourceMethod) candidate.getMethod(); - XmlElementDeclGem xmlElementDecl = - XmlElementDeclGem.instanceOn( candidateMethod.getExecutable() ); + XmlElementDeclInfo xmlElementDeclInfo = getXmlElementDeclInfo( candidateMethod.getExecutable() ); - if ( xmlElementDecl == null ) { + if ( xmlElementDeclInfo == null ) { continue; } - String name = xmlElementDecl.name().get(); - TypeMirror scope = xmlElementDecl.scope().getValue(); + String name = xmlElementDeclInfo.nameValue(); + TypeMirror scope = xmlElementDeclInfo.scopeType(); boolean nameIsSetAndMatches = name != null && name.equals( xmlElementRefInfo.nameValue() ); boolean scopeIsSetAndMatches = @@ -142,9 +142,9 @@ private XmlElementRefInfo findXmlElementRef(Type resultType, String targetProper for ( Element enclosed : currentElement.getEnclosedElements() ) { if ( enclosed.getKind().equals( ElementKind.FIELD ) && enclosed.getSimpleName().contentEquals( targetPropertyName ) ) { - XmlElementRefGem xmlElementRef = XmlElementRefGem.instanceOn( enclosed ); - if ( xmlElementRef != null ) { - return new XmlElementRefInfo( xmlElementRef.name().get(), currentMirror ); + XmlElementRefInfo xmlElementRefInfo = getXmlElementRefInfo( enclosed ); + if ( xmlElementRefInfo != null ) { + return new XmlElementRefInfo( xmlElementRefInfo.nameValue(), currentMirror ); } } } @@ -154,7 +154,11 @@ private XmlElementRefInfo findXmlElementRef(Type resultType, String targetProper return defaultInfo; } - private static class XmlElementRefInfo { + abstract XmlElementDeclInfo getXmlElementDeclInfo(Element element); + + abstract XmlElementRefInfo getXmlElementRefInfo(Element element); + + static class XmlElementRefInfo { private final String nameValue; private final TypeMirror sourceType; @@ -163,12 +167,37 @@ private static class XmlElementRefInfo { this.sourceType = sourceType; } - public String nameValue() { + String nameValue() { return nameValue; } - public TypeMirror sourceType() { + TypeMirror sourceType() { return sourceType; } } + + /** + * A class, whose purpose is to combine the use of + * {@link org.mapstruct.ap.internal.gem.XmlElementDeclGem} + * and + * {@link org.mapstruct.ap.internal.gem.jakarta.XmlElementDeclGem}. + */ + static class XmlElementDeclInfo { + + private final String nameValue; + private final TypeMirror scopeType; + + XmlElementDeclInfo(String nameValue, TypeMirror scopeType) { + this.nameValue = nameValue; + this.scopeType = scopeType; + } + + String nameValue() { + return nameValue; + } + + TypeMirror scopeType() { + return scopeType; + } + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java index db18673ba2..c89877062e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java @@ -10,7 +10,8 @@ */ public final class JaxbConstants { - public static final String JAXB_ELEMENT_FQN = "javax.xml.bind.JAXBElement"; + public static final String JAVAX_JAXB_ELEMENT_FQN = "javax.xml.bind.JAXBElement"; + public static final String JAKARTA_JAXB_ELEMENT_FQN = "jakarta.xml.bind.JAXBElement"; private JaxbConstants() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java index 2dc41b822d..c8ee922af9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java @@ -29,6 +29,8 @@ import org.mapstruct.ap.test.builtin.bean.BigDecimalProperty; import org.mapstruct.ap.test.builtin.bean.CalendarProperty; import org.mapstruct.ap.test.builtin.bean.DateProperty; +import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementListProperty; +import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementProperty; import org.mapstruct.ap.test.builtin.bean.JaxbElementListProperty; import org.mapstruct.ap.test.builtin.bean.JaxbElementProperty; import org.mapstruct.ap.test.builtin.bean.SomeType; @@ -45,6 +47,8 @@ import org.mapstruct.ap.test.builtin.mapper.DateToCalendarMapper; import org.mapstruct.ap.test.builtin.mapper.DateToXmlGregCalMapper; import org.mapstruct.ap.test.builtin.mapper.IterableSourceTargetMapper; +import org.mapstruct.ap.test.builtin.mapper.JakartaJaxbListMapper; +import org.mapstruct.ap.test.builtin.mapper.JakartaJaxbMapper; import org.mapstruct.ap.test.builtin.mapper.JaxbListMapper; import org.mapstruct.ap.test.builtin.mapper.JaxbMapper; import org.mapstruct.ap.test.builtin.mapper.MapSourceTargetMapper; @@ -58,6 +62,7 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaJaxb; import org.mapstruct.ap.testutil.WithJavaxJaxb; import static org.assertj.core.api.Assertions.assertThat; @@ -101,6 +106,23 @@ public void shouldApplyBuiltInOnJAXBElement() { assertThat( target.publicProp ).isEqualTo( "PUBLIC TEST" ); } + @ProcessorTest + @WithClasses( { + JakartaJaxbMapper.class, + JakartaJaxbElementProperty.class, + } ) + @WithJakartaJaxb + public void shouldApplyBuiltInOnJakartaJaxbElement() { + JakartaJaxbElementProperty source = new JakartaJaxbElementProperty(); + source.setProp( createJakartaJaxb( "TEST" ) ); + source.publicProp = createJakartaJaxb( "PUBLIC TEST" ); + + StringProperty target = JakartaJaxbMapper.INSTANCE.map( source ); + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( "TEST" ); + assertThat( target.publicProp ).isEqualTo( "PUBLIC TEST" ); + } + @ProcessorTest @WithClasses( { JaxbMapper.class, @@ -128,6 +150,33 @@ public void shouldApplyBuiltInOnJAXBElementExtra() { assertThat( target2.getProp() ).isNotNull(); } + @ProcessorTest + @WithClasses( { + JakartaJaxbMapper.class, + JakartaJaxbElementProperty.class, + } ) + @WithJakartaJaxb + @IssueKey( "1698" ) + public void shouldApplyBuiltInOnJakartaJAXBElementExtra() { + JakartaJaxbElementProperty source = new JakartaJaxbElementProperty(); + source.setProp( createJakartaJaxb( "5" ) ); + source.publicProp = createJakartaJaxb( "5" ); + + BigDecimalProperty target = JakartaJaxbMapper.INSTANCE.mapBD( source ); + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( new BigDecimal( "5" ) ); + assertThat( target.publicProp ).isEqualTo( new BigDecimal( "5" ) ); + + JakartaJaxbElementProperty source2 = new JakartaJaxbElementProperty(); + source2.setProp( createJakartaJaxb( "5" ) ); + source2.publicProp = createJakartaJaxb( "5" ); + + SomeTypeProperty target2 = JakartaJaxbMapper.INSTANCE.mapSomeType( source2 ); + assertThat( target2 ).isNotNull(); + assertThat( target2.publicProp ).isNotNull(); + assertThat( target2.getProp() ).isNotNull(); + } + @ProcessorTest @WithClasses( { JaxbListMapper.class, @@ -147,6 +196,24 @@ public void shouldApplyBuiltInOnJAXBElementList() { assertThat( target.publicProp.get( 0 ) ).isEqualTo( "PUBLIC TEST2" ); } + @ProcessorTest + @WithClasses( { + JakartaJaxbListMapper.class, + JakartaJaxbElementListProperty.class, + } ) + @WithJakartaJaxb + @IssueKey( "141" ) + public void shouldApplyBuiltInOnJakartaJAXBElementList() { + JakartaJaxbElementListProperty source = new JakartaJaxbElementListProperty(); + source.setProp( createJakartaJaxbList( "TEST2" ) ); + source.publicProp = createJakartaJaxbList( "PUBLIC TEST2" ); + + StringListProperty target = JakartaJaxbListMapper.INSTANCE.map( source ); + assertThat( target ).isNotNull(); + assertThat( target.getProp().get( 0 ) ).isEqualTo( "TEST2" ); + assertThat( target.publicProp.get( 0 ) ).isEqualTo( "PUBLIC TEST2" ); + } + @ProcessorTest @WithClasses( DateToXmlGregCalMapper.class ) public void shouldApplyBuiltInOnDateToXmlGregCal() throws ParseException { @@ -414,12 +481,22 @@ private JAXBElement createJaxb(String test) { return new JAXBElement<>( new QName( "www.mapstruct.org", "test" ), String.class, test ); } + private jakarta.xml.bind.JAXBElement createJakartaJaxb(String test) { + return new jakarta.xml.bind.JAXBElement<>( new QName( "www.mapstruct.org", "test" ), String.class, test ); + } + private List> createJaxbList(String test) { List> result = new ArrayList<>(); result.add( createJaxb( test ) ); return result; } + private List> createJakartaJaxbList(String test) { + List> result = new ArrayList<>(); + result.add( createJakartaJaxb( test ) ); + return result; + } + private Date createDate(String date) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" ); return sdf.parse( date ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementListProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementListProperty.java new file mode 100644 index 0000000000..f6ab537253 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementListProperty.java @@ -0,0 +1,28 @@ +/* + * 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.builtin.bean; + +import java.util.List; + +import jakarta.xml.bind.JAXBElement; + +public class JakartaJaxbElementListProperty { + + // CHECKSTYLE:OFF + public List> publicProp; + // CHECKSTYLE:ON + + private List> prop; + + public List> getProp() { + return prop; + } + + public void setProp( List> prop ) { + this.prop = prop; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementProperty.java new file mode 100644 index 0000000000..0336afe814 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementProperty.java @@ -0,0 +1,25 @@ +/* + * 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.builtin.bean; + +import jakarta.xml.bind.JAXBElement; + +public class JakartaJaxbElementProperty { + + // CHECKSTYLE:OFF + public JAXBElement publicProp; + // CHECKSTYLE:ON + + private JAXBElement prop; + + public JAXBElement getProp() { + return prop; + } + + public void setProp( JAXBElement prop ) { + this.prop = prop; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbListMapper.java new file mode 100644 index 0000000000..602e2180f1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbListMapper.java @@ -0,0 +1,19 @@ +/* + * 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.builtin.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementListProperty; +import org.mapstruct.ap.test.builtin.bean.StringListProperty; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface JakartaJaxbListMapper { + + JakartaJaxbListMapper INSTANCE = Mappers.getMapper( JakartaJaxbListMapper.class ); + + StringListProperty map(JakartaJaxbElementListProperty source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbMapper.java new file mode 100644 index 0000000000..9334792572 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbMapper.java @@ -0,0 +1,31 @@ +/* + * 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.builtin.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builtin.bean.BigDecimalProperty; +import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementProperty; +import org.mapstruct.ap.test.builtin.bean.SomeType; +import org.mapstruct.ap.test.builtin.bean.SomeTypeProperty; +import org.mapstruct.ap.test.builtin.bean.StringProperty; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface JakartaJaxbMapper { + + JakartaJaxbMapper INSTANCE = Mappers.getMapper( JakartaJaxbMapper.class ); + + StringProperty map(JakartaJaxbElementProperty source); + + BigDecimalProperty mapBD(JakartaJaxbElementProperty source); + + SomeTypeProperty mapSomeType(JakartaJaxbElementProperty source); + + @SuppressWarnings( "unused" ) + default SomeType map( String in ) { + return new SomeType(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaJaxb.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaJaxb.java new file mode 100644 index 0000000000..86c9d83a54 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaJaxb.java @@ -0,0 +1,27 @@ +/* + * 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.testutil; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation that adds the needed Jakarta XML Binding dependencies. + * + * @author Iaroslav Bogdanchikov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "jakarta.xml.bind", +}) +public @interface WithJakartaJaxb { + +} From 97c67552882d6fb1c6ab51bf14a6c9d289e652c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Campanero=20Ortiz?= Date: Sun, 4 Sep 2022 13:58:10 +0200 Subject: [PATCH 118/363] #2963 Add support for enum to integer conversion --- .../chapter-5-data-type-conversions.asciidoc | 3 + .../ap/internal/conversion/Conversions.java | 14 +++- .../conversion/EnumToIntegerConversion.java | 38 +++++++++ .../_enum/EnumToIntegerConversionTest.java | 77 +++++++++++++++++++ .../conversion/_enum/EnumToIntegerEnum.java | 14 ++++ .../conversion/_enum/EnumToIntegerMapper.java | 19 +++++ .../conversion/_enum/EnumToIntegerSource.java | 28 +++++++ .../conversion/_enum/EnumToIntegerTarget.java | 28 +++++++ 8 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/EnumToIntegerConversion.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerEnum.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerTarget.java diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index e456af01ba..51ccb21a46 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -43,6 +43,9 @@ public interface CarMapper { ==== * Between `enum` types and `String`. +* Between `enum` types and `Integer`, according to `enum.ordinal()`. +** When converting from an `Integer`, the value needs to be less than the number of values of the enum, otherwise an `ArrayOutOfBoundsException` is thrown. + * Between big number types (`java.math.BigInteger`, `java.math.BigDecimal`) and Java primitive types (including their wrappers) as well as String. A format string as understood by `java.text.DecimalFormat` can be specified. .Conversion from BigDecimal to String diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index e012481054..e947c78571 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -41,6 +41,7 @@ public class Conversions { private final Map conversions = new HashMap<>(); private final Type enumType; private final Type stringType; + private final Type integerType; private final TypeFactory typeFactory; public Conversions(TypeFactory typeFactory) { @@ -48,6 +49,7 @@ public Conversions(TypeFactory typeFactory) { this.enumType = typeFactory.getType( Enum.class ); this.stringType = typeFactory.getType( String.class ); + this.integerType = typeFactory.getType( Integer.class ); //native types <> native types, including wrappers registerNativeTypeConversion( byte.class, Byte.class ); @@ -185,6 +187,8 @@ public Conversions(TypeFactory typeFactory) { //misc. register( Enum.class, String.class, new EnumStringConversion() ); + register( Enum.class, Integer.class, new EnumToIntegerConversion() ); + register( Enum.class, int.class, new EnumToIntegerConversion() ); register( Date.class, String.class, new DateToStringConversion() ); register( BigDecimal.class, BigInteger.class, new BigDecimalToBigIntegerConversion() ); @@ -324,10 +328,16 @@ private void register(String sourceTypeName, Class targetClass, ConversionPro } public ConversionProvider getConversion(Type sourceType, Type targetType) { - if ( sourceType.isEnumType() && targetType.equals( stringType ) ) { + if ( sourceType.isEnumType() && + ( targetType.equals( stringType ) || + targetType.getBoxedEquivalent().equals( integerType ) ) + ) { sourceType = enumType; } - else if ( targetType.isEnumType() && sourceType.equals( stringType ) ) { + else if ( targetType.isEnumType() && + ( sourceType.equals( stringType ) || + sourceType.getBoxedEquivalent().equals( integerType ) ) + ) { targetType = enumType; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/EnumToIntegerConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/EnumToIntegerConversion.java new file mode 100644 index 0000000000..b43dc4a48f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/EnumToIntegerConversion.java @@ -0,0 +1,38 @@ +/* + * 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.internal.conversion; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Conversion between {@link Enum} and {@link Integer} types. + * + * @author Jose Carlos Campanero Ortiz + */ +public class EnumToIntegerConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".ordinal()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".values()[ ]"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return asSet( + conversionContext.getTargetType() + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java new file mode 100644 index 0000000000..1302c22bc9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java @@ -0,0 +1,77 @@ +/* + * 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.conversion._enum; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests conversions between {@link Enum} and Integer. + * + * @author Jose Carlos Campanero Ortiz + */ +@IssueKey("2963") +@WithClasses({ + EnumToIntegerSource.class, + EnumToIntegerTarget.class, + EnumToIntegerMapper.class, + EnumToIntegerEnum.class +}) +public class EnumToIntegerConversionTest { + + @ProcessorTest + public void shouldApplyEnumToIntegerConversion() { + EnumToIntegerSource source = new EnumToIntegerSource(); + + for ( EnumToIntegerEnum value: EnumToIntegerEnum.values() ) { + source.setEnumValue( value ); + + EnumToIntegerTarget target = EnumToIntegerMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getEnumValue() ).isEqualTo( source.getEnumValue().ordinal() ); + } + } + + @ProcessorTest + public void shouldApplyReverseEnumToIntegerConversion() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + int numberOfValues = EnumToIntegerEnum.values().length; + for ( int value = 0; value < numberOfValues; value++ ) { + target.setEnumValue( value ); + + EnumToIntegerSource source = EnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).isEqualTo( EnumToIntegerEnum.values()[ target.getEnumValue() ] ); + } + } + + @ProcessorTest + public void shouldHandleOutOfBoundsEnumOrdinal() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + target.setInvalidEnumValue( EnumToIntegerEnum.values().length + 1 ); + + assertThatThrownBy( () -> EnumToIntegerMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( ArrayIndexOutOfBoundsException.class ); + } + + @ProcessorTest + public void shouldHandleNullIntegerValue() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + EnumToIntegerSource source = EnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).isNull(); + assertThat( source.getInvalidEnumValue() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerEnum.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerEnum.java new file mode 100644 index 0000000000..4aad7bef57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerEnum.java @@ -0,0 +1,14 @@ +/* + * 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.conversion._enum; + +public enum EnumToIntegerEnum { + ARBITRARY_VALUE_ZERO, + ARBITRARY_VALUE_ONE, + ARBITRARY_VALUE_TWO, + ARBITRARY_VALUE_THREE +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerMapper.java new file mode 100644 index 0000000000..946ee7c463 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerMapper.java @@ -0,0 +1,19 @@ +/* + * 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.conversion._enum; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface EnumToIntegerMapper { + EnumToIntegerMapper INSTANCE = Mappers.getMapper( EnumToIntegerMapper.class ); + + EnumToIntegerTarget sourceToTarget(EnumToIntegerSource source); + + EnumToIntegerSource targetToSource(EnumToIntegerTarget target); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerSource.java new file mode 100644 index 0000000000..4e60311d9b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerSource.java @@ -0,0 +1,28 @@ +/* + * 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.conversion._enum; + +public class EnumToIntegerSource { + private EnumToIntegerEnum enumValue; + + private EnumToIntegerEnum invalidEnumValue; + + public EnumToIntegerEnum getEnumValue() { + return enumValue; + } + + public void setEnumValue(final EnumToIntegerEnum enumValue) { + this.enumValue = enumValue; + } + + public EnumToIntegerEnum getInvalidEnumValue() { + return invalidEnumValue; + } + + public void setInvalidEnumValue(EnumToIntegerEnum invalidEnumValue) { + this.invalidEnumValue = invalidEnumValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerTarget.java new file mode 100644 index 0000000000..a8819c27ed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerTarget.java @@ -0,0 +1,28 @@ +/* + * 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.conversion._enum; + +public class EnumToIntegerTarget { + private Integer enumValue; + + private Integer invalidEnumValue; + + public Integer getEnumValue() { + return enumValue; + } + + public void setEnumValue(final Integer enumValue) { + this.enumValue = enumValue; + } + + public Integer getInvalidEnumValue() { + return invalidEnumValue; + } + + public void setInvalidEnumValue(Integer invalidEnumValue) { + this.invalidEnumValue = invalidEnumValue; + } +} From d593afed694e407c108596df584bd7fc739c2f5c Mon Sep 17 00:00:00 2001 From: Prasanth Omanakuttan Date: Mon, 12 Sep 2022 22:15:22 +0530 Subject: [PATCH 119/363] Avoid unnecessary unboxing of Boolean (#3003) --- .../java/org/mapstruct/ap/MappingProcessor.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 4f9e0fa384..2a4af558f2 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -8,7 +8,6 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; @@ -119,7 +118,7 @@ public class MappingProcessor extends AbstractProcessor { *

      * If the hierarchy of a mapper's source/target types is never completed (i.e. the missing super-types are not * generated by other processors), this mapper will not be generated; That's fine, the compiler will raise an error - * due to the inconsistent Java types used as source or target anyways. + * due to the inconsistent Java types used as source or target anyway. */ private Set deferredMappers = new HashSet<>(); @@ -142,15 +141,15 @@ private Options createOptions() { String unmappedSourcePolicy = processingEnv.getOptions().get( UNMAPPED_SOURCE_POLICY ); return new Options( - Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), - Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ), + Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), + Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ), unmappedTargetPolicy != null ? ReportingPolicyGem.valueOf( unmappedTargetPolicy.toUpperCase() ) : null, unmappedSourcePolicy != null ? ReportingPolicyGem.valueOf( unmappedSourcePolicy.toUpperCase() ) : null, processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ), processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ), - Boolean.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), - Boolean.valueOf( processingEnv.getOptions().get( DISABLE_BUILDERS ) ), - Boolean.valueOf( processingEnv.getOptions().get( VERBOSE ) ) + Boolean.parseBoolean( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), + Boolean.parseBoolean( processingEnv.getOptions().get( DISABLE_BUILDERS ) ), + Boolean.parseBoolean( processingEnv.getOptions().get( VERBOSE ) ) ); } @@ -352,7 +351,7 @@ private R process(ProcessorContext context, ModelElementProcessor p /** * Retrieves all model element processors, ordered by their priority value - * (with the method retrieval processor having the lowest priority value (1) + * (with the method retrieval processor having the lowest priority value (1)) * and the code generation processor the highest priority value. * * @return A list with all model element processors. @@ -372,7 +371,7 @@ private R process(ProcessorContext context, ModelElementProcessor p processors.add( processorIterator.next() ); } - Collections.sort( processors, new ProcessorComparator() ); + processors.sort( new ProcessorComparator() ); return processors; } From 8d670e7db72961a6a3470a8fdc85ec164007ca51 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 4 Sep 2022 09:39:27 +0200 Subject: [PATCH 120/363] #3008 Replace old issue template with new GitHub issue form templates --- .github/ISSUE_TEMPLATE/bug_report.yml | 45 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 16 ++++++++ .github/ISSUE_TEMPLATE/feature_request.yml | 43 +++++++++++++++++++++ ISSUE_TEMPLATE.md | 9 ----- 4 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml delete mode 100644 ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..b6703e1800 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,45 @@ +name: Bug report +description: Create a report and help us improve +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Please fill in all required fields with as many details as possible. + - type: textarea + id: expected + attributes: + label: Expected behavior + description: | + Describe what you were expecting MapStruct to do + placeholder: | + Here you can also add the generated code that you would like MapStruct to generate + - type: textarea + id: actual + attributes: + label: Actual behavior + description: | + Describe what you observed MapStruct did instead + placeholder: | + Here you can also add the generated code that MapStruct generated + - type: textarea + id: steps + attributes: + label: Steps to reproduce the problem + description: | + - Share your mapping configuration + - An [MCVE (Minimal Complete Verifiable Example)](https://stackoverflow.com/help/minimal-reproducible-example) can be helpful to provide a complete reproduction case + placeholder: | + Share your MapStruct configuration + validations: + required: true + - type: input + id: mapstruct-version + attributes: + label: MapStruct Version + description: | + Which MapStruct version did you use? + Note: While you can obviously continue using older versions of MapStruct, it may well be that your bug is already fixed. If you're using an older version, please also try to reproduce the bug in the latest version of MapStruct before reporting it. + placeholder: ex. MapStruct 1.5.2 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..5b7ced86a4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,16 @@ +contact_links: + - name: MapStruct Discussions + url: https://github.com/mapstruct/mapstruct/discussions + about: Please use the MapStruct GitHub Discussions for open ended discussions and to reach out to the community. + - name: Stack Overflow + url: https://stackoverflow.com/questions/tagged/mapstruct + about: For questions about how to use MapStruct, consider asking your question on Stack Overflow, tagged [mapstruct]. + - name: Documentation + url: https://mapstruct.org/documentation/stable/reference/html/ + about: The MapStruct reference documentation. + - name: Gitter Chat + url: https://gitter.im/mapstruct/mapstruct-users + about: For realtime communication with the MapStruct community, consider writing in our Gitter chat room. + - name: MapStruct Examples + url: https://github.com/mapstruct/mapstruct-examples + about: Some examples of what can be achieved with MapStruct. (contributions are always welcome) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..191bba8345 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,43 @@ +name: Feature Request +description: Suggest an idea +body: + - type: markdown + attributes: + value: | + Please describe the use-case you have. This will better help us understand the context in which you're looking for a new feature. + - type: textarea + id: use-case + attributes: + label: Use case + description: | + Please describe the use-case you have. This will better help us understand the context in which you're looking for a new feature. + placeholder: Describe the use-case here + validations: + required: true + - type: textarea + id: solution + attributes: + label: Generated Code + description: | + Please describe the possible generated code you'd like to see in MapStruct generate. + Please note, it's not always easy to describe a good solution. Describing the use-case above is much more important to us. + placeholder: Describe the possible solution here. + validations: + required: false + - type: textarea + id: workarounds + attributes: + label: Possible workarounds + description: | + Please describe the possible workarounds you've implemented to work around the lacking functionality. + placeholder: Describe the possible workarounds here. + validations: + required: false + - type: input + id: mapstruct-version + attributes: + label: MapStruct Version + description: What MapStruct version and edition did you try? + placeholder: ex. MapStruct 1.5.2 + validations: + required: false diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index ecaf441514..0000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,9 +0,0 @@ -- [ ] Is this an issue (and hence not a question)? - -If this is a question how to use MapStruct there are several resources available. -- Our reference- and API [documentation](http://mapstruct.org/documentation/reference-guide/). -- Our [examples](https://github.com/mapstruct/mapstruct-examples) repository (contributions always welcome) -- Our [FAQ](http://mapstruct.org/faq/) -- [StackOverflow](https://stackoverflow.com), tag MapStruct -- [Gitter](https://gitter.im/mapstruct/mapstruct-users) (you usually get fast feedback) -- Our [google group](https://groups.google.com/forum/#!forum/mapstruct-users) \ No newline at end of file From 811cd569bb80ed45f0da74cb603d9bfea774ad0c Mon Sep 17 00:00:00 2001 From: Johnny Lim Date: Tue, 27 Sep 2022 02:02:01 +0900 Subject: [PATCH 121/363] Javadoc and documentation polishing (#3026) --- core/src/main/java/org/mapstruct/EnumMapping.java | 2 +- .../java/org/mapstruct/NullValueCheckStrategy.java | 2 +- .../mapstruct/ap/internal/model/HelperMethod.java | 2 +- .../internal/model/source/builtin/BuiltInMethod.java | 2 +- .../java/org/mapstruct/ap/internal/util/Message.java | 2 +- .../ap/internal/model/StreamMappingMethod.ftl | 2 +- .../mapstruct/ap/test/bugs/_1719/Issue1719Test.java | 4 ++-- .../test/context/ContextParameterErroneousTest.java | 2 +- readme.md | 12 ++++++------ 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/mapstruct/EnumMapping.java b/core/src/main/java/org/mapstruct/EnumMapping.java index ba3a5e0cb0..375f969b01 100644 --- a/core/src/main/java/org/mapstruct/EnumMapping.java +++ b/core/src/main/java/org/mapstruct/EnumMapping.java @@ -98,7 +98,7 @@ *

        *
      • {@link MappingConstants#SUFFIX_TRANSFORMATION} - applies the given {@link #configuration()} as a * suffix to the source enum
      • - *
      • {@link MappingConstants#STRIP_SUFFIX_TRANSFORMATION} - strips the the given {@link #configuration()} + *
      • {@link MappingConstants#STRIP_SUFFIX_TRANSFORMATION} - strips the given {@link #configuration()} * from the end of the source enum
      • *
      • {@link MappingConstants#PREFIX_TRANSFORMATION} - applies the given {@link #configuration()} as a * prefix to the source enum
      • diff --git a/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java b/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java index 446b879d92..22dba7c58b 100644 --- a/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java @@ -8,7 +8,7 @@ /** * Strategy for dealing with null source values. * - * Note: This strategy is not in effect when the a specific source presence check method is defined + * Note: This strategy is not in effect when a specific source presence check method is defined * in the service provider interface (SPI). *

        * Note: some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder 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 35b35aee73..6ff85c585f 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 @@ -176,7 +176,7 @@ public boolean equals(Object obj) { * * @param parameter source * @param returnType target - * @return {@code true}, iff the the type variables match + * @return {@code true}, iff the type variables match */ public boolean doTypeVarsMatch(Type parameter, Type returnType) { return true; 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 f46b201576..6d41872159 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 @@ -190,7 +190,7 @@ public boolean equals(Object obj) { * * @param parameter source * @param returnType target - * @return {@code true}, iff the the type variables match + * @return {@code true}, iff the type variables match */ public boolean doTypeVarsMatch(Type parameter, Type returnType) { return true; 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 92951ad521..29600489fb 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 @@ -159,7 +159,7 @@ public enum Message { RETRIEVAL_NO_INPUT_ARGS( "Can't generate mapping method with no input arguments." ), RETRIEVAL_DUPLICATE_MAPPING_TARGETS( "Can't generate mapping method with more than one @MappingTarget parameter." ), RETRIEVAL_VOID_MAPPING_METHOD( "Can't generate mapping method with return type void." ), - RETRIEVAL_NON_ASSIGNABLE_RESULTTYPE( "The result type is not assignable to the the return type." ), + RETRIEVAL_NON_ASSIGNABLE_RESULTTYPE( "The result type is not assignable to the return type." ), RETRIEVAL_ITERABLE_TO_NON_ITERABLE( "Can't generate mapping method from iterable type from java stdlib to non-iterable type." ), RETRIEVAL_MAPPING_HAS_TARGET_TYPE_PARAMETER( "Can't generate mapping method that has a parameter annotated with @TargetType." ), RETRIEVAL_NON_ITERABLE_TO_ITERABLE( "Can't generate mapping method from non-iterable type to iterable type from java stdlib." ), diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl index 7b5b44bd56..29977bdd5d 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl @@ -114,7 +114,7 @@ <#else> <#-- Streams are immutable so we can't update them --> <#if !existingInstanceMapping> - <#--TODO fhr: after the the result is no longer the same instance, how does it affect the + <#--TODO fhr: after the result is no longer the same instance, how does it affect the Before mapping methods. Does it even make sense to have before mapping on a stream? --> <#if sourceParameter.type.arrayType> <@returnLocalVarDefOrUpdate>Stream.of( ${sourceParameter.name} )<@streamMapSupplier />; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Test.java index 182e1849be..7a8665e49f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Test.java @@ -23,8 +23,8 @@ public class Issue1719Test { /** * For adder methods MapStuct cannot generate an update method. MapStruct would cannot know how to remove objects - * from the child-parent relation. It cannot even assume that the the collection can be cleared at forehand. - * Therefore the only sensible choice is for MapStruct to create a create method for the target elements. + * from the child-parent relation. It cannot even assume that the collection can be cleared at forehand. + * Therefore, the only sensible choice is for MapStruct to create a create method for the target elements. */ @ProcessorTest @WithClasses(Issue1719Mapper.class) 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 index 77fefe756e..46bb08cb70 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java @@ -19,7 +19,7 @@ /** * Tests the erroneous usage of the {@link Context} annotation in the following situations: *

          - *
        • using the the same context parameter type twice in the same method + *
        • using the same context parameter type twice in the same method *
        * * @author Andreas Gudian diff --git a/readme.md b/readme.md index e5811060a6..e1ee8f9deb 100644 --- a/readme.md +++ b/readme.md @@ -114,7 +114,7 @@ plugins { dependencies { ... - compile 'org.mapstruct:mapstruct:1.5.2.Final' + implementation 'org.mapstruct:mapstruct:1.5.2.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final' testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final' // if you are using mapstruct in test code @@ -122,7 +122,7 @@ dependencies { ... ``` -If you don't work with a dependency management tool, you can obtain a distribution bundle from [SourceForge](https://sourceforge.net/projects/mapstruct/files/). +If you don't work with a dependency management tool, you can obtain a distribution bundle from [Releases page](https://github.com/mapstruct/mapstruct/releases). ## Documentation and getting help @@ -132,16 +132,16 @@ To learn more about MapStruct, refer to the [project homepage](http://mapstruct. MapStruct uses Maven for its build. Java 11 is required for building MapStruct from source. To build the complete project, run - mvn clean install + ./mvnw clean install from the root of the project directory. To skip the distribution module, run - mvn clean install -DskipDistribution=true + ./mvnw clean install -DskipDistribution=true ## Importing into IDE -MapStruct uses the gem annotation processor to generate mapping gems for it's own annotations. -Therefore for seamless integration within an IDE annotation processing needs to be enabled. +MapStruct uses the gem annotation processor to generate mapping gems for its own annotations. +Therefore, for seamless integration within an IDE annotation processing needs to be enabled. ### IntelliJ From 73e8fd6152153ba8c2f76fc5e9a84b16d33f6a0b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Tue, 27 Sep 2022 09:35:54 +0200 Subject: [PATCH 122/363] #2840, #2913, #2921: MethodMatcher should not match widening methods In the MethodMatcher we need to do a special check when the target type is primitive. The reason for that is that a Long is assignable to a primitive double. However, doing that means that information can be lost and thus we should not pick such methods. When the target type is primitive, then a method will be matched if and only if boxed equivalent of the target type is assignable to the boxed equivalent of the candidate return type --- .../internal/model/source/MethodMatcher.java | 11 +++ .../ap/test/bugs/_2840/Issue2840Mapper.java | 51 +++++++++++ .../ap/test/bugs/_2840/Issue2840Test.java | 31 +++++++ .../ap/test/bugs/_2913/Issue2913Mapper.java | 85 +++++++++++++++++++ .../ap/test/bugs/_2913/Issue2913Test.java | 35 ++++++++ .../ap/test/bugs/_2921/Issue2921Mapper.java | 49 +++++++++++ .../ap/test/bugs/_2921/Issue2921Test.java | 28 ++++++ 7 files changed, 290 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java index baa3d7eac5..42c26f09a0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java @@ -82,6 +82,17 @@ boolean matches(List sourceTypes, Type targetType) { // (the relation target / target type, target type being a class) if ( !analyser.candidateReturnType.isVoid() ) { + if ( targetType.isPrimitive() ) { + // If the target type is primitive + // then we are going to check if its boxed equivalent + // is assignable to the candidate return type + // This is done because primitives can be assigned from their own narrower counterparts + // directly without any casting. + // e.g. a Long is assignable to a primitive double + // However, in order not to lose information we are not going to allow this + return targetType.getBoxedEquivalent() + .isAssignableTo( analyser.candidateReturnType.getBoxedEquivalent() ); + } if ( !( analyser.candidateReturnType.isAssignableTo( targetType ) ) ) { return false; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Mapper.java new file mode 100644 index 0000000000..d2baac8cac --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Mapper.java @@ -0,0 +1,51 @@ +/* + * 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._2840; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2840Mapper { + + Issue2840Mapper INSTANCE = + Mappers.getMapper( Issue2840Mapper.class ); + + Issue2840Mapper.Target map(Short shortValue, Integer intValue); + + default int toInt(Number number) { + return number.intValue() + 5; + } + + default short toShort(Number number) { + return (short) (number.shortValue() + 10); + } + + class Target { + + private int intValue; + private short shortValue; + + public int getIntValue() { + return intValue; + } + + public void setIntValue(int intValue) { + this.intValue = intValue; + } + + public short getShortValue() { + return shortValue; + } + + public void setShortValue(short shortValue) { + this.shortValue = shortValue; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Test.java new file mode 100644 index 0000000000..1c6b19306f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Test.java @@ -0,0 +1,31 @@ + +/* + * 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._2840; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2840") +@WithClasses({ + Issue2840Mapper.class, +}) +class Issue2840Test { + + @ProcessorTest + void shouldUseMethodWithMostSpecificReturnType() { + Issue2840Mapper.Target target = Issue2840Mapper.INSTANCE.map( (short) 10, 50 ); + + assertThat( target.getShortValue() ).isEqualTo( (short) 20 ); + assertThat( target.getIntValue() ).isEqualTo( 55 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Mapper.java new file mode 100644 index 0000000000..095f5457fd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Mapper.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._2913; + +import java.math.BigDecimal; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2913Mapper { + + Issue2913Mapper INSTANCE = Mappers.getMapper( Issue2913Mapper.class ); + + @Mapping(target = "doublePrimitiveValue", source = "rounding") + @Mapping(target = "doubleValue", source = "rounding") + @Mapping(target = "longPrimitiveValue", source = "rounding") + @Mapping(target = "longValue", source = "rounding") + Target map(Source source); + + default Long mapAmount(BigDecimal amount) { + return amount != null ? amount.movePointRight( 2 ).longValue() : null; + } + + class Target { + + private double doublePrimitiveValue; + private Double doubleValue; + private long longPrimitiveValue; + private Long longValue; + + public double getDoublePrimitiveValue() { + return doublePrimitiveValue; + } + + public void setDoublePrimitiveValue(double doublePrimitiveValue) { + this.doublePrimitiveValue = doublePrimitiveValue; + } + + public Double getDoubleValue() { + return doubleValue; + } + + public void setDoubleValue(Double doubleValue) { + this.doubleValue = doubleValue; + } + + public long getLongPrimitiveValue() { + return longPrimitiveValue; + } + + public void setLongPrimitiveValue(long longPrimitiveValue) { + this.longPrimitiveValue = longPrimitiveValue; + } + + public Long getLongValue() { + return longValue; + } + + public void setLongValue(Long longValue) { + this.longValue = longValue; + } + } + + class Source { + + private final BigDecimal rounding; + + public Source(BigDecimal rounding) { + this.rounding = rounding; + } + + public BigDecimal getRounding() { + return rounding; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Test.java new file mode 100644 index 0000000000..7257c9ab7e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Test.java @@ -0,0 +1,35 @@ +/* + * 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._2913; + +import java.math.BigDecimal; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2913") +@WithClasses({ + Issue2913Mapper.class, +}) +class Issue2913Test { + + @ProcessorTest + void shouldNotWidenWithUserDefinedMethods() { + Issue2913Mapper.Source source = new Issue2913Mapper.Source( BigDecimal.valueOf( 10.543 ) ); + Issue2913Mapper.Target target = Issue2913Mapper.INSTANCE.map( source ); + + assertThat( target.getDoubleValue() ).isEqualTo( 10.543 ); + assertThat( target.getDoublePrimitiveValue() ).isEqualTo( 10.543 ); + assertThat( target.getLongValue() ).isEqualTo( 1054 ); + assertThat( target.getLongPrimitiveValue() ).isEqualTo( 1054 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Mapper.java new file mode 100644 index 0000000000..d686fcbc16 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Mapper.java @@ -0,0 +1,49 @@ +/* + * 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._2921; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2921Mapper { + + Issue2921Mapper INSTANCE = Mappers.getMapper( Issue2921Mapper.class ); + + Target map(Source source); + + default Short toShort(Integer value) { + throw new UnsupportedOperationException( "toShort method should not be used" ); + } + + class Source { + private final Integer value; + + public Source(Integer value) { + this.value = value; + } + + public Integer getValue() { + return value; + } + } + + class Target { + private final int value; + + public Target(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Test.java new file mode 100644 index 0000000000..5b8dd0386c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Test.java @@ -0,0 +1,28 @@ +/* + * 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._2921; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2921") +@WithClasses({ + Issue2921Mapper.class, +}) +class Issue2921Test { + + @ProcessorTest + void shouldNotUseIntegerToShortForMappingIntegerToInt() { + Issue2921Mapper.Target target = Issue2921Mapper.INSTANCE.map( new Issue2921Mapper.Source( 10 ) ); + assertThat( target.getValue() ).isEqualTo( 10 ); + } +} From e979f506fac6aeabfa3a1993b3b986449fcb7b3a Mon Sep 17 00:00:00 2001 From: Orange Add <48479242+chenzijia12300@users.noreply.github.com> Date: Thu, 29 Sep 2022 04:17:59 +0800 Subject: [PATCH 123/363] #2773 Copy `@Deprecated` annotation from method or mapper to implementation --- ...eatureCompilationExclusionCliEnhancer.java | 1 + .../test/resources/fullFeatureTest/pom.xml | 2 + .../ap/internal/gem/GemGenerator.java | 1 + .../model/AdditionalAnnotationsBuilder.java | 42 ++++++++++++- .../test/annotatewith/AnnotateWithTest.java | 1 - .../deprecated/DeprecatedMapperWithClass.java | 13 ++++ .../DeprecatedMapperWithMethod.java | 44 +++++++++++++ .../deprecated/DeprecatedTest.java | 56 +++++++++++++++++ .../deprecated/RepeatDeprecatedMapper.java | 15 +++++ .../jdk11/DeprecatedMapperWithClass.java | 13 ++++ .../jdk11/DeprecatedMapperWithMethod.java | 44 +++++++++++++ .../deprecated/jdk11/DeprecatedTest.java | 63 +++++++++++++++++++ .../RepeatDeprecatedMapperWithParams.java | 21 +++++++ 13 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithClass.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithMethod.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/RepeatDeprecatedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithClass.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithMethod.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/RepeatDeprecatedMapperWithParams.java diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index 0f261d6568..e017d1b003 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -31,6 +31,7 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces case JAVA_8: additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/annotatewith/deprecated/jdk11/*.java" ); break; case JAVA_9: // TODO find out why this fails: diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index ac69114bd6..8a62f48588 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -25,6 +25,7 @@ x x x + x @@ -45,6 +46,7 @@ ${additionalExclude2} ${additionalExclude3} ${additionalExclude4} + ${additionalExclude5} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index cbb59f4e57..5caea8a008 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -45,6 +45,7 @@ * * @author Gunnar Morling */ +@GemDefinition(Deprecated.class) @GemDefinition(AnnotateWith.class) @GemDefinition(AnnotateWith.Element.class) @GemDefinition(AnnotateWiths.class) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java index f28cdfef9c..3f0b2123ae 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java @@ -10,6 +10,7 @@ import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -31,6 +32,7 @@ import org.mapstruct.ap.internal.gem.AnnotateWithGem; import org.mapstruct.ap.internal.gem.AnnotateWithsGem; +import org.mapstruct.ap.internal.gem.DeprecatedGem; import org.mapstruct.ap.internal.gem.ElementGem; import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; @@ -55,7 +57,6 @@ public class AdditionalAnnotationsBuilder extends RepeatableAnnotations { private static final String ANNOTATE_WITH_FQN = "org.mapstruct.AnnotateWith"; private static final String ANNOTATE_WITHS_FQN = "org.mapstruct.AnnotateWiths"; - private TypeFactory typeFactory; private FormattingMessager messager; @@ -90,6 +91,45 @@ protected void addInstances(AnnotateWithsGem gem, Element source, Set getProcessedAnnotations(Element source) { + Set processedAnnotations = super.getProcessedAnnotations( source ); + return addDeprecatedAnnotation( source, processedAnnotations ); + } + + private Set addDeprecatedAnnotation(Element source, Set annotations) { + DeprecatedGem deprecatedGem = DeprecatedGem.instanceOn( source ); + if ( deprecatedGem == null ) { + return annotations; + } + Type deprecatedType = typeFactory.getType( Deprecated.class ); + if ( annotations.stream().anyMatch( annotation -> annotation.getType().equals( deprecatedType ) ) ) { + messager.printMessage( + source, + deprecatedGem.mirror(), + Message.ANNOTATE_WITH_DUPLICATE, + deprecatedType.describe() ); + return annotations; + } + List annotationElements = new ArrayList<>(); + if ( deprecatedGem.since() != null && deprecatedGem.since().hasValue() ) { + annotationElements.add( new AnnotationElement( + AnnotationElementType.STRING, + "since", + Collections.singletonList( deprecatedGem.since().getValue() ) + ) ); + } + if ( deprecatedGem.forRemoval() != null && deprecatedGem.forRemoval().hasValue() ) { + annotationElements.add( new AnnotationElement( + AnnotationElementType.BOOLEAN, + "forRemoval", + Collections.singletonList( deprecatedGem.forRemoval().getValue() ) + ) ); + } + annotations.add( new Annotation(deprecatedType, annotationElements ) ); + return annotations; + } + private void addAndValidateMapping(Set mappings, Element source, AnnotateWithGem gem, Annotation anno) { if ( anno.getType().getTypeElement().getAnnotation( Repeatable.class ) == null ) { if ( mappings.stream().anyMatch( existing -> existing.getType().equals( anno.getType() ) ) ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java index e3b20f547e..3687fe2bc0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java @@ -593,5 +593,4 @@ public void valueMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodE Method method = mapper.getClass().getMethod( "map", String.class ); assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull(); } - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithClass.java new file mode 100644 index 0000000000..d0164e8be0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithClass.java @@ -0,0 +1,13 @@ +/* + * 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.annotatewith.deprecated; + +import org.mapstruct.Mapper; + +@Mapper +@Deprecated +public class DeprecatedMapperWithClass { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithMethod.java new file mode 100644 index 0000000000..b6c2c8eb62 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithMethod.java @@ -0,0 +1,44 @@ +/* + * 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.annotatewith.deprecated; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; + +@Mapper +public interface DeprecatedMapperWithMethod { + + @AnnotateWith(CustomMethodOnlyAnnotation.class) + @Deprecated + Target map(Source source); + + class Source { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.java new file mode 100644 index 0000000000..5abca7171e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.java @@ -0,0 +1,56 @@ +/* + * 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.annotatewith.deprecated; + +import java.lang.reflect.Method; +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; +import org.mapstruct.ap.testutil.ProcessorTest; +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.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author orange add + */ +public class DeprecatedTest { + + @ProcessorTest + @WithClasses( { DeprecatedMapperWithMethod.class, CustomMethodOnlyAnnotation.class} ) + public void deprecatedWithMethodCorrectCopy() throws NoSuchMethodException { + DeprecatedMapperWithMethod mapper = Mappers.getMapper( DeprecatedMapperWithMethod.class ); + Method method = mapper.getClass().getMethod( "map", DeprecatedMapperWithMethod.Source.class ); + Deprecated annotation = method.getAnnotation( Deprecated.class ); + assertThat( annotation ).isNotNull(); + } + + @ProcessorTest + @WithClasses(DeprecatedMapperWithClass.class) + public void deprecatedWithClassCorrectCopy() { + DeprecatedMapperWithClass mapper = Mappers.getMapper( DeprecatedMapperWithClass.class ); + Deprecated annotation = mapper.getClass().getAnnotation( Deprecated.class ); + assertThat( annotation ).isNotNull(); + } + + @ProcessorTest + @WithClasses(RepeatDeprecatedMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = RepeatDeprecatedMapper.class, + message = "Annotation \"Deprecated\" is already present with the " + + "same elements configuration." + ) + } + ) + public void deprecatedWithRepeat() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/RepeatDeprecatedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/RepeatDeprecatedMapper.java new file mode 100644 index 0000000000..dd085f101c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/RepeatDeprecatedMapper.java @@ -0,0 +1,15 @@ +/* + * 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.annotatewith.deprecated; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +@Mapper +@Deprecated +@AnnotateWith(Deprecated.class) +public class RepeatDeprecatedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithClass.java new file mode 100644 index 0000000000..2e0507f5ab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithClass.java @@ -0,0 +1,13 @@ +/* + * 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.annotatewith.deprecated.jdk11; + +import org.mapstruct.Mapper; + +@Mapper +@Deprecated(since = "11") +public class DeprecatedMapperWithClass { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithMethod.java new file mode 100644 index 0000000000..bf898e2043 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithMethod.java @@ -0,0 +1,44 @@ +/* + * 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.annotatewith.deprecated.jdk11; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; + +@Mapper +public interface DeprecatedMapperWithMethod { + + @AnnotateWith(CustomMethodOnlyAnnotation.class) + @Deprecated(since = "18", forRemoval = false) + Target map(Source source); + + class Source { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.java new file mode 100644 index 0000000000..4e10b6ded6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.java @@ -0,0 +1,63 @@ +/* + * 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.annotatewith.deprecated.jdk11; + +import java.lang.reflect.Method; +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; +import org.mapstruct.ap.testutil.ProcessorTest; +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.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author orange add + */ +public class DeprecatedTest { + @ProcessorTest + @WithClasses({ DeprecatedMapperWithMethod.class, CustomMethodOnlyAnnotation.class}) + public void deprecatedWithMethodCorrectCopyForJdk11() throws NoSuchMethodException { + DeprecatedMapperWithMethod mapper = Mappers.getMapper( DeprecatedMapperWithMethod.class ); + Method method = mapper.getClass().getMethod( "map", DeprecatedMapperWithMethod.Source.class ); + Deprecated annotation = method.getAnnotation( Deprecated.class ); + assertThat( annotation ).isNotNull(); + assertThat( annotation.since() ).isEqualTo( "18" ); + assertThat( annotation.forRemoval() ).isEqualTo( false ); + } + + @ProcessorTest + @WithClasses(DeprecatedMapperWithClass.class) + public void deprecatedWithClassCorrectCopyForJdk11() { + DeprecatedMapperWithClass mapper = Mappers.getMapper( DeprecatedMapperWithClass.class ); + Deprecated annotation = mapper.getClass().getAnnotation( Deprecated.class ); + assertThat( annotation ).isNotNull(); + assertThat( annotation.since() ).isEqualTo( "11" ); + } + + @ProcessorTest + @WithClasses( { RepeatDeprecatedMapperWithParams.class}) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = RepeatDeprecatedMapperWithParams.class, + message = "Annotation \"Deprecated\" is already present with the " + + "same elements configuration." + ) + } + ) + public void bothExistPriorityAnnotateWithForJdk11() { + RepeatDeprecatedMapperWithParams mapper = Mappers.getMapper( RepeatDeprecatedMapperWithParams.class ); + Deprecated deprecated = mapper.getClass().getAnnotation( Deprecated.class ); + assertThat( deprecated ).isNotNull(); + assertThat( deprecated.since() ).isEqualTo( "1.5" ); + assertThat( deprecated.forRemoval() ).isEqualTo( false ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/RepeatDeprecatedMapperWithParams.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/RepeatDeprecatedMapperWithParams.java new file mode 100644 index 0000000000..971791bfd2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/RepeatDeprecatedMapperWithParams.java @@ -0,0 +1,21 @@ +/* + * 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.annotatewith.deprecated.jdk11; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +@Mapper +@Deprecated( since = "1.8" ) +@AnnotateWith( + value = Deprecated.class, + elements = { + @AnnotateWith.Element( name = "forRemoval", booleans = false), + @AnnotateWith.Element( name = "since", strings = "1.5") + } +) +public class RepeatDeprecatedMapperWithParams { +} From 608d476ed26ce4ac3096e2d9a2ee2a8226e42a00 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Thu, 29 Sep 2022 21:35:51 +0200 Subject: [PATCH 124/363] #3018: Use MappingControl with SubclassMapping --- .../ap/internal/model/BeanMappingMethod.java | 2 +- .../test/subclassmapping/DeepCloneMapper.java | 23 ++++++++++++++ .../DeepCloneMethodMapper.java | 26 ++++++++++++++++ .../subclassmapping/SubclassMappingTest.java | 30 +++++++++++++++++++ .../test/subclassmapping/mappables/Car.java | 1 + 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java 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 4f2dc4e5f1..24e160b9de 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 @@ -405,7 +405,7 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap Collections.emptyList(), subclassMappingOptions.getTarget(), ctx.getTypeUtils() ).withSourceRHS( rightHandSide ), - null, + subclassMappingOptions.getMappingControl( ctx.getElementUtils() ), null, false ); Assignment assignment = ctx diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMapper.java new file mode 100644 index 0000000000..24d0244729 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMapper.java @@ -0,0 +1,23 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Bike; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle; +import org.mapstruct.control.DeepClone; +import org.mapstruct.factory.Mappers; + +@Mapper(mappingControl = DeepClone.class) +public interface DeepCloneMapper { + DeepCloneMapper INSTANCE = Mappers.getMapper( DeepCloneMapper.class ); + + @SubclassMapping( source = Car.class, target = Car.class ) + @SubclassMapping( source = Bike.class, target = Bike.class ) + Vehicle map(Vehicle vehicle); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java new file mode 100644 index 0000000000..3f276b24cd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java @@ -0,0 +1,26 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Bike; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle; +import org.mapstruct.control.DeepClone; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeepCloneMethodMapper { + DeepCloneMethodMapper INSTANCE = Mappers.getMapper( DeepCloneMethodMapper.class ); + + @SubclassMapping( source = Car.class, target = Car.class ) + @SubclassMapping( source = Bike.class, target = Bike.class ) + @BeanMapping( mappingControl = DeepClone.class, nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL ) + Vehicle map(Vehicle vehicle); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java index 7df555ecc2..6f9b6e160c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java @@ -52,6 +52,36 @@ void mappingIsDoneUsingSubclassMapping() { .containsExactly( CarDto.class, BikeDto.class ); } + @ProcessorTest + @WithClasses( DeepCloneMapper.class ) + void deepCloneMappingClonesObjects() { + Car car = new Car(); + car.setManual( true ); + car.setName( "namedCar" ); + car.setVehicleManufacturingCompany( "veMac" ); + + Vehicle result = DeepCloneMapper.INSTANCE.map( car ); + + assertThat( result ).isInstanceOf( Car.class ); + assertThat( result ).isNotSameAs( car ); + assertThat( result ).usingRecursiveComparison().isEqualTo( car ); + } + + @ProcessorTest + @WithClasses( DeepCloneMethodMapper.class ) + void deepCloneMappingOnMethodClonesObjects() { + Car car = new Car(); + car.setManual( true ); + car.setName( "namedCar" ); + car.setVehicleManufacturingCompany( "veMac" ); + + Vehicle result = DeepCloneMethodMapper.INSTANCE.map( car ); + + assertThat( result ).isInstanceOf( Car.class ); + assertThat( result ).isNotSameAs( car ); + assertThat( result ).usingRecursiveComparison().isEqualTo( car ); + } + @ProcessorTest @WithClasses( SimpleSubclassMapper.class ) void inverseMappingIsDoneUsingSubclassMapping() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java index 71b2446d34..6f33a6cb61 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/mappables/Car.java @@ -15,4 +15,5 @@ public boolean isManual() { public void setManual(boolean manual) { this.manual = manual; } + } From af1eab0ece084aa04798e2d86fc20bd65bfa784e Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Sep 2022 22:10:27 +0200 Subject: [PATCH 125/363] #2743 BeanMappingOptions should not be inherited for forged methods --- .../model/source/BeanMappingOptions.java | 7 +- .../model/source/MappingMethodOptions.java | 2 +- .../ap/test/bugs/_2743/Issue2743Mapper.java | 75 +++++++++++++++++++ .../ap/test/bugs/_2743/Issue2743Test.java | 24 ++++++ 4 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index 60aac32e54..b73b4084fd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -54,13 +54,16 @@ public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping) return options; } + public static BeanMappingOptions empty(DelegatingOptions delegatingOptions) { + return new BeanMappingOptions( null, Collections.emptyList(), null, delegatingOptions ); + } + public static BeanMappingOptions getInstanceOn(BeanMappingGem beanMapping, MapperOptions mapperOptions, ExecutableElement method, FormattingMessager messager, TypeUtils typeUtils, TypeFactory typeFactory ) { if ( beanMapping == null || !isConsistent( beanMapping, method, messager ) ) { - BeanMappingOptions options = new BeanMappingOptions( null, Collections.emptyList(), null, mapperOptions ); - return options; + return empty( mapperOptions ); } Objects.requireNonNull( method ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java index 4a140c4fb9..a1a64872a9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -365,7 +365,7 @@ public static MappingMethodOptions getForgedMethodInheritedOptions(MappingMethod options.mappings, options.iterableMapping, options.mapMapping, - options.beanMapping, + BeanMappingOptions.empty( options.beanMapping.next() ), options.enumMappingOptions, options.valueMappings, Collections.emptySet(), diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Mapper.java new file mode 100644 index 0000000000..db3c0e6d49 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Mapper.java @@ -0,0 +1,75 @@ +/* + * 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._2743; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface Issue2743Mapper { + + @BeanMapping(ignoreUnmappedSourceProperties = { "number" }) + Target map(Source source); + + class Source { + + private final int number = 10; + private final NestedSource nested; + + public Source(NestedSource nested) { + this.nested = nested; + } + + public int getNumber() { + return number; + } + + public NestedSource getNested() { + return nested; + } + } + + class NestedSource { + private final String value; + + public NestedSource(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final NestedTarget nested; + + public Target(NestedTarget nested) { + this.nested = nested; + } + + public NestedTarget getNested() { + return nested; + } + } + + class NestedTarget { + private final String value; + + public NestedTarget(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Test.java new file mode 100644 index 0000000000..5c2bb61279 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Test.java @@ -0,0 +1,24 @@ +/* + * 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._2743; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2743") +@WithClasses({ + Issue2743Mapper.class +}) +class Issue2743Test { + + @ProcessorTest + void shouldCompile() { + } +} From 90a487ac0641c39ebec6680567cc97eaede49365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Campanero=20Ortiz?= Date: Sat, 1 Oct 2022 13:47:49 +0200 Subject: [PATCH 126/363] #1427 Add support for custom name in Spring stereotype annotations --- .../processor/SpringComponentProcessor.java | 67 +++++++++++++- .../spring/annotateWith/CustomStereotype.java | 21 +++++ ...ustomerSpringComponentQualifiedMapper.java | 20 ++++ ...stomerSpringControllerQualifiedMapper.java | 19 ++++ ...SpringCustomStereotypeQualifiedMapper.java | 21 +++++ ...stomerSpringRepositoryQualifiedMapper.java | 19 ++++ .../CustomerSpringServiceQualifiedMapper.java | 19 ++++ .../SpringAnnotateWithMapperTest.java | 91 +++++++++++++++++++ 8 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomStereotype.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringComponentQualifiedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringControllerQualifiedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringCustomStereotypeQualifiedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringRepositoryQualifiedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringServiceQualifiedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/SpringAnnotateWithMapperTest.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java index 4efc537c3d..84a672bcc9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java @@ -8,7 +8,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; @@ -16,6 +18,13 @@ import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; + +import static javax.lang.model.element.ElementKind.PACKAGE; + /** * A {@link ModelElementProcessor} which converts the given {@link Mapper} * object into a Spring bean in case Spring is configured as the @@ -25,6 +34,7 @@ * @author Andreas Gudian */ public class SpringComponentProcessor extends AnnotationBasedComponentModelProcessor { + @Override protected String getComponentModelIdentifier() { return MappingConstantsGem.ComponentModelGem.SPRING; @@ -33,7 +43,9 @@ protected String getComponentModelIdentifier() { @Override protected List getTypeAnnotations(Mapper mapper) { List typeAnnotations = new ArrayList<>(); - typeAnnotations.add( component() ); + if ( !isAlreadyAnnotatedAsSpringStereotype( mapper ) ) { + typeAnnotations.add( component() ); + } if ( mapper.getDecorator() != null ) { typeAnnotations.add( qualifierDelegate() ); @@ -91,4 +103,57 @@ private Annotation primary() { private Annotation component() { return new Annotation( getTypeFactory().getType( "org.springframework.stereotype.Component" ) ); } + + private boolean isAlreadyAnnotatedAsSpringStereotype(Mapper mapper) { + Set handledElements = new HashSet<>(); + return mapper.getAnnotations() + .stream() + .anyMatch( + annotation -> isOrIncludesComponentAnnotation( annotation, handledElements ) + ); + } + + private boolean isOrIncludesComponentAnnotation(Annotation annotation, Set handledElements) { + return isOrIncludesComponentAnnotation( + annotation.getType().getTypeElement(), handledElements + ); + } + + private boolean isOrIncludesComponentAnnotation(Element element, Set handledElements) { + if ( "org.springframework.stereotype.Component".equals( + ( (TypeElement) element ).getQualifiedName().toString() + )) { + return true; + } + + for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { + Element annotationMirrorElement = annotationMirror.getAnnotationType().asElement(); + // Bypass java lang annotations to improve performance avoiding unnecessary checks + if ( !isAnnotationInPackage( annotationMirrorElement, "java.lang.annotation" ) && + !handledElements.contains( annotationMirrorElement ) ) { + handledElements.add( annotationMirrorElement ); + boolean isOrIncludesComponentAnnotation = isOrIncludesComponentAnnotation( + annotationMirrorElement, handledElements + ); + + if ( isOrIncludesComponentAnnotation ) { + return true; + } + } + } + + return false; + } + + private PackageElement getPackageOf( Element element ) { + while ( element.getKind() != PACKAGE ) { + element = element.getEnclosingElement(); + } + + return (PackageElement) element; + } + + private boolean isAnnotationInPackage(Element element, String packageFQN) { + return packageFQN.equals( getPackageOf( element ).getQualifiedName().toString() ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomStereotype.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomStereotype.java new file mode 100644 index 0000000000..ccc196c5d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomStereotype.java @@ -0,0 +1,21 @@ +/* + * 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.injectionstrategy.spring.annotateWith; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.stereotype.Component; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface CustomStereotype { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringComponentQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringComponentQualifiedMapper.java new file mode 100644 index 0000000000..be08ff0b1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringComponentQualifiedMapper.java @@ -0,0 +1,20 @@ +/* + * 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.injectionstrategy.spring.annotateWith; + +import org.springframework.stereotype.Component; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; + +/** + * @author Ben Zegveld + */ +@AnnotateWith( value = Component.class, elements = @AnnotateWith.Element( strings = "AnnotateWithComponent" ) ) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING ) +public interface CustomerSpringComponentQualifiedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringControllerQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringControllerQualifiedMapper.java new file mode 100644 index 0000000000..7203fad7a9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringControllerQualifiedMapper.java @@ -0,0 +1,19 @@ +/* + * 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.injectionstrategy.spring.annotateWith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.stereotype.Controller; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@AnnotateWith( value = Controller.class, elements = @AnnotateWith.Element( strings = "AnnotateWithController" ) ) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING ) +public interface CustomerSpringControllerQualifiedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringCustomStereotypeQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringCustomStereotypeQualifiedMapper.java new file mode 100644 index 0000000000..18a062497d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringCustomStereotypeQualifiedMapper.java @@ -0,0 +1,21 @@ +/* + * 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.injectionstrategy.spring.annotateWith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@AnnotateWith( + value = CustomStereotype.class, + elements = @AnnotateWith.Element( strings = "AnnotateWithCustomStereotype" ) +) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING ) +public interface CustomerSpringCustomStereotypeQualifiedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringRepositoryQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringRepositoryQualifiedMapper.java new file mode 100644 index 0000000000..7bbefbee2b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringRepositoryQualifiedMapper.java @@ -0,0 +1,19 @@ +/* + * 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.injectionstrategy.spring.annotateWith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.stereotype.Repository; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@AnnotateWith( value = Repository.class, elements = @AnnotateWith.Element( strings = "AnnotateWithRepository" ) ) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING ) +public interface CustomerSpringRepositoryQualifiedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringServiceQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringServiceQualifiedMapper.java new file mode 100644 index 0000000000..52dff8ef8f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringServiceQualifiedMapper.java @@ -0,0 +1,19 @@ +/* + * 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.injectionstrategy.spring.annotateWith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.stereotype.Service; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@AnnotateWith( value = Service.class, elements = @AnnotateWith.Element( strings = "AnnotateWithService" ) ) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING ) +public interface CustomerSpringServiceQualifiedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/SpringAnnotateWithMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/SpringAnnotateWithMapperTest.java new file mode 100644 index 0000000000..cdc741814c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/SpringAnnotateWithMapperTest.java @@ -0,0 +1,91 @@ +/* + * 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.injectionstrategy.spring.annotateWith; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * Test field injection for component model spring. + * + * @author Filip Hrisafov + * @author Jose Carlos Campanero Ortiz + */ +@WithClasses({ + CustomerSpringComponentQualifiedMapper.class, + CustomerSpringControllerQualifiedMapper.class, + CustomerSpringServiceQualifiedMapper.class, + CustomerSpringRepositoryQualifiedMapper.class, + CustomStereotype.class, + CustomerSpringCustomStereotypeQualifiedMapper.class +}) +@IssueKey( "1427" ) +@WithSpring +public class SpringAnnotateWithMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveComponentAnnotatedQualifiedMapper() { + + // then + generatedSource.forMapper( CustomerSpringComponentQualifiedMapper.class ) + .content() + .contains( "@Component(value = \"AnnotateWithComponent\")" ) + .doesNotContain( "@Component" + System.lineSeparator() ); + + } + + @ProcessorTest + public void shouldHaveControllerAnnotatedQualifiedMapper() { + + // then + generatedSource.forMapper( CustomerSpringControllerQualifiedMapper.class ) + .content() + .contains( "@Controller(value = \"AnnotateWithController\")" ) + .doesNotContain( "@Component" ); + + } + + @ProcessorTest + public void shouldHaveServiceAnnotatedQualifiedMapper() { + + // then + generatedSource.forMapper( CustomerSpringServiceQualifiedMapper.class ) + .content() + .contains( "@Service(value = \"AnnotateWithService\")" ) + .doesNotContain( "@Component" ); + + } + + @ProcessorTest + public void shouldHaveRepositoryAnnotatedQualifiedMapper() { + + // then + generatedSource.forMapper( CustomerSpringRepositoryQualifiedMapper.class ) + .content() + .contains( "@Repository(value = \"AnnotateWithRepository\")" ) + .doesNotContain( "@Component" ); + + } + + @ProcessorTest + public void shouldHaveCustomStereotypeAnnotatedQualifiedMapper() { + + // then + generatedSource.forMapper( CustomerSpringCustomStereotypeQualifiedMapper.class ) + .content() + .contains( "@CustomStereotype(value = \"AnnotateWithCustomStereotype\")" ) + .doesNotContain( "@Component" ); + + } + +} From 411cc24dac06892dc7e5582aff9ce409302916b8 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sun, 2 Oct 2022 09:34:03 +0200 Subject: [PATCH 127/363] #2955 Fix `@AfterMapping` with return type not called for update mappings --- .../model/LifecycleMethodResolver.java | 2 +- .../CallbacksWithReturnValuesTest.java | 46 +++++++++++++++---- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java index 87527e87f8..8b44dee254 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -141,7 +141,7 @@ private static List collectLifecycleCallbackMe callbackMethods, Collections.emptyList(), targetType, - method.getReturnType(), + method.getResultType(), SelectionCriteria.forLifecycleMethods( selectionParameters ) ); return toLifecycleCallbackMethodRefs( 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 8c391bbfeb..7d6eaacb79 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 @@ -8,6 +8,7 @@ import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.AfterEach; import org.mapstruct.ap.test.callbacks.returning.NodeMapperContext.ContextListener; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; @@ -25,23 +26,29 @@ @WithClasses( { Attribute.class, AttributeDto.class, Node.class, NodeDto.class, NodeMapperDefault.class, NodeMapperWithContext.class, NodeMapperContext.class, Number.class, NumberMapperDefault.class, NumberMapperContext.class, NumberMapperWithContext.class } ) -public class CallbacksWithReturnValuesTest { +class CallbacksWithReturnValuesTest { + @AfterEach + void cleanup() { + NumberMapperContext.clearCache(); + NumberMapperContext.clearVisited(); + } + @ProcessorTest - public void mappingWithDefaultHandlingRaisesStackOverflowError() { + void mappingWithDefaultHandlingRaisesStackOverflowError() { Node root = buildNodes(); assertThatThrownBy( () -> NodeMapperDefault.INSTANCE.nodeToNodeDto( root ) ) .isInstanceOf( StackOverflowError.class ); } @ProcessorTest - public void updatingWithDefaultHandlingRaisesStackOverflowError() { + void updatingWithDefaultHandlingRaisesStackOverflowError() { Node root = buildNodes(); assertThatThrownBy( () -> NodeMapperDefault.INSTANCE.nodeToNodeDto( root, new NodeDto() ) ) .isInstanceOf( StackOverflowError.class ); } @ProcessorTest - public void mappingWithContextCorrectlyResolvesCycles() { + void mappingWithContextCorrectlyResolvesCycles() { final AtomicReference contextLevel = new AtomicReference<>( null ); ContextListener contextListener = new ContextListener() { @Override @@ -75,28 +82,49 @@ private static Node buildNodes() { } @ProcessorTest - public void numberMappingWithoutContextDoesNotUseCache() { + void numberMappingWithoutContextDoesNotUseCache() { Number n1 = NumberMapperDefault.INSTANCE.integerToNumber( 2342 ); Number n2 = NumberMapperDefault.INSTANCE.integerToNumber( 2342 ); + assertThat( n1 ).isEqualTo( n2 ); assertThat( n1 ).isNotSameAs( n2 ); } @ProcessorTest - public void numberMappingWithContextUsesCache() { + void numberMappingWithContextUsesCache() { NumberMapperContext.putCache( new Number( 2342 ) ); Number n1 = NumberMapperWithContext.INSTANCE.integerToNumber( 2342 ); Number n2 = NumberMapperWithContext.INSTANCE.integerToNumber( 2342 ); + assertThat( n1 ).isEqualTo( n2 ); assertThat( n1 ).isSameAs( n2 ); - NumberMapperContext.clearCache(); } @ProcessorTest - public void numberMappingWithContextCallsVisitNumber() { + void numberMappingWithContextCallsVisitNumber() { Number n1 = NumberMapperWithContext.INSTANCE.integerToNumber( 1234 ); Number n2 = NumberMapperWithContext.INSTANCE.integerToNumber( 5678 ); + assertThat( NumberMapperContext.getVisited() ).isEqualTo( Arrays.asList( n1, n2 ) ); - NumberMapperContext.clearVisited(); + } + + @ProcessorTest + @IssueKey( "2955" ) + void numberUpdateMappingWithContextUsesCacheAndThereforeDoesNotVisitNumber() { + Number target = new Number(); + Number expectedReturn = new Number( 2342 ); + NumberMapperContext.putCache( expectedReturn ); + NumberMapperWithContext.INSTANCE.integerToNumber( 2342, target ); + + assertThat( NumberMapperContext.getVisited() ).isEmpty(); + } + + @ProcessorTest + @IssueKey( "2955" ) + void numberUpdateMappingWithContextCallsVisitNumber() { + Number target = new Number(); + NumberMapperWithContext.INSTANCE.integerToNumber( 2342, target ); + + assertThat( NumberMapperContext.getVisited() ).contains( target ); } } From 266c5fa41ce6a07c4fb11b745b461c4ca19a8301 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 2 Oct 2022 19:20:13 +0200 Subject: [PATCH 128/363] #1216 Pick candidate method with most specific return type When there are multiple candidate methods returning different types. We should be able to use the method with the most specific return type (if such a method exists) --- .../source/selector/MethodSelectors.java | 4 +- .../MostSpecificResultTypeSelector.java | 45 +++++++++++++ .../source/selector/SelectionCriteria.java | 8 +++ ...MostSpecificResultTypeSelectingMapper.java | 31 +++++++++ .../selection/resulttype/FruitFamily.java | 22 ++++++ .../resulttype/InheritanceSelectionTest.java | 67 +++++++++++++++++++ ...MostSpecificResultTypeSelectingMapper.java | 30 +++++++++ ...ecificResultTypeSelectingUpdateMapper.java | 65 ++++++++++++++++++ 8 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/FruitFamily.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingUpdateMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java index 519e1c3d6d..74fa1a33a3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java @@ -37,7 +37,9 @@ public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, TypeFacto new InheritanceSelector(), new CreateOrUpdateSelector(), new SourceRhsSelector(), - new FactoryParameterSelector() ); + new FactoryParameterSelector(), + new MostSpecificResultTypeSelector() + ); } /** diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java new file mode 100644 index 0000000000..6e86a30bc7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java @@ -0,0 +1,45 @@ +/* + * 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.internal.model.source.selector; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; + +/** + * @author Filip Hrisafov + */ +public class MostSpecificResultTypeSelector implements MethodSelector { + + @Override + public List> getMatchingMethods(Method mappingMethod, + List> candidates, + List sourceTypes, Type mappingTargetType, + Type returnType, SelectionCriteria criteria) { + if ( candidates.size() < 2 || !criteria.isForMapping() || criteria.getQualifyingResultType() != null) { + return candidates; + } + + List> result = new ArrayList<>(); + + for ( SelectedMethod candidate : candidates ) { + if ( candidate.getMethod() + .getResultType() + .getBoxedEquivalent() + .equals( mappingTargetType.getBoxedEquivalent() ) ) { + // If the result type is the same as the target type + // then this candidate has the most specific match and should be used + result.add( candidate ); + } + } + + + // If not most specific types were found then return the current candidates + return result.isEmpty() ? candidates : result; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java index da8bae365a..7e12bbd188 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java @@ -68,6 +68,14 @@ public SelectionCriteria(SelectionParameters selectionParameters, MappingControl this.type = type; } + /** + * + * @return {@code true} if only mapping methods should be selected + */ + public boolean isForMapping() { + return type == null || type == Type.PREFER_UPDATE_MAPPING; + } + /** * @return true if factory methods should be selected, false otherwise. */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.java new file mode 100644 index 0000000000..28a925c32b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.java @@ -0,0 +1,31 @@ +/* + * 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.selection.resulttype; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousAmbiguousMostSpecificResultTypeSelectingMapper { + + @Mapping( target = "apple", source = "fruit") + AppleFamily map(FruitFamily fruitFamily); + + default GoldenDelicious toGolden(IsFruit fruit) { + return fruit != null ? new GoldenDelicious( fruit.getType() ) : null; + } + + default Apple toApple1(IsFruit fruit) { + return fruit != null ? new Apple( fruit.getType() ) : null; + } + + default Apple toApple2(IsFruit fruit) { + return fruit != null ? new Apple( fruit.getType() ) : null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/FruitFamily.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/FruitFamily.java new file mode 100644 index 0000000000..347e5a0be0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/FruitFamily.java @@ -0,0 +1,22 @@ +/* + * 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.selection.resulttype; + +/** + * @author Filip Hrisafov + */ +public class FruitFamily { + + private IsFruit fruit; + + public IsFruit getFruit() { + return fruit; + } + + public void setFruit(IsFruit fruit) { + this.fruit = fruit; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java index a6a4f1ad05..05245bafe0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java @@ -240,4 +240,71 @@ public void testShouldUseConstructorFromResultType() { .hasMessage( "Not allowed to change citrus type" ); assertThat( citrus.getType() ).isEqualTo( "lemon" ); } + + @ProcessorTest + @IssueKey("1216") + @WithClasses({ + Citrus.class, + GoldenDelicious.class, + FruitFamily.class, + AppleFamily.class, + MostSpecificResultTypeSelectingMapper.class, + Citrus.class + }) + public void testShouldUseMethodWithMostSpecificReturnType() { + FruitFamily fruitFamily = new FruitFamily(); + fruitFamily.setFruit( new Citrus( "citrus" ) ); + AppleFamily appleFamily = MostSpecificResultTypeSelectingMapper.INSTANCE.map( fruitFamily ); + + assertThat( appleFamily.getApple() ).isExactlyInstanceOf( Apple.class ); + assertThat( appleFamily.getApple().getType() ).isEqualTo( "citrus" ); + } + + @ProcessorTest + @IssueKey("1216") + @WithClasses({ + Citrus.class, + FruitFamily.class, + GoldenDelicious.class, + MostSpecificResultTypeSelectingUpdateMapper.class, + Citrus.class + }) + public void testShouldUseMethodWithMostSpecificReturnTypeForUpdateMappings() { + FruitFamily fruitFamily = new FruitFamily(); + fruitFamily.setFruit( new Citrus( "citrus" ) ); + MostSpecificResultTypeSelectingUpdateMapper.Target target = + new MostSpecificResultTypeSelectingUpdateMapper.Target( + new Apple( "from_test" ), + new GoldenDelicious( "from_test" ) + ); + MostSpecificResultTypeSelectingUpdateMapper.INSTANCE.update( target, fruitFamily ); + + assertThat( target.getApple() ).isExactlyInstanceOf( Apple.class ); + assertThat( target.getApple().getType() ).isEqualTo( "apple updated citrus" ); + assertThat( target.getGoldenApple() ).isExactlyInstanceOf( GoldenDelicious.class ); + assertThat( target.getGoldenApple().getType() ).isEqualTo( "golden updated citrus" ); + } + + @ProcessorTest + @IssueKey("1216") + @WithClasses({ + GoldenDelicious.class, + FruitFamily.class, + AppleFamily.class, + ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousAmbiguousMostSpecificResultTypeSelectingMapper.class, + kind = Kind.ERROR, + line = 17, + message = "Ambiguous mapping methods found for mapping property \"IsFruit fruit\" to Apple: " + + "Apple toApple1(IsFruit fruit), Apple toApple2(IsFruit fruit). " + + "See https://mapstruct.org/faq/#ambiguous for more info." + ) + } + ) + public void testAmbiguousMostSpecificResultTypeErroneous() { + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingMapper.java new file mode 100644 index 0000000000..16d9750ec2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingMapper.java @@ -0,0 +1,30 @@ +/* + * 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.selection.resulttype; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MostSpecificResultTypeSelectingMapper { + + MostSpecificResultTypeSelectingMapper INSTANCE = Mappers.getMapper( MostSpecificResultTypeSelectingMapper.class ); + + @Mapping( target = "apple", source = "fruit") + AppleFamily map(FruitFamily fruitFamily); + + default GoldenDelicious toGolden(IsFruit fruit) { + return fruit != null ? new GoldenDelicious( fruit.getType() ) : null; + } + + default Apple toApple(IsFruit fruit) { + return fruit != null ? new Apple( fruit.getType() ) : null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingUpdateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingUpdateMapper.java new file mode 100644 index 0000000000..5f270116d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/MostSpecificResultTypeSelectingUpdateMapper.java @@ -0,0 +1,65 @@ +/* + * 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.selection.resulttype; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MostSpecificResultTypeSelectingUpdateMapper { + + MostSpecificResultTypeSelectingUpdateMapper INSTANCE = Mappers.getMapper( + MostSpecificResultTypeSelectingUpdateMapper.class ); + + @Mapping(target = "apple", source = "fruit") + @Mapping(target = "goldenApple", source = "fruit") + void update(@MappingTarget Target target, FruitFamily fruitFamily); + + default void updateGolden(@MappingTarget GoldenDelicious target, IsFruit fruit) { + target.setType( "golden updated " + fruit.getType() ); + } + + default void updateApple(@MappingTarget Apple target, IsFruit fruit) { + target.setType( "apple updated " + fruit.getType() ); + } + + @ObjectFactory + default GoldenDelicious createGolden() { + return new GoldenDelicious( "from_object_factory" ); + } + + class Target { + protected Apple apple; + protected GoldenDelicious goldenApple; + + public Target(Apple apple, GoldenDelicious goldenApple) { + this.apple = apple; + this.goldenApple = goldenApple; + } + + public Apple getApple() { + return apple; + } + + public void setApple(Apple apple) { + this.apple = apple; + } + + public GoldenDelicious getGoldenApple() { + return goldenApple; + } + + public void setGoldenApple(GoldenDelicious goldenApple) { + this.goldenApple = goldenApple; + } + } +} From 3a325ea66bdd07741d1bda43d3c77c2709464ea7 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 3 Oct 2022 21:12:19 +0200 Subject: [PATCH 129/363] #3036 Fix compile errors when intersection types are used in lifecycle methods --- ...eatureCompilationExclusionCliEnhancer.java | 4 + .../test/resources/fullFeatureTest/pom.xml | 1 + .../ap/internal/model/common/Type.java | 29 ++++++ .../internal/model/source/MethodMatcher.java | 3 +- .../LifecycleIntersectionMapper.java | 96 +++++++++++++++++++ .../wildcards/WildCardTest.java | 20 ++++ 6 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index e017d1b003..e64b207990 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -32,6 +32,10 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/annotatewith/deprecated/jdk11/*.java" ); + if ( processorType == ProcessorTest.ProcessorType.ECLIPSE_JDT ) { + additionalExcludes.add( + "org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java" ); + } break; case JAVA_9: // TODO find out why this fails: diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index 8a62f48588..9c17f6c7d5 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -26,6 +26,7 @@ x x x + x diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index fd7f3d6904..6d86aad452 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -28,6 +28,7 @@ import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.IntersectionType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -114,6 +115,7 @@ public class Type extends ModelElement implements Comparable { private List alternativeTargetAccessors = null; private Type boundingBase = null; + private List boundTypes = null; private Type boxedEquivalent = null; @@ -354,6 +356,10 @@ public boolean isTypeVar() { return (typeMirror.getKind() == TypeKind.TYPEVAR); } + public boolean isIntersection() { + return typeMirror.getKind() == TypeKind.INTERSECTION; + } + public boolean isJavaLangType() { return packageName != null && packageName.startsWith( "java." ); } @@ -1264,6 +1270,29 @@ public Type getTypeBound() { return boundingBase; } + public List getTypeBounds() { + if ( this.boundTypes != null ) { + return boundTypes; + } + Type bound = getTypeBound(); + if ( bound == null ) { + this.boundTypes = Collections.emptyList(); + } + else if ( !bound.isIntersection() ) { + this.boundTypes = Collections.singletonList( bound ); + } + else { + List bounds = ( (IntersectionType) bound.typeMirror ).getBounds(); + this.boundTypes = new ArrayList<>( bounds.size() ); + for ( TypeMirror mirror : bounds ) { + boundTypes.add( typeFactory.getType( mirror ) ); + } + } + + return this.boundTypes; + + } + public boolean hasAccessibleConstructor() { if ( hasAccessibleConstructor == null ) { hasAccessibleConstructor = false; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java index 42c26f09a0..3d8bfea135 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java @@ -325,8 +325,7 @@ else if ( resolved.getParameter().isWildCardBoundByTypeVar() */ private boolean candidatesWithinBounds(Map methodParCandidates ) { for ( Map.Entry entry : methodParCandidates.entrySet() ) { - Type bound = entry.getKey().getTypeBound(); - if ( bound != null ) { + for ( Type bound : entry.getKey().getTypeBounds() ) { for ( Type.ResolvedPair pair : entry.getValue().pairs ) { if ( entry.getKey().hasUpperBound() ) { if ( !pair.getMatch().asRawType().isAssignableTo( bound.asRawType() ) ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java new file mode 100644 index 0000000000..c466ee0d91 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java @@ -0,0 +1,96 @@ +/* + * 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.selection.methodgenerics.wildcards; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface LifecycleIntersectionMapper { + + LifecycleIntersectionMapper INSTANCE = Mappers.getMapper( LifecycleIntersectionMapper.class ); + + @Mapping(target = "realm", ignore = true) + RealmTarget mapRealm(String source); + + @Mapping(target = "uniqueRealm", ignore = true) + UniqueRealmTarget mapUniqueRealm(String source); + + @Mapping(target = "realm", ignore = true) + @Mapping(target = "uniqueRealm", ignore = true) + BothRealmsTarget mapBothRealms(String source); + + @AfterMapping + default void afterMapping(String source, @MappingTarget T target) { + target.setRealm( "realm_" + source ); + target.setUniqueRealm( "uniqueRealm_" + source ); + } + + interface RealmObject { + void setRealm(String realm); + } + + interface UniqueRealmObject { + void setUniqueRealm(String realm); + } + + class RealmTarget implements RealmObject { + + protected String realm; + + public String getRealm() { + return realm; + } + + @Override + public void setRealm(String realm) { + this.realm = realm; + } + } + + class UniqueRealmTarget implements UniqueRealmObject { + protected String uniqueRealm; + + @Override + public void setUniqueRealm(String uniqueRealm) { + this.uniqueRealm = uniqueRealm; + } + + public String getUniqueRealm() { + return uniqueRealm; + } + } + + class BothRealmsTarget implements RealmObject, UniqueRealmObject { + + protected String realm; + protected String uniqueRealm; + + public String getRealm() { + return realm; + } + + @Override + public void setRealm(String realm) { + this.realm = realm; + } + + public String getUniqueRealm() { + return uniqueRealm; + } + + @Override + public void setUniqueRealm(String uniqueRealm) { + this.uniqueRealm = uniqueRealm; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/WildCardTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/WildCardTest.java index a228304c72..9e27129635 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/WildCardTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/WildCardTest.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.selection.methodgenerics.wildcards; +import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.runner.Compiler; @@ -54,4 +55,23 @@ public void testIntersectionRelation() { assertThat( target ).isNotNull(); assertThat( target.getProp() ).isEqualTo( typeC ); } + + // Eclipse does not handle intersection types correctly (TODO: worthwhile to investigate?) + @ProcessorTest(Compiler.JDK) + @WithClasses(LifecycleIntersectionMapper.class) + @IssueKey("3036") + public void testLifecycleIntersection() { + + LifecycleIntersectionMapper.RealmTarget realmTarget = LifecycleIntersectionMapper.INSTANCE.mapRealm( "test" ); + assertThat( realmTarget.getRealm() ).isNull(); + + LifecycleIntersectionMapper.UniqueRealmTarget uniqueRealmTarget = + LifecycleIntersectionMapper.INSTANCE.mapUniqueRealm( "test" ); + assertThat( uniqueRealmTarget.getUniqueRealm() ).isNull(); + + LifecycleIntersectionMapper.BothRealmsTarget bothRealmsTarget = + LifecycleIntersectionMapper.INSTANCE.mapBothRealms( "test" ); + assertThat( bothRealmsTarget.getRealm() ).isEqualTo( "realm_test" ); + assertThat( bothRealmsTarget.getUniqueRealm() ).isEqualTo( "uniqueRealm_test" ); + } } From 481ab36ca35f53a6160bac179c495d6a1774388e Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 3 Oct 2022 21:16:25 +0200 Subject: [PATCH 130/363] #3036 Add missing exclude to full feature test --- integrationtest/src/test/resources/fullFeatureTest/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index 9c17f6c7d5..1a31b28221 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -48,6 +48,7 @@ ${additionalExclude3} ${additionalExclude4} ${additionalExclude5} + ${additionalExclude6} From a5d3542c24419ff2a66132f3ce2bae12182e3ac0 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 7 Oct 2022 20:43:15 +0200 Subject: [PATCH 131/363] Update latest release version to 1.5.3.Final --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index e1ee8f9deb..58d1c515bb 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.2.Final-blue.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.3.Final-blue.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final) [![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/master/LICENSE.txt) @@ -68,7 +68,7 @@ For Maven-based projects, add the following to your POM file in order to use Map ```xml ... - 1.5.2.Final + 1.5.3.Final ... @@ -114,10 +114,10 @@ plugins { dependencies { ... - implementation 'org.mapstruct:mapstruct:1.5.2.Final' + implementation 'org.mapstruct:mapstruct:1.5.3.Final' - annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final' - testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final' // if you are using mapstruct in test code + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final' + testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final' // if you are using mapstruct in test code } ... ``` From 81b2f70dac328d96d788d7746a57fbeb3978e543 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 2 Oct 2022 19:36:20 +0200 Subject: [PATCH 132/363] #3039 Upgrade FreeMarker to 2.3.31 --- distribution/pom.xml | 2 +- distribution/src/main/assembly/dist.xml | 6 +----- parent/pom.xml | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/distribution/pom.xml b/distribution/pom.xml index 11fb7c9f37..be82f52b3c 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -66,7 +66,7 @@ org.freemarker freemarker ${project.build.directory}/freemarker-unpacked - META-INF/LICENSE.txt,META-INF/NOTICE.txt + META-INF/LICENSE diff --git a/distribution/src/main/assembly/dist.xml b/distribution/src/main/assembly/dist.xml index f519e5fc9c..f0c727f8da 100644 --- a/distribution/src/main/assembly/dist.xml +++ b/distribution/src/main/assembly/dist.xml @@ -42,11 +42,7 @@ / - target/freemarker-unpacked/META-INF/NOTICE.txt - / - - - target/freemarker-unpacked/META-INF/LICENSE.txt + target/freemarker-unpacked/META-INF/LICENSE etc/freemarker diff --git a/parent/pom.xml b/parent/pom.xml index 3f536034c7..e87db14551 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -109,7 +109,7 @@ org.freemarker freemarker - 2.3.21 + 2.3.31 org.assertj From bb099a55ee3c1b9ae7fc2fbad6c6f9c8fe3ae911 Mon Sep 17 00:00:00 2001 From: Orange Add <48479242+chenzijia12300@users.noreply.github.com> Date: Fri, 4 Nov 2022 05:02:58 +0800 Subject: [PATCH 133/363] #3040: Allow using only `BeanMapping#mappingControl` --- .../mapstruct/ap/internal/model/source/BeanMappingOptions.java | 1 + .../ap/test/subclassmapping/DeepCloneMethodMapper.java | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index b73b4084fd..90ad0aabb4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -92,6 +92,7 @@ public static BeanMappingOptions getInstanceOn(BeanMappingGem beanMapping, Mappe private static boolean isConsistent(BeanMappingGem gem, ExecutableElement method, FormattingMessager messager) { if ( !gem.resultType().hasValue() + && !gem.mappingControl().hasValue() && !gem.qualifiedBy().hasValue() && !gem.qualifiedByName().hasValue() && !gem.ignoreUnmappedSourceProperties().hasValue() diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java index 3f276b24cd..036b98a318 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/DeepCloneMethodMapper.java @@ -7,7 +7,6 @@ import org.mapstruct.BeanMapping; import org.mapstruct.Mapper; -import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.SubclassMapping; import org.mapstruct.ap.test.subclassmapping.mappables.Bike; import org.mapstruct.ap.test.subclassmapping.mappables.Car; @@ -21,6 +20,6 @@ public interface DeepCloneMethodMapper { @SubclassMapping( source = Car.class, target = Car.class ) @SubclassMapping( source = Bike.class, target = Bike.class ) - @BeanMapping( mappingControl = DeepClone.class, nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL ) + @BeanMapping( mappingControl = DeepClone.class ) Vehicle map(Vehicle vehicle); } From 6a394ad466b5bda8b17e2406b56be34be3312e21 Mon Sep 17 00:00:00 2001 From: Orange Add <48479242+chenzijia12300@users.noreply.github.com> Date: Fri, 4 Nov 2022 06:21:43 +0800 Subject: [PATCH 134/363] #3037 Support `@ValueMapping` in meta annotations --- .../main/java/org/mapstruct/ValueMapping.java | 2 +- .../java/org/mapstruct/ValueMappings.java | 2 +- .../chapter-8-mapping-values.asciidoc | 39 +++++++++++++++ .../model/source/ValueMappingOptions.java | 4 +- .../processor/MethodRetrievalProcessor.java | 39 ++++++++++----- .../composition/CustomValueAnnotation.java | 21 ++++++++ .../composition/ValueCompositionTest.java | 48 +++++++++++++++++++ .../ValueMappingCompositionMapper.java | 25 ++++++++++ 8 files changed, 164 insertions(+), 16 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/value/composition/CustomValueAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueCompositionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueMappingCompositionMapper.java diff --git a/core/src/main/java/org/mapstruct/ValueMapping.java b/core/src/main/java/org/mapstruct/ValueMapping.java index 21ea5f4a83..7ad3726e48 100644 --- a/core/src/main/java/org/mapstruct/ValueMapping.java +++ b/core/src/main/java/org/mapstruct/ValueMapping.java @@ -84,7 +84,7 @@ */ @Repeatable(ValueMappings.class) @Retention(RetentionPolicy.CLASS) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) public @interface ValueMapping { /** * The source value constant to use for this mapping. diff --git a/core/src/main/java/org/mapstruct/ValueMappings.java b/core/src/main/java/org/mapstruct/ValueMappings.java index faefbf7105..bb593f53d1 100644 --- a/core/src/main/java/org/mapstruct/ValueMappings.java +++ b/core/src/main/java/org/mapstruct/ValueMappings.java @@ -40,7 +40,7 @@ * * @author Sjaak Derksen */ -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.CLASS) public @interface ValueMappings { diff --git a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc index 24c7324f1d..ce16b33ecd 100644 --- a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc +++ b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc @@ -266,3 +266,42 @@ MapStruct provides the following out of the box enum name transformation strateg It is also possible to register custom strategies. For more information on how to do that have a look at <> + +[[value-mapping-composition]] +=== ValueMapping Composition + +The `@ValueMapping` annotation supports now `@Target` with `ElementType#ANNOTATION_TYPE` in addition to `ElementType#METHOD`. +This allows `@ValueMapping` to be used on other (user defined) annotations for re-use purposes. +For example: + +.Custom value mapping annotations +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Retention( RetentionPolicy.CLASS ) +@ValueMapping(source = "EXTRA", target = "SPECIAL") +@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "DEFAULT") +public @interface CustomValueAnnotation { +} +---- +==== +It can be used to describe some common value mapping relationships to avoid duplicate declarations, as in the following example: + +.Using custom combination annotations +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface ValueMappingCompositionMapper { + + @CustomValueAnnotation + ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); + + @CustomValueAnnotation + @ValueMapping(source = "STANDARD", target = "SPECIAL") + ExternalOrderType duplicateAnnotation(OrderType orderType); +} +---- +==== diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java index 963683b5c5..9c412733c4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java @@ -5,8 +5,8 @@ */ package org.mapstruct.ap.internal.model.source; -import java.util.List; import java.util.Objects; +import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; @@ -34,7 +34,7 @@ public class ValueMappingOptions { private final AnnotationValue targetAnnotationValue; public static void fromMappingsGem(ValueMappingsGem mappingsGem, ExecutableElement method, - FormattingMessager messager, List mappings) { + FormattingMessager messager, Set mappings) { boolean anyFound = false; for ( ValueMappingGem mappingGem : mappingsGem.value().get() ) { 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 bda33b6a92..4ad531d334 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 @@ -69,7 +69,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor getValueMappings(ExecutableElement method) { - List valueMappings = new ArrayList<>(); + Set processedAnnotations = new RepeatValueMappings().getProcessedAnnotations( method ); + return new ArrayList<>(processedAnnotations); + } - ValueMappingGem mappingAnnotation = ValueMappingGem.instanceOn( method ); - ValueMappingsGem mappingsAnnotation = ValueMappingsGem.instanceOn( method ); + private class RepeatValueMappings + extends RepeatableAnnotations { - if ( mappingAnnotation != null ) { - ValueMappingOptions valueMapping = ValueMappingOptions.fromMappingGem( mappingAnnotation ); - if ( valueMapping != null ) { - valueMappings.add( valueMapping ); - } + protected RepeatValueMappings() { + super( elementUtils, VALUE_MAPPING_FQN, VALUE_MAPPINGS_FQN ); + } + + @Override + protected ValueMappingGem singularInstanceOn(Element element) { + return ValueMappingGem.instanceOn( element ); } - if ( mappingsAnnotation != null ) { - ValueMappingOptions.fromMappingsGem( mappingsAnnotation, method, messager, valueMappings ); + @Override + protected ValueMappingsGem multipleInstanceOn(Element element) { + return ValueMappingsGem.instanceOn( element ); + } + + @Override + protected void addInstance(ValueMappingGem gem, Element source, Set mappings) { + ValueMappingOptions valueMappingOptions = ValueMappingOptions.fromMappingGem( gem ); + mappings.add( valueMappingOptions ); } - return valueMappings; + @Override + protected void addInstances(ValueMappingsGem gems, Element source, Set mappings) { + ValueMappingOptions.fromMappingsGem( gems, (ExecutableElement) source, messager, mappings ); + } } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/value/composition/CustomValueAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/value/composition/CustomValueAnnotation.java new file mode 100644 index 0000000000..f9a7500a35 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/value/composition/CustomValueAnnotation.java @@ -0,0 +1,21 @@ +/* + * 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.value.composition; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; + +/** + * @author orange add + */ +@Retention( RetentionPolicy.CLASS ) +@ValueMapping(source = "EXTRA", target = "SPECIAL") +@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "DEFAULT") +public @interface CustomValueAnnotation { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueCompositionTest.java b/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueCompositionTest.java new file mode 100644 index 0000000000..d6d8ce0bed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueCompositionTest.java @@ -0,0 +1,48 @@ +/* + * 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.value.composition; + +import org.mapstruct.ap.test.value.ExternalOrderType; +import org.mapstruct.ap.test.value.OrderType; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; +/** + * @author orange add + */ +@IssueKey("3037") +@WithClasses({ + ValueMappingCompositionMapper.class, + ExternalOrderType.class, + OrderType.class, + CustomValueAnnotation.class +}) +public class ValueCompositionTest { + + @ProcessorTest + public void shouldValueCompositionSuccess() { + ValueMappingCompositionMapper compositionMapper = Mappers.getMapper( ValueMappingCompositionMapper.class ); + assertThat( compositionMapper.orderTypeToExternalOrderType( OrderType.EXTRA ) ) + .isEqualTo( ExternalOrderType.SPECIAL ); + assertThat( compositionMapper.orderTypeToExternalOrderType( OrderType.NORMAL ) ) + .isEqualTo( ExternalOrderType.DEFAULT ); + } + + @ProcessorTest + public void duplicateValueMappingAnnotation() { + ValueMappingCompositionMapper compositionMapper = Mappers.getMapper( ValueMappingCompositionMapper.class ); + assertThat( compositionMapper.duplicateAnnotation( OrderType.EXTRA ) ) + .isEqualTo( ExternalOrderType.SPECIAL ); + assertThat( compositionMapper.duplicateAnnotation( OrderType.STANDARD ) ) + .isEqualTo( ExternalOrderType.SPECIAL ); + assertThat( compositionMapper.duplicateAnnotation( OrderType.NORMAL ) ) + .isEqualTo( ExternalOrderType.DEFAULT ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueMappingCompositionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueMappingCompositionMapper.java new file mode 100644 index 0000000000..c3e0e64d69 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/value/composition/ValueMappingCompositionMapper.java @@ -0,0 +1,25 @@ +/* + * 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.value.composition; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ap.test.value.ExternalOrderType; +import org.mapstruct.ap.test.value.OrderType; + +/** + * @author orange add + */ +@Mapper +public interface ValueMappingCompositionMapper { + + @CustomValueAnnotation + ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); + + @CustomValueAnnotation + @ValueMapping(source = "STANDARD", target = "SPECIAL") + ExternalOrderType duplicateAnnotation(OrderType orderType); +} From 93f7c3b8eadef4ad37b35ae1bb4e6ac41b09a781 Mon Sep 17 00:00:00 2001 From: Orange Add <48479242+chenzijia12300@users.noreply.github.com> Date: Fri, 4 Nov 2022 06:22:35 +0800 Subject: [PATCH 135/363] #3015 Fix annotations to the forged methods --- .../model/AbstractMappingMethodBuilder.java | 4 + .../ap/internal/model/ValueMappingMethod.java | 20 ++- .../ap/test/bugs/_3015/Issue3015Mapper.java | 153 ++++++++++++++++++ .../ap/test/bugs/_3015/Issue3015Test.java | 38 +++++ 4 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index 6efb67ba9c..c23dd50430 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -14,6 +14,7 @@ import org.mapstruct.ap.internal.util.Strings; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -121,6 +122,9 @@ public ForgedMethodHistory getDescription() { } public List getMethodAnnotations() { + if ( method instanceof ForgedMethod ) { + return Collections.emptyList(); + } AdditionalAnnotationsBuilder additionalAnnotationsBuilder = new AdditionalAnnotationsBuilder( ctx.getElementUtils(), 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 f61e80c2d4..6b33faa464 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 @@ -6,6 +6,7 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -117,13 +118,20 @@ else if ( sourceType.isString() && targetType.isEnumType() ) { LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables ); List afterMappingMethods = LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables ); - AdditionalAnnotationsBuilder additionalAnnotationsBuilder = + List annotations; + if ( method instanceof ForgedMethod ) { + annotations = Collections.emptyList(); + } + else { + annotations = new ArrayList<>(); + AdditionalAnnotationsBuilder additionalAnnotationsBuilder = new AdditionalAnnotationsBuilder( - ctx.getElementUtils(), - ctx.getTypeFactory(), - ctx.getMessager() ); - List annotations = new ArrayList<>(); - annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); + ctx.getElementUtils(), + ctx.getTypeFactory(), + ctx.getMessager() ); + + annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); + } // finally return a mapping return new ValueMappingMethod( method, annotations, diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Mapper.java new file mode 100644 index 0000000000..1683db8f39 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Mapper.java @@ -0,0 +1,153 @@ +/* + * 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._3015; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; + +/** + * @author orange add + */ +@Mapper +public interface Issue3015Mapper { + + @AnnotateWith( CustomMethodOnlyAnnotation.class ) + Target map(Source source); + + class Source { + + private NestedSource nested; + private List list; + private Stream stream; + private AnnotateSourceEnum annotateWithEnum; + private Map map; + + public NestedSource getNested() { + return nested; + } + + public void setNested(NestedSource nested) { + this.nested = nested; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public Stream getStream() { + return stream; + } + + public void setStream(Stream stream) { + this.stream = stream; + } + + public AnnotateSourceEnum getAnnotateWithEnum() { + return annotateWithEnum; + } + + public void setAnnotateWithEnum(AnnotateSourceEnum annotateWithEnum) { + this.annotateWithEnum = annotateWithEnum; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + } + + class Target { + private NestedTarget nested; + private List list; + private Stream stream; + private AnnotateTargetEnum annotateWithEnum; + private Map map; + + public NestedTarget getNested() { + return nested; + } + + public void setNested(NestedTarget nested) { + this.nested = nested; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public Stream getStream() { + return stream; + } + + public void setStream(Stream stream) { + this.stream = stream; + } + + public AnnotateTargetEnum getAnnotateWithEnum() { + return annotateWithEnum; + } + + public void setAnnotateWithEnum(AnnotateTargetEnum annotateWithEnum) { + this.annotateWithEnum = annotateWithEnum; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + } + + enum AnnotateSourceEnum { + EXISTING; + } + + enum AnnotateTargetEnum { + EXISTING; + } + + class NestedSource { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class NestedTarget { + private Integer value; + + public Integer getValue() { + return value; + } + + public void setValue(Integer value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Test.java new file mode 100644 index 0000000000..96004b8c2d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Test.java @@ -0,0 +1,38 @@ +/* + * 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._3015; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author orange add + */ +@WithClasses({ + Issue3015Mapper.class, + CustomMethodOnlyAnnotation.class +}) +class Issue3015Test { + + @ProcessorTest + void noNeedPassAnnotationToForgeMethod() { + Issue3015Mapper mapper = Mappers.getMapper( Issue3015Mapper.class ); + Method[] declaredMethods = mapper.getClass().getDeclaredMethods(); + List annotationMethods = Arrays.stream( declaredMethods ) + .filter( method -> method.getAnnotation( CustomMethodOnlyAnnotation.class ) != null ) + .collect( Collectors.toList() ); + assertThat( annotationMethods ).hasSize( 1 ); + } +} From 16e3ceadec76ad4f3657e8af2f99172241b8dfd3 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 3 Nov 2022 23:29:47 +0100 Subject: [PATCH 136/363] #2952 Do not treat a getter as an alternative write accessor when using `CollectionMappingStrategy#TARGET_IMMUTABLE` --- .../mapstruct/CollectionMappingStrategy.java | 48 ++++++++++++++ .../ap/internal/model/common/Type.java | 7 +++ .../ap/test/bugs/_2952/Issue2952Mapper.java | 62 +++++++++++++++++++ .../ap/test/bugs/_2952/Issue2952Test.java | 29 +++++++++ .../CupboardEntityOnlyGetter.java | 3 +- ...apper.java => CupboardNoSetterMapper.java} | 4 +- .../immutabletarget/ImmutableProductTest.java | 23 +++---- 7 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java rename processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/{ErroneousCupboardMapper.java => CupboardNoSetterMapper.java} (80%) diff --git a/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java b/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java index afaba97370..0ea3ee7df5 100644 --- a/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java @@ -7,6 +7,54 @@ /** * Strategy for propagating the value of collection-typed properties from source to target. + *

        + * In the table below, the dash {@code -} indicates a property name. + * Next, the trailing {@code s} indicates the plural form. + * The table explains the options and how they are applied to the presence / absence of a + * {@code set-s}, {@code add-} and / or {@code get-s} method on the target object. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
        Collection mapping strategy options
        OptionOnly target set-s AvailableOnly target add- AvailableBoth set-s/add- AvailableNo set-s/add- AvailableExisting Target ({@code @TargetType})
        {@link #ACCESSOR_ONLY}set-sget-sset-sget-sget-s
        {@link #SETTER_PREFERRED}set-sadd-set-sget-sget-s
        {@link #ADDER_PREFERRED}set-sadd-add-get-sget-s
        {@link #TARGET_IMMUTABLE}set-sexceptionset-sexceptionset-s
        * * @author Sjaak Derksen */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 6d86aad452..6894906266 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -788,6 +788,13 @@ else if ( candidate.getAccessorType() == AccessorType.GETTER ) { // an adder has been found (according strategy) so overrule current choice. candidate = adderMethod; } + + if ( cmStrategy == CollectionMappingStrategyGem.TARGET_IMMUTABLE + && candidate.getAccessorType() == AccessorType.GETTER ) { + // If the collection mapping strategy is target immutable + // then the getter method cannot be used as a setter + continue; + } } else if ( candidate.getAccessorType() == AccessorType.FIELD && ( Executables.isFinal( candidate ) || result.containsKey( targetPropertyName ) ) ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java new file mode 100644 index 0000000000..885d74448f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java @@ -0,0 +1,62 @@ +/* + * 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._2952; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE) +public interface Issue2952Mapper { + + Issue2952Mapper INSTANCE = Mappers.getMapper( Issue2952Mapper.class ); + + Target map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + private final Map attributes = new HashMap<>(); + private final List values = new ArrayList<>(); + private String value; + + public Map getAttributes() { + return Collections.unmodifiableMap( attributes ); + } + + public List getValues() { + return Collections.unmodifiableList( values ); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java new file mode 100644 index 0000000000..7a517ec979 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java @@ -0,0 +1,29 @@ +/* + * 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._2952; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2952") +@WithClasses({ + Issue2952Mapper.class +}) +class Issue2952Test { + + @ProcessorTest + void shouldCorrectIgnoreImmutableIterable() { + Issue2952Mapper.Target target = Issue2952Mapper.INSTANCE.map( new Issue2952Mapper.Source( "test" ) ); + + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java index bbf3a6d24e..1f2f2f00b7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.collection.immutabletarget; +import java.util.ArrayList; import java.util.List; /** @@ -13,7 +14,7 @@ */ public class CupboardEntityOnlyGetter { - private List content; + private List content = new ArrayList<>(); public List getContent() { return content; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ErroneousCupboardMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.java similarity index 80% rename from processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ErroneousCupboardMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.java index 028ac4d765..a6674e629f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ErroneousCupboardMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.java @@ -15,9 +15,9 @@ * @author Sjaak Derksen */ @Mapper( collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE ) -public interface ErroneousCupboardMapper { +public interface CupboardNoSetterMapper { - ErroneousCupboardMapper INSTANCE = Mappers.getMapper( ErroneousCupboardMapper.class ); + CupboardNoSetterMapper INSTANCE = Mappers.getMapper( CupboardNoSetterMapper.class ); void map( CupboardDto in, @MappingTarget CupboardEntityOnlyGetter out ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java index b3d2096bc3..de22436296 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java @@ -11,9 +11,6 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; 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 static org.assertj.core.api.Assertions.assertThat; @@ -41,19 +38,17 @@ public void shouldHandleImmutableTarget() { @ProcessorTest @WithClasses({ - ErroneousCupboardMapper.class, + CupboardNoSetterMapper.class, CupboardEntityOnlyGetter.class }) - @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic(type = ErroneousCupboardMapper.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 22, - message = "No write accessor found for property \"content\" in target type.") - } - ) - public void testShouldFailOnPropertyMappingNoPropertySetterOnlyGetter() { + public void shouldIgnoreImmutableTarget() { + CupboardDto in = new CupboardDto(); + in.setContent( Arrays.asList( "flour", "peas" ) ); + CupboardEntityOnlyGetter out = new CupboardEntityOnlyGetter(); + out.getContent().add( "bread" ); + CupboardNoSetterMapper.INSTANCE.map( in, out ); + + assertThat( out.getContent() ).containsExactly( "bread" ); } } From 8894cd5935f238ae32338f49cd4595e4562f87f2 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Fri, 4 Nov 2022 14:21:05 +0100 Subject: [PATCH 137/363] #3057: limit do not allow self to subclassmappings. (#3063) * #3057: limit do not allow self to subclassmappings. * #3057: determine method candidates after all other fields are set in the constructor. Co-authored-by: Ben Zegveld Co-authored-by: Filip Hrisafov --- .../ap/internal/model/BeanMappingMethod.java | 6 +- .../source/selector/SelectionCriteria.java | 10 ++++ .../creation/MappingResolverImpl.java | 8 ++- .../ap/test/bugs/_3057/Issue3057Mapper.java | 55 +++++++++++++++++++ .../test/bugs/_3057/Issue3057MapperTest.java | 34 ++++++++++++ 5 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057MapperTest.java 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 24e160b9de..0ea4ecd197 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 @@ -399,15 +399,13 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap "SubclassMapping for " + sourceType.getFullyQualifiedName() ); SelectionCriteria criteria = SelectionCriteria - .forMappingMethods( + .forSubclassMappingMethods( new SelectionParameters( Collections.emptyList(), Collections.emptyList(), subclassMappingOptions.getTarget(), ctx.getTypeUtils() ).withSourceRHS( rightHandSide ), - subclassMappingOptions.getMappingControl( ctx.getElementUtils() ), - null, - false ); + subclassMappingOptions.getMappingControl( ctx.getElementUtils() ) ); Assignment assignment = ctx .getMappingResolver() .getTargetAssignment( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java index 7e12bbd188..2d288dd56e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java @@ -149,6 +149,10 @@ public boolean isAllow2Steps() { return allow2Steps; } + public boolean isSelfAllowed() { + return type != Type.SELF_NOT_ALLOWED; + } + public static SelectionCriteria forMappingMethods(SelectionParameters selectionParameters, MappingControl mappingControl, String targetPropertyName, boolean preferUpdateMapping) { @@ -173,10 +177,16 @@ public static SelectionCriteria forPresenceCheckMethods(SelectionParameters sele return new SelectionCriteria( selectionParameters, null, null, Type.PRESENCE_CHECK ); } + public static SelectionCriteria forSubclassMappingMethods(SelectionParameters selectionParameters, + MappingControl mappingControl) { + return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED ); + } + public enum Type { PREFER_UPDATE_MAPPING, OBJECT_FACTORY, LIFECYCLE_CALLBACK, PRESENCE_CHECK, + SELF_NOT_ALLOWED, } } 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 5a2af176f6..12b345d238 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 @@ -196,7 +196,6 @@ private ResolvingAttempt(List sourceModel, Method mappingMethod, ForgedM this.mappingMethod = mappingMethod; this.description = description; - this.methods = filterPossibleCandidateMethods( sourceModel, mappingMethod ); this.formattingParameters = formattingParameters == null ? FormattingParameters.EMPTY : formattingParameters; this.sourceRHS = sourceRHS; @@ -207,13 +206,14 @@ private ResolvingAttempt(List sourceModel, Method mappingMethod, ForgedM this.builtIns = builtIns; this.messager = messager; this.reportingLimitAmbiguous = verboseLogging ? Integer.MAX_VALUE : LIMIT_REPORTING_AMBIGUOUS; + this.methods = filterPossibleCandidateMethods( sourceModel, mappingMethod ); } // CHECKSTYLE:ON private List filterPossibleCandidateMethods(List candidateMethods, T mappingMethod) { List result = new ArrayList<>( candidateMethods.size() ); for ( T candidate : candidateMethods ) { - if ( isCandidateForMapping( candidate ) && !candidate.equals( mappingMethod )) { + if ( isCandidateForMapping( candidate ) && isNotSelfOrSelfAllowed( mappingMethod, candidate )) { result.add( candidate ); } } @@ -221,6 +221,10 @@ private List filterPossibleCandidateMethods(List candid return result; } + private boolean isNotSelfOrSelfAllowed(T mappingMethod, T candidate) { + return selectionCriteria == null || selectionCriteria.isSelfAllowed() || !candidate.equals( mappingMethod ); + } + private Assignment getTargetAssignment(Type sourceType, Type targetType) { Assignment assignment; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057Mapper.java new file mode 100644 index 0000000000..d8c444d8c5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057Mapper.java @@ -0,0 +1,55 @@ +/* + * 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._3057; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface Issue3057Mapper { + + Issue3057Mapper INSTANCE = Mappers.getMapper( Issue3057Mapper.class ); + + class Source { + private Source self; + + public Source getSelf() { + return self; + } + + public void setSelf(Source self) { + this.self = self; + } + } + + class Target { + private Target self; + private String value; + + public Target getSelf() { + return self; + } + + public void setSelf(Target self) { + this.self = self; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + @Mapping( target = "value", constant = "constantValue" ) + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057MapperTest.java new file mode 100644 index 0000000000..7e64f529f3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057MapperTest.java @@ -0,0 +1,34 @@ +/* + * 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._3057; + +import org.mapstruct.ap.test.bugs._3057.Issue3057Mapper.Source; +import org.mapstruct.ap.test.bugs._3057.Issue3057Mapper.Target; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ben Zegveld + */ +@WithClasses(Issue3057Mapper.class) +@IssueKey("3057") +class Issue3057MapperTest { + + @ProcessorTest + void mapsSelf() { + Source sourceOuter = new Issue3057Mapper.Source(); + Source sourceInner = new Issue3057Mapper.Source(); + sourceOuter.setSelf( sourceInner ); + + Target targetOuter = Issue3057Mapper.INSTANCE.map( sourceOuter ); + + assertThat( targetOuter.getValue() ).isEqualTo( "constantValue" ); + assertThat( targetOuter.getSelf().getValue() ).isEqualTo( "constantValue" ); + } +} From 82b19b0d8a7addcb137b1d53e9e753fde0957304 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 12 Nov 2022 10:04:03 +0100 Subject: [PATCH 138/363] #3077 Add test case --- .../ap/test/bugs/_3077/Issue3077Mapper.java | 65 +++++++++++++++++++ .../test/bugs/_3077/Issue3077MapperTest.java | 31 +++++++++ 2 files changed, 96 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077MapperTest.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077Mapper.java new file mode 100644 index 0000000000..f1a630a444 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077Mapper.java @@ -0,0 +1,65 @@ +/* + * 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._3077; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3077Mapper { + + Issue3077Mapper INSTANCE = Mappers.getMapper( Issue3077Mapper.class ); + + class Source { + private final String source; + private final Source self; + + public Source(String source, Source self) { + this.source = source; + this.self = self; + } + + public String getSource() { + return source; + } + + public Source getSelf() { + return self; + } + } + + class Target { + private String value; + private Target self; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Target getSelf() { + return self; + } + + public void setSelf(Target self) { + this.self = self; + } + + } + + @Named("self") + @Mapping(target = "value", source = "source") + @Mapping(target = "self", qualifiedByName = "self") + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077MapperTest.java new file mode 100644 index 0000000000..bfba7988aa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077MapperTest.java @@ -0,0 +1,31 @@ +/* + * 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._3077; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3077Mapper.class) +@IssueKey("3057") +class Issue3077MapperTest { + + @ProcessorTest + void mapsSelf() { + Issue3077Mapper.Source sourceInner = new Issue3077Mapper.Source( "inner", null ); + Issue3077Mapper.Source sourceOuter = new Issue3077Mapper.Source( "outer", sourceInner ); + + Issue3077Mapper.Target targetOuter = Issue3077Mapper.INSTANCE.map( sourceOuter ); + + assertThat( targetOuter.getValue() ).isEqualTo( "outer" ); + assertThat( targetOuter.getSelf().getValue() ).isEqualTo( "inner" ); + } +} From fd27380185fe83feb6d26308c7d358025783ff8f Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 13 Nov 2022 14:22:25 +0100 Subject: [PATCH 139/363] #2953 Add support for globally defining nullValueMapMappingStrategy and nullValueIterableMappingStrategy --- .../main/asciidoc/chapter-2-set-up.asciidoc | 24 ++++++++++ .../org/mapstruct/ap/MappingProcessor.java | 18 +++++++- .../internal/model/source/DefaultOptions.java | 8 ++++ .../mapstruct/ap/internal/option/Options.java | 20 ++++++++- .../test/nullvaluemapping/CarListMapper.java | 30 +++++++++++++ .../CarListMapperSettingOnMapper.java | 31 +++++++++++++ .../test/nullvaluemapping/CarMapMapper.java | 30 +++++++++++++ .../CarMapMapperSettingOnMapper.java | 31 +++++++++++++ .../NullValueIterableMappingStrategyTest.java | 45 +++++++++++++++++++ .../NullValueMapMappingStrategyTest.java | 45 +++++++++++++++++++ 10 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapperSettingOnMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapperSettingOnMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueIterableMappingStrategyTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMapMappingStrategyTest.java diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index 601bc42a99..12c399c1f5 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -266,6 +266,30 @@ If a policy is given for a specific bean mapping via `@BeanMapping#ignoreUnmappe disableBuilders` |If set to `true`, then MapStruct will not use builder patterns when doing the mapping. This is equivalent to doing `@Mapper( builder = @Builder( disableBuilder = true ) )` for all of your mappers. |`false` + +|`mapstruct.nullValueIterableMappingStrategy` +|The strategy to be applied when `null` is passed as a source value to an iterable mapping. + +Supported values are: + +* `RETURN_NULL`: if `null` is passed as a source value, then `null` will be returned +* `RETURN_DEFAULT`: if `null` is passed then a default value (empty collection) will be returned + +If a strategy is given for a specific mapper via `@Mapper#nullValueIterableMappingStrategy()`, the value from the annotation takes precedence. +If a strategy is given for a specific iterable mapping via `@IterableMapping#nullValueMappingStrategy()`, it takes precedence over both `@Mapper#nullValueIterableMappingStrategy()` and the option. +|`RETURN_NULL` + +|`mapstruct.nullValueMapMappingStrategy` +|The strategy to be applied when `null` is passed as a source value to a map mapping. + +Supported values are: + +* `RETURN_NULL`: if `null` is passed as a source value, then `null` will be returned +* `RETURN_DEFAULT`: if `null` is passed then a default value (empty map) will be returned + +If a strategy is given for a specific mapper via `@Mapper#nullValueMapMappingStrategy()`, the value from the annotation takes precedence. +If a strategy is given for a specific map mapping via `@MapMapping#nullValueMappingStrategy()`, it takes precedence over both `@Mapper#nullValueMapMappingStrategy()` and the option. +|`RETURN_NULL` |=== === Using MapStruct with the Java Module System diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 2a4af558f2..8c842f007b 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -12,6 +12,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; @@ -32,6 +33,7 @@ import javax.lang.model.util.ElementKindVisitor6; import javax.tools.Diagnostic.Kind; +import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.gem.MapperGem; @@ -87,7 +89,9 @@ MappingProcessor.DEFAULT_COMPONENT_MODEL, MappingProcessor.DEFAULT_INJECTION_STRATEGY, MappingProcessor.DISABLE_BUILDERS, - MappingProcessor.VERBOSE + MappingProcessor.VERBOSE, + MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, + MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY, }) public class MappingProcessor extends AbstractProcessor { @@ -106,6 +110,8 @@ public class MappingProcessor extends AbstractProcessor { protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; protected static final String DISABLE_BUILDERS = "mapstruct.disableBuilders"; protected static final String VERBOSE = "mapstruct.verbose"; + protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy"; + protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy"; private Options options; @@ -139,6 +145,9 @@ public synchronized void init(ProcessingEnvironment processingEnv) { private Options createOptions() { String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY ); String unmappedSourcePolicy = processingEnv.getOptions().get( UNMAPPED_SOURCE_POLICY ); + String nullValueIterableMappingStrategy = processingEnv.getOptions() + .get( NULL_VALUE_ITERABLE_MAPPING_STRATEGY ); + String nullValueMapMappingStrategy = processingEnv.getOptions().get( NULL_VALUE_MAP_MAPPING_STRATEGY ); return new Options( Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), @@ -149,7 +158,12 @@ private Options createOptions() { processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ), Boolean.parseBoolean( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), Boolean.parseBoolean( processingEnv.getOptions().get( DISABLE_BUILDERS ) ), - Boolean.parseBoolean( processingEnv.getOptions().get( VERBOSE ) ) + Boolean.parseBoolean( processingEnv.getOptions().get( VERBOSE ) ), + nullValueIterableMappingStrategy != null ? + NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) : + null, + nullValueMapMappingStrategy != null ? + NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index 5c76e7face..c754d3c395 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -132,10 +132,18 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { } public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { + NullValueMappingStrategyGem nullValueIterableMappingStrategy = options.getNullValueIterableMappingStrategy(); + if ( nullValueIterableMappingStrategy != null ) { + return nullValueIterableMappingStrategy; + } return NullValueMappingStrategyGem.valueOf( mapper.nullValueIterableMappingStrategy().getDefaultValue() ); } public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { + NullValueMappingStrategyGem nullValueMapMappingStrategy = options.getNullValueMapMappingStrategy(); + if ( nullValueMapMappingStrategy != null ) { + return nullValueMapMappingStrategy; + } return NullValueMappingStrategyGem.valueOf( mapper.nullValueMapMappingStrategy().getDefaultValue() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index 65089cc139..a544374c0e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.option; +import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem; /** @@ -23,14 +24,21 @@ public class Options { private final String defaultInjectionStrategy; private final boolean disableBuilders; private final boolean verbose; + private final NullValueMappingStrategyGem nullValueIterableMappingStrategy; + private final NullValueMappingStrategyGem nullValueMapMappingStrategy; + //CHECKSTYLE:OFF public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, ReportingPolicyGem unmappedTargetPolicy, ReportingPolicyGem unmappedSourcePolicy, String defaultComponentModel, String defaultInjectionStrategy, boolean alwaysGenerateSpi, boolean disableBuilders, - boolean verbose) { + boolean verbose, + NullValueMappingStrategyGem nullValueIterableMappingStrategy, + NullValueMappingStrategyGem nullValueMapMappingStrategy + ) { + //CHECKSTYLE:ON this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorVersionComment = suppressGeneratorVersionComment; this.unmappedTargetPolicy = unmappedTargetPolicy; @@ -40,6 +48,8 @@ public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVers this.alwaysGenerateSpi = alwaysGenerateSpi; this.disableBuilders = disableBuilders; this.verbose = verbose; + this.nullValueIterableMappingStrategy = nullValueIterableMappingStrategy; + this.nullValueMapMappingStrategy = nullValueMapMappingStrategy; } public boolean isSuppressGeneratorTimestamp() { @@ -77,4 +87,12 @@ public boolean isDisableBuilders() { public boolean isVerbose() { return verbose; } + + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { + return nullValueIterableMappingStrategy; + } + + public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { + return nullValueMapMappingStrategy; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapper.java new file mode 100644 index 0000000000..d239f9ace7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapper.java @@ -0,0 +1,30 @@ +/* + * 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.nullvaluemapping; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CarListMapper { + + CarListMapper INSTANCE = Mappers.getMapper( CarListMapper.class ); + + @Mapping(target = "seatCount", ignore = true) + @Mapping(target = "model", ignore = true) + @Mapping(target = "catalogId", ignore = true) + CarDto map(Car car); + + List carsToCarDtoList(List cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapperSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapperSettingOnMapper.java new file mode 100644 index 0000000000..860b17654a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapperSettingOnMapper.java @@ -0,0 +1,31 @@ +/* + * 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.nullvaluemapping; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_NULL) +public interface CarListMapperSettingOnMapper { + + CarListMapperSettingOnMapper INSTANCE = Mappers.getMapper( CarListMapperSettingOnMapper.class ); + + @Mapping(target = "seatCount", ignore = true) + @Mapping(target = "model", ignore = true) + @Mapping(target = "catalogId", ignore = true) + CarDto map(Car car); + + List carsToCarDtoList(List cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapper.java new file mode 100644 index 0000000000..0227949f4d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapper.java @@ -0,0 +1,30 @@ +/* + * 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.nullvaluemapping; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CarMapMapper { + + CarMapMapper INSTANCE = Mappers.getMapper( CarMapMapper.class ); + + @Mapping(target = "seatCount", ignore = true) + @Mapping(target = "model", ignore = true) + @Mapping(target = "catalogId", ignore = true) + CarDto map(Car car); + + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapperSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapperSettingOnMapper.java new file mode 100644 index 0000000000..adb99887bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapperSettingOnMapper.java @@ -0,0 +1,31 @@ +/* + * 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.nullvaluemapping; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValueMapMappingStrategy = NullValueMappingStrategy.RETURN_NULL) +public interface CarMapMapperSettingOnMapper { + + CarMapMapperSettingOnMapper INSTANCE = Mappers.getMapper( CarMapMapperSettingOnMapper.class ); + + @Mapping(target = "seatCount", ignore = true) + @Mapping(target = "model", ignore = true) + @Mapping(target = "catalogId", ignore = true) + CarDto map(Car car); + + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueIterableMappingStrategyTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueIterableMappingStrategyTest.java new file mode 100644 index 0000000000..c4acecd64f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueIterableMappingStrategyTest.java @@ -0,0 +1,45 @@ +/* + * 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.nullvaluemapping; + +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CarDto.class, + Car.class +}) +@IssueKey("2953") +public class NullValueIterableMappingStrategyTest { + + @ProcessorTest + @ProcessorOption(name = "mapstruct.nullValueIterableMappingStrategy", value = "return_default") + @WithClasses({ + CarListMapper.class + }) + void globalNullIterableMappingStrategy() { + assertThat( CarListMapper.INSTANCE.carsToCarDtoList( null ) ).isEmpty(); + } + + @ProcessorTest + @ProcessorOption(name = "mapstruct.nullValueIterableMappingStrategy", value = "return_default") + @WithClasses({ + CarListMapperSettingOnMapper.class + }) + void globalNullMapMappingStrategyWithOverrideInMapper() { + // Explicit definition in @Mapper should override global + assertThat( CarListMapperSettingOnMapper.INSTANCE.carsToCarDtoList( null ) ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMapMappingStrategyTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMapMappingStrategyTest.java new file mode 100644 index 0000000000..33b5942f85 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMapMappingStrategyTest.java @@ -0,0 +1,45 @@ +/* + * 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.nullvaluemapping; + +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CarDto.class, + Car.class +}) +@IssueKey("2953") +public class NullValueMapMappingStrategyTest { + + @ProcessorTest + @ProcessorOption(name = "mapstruct.nullValueMapMappingStrategy", value = "return_default") + @WithClasses({ + CarMapMapper.class + }) + void globalNullMapMappingStrategy() { + assertThat( CarMapMapper.INSTANCE.carsToCarDtoMap( null ) ).isEmpty(); + } + + @ProcessorTest + @ProcessorOption(name = "mapstruct.nullValueMapMappingStrategy", value = "return_default") + @WithClasses({ + CarMapMapperSettingOnMapper.class + }) + void globalNullMapMappingStrategyWithOverrideInMapper() { + // Explicit definition in @Mapper should override global + assertThat( CarMapMapperSettingOnMapper.INSTANCE.carsToCarDtoMap( null ) ).isNull(); + } +} From a7ba12676d237957be0753076f436d7974c9f7f4 Mon Sep 17 00:00:00 2001 From: Claudio Nave Date: Sun, 5 Feb 2023 12:17:02 +0100 Subject: [PATCH 140/363] #3110 Fix throws declaration for ValueMapping annotated methods (#3122) #3110 Fix throws declaration for ValueMapping annotated methods --- .../ap/internal/model/ValueMappingMethod.ftl | 10 ++++++- .../ap/test/bugs/_3110/Issue3110Mapper.java | 29 +++++++++++++++++++ .../test/bugs/_3110/Issue3110MapperTest.java | 27 +++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110MapperTest.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl index 2c3e2b6410..016cfcd182 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl @@ -10,7 +10,7 @@ <#nt><@includeModel object=annotation/> <#if overridden>@Override -<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, ) { +<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list beforeMappingReferencesWithoutMappingTarget as callback> <@includeModel object=callback targetBeanName=resultName targetType=resultType/> <#if !callback_has_next> @@ -69,3 +69,11 @@ +<#macro throws> + <#if (thrownTypes?size > 0)><#lt> throws <@compress single_line=true> + <#list thrownTypes as exceptionType> + <@includeModel object=exceptionType/> + <#if exceptionType_has_next>, <#t> + + + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110Mapper.java new file mode 100644 index 0000000000..12dd823760 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110Mapper.java @@ -0,0 +1,29 @@ +/* + * 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._3110; + +import org.mapstruct.EnumMapping; +import org.mapstruct.Mapper; + +@Mapper +public interface Issue3110Mapper { + enum SourceEnum { + FOO, BAR + } + + enum TargetEnum { + FOO, BAR + } + + class CustomCheckedException extends Exception { + public CustomCheckedException(String message) { + super( message ); + } + } + + @EnumMapping(unexpectedValueMappingException = CustomCheckedException.class) + TargetEnum map(SourceEnum sourceEnum) throws CustomCheckedException; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110MapperTest.java new file mode 100644 index 0000000000..3b256ba100 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110MapperTest.java @@ -0,0 +1,27 @@ +/* + * 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._3110; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +@WithClasses({ + Issue3110Mapper.class +}) +@IssueKey("3110") +class Issue3110MapperTest { + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void throwsException() { + generatedSource.forMapper( Issue3110Mapper.class ).content() + .contains( "throws CustomCheckedException" ); + } +} From 89db26a1af35fb759a2fa74b8bc7586cb743c454 Mon Sep 17 00:00:00 2001 From: Claudio Nave Date: Fri, 17 Mar 2023 09:06:13 +0100 Subject: [PATCH 141/363] #3119 Add qualifiedBy and qualifiedByName to SubclassMapping annotation --- .../main/java/org/mapstruct/Qualifier.java | 1 + .../java/org/mapstruct/SubclassMapping.java | 26 ++ ...apter-10-advanced-mapping-options.asciidoc | 2 + .../ap/internal/model/BeanMappingMethod.java | 20 +- .../model/source/SubclassMappingOptions.java | 33 +- .../subclassmapping/qualifier/Composer.java | 18 ++ .../qualifier/ComposerDto.java | 18 ++ .../ErroneousSubclassQualifiedByMapper.java | 16 + ...rroneousSubclassQualifiedByNameMapper.java | 16 + .../test/subclassmapping/qualifier/Light.java | 19 ++ .../qualifier/NonExistent.java | 19 ++ .../subclassmapping/qualifier/Rossini.java | 20 ++ .../subclassmapping/qualifier/RossiniDto.java | 20 ++ .../qualifier/RossiniMapper.java | 50 +++ .../qualifier/SubclassQualifiedByMapper.java | 37 +++ .../SubclassQualifiedByNameMapper.java | 37 +++ .../SubclassQualifierMapperTest.java | 297 ++++++++++++++++++ .../subclassmapping/qualifier/Unused.java | 19 ++ .../subclassmapping/qualifier/Vivaldi.java | 20 ++ .../subclassmapping/qualifier/VivaldiDto.java | 20 ++ .../qualifier/VivaldiMapper.java | 50 +++ 21 files changed, 740 insertions(+), 18 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Composer.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ComposerDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByNameMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Light.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/NonExistent.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Rossini.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByNameMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Unused.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Vivaldi.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiMapper.java diff --git a/core/src/main/java/org/mapstruct/Qualifier.java b/core/src/main/java/org/mapstruct/Qualifier.java index be51f49e04..9e18c3e420 100644 --- a/core/src/main/java/org/mapstruct/Qualifier.java +++ b/core/src/main/java/org/mapstruct/Qualifier.java @@ -21,6 +21,7 @@ *

      • {@link IterableMapping#qualifiedBy() }
      • *
      • {@link MapMapping#keyQualifiedBy() }
      • *
      • {@link MapMapping#valueQualifiedBy() }
      • + *
      • {@link SubclassMapping#qualifiedBy() }
      • *
      *

      Example:

      *
      
      diff --git a/core/src/main/java/org/mapstruct/SubclassMapping.java b/core/src/main/java/org/mapstruct/SubclassMapping.java
      index bfd4f9bec5..ccf8d4d472 100644
      --- a/core/src/main/java/org/mapstruct/SubclassMapping.java
      +++ b/core/src/main/java/org/mapstruct/SubclassMapping.java
      @@ -5,6 +5,7 @@
        */
       package org.mapstruct;
       
      +import java.lang.annotation.Annotation;
       import java.lang.annotation.ElementType;
       import java.lang.annotation.Repeatable;
       import java.lang.annotation.Retention;
      @@ -81,4 +82,29 @@
            * @return the target subclass to map the source to.
            */
           Class target();
      +
      +    /**
      +     * A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple
      +     * mapping methods (hand written or generated) qualify and thus would result in an 'Ambiguous mapping methods found'
      +     * error. A qualifier is a custom annotation and can be placed on a hand written mapper class or a method.
      +     *
      +     * @return the qualifiers
      +     * @see Qualifier
      +     */
      +    Class[] qualifiedBy() default {};
      +
      +    /**
      +     * String-based form of qualifiers; When looking for a suitable mapping method for a given property, MapStruct will
      +     * only consider those methods carrying directly or indirectly (i.e. on the class-level) a {@link Named} annotation
      +     * for each of the specified qualifier names.
      +     * 

      + * Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and + * are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large + * number of qualifiers as no custom annotation types are needed. + * + * @return One or more qualifier name(s) + * @see #qualifiedBy() + * @see Named + */ + String[] qualifiedByName() default {}; } diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index a6e42b6d48..87dffce31c 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -141,6 +141,8 @@ In the case that the `Fruit` is an abstract class or an interface, you would get To allow mappings for abstract classes or interfaces you need to set the `subclassExhaustiveStrategy` to `RUNTIME_EXCEPTION`, you can do this at the `@MapperConfig`, `@Mapper` or `@BeanMapping` annotations. If you then pass a `GrapeDto` an `IllegalArgumentException` will be thrown because it is unknown how to map a `GrapeDto`. Adding the missing (`@SubclassMapping`) for it will fix that. +<> can be used to further control which methods may be chosen to map a specific subclass. For that, you will need to use one of `SubclassMapping#qualifiedByName` or `SubclassMapping#qualifiedBy`. + [TIP] ==== If the mapping method for the subclasses does not exist it will be created and any other annotations on the fruit mapping method will be inherited by the newly generated mappings. 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 0ea4ecd197..29d27c8d68 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 @@ -399,13 +399,10 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap "SubclassMapping for " + sourceType.getFullyQualifiedName() ); SelectionCriteria criteria = SelectionCriteria - .forSubclassMappingMethods( - new SelectionParameters( - Collections.emptyList(), - Collections.emptyList(), - subclassMappingOptions.getTarget(), - ctx.getTypeUtils() ).withSourceRHS( rightHandSide ), - subclassMappingOptions.getMappingControl( ctx.getElementUtils() ) ); + .forSubclassMappingMethods( + subclassMappingOptions.getSelectionParameters().withSourceRHS( rightHandSide ), + subclassMappingOptions.getMappingControl( ctx.getElementUtils() ) + ); Assignment assignment = ctx .getMappingResolver() .getTargetAssignment( @@ -424,10 +421,13 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap String sourceArgument = null; for ( Parameter parameter : method.getSourceParameters() ) { if ( ctx - .getTypeUtils() - .isAssignable( sourceType.getTypeMirror(), parameter.getType().getTypeMirror() ) ) { + .getTypeUtils() + .isAssignable( sourceType.getTypeMirror(), parameter.getType().getTypeMirror() ) ) { sourceArgument = parameter.getName(); - assignment.setSourceLocalVarName( "(" + sourceType.createReferenceName() + ") " + sourceArgument ); + if ( assignment != null ) { + assignment.setSourceLocalVarName( + "(" + sourceType.createReferenceName() + ") " + sourceArgument ); + } } } return new SubclassMapping( sourceType, sourceArgument, targetType, assignment ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java index 08df8083bc..6ed1117ebd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java @@ -33,12 +33,15 @@ public class SubclassMappingOptions extends DelegatingOptions { private final TypeMirror source; private final TypeMirror target; private final TypeUtils typeUtils; + private final SelectionParameters selectionParameters; - public SubclassMappingOptions(TypeMirror source, TypeMirror target, TypeUtils typeUtils, DelegatingOptions next) { + public SubclassMappingOptions(TypeMirror source, TypeMirror target, TypeUtils typeUtils, DelegatingOptions next, + SelectionParameters selectionParameters) { super( next ); this.source = source; this.target = target; this.typeUtils = typeUtils; + this.selectionParameters = selectionParameters; } @Override @@ -117,6 +120,10 @@ public TypeMirror getTarget() { return target; } + public SelectionParameters getSelectionParameters() { + return selectionParameters; + } + public static void addInstances(SubclassMappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, FormattingMessager messager, TypeUtils typeUtils, Set mappings, @@ -154,6 +161,12 @@ public static void addInstance(SubclassMappingGem subclassMapping, ExecutableEle TypeMirror sourceSubclass = subclassMapping.source().getValue(); TypeMirror targetSubclass = subclassMapping.target().getValue(); + SelectionParameters selectionParameters = new SelectionParameters( + subclassMapping.qualifiedBy().get(), + subclassMapping.qualifiedByName().get(), + targetSubclass, + typeUtils + ); mappings .add( @@ -161,20 +174,24 @@ public static void addInstance(SubclassMappingGem subclassMapping, ExecutableEle sourceSubclass, targetSubclass, typeUtils, - beanMappingOptions ) ); + beanMappingOptions, + selectionParameters + ) ); } public static List copyForInverseInheritance(Set subclassMappings, - BeanMappingOptions beanMappingOptions) { + BeanMappingOptions beanMappingOptions) { // we are not interested in keeping it unique at this point. List mappings = new ArrayList<>(); for ( SubclassMappingOptions subclassMapping : subclassMappings ) { mappings.add( - new SubclassMappingOptions( - subclassMapping.target, - subclassMapping.source, - subclassMapping.typeUtils, - beanMappingOptions ) ); + new SubclassMappingOptions( + subclassMapping.target, + subclassMapping.source, + subclassMapping.typeUtils, + beanMappingOptions, + subclassMapping.selectionParameters + ) ); } return mappings; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Composer.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Composer.java new file mode 100644 index 0000000000..116304ae5d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Composer.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.qualifier; + +public class Composer { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ComposerDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ComposerDto.java new file mode 100644 index 0000000000..1f9d27681f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ComposerDto.java @@ -0,0 +1,18 @@ +/* + * 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.subclassmapping.qualifier; + +public class ComposerDto { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java new file mode 100644 index 0000000000..26cdd4715e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java @@ -0,0 +1,16 @@ +/* + * 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.subclassmapping.qualifier; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassMapping; + +@Mapper(uses = { RossiniMapper.class, VivaldiMapper.class }) +public interface ErroneousSubclassQualifiedByMapper { + @SubclassMapping(source = Rossini.class, target = RossiniDto.class, qualifiedBy = NonExistent.class) + @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class) + ComposerDto toDto(Composer composer); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByNameMapper.java new file mode 100644 index 0000000000..23887baf0f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByNameMapper.java @@ -0,0 +1,16 @@ +/* + * 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.subclassmapping.qualifier; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassMapping; + +@Mapper(uses = { RossiniMapper.class, VivaldiMapper.class }) +public interface ErroneousSubclassQualifiedByNameMapper { + @SubclassMapping(source = Rossini.class, target = RossiniDto.class, qualifiedByName = "non-existent") + @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class) + ComposerDto toDto(Composer composer); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Light.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Light.java new file mode 100644 index 0000000000..0a24c561c9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Light.java @@ -0,0 +1,19 @@ +/* + * 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.subclassmapping.qualifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.mapstruct.Qualifier; + +@Qualifier +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface Light { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/NonExistent.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/NonExistent.java new file mode 100644 index 0000000000..d1d74f76f7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/NonExistent.java @@ -0,0 +1,19 @@ +/* + * 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.subclassmapping.qualifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.mapstruct.Qualifier; + +@Qualifier +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface NonExistent { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Rossini.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Rossini.java new file mode 100644 index 0000000000..cfd41847d0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Rossini.java @@ -0,0 +1,20 @@ +/* + * 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.subclassmapping.qualifier; + +import java.util.List; + +public class Rossini extends Composer { + private List crescendo; + + public List getCrescendo() { + return crescendo; + } + + public void setCrescendo(List crescendo) { + this.crescendo = crescendo; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniDto.java new file mode 100644 index 0000000000..355de1876e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniDto.java @@ -0,0 +1,20 @@ +/* + * 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.subclassmapping.qualifier; + +import java.util.List; + +public class RossiniDto extends ComposerDto { + private List crescendo; + + public List getCrescendo() { + return crescendo; + } + + public void setCrescendo(List crescendo) { + this.crescendo = crescendo; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniMapper.java new file mode 100644 index 0000000000..c4bb5e2497 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/RossiniMapper.java @@ -0,0 +1,50 @@ +/* + * 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.subclassmapping.qualifier; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +@Mapper +public interface RossiniMapper { + RossiniDto toDto(Rossini rossini); + + @Light + @Mapping(target = "crescendo", ignore = true) + RossiniDto toDtoLight(Rossini source); + + @Named("light") + @Mapping(target = "crescendo", ignore = true) + RossiniDto toDtoLightNamed(Rossini source); + + @Unused + @BeanMapping(ignoreByDefault = true) + RossiniDto toDtoUnused(Rossini source); + + @Named("unused") + @BeanMapping(ignoreByDefault = true) + RossiniDto toDtoUnusedNamed(Rossini source); + + Rossini fromDto(RossiniDto rossini); + + @Light + @Mapping(target = "crescendo", ignore = true) + Rossini fromDtoLight(RossiniDto source); + + @Named("light") + @Mapping(target = "crescendo", ignore = true) + Rossini fromDtoLightNamed(RossiniDto source); + + @Unused + @BeanMapping(ignoreByDefault = true) + Rossini fromDtoUnused(RossiniDto source); + + @Named("unused") + @BeanMapping(ignoreByDefault = true) + Rossini fromDtoUnusedNamed(RossiniDto source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByMapper.java new file mode 100644 index 0000000000..7f486804fd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByMapper.java @@ -0,0 +1,37 @@ +/* + * 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.subclassmapping.qualifier; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper(uses = { RossiniMapper.class, VivaldiMapper.class }) +public interface SubclassQualifiedByMapper { + SubclassQualifiedByMapper INSTANCE = Mappers.getMapper( SubclassQualifiedByMapper.class ); + + @SubclassMapping(source = Rossini.class, target = RossiniDto.class) + @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class) + ComposerDto toDto(Composer composer); + + @SubclassMapping(source = Rossini.class, target = RossiniDto.class, qualifiedBy = Light.class) + @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class, qualifiedBy = Light.class) + ComposerDto toDtoLight(Composer composer); + + @SubclassMapping(source = Rossini.class, target = RossiniDto.class) + @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class, qualifiedBy = Light.class) + ComposerDto toDtoLightJustVivaldi(Composer composer); + + @InheritInverseConfiguration(name = "toDto") + Composer fromDto(ComposerDto composer); + + @InheritInverseConfiguration(name = "toDtoLight") + Composer fromDtoLight(ComposerDto composer); + + @InheritInverseConfiguration(name = "toDtoLightJustVivaldi") + Composer fromDtoLightJustVivaldi(ComposerDto composer); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByNameMapper.java new file mode 100644 index 0000000000..e4816ef732 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifiedByNameMapper.java @@ -0,0 +1,37 @@ +/* + * 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.subclassmapping.qualifier; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper(uses = { RossiniMapper.class, VivaldiMapper.class }) +public interface SubclassQualifiedByNameMapper { + SubclassQualifiedByNameMapper INSTANCE = Mappers.getMapper( SubclassQualifiedByNameMapper.class ); + + @SubclassMapping(source = Rossini.class, target = RossiniDto.class) + @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class) + ComposerDto toDto(Composer composer); + + @SubclassMapping(source = Rossini.class, target = RossiniDto.class, qualifiedByName = "light") + @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class, qualifiedByName = "light") + ComposerDto toDtoLight(Composer composer); + + @SubclassMapping(source = Rossini.class, target = RossiniDto.class) + @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class, qualifiedByName = "light") + ComposerDto toDtoLightJustVivaldi(Composer composer); + + @InheritInverseConfiguration(name = "toDto") + Composer fromDto(ComposerDto composer); + + @InheritInverseConfiguration(name = "toDtoLight") + Composer fromDtoLight(ComposerDto composer); + + @InheritInverseConfiguration(name = "toDtoLightJustVivaldi") + Composer fromDtoLightJustVivaldi(ComposerDto composer); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java new file mode 100644 index 0000000000..df67c27013 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java @@ -0,0 +1,297 @@ +/* + * 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.subclassmapping.qualifier; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Composer.class, + ComposerDto.class, + Rossini.class, + RossiniDto.class, + Vivaldi.class, + VivaldiDto.class, + VivaldiDto.class, + VivaldiDto.class, + Light.class, + Unused.class, + NonExistent.class, + RossiniMapper.class, + VivaldiMapper.class +}) +@IssueKey("3119") +public class SubclassQualifierMapperTest { + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses(SubclassQualifiedByMapper.class) + void subclassQualifiedBy() { + Rossini rossini = buildRossini(); + + Vivaldi vivaldi = buildVivaldi(); + + RossiniDto rossiniDto = (RossiniDto) SubclassQualifiedByMapper.INSTANCE.toDto( rossini ); + RossiniDto rossiniDtoLight = (RossiniDto) SubclassQualifiedByMapper.INSTANCE.toDtoLight( rossini ); + + VivaldiDto vivaldiDto = (VivaldiDto) SubclassQualifiedByMapper.INSTANCE.toDto( vivaldi ); + VivaldiDto vivaldiDtoLight = (VivaldiDto) SubclassQualifiedByMapper.INSTANCE.toDtoLight( vivaldi ); + + assertThat( rossiniDto.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossiniDto.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" ); + assertThat( rossiniDtoLight.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossiniDtoLight.getCrescendo() ).isNull(); + + assertThat( vivaldiDto.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldiDto.getSeasons() ).containsExactly( "spring", "winter" ); + assertThat( vivaldiDtoLight.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldiDtoLight.getSeasons() ).isNull(); + } + + @ProcessorTest + @WithClasses(SubclassQualifiedByMapper.class) + void subclassQualifiedByOnlyOne() { + Rossini rossini = buildRossini(); + + Vivaldi vivaldi = buildVivaldi(); + + RossiniDto rossiniDto = (RossiniDto) SubclassQualifiedByMapper.INSTANCE.toDtoLightJustVivaldi( rossini ); + + VivaldiDto vivaldiDto = (VivaldiDto) SubclassQualifiedByMapper.INSTANCE.toDtoLightJustVivaldi( vivaldi ); + + assertThat( rossiniDto.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossiniDto.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" ); + + assertThat( vivaldiDto.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldiDto.getSeasons() ).isNull(); + } + + @ProcessorTest + @WithClasses(SubclassQualifiedByNameMapper.class) + void subclassQualifiedByName() { + Rossini rossini = buildRossini(); + + Vivaldi vivaldi = buildVivaldi(); + + RossiniDto rossiniDto = (RossiniDto) SubclassQualifiedByNameMapper.INSTANCE.toDto( rossini ); + RossiniDto rossiniDtoLight = (RossiniDto) SubclassQualifiedByNameMapper.INSTANCE.toDtoLight( rossini ); + + VivaldiDto vivaldiDto = (VivaldiDto) SubclassQualifiedByNameMapper.INSTANCE.toDto( vivaldi ); + VivaldiDto vivaldiDtoLight = (VivaldiDto) SubclassQualifiedByNameMapper.INSTANCE.toDtoLight( vivaldi ); + + assertThat( rossiniDto.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossiniDto.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" ); + assertThat( rossiniDtoLight.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossiniDtoLight.getCrescendo() ).isNull(); + + assertThat( vivaldiDto.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldiDto.getSeasons() ).containsExactly( "spring", "winter" ); + assertThat( vivaldiDtoLight.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldiDtoLight.getSeasons() ).isNull(); + } + + @ProcessorTest + @WithClasses(SubclassQualifiedByNameMapper.class) + void subclassQualifiedByNameOnlyOne() { + Rossini rossini = buildRossini(); + + Vivaldi vivaldi = buildVivaldi(); + + RossiniDto rossiniDto = (RossiniDto) SubclassQualifiedByNameMapper.INSTANCE.toDtoLightJustVivaldi( rossini ); + + VivaldiDto vivaldiDto = (VivaldiDto) SubclassQualifiedByNameMapper.INSTANCE.toDtoLightJustVivaldi( vivaldi ); + + assertThat( rossiniDto.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossiniDto.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" ); + + assertThat( vivaldiDto.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldiDto.getSeasons() ).isNull(); + } + + @ProcessorTest + @WithClasses(SubclassQualifiedByMapper.class) + void subclassQualifiedByInverse() { + RossiniDto rossiniDto = buildRossiniDto(); + + VivaldiDto vivaldiDto = buildVivaldiDto(); + + Rossini rossini = (Rossini) SubclassQualifiedByMapper.INSTANCE.fromDto( rossiniDto ); + Rossini rossiniLight = (Rossini) SubclassQualifiedByMapper.INSTANCE.fromDtoLight( rossiniDto ); + + Vivaldi vivaldi = (Vivaldi) SubclassQualifiedByMapper.INSTANCE.fromDto( vivaldiDto ); + Vivaldi vivaldiLight = (Vivaldi) SubclassQualifiedByMapper.INSTANCE.fromDtoLight( vivaldiDto ); + + assertThat( rossini.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossini.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" ); + assertThat( rossiniLight.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossiniLight.getCrescendo() ).isNull(); + + assertThat( vivaldi.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldi.getSeasons() ).containsExactly( "spring", "winter" ); + assertThat( vivaldiLight.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldiLight.getSeasons() ).isNull(); + } + + @ProcessorTest + @WithClasses(SubclassQualifiedByMapper.class) + void subclassQualifiedByOnlyOneInverse() { + RossiniDto rossiniDto = buildRossiniDto(); + + VivaldiDto vivaldiDto = buildVivaldiDto(); + + Rossini rossini = (Rossini) SubclassQualifiedByMapper.INSTANCE.fromDtoLightJustVivaldi( rossiniDto ); + + Vivaldi vivaldi = (Vivaldi) SubclassQualifiedByMapper.INSTANCE.fromDtoLightJustVivaldi( vivaldiDto ); + + assertThat( rossini.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossini.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" ); + + assertThat( vivaldi.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldi.getSeasons() ).isNull(); + } + + @ProcessorTest + @WithClasses(SubclassQualifiedByNameMapper.class) + void subclassQualifiedByNameInverse() { + RossiniDto rossiniDto = buildRossiniDto(); + + VivaldiDto vivaldiDto = buildVivaldiDto(); + + Rossini rossini = (Rossini) SubclassQualifiedByNameMapper.INSTANCE.fromDto( rossiniDto ); + Rossini rossiniLight = (Rossini) SubclassQualifiedByNameMapper.INSTANCE.fromDtoLight( rossiniDto ); + + Vivaldi vivaldi = (Vivaldi) SubclassQualifiedByNameMapper.INSTANCE.fromDto( vivaldiDto ); + Vivaldi vivaldiLight = (Vivaldi) SubclassQualifiedByNameMapper.INSTANCE.fromDtoLight( vivaldiDto ); + + assertThat( rossini.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossini.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" ); + assertThat( rossiniLight.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossiniLight.getCrescendo() ).isNull(); + + assertThat( vivaldi.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldi.getSeasons() ).containsExactly( "spring", "winter" ); + assertThat( vivaldiLight.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldiLight.getSeasons() ).isNull(); + } + + @ProcessorTest + @WithClasses(SubclassQualifiedByNameMapper.class) + void subclassQualifiedByNameOnlyOneInverse() { + RossiniDto rossiniDto = buildRossiniDto(); + + VivaldiDto vivaldiDto = buildVivaldiDto(); + + Rossini rossini = (Rossini) SubclassQualifiedByNameMapper.INSTANCE.fromDtoLightJustVivaldi( rossiniDto ); + + Vivaldi vivaldi = (Vivaldi) SubclassQualifiedByNameMapper.INSTANCE.fromDtoLightJustVivaldi( vivaldiDto ); + + assertThat( rossini.getName() ).isEqualTo( "gioacchino" ); + assertThat( rossini.getCrescendo() ).containsExactly( "andante", "allegro", "vivace" ); + + assertThat( vivaldi.getName() ).isEqualTo( "antonio" ); + assertThat( vivaldi.getSeasons() ).isNull(); + } + + @ProcessorTest + @WithClasses(SubclassQualifiedByMapper.class) + void subclassQualifiedByDoesNotContainUnused() { + generatedSource.forMapper( SubclassQualifiedByMapper.class ) + .content() + .doesNotContain( "DtoUnused" ); + } + + @ProcessorTest + @WithClasses(SubclassQualifiedByNameMapper.class) + void subclassQualifiedByNameDoesNotContainUnused() { + generatedSource.forMapper( SubclassQualifiedByNameMapper.class ) + .content() + .doesNotContain( "DtoUnused" ); + } + + @ProcessorTest + @WithClasses(ErroneousSubclassQualifiedByMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = @Diagnostic( + type = ErroneousSubclassQualifiedByMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 15, + message = "Qualifier error. No method found annotated with: [ @NonExistent ]. " + + "See https://mapstruct.org/faq/#qualifier for more info." + ) + ) + void subclassQualifiedByErroneous() { + } + + @ProcessorTest + @WithClasses(ErroneousSubclassQualifiedByNameMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = @Diagnostic( + type = ErroneousSubclassQualifiedByNameMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 15, + message = "Qualifier error. No method found annotated with @Named#value: [ non-existent ]. " + + "See https://mapstruct.org/faq/#qualifier for more info." + ) + ) + void subclassQualifiedByNameErroneous() { + } + + private Rossini buildRossini() { + Rossini rossini = new Rossini(); + rossini.setName( "gioacchino" ); + List crescendo = new ArrayList<>(); + crescendo.add( "andante" ); + crescendo.add( "allegro" ); + crescendo.add( "vivace" ); + rossini.setCrescendo( crescendo ); + return rossini; + } + + private Vivaldi buildVivaldi() { + Vivaldi vivaldi = new Vivaldi(); + vivaldi.setName( "antonio" ); + List season = new ArrayList<>(); + season.add( "spring" ); + season.add( "winter" ); + vivaldi.setSeasons( season ); + return vivaldi; + } + + private RossiniDto buildRossiniDto() { + RossiniDto rossiniDto = new RossiniDto(); + rossiniDto.setName( "gioacchino" ); + List crescendo = new ArrayList<>(); + crescendo.add( "andante" ); + crescendo.add( "allegro" ); + crescendo.add( "vivace" ); + rossiniDto.setCrescendo( crescendo ); + return rossiniDto; + } + + private VivaldiDto buildVivaldiDto() { + VivaldiDto vivaldiDto = new VivaldiDto(); + vivaldiDto.setName( "antonio" ); + List season = new ArrayList<>(); + season.add( "spring" ); + season.add( "winter" ); + vivaldiDto.setSeasons( season ); + return vivaldiDto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Unused.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Unused.java new file mode 100644 index 0000000000..10da0434e2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Unused.java @@ -0,0 +1,19 @@ +/* + * 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.subclassmapping.qualifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.mapstruct.Qualifier; + +@Qualifier +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface Unused { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Vivaldi.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Vivaldi.java new file mode 100644 index 0000000000..75df3d66f7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/Vivaldi.java @@ -0,0 +1,20 @@ +/* + * 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.subclassmapping.qualifier; + +import java.util.List; + +public class Vivaldi extends Composer { + private List seasons; + + public List getSeasons() { + return seasons; + } + + public void setSeasons(List seasons) { + this.seasons = seasons; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiDto.java new file mode 100644 index 0000000000..79cd5fef33 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiDto.java @@ -0,0 +1,20 @@ +/* + * 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.subclassmapping.qualifier; + +import java.util.List; + +public class VivaldiDto extends ComposerDto { + private List seasons; + + public List getSeasons() { + return seasons; + } + + public void setSeasons(List seasons) { + this.seasons = seasons; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiMapper.java new file mode 100644 index 0000000000..2b3b1fa3e7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/VivaldiMapper.java @@ -0,0 +1,50 @@ +/* + * 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.subclassmapping.qualifier; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +@Mapper +public interface VivaldiMapper { + VivaldiDto toDto(Vivaldi vivaldi); + + @Light + @Mapping(target = "seasons", ignore = true) + VivaldiDto toDtoLight(Vivaldi vivaldi); + + @Named("light") + @Mapping(target = "seasons", ignore = true) + VivaldiDto toDtoLightNamed(Vivaldi vivaldi); + + @Unused + @BeanMapping(ignoreByDefault = true) + VivaldiDto toDtoUnused(Vivaldi vivaldi); + + @Named("unused") + @BeanMapping(ignoreByDefault = true) + VivaldiDto toDtoUnusedNamed(Vivaldi vivaldi); + + Vivaldi fromDto(VivaldiDto vivaldi); + + @Light + @Mapping(target = "seasons", ignore = true) + Vivaldi fromDtoLight(VivaldiDto vivaldi); + + @Named("light") + @Mapping(target = "seasons", ignore = true) + Vivaldi fromDtoLightNamed(VivaldiDto vivaldi); + + @Unused + @BeanMapping(ignoreByDefault = true) + Vivaldi fromDtoUnused(VivaldiDto vivaldi); + + @Named("unused") + @BeanMapping(ignoreByDefault = true) + Vivaldi fromDtoUnusedNamed(VivaldiDto vivaldi); +} From 62c7ce1cdf0ccc5528dca84decaf07cbecadaed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Campanero=20Ortiz?= Date: Thu, 13 Apr 2023 21:48:13 +0200 Subject: [PATCH 142/363] #3112 Document in the reference guide --- .../chapter-8-mapping-values.asciidoc | 63 +++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc index ce16b33ecd..b9cb308d4f 100644 --- a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc +++ b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc @@ -65,7 +65,7 @@ public class OrderMapperImpl implements OrderMapper { ---- ==== By default an error will be raised by MapStruct in case a constant of the source enum type does not have a corresponding constant with the same name in the target type and also is not mapped to another constant via `@ValueMapping`. This ensures that all constants are mapped in a safe and predictable manner. The generated -mapping method will throw an IllegalStateException if for some reason an unrecognized source value occurs. +mapping method will throw an `IllegalStateException` if for some reason an unrecognized source value occurs. MapStruct also has a mechanism for mapping any remaining (unspecified) mappings to a default. This can be used only once in a set of value mappings and only applies to the source. It comes in two flavors: `` and ``. They cannot be used at the same time. @@ -75,14 +75,18 @@ MapStruct will *not* attempt such name based mapping for `` and di MapStruct is able to handle `null` sources and `null` targets by means of the `` keyword. +In addition, the constant value `` can be used for throwing an exception for particular value mappings. This value is only applicable to `ValueMapping#target()` and not `ValueMapping#source()` since MapStruct can't map from exceptions. + [TIP] ==== -Constants for ``, `` and `` are available in the `MappingConstants` class. +Constants for ``, ``, `` and `` are available in the `MappingConstants` class. ==== Finally `@InheritInverseConfiguration` and `@InheritConfiguration` can be used in combination with `@ValueMappings`. `` and `` will be ignored in that case. -.Enum mapping method, and +The following code snippets exemplify the use of the aforementioned constants. + +.Enum mapping method, `` and `` ==== [source, java, linenums] [subs="verbatim,attributes"] @@ -102,7 +106,7 @@ public interface SpecialOrderMapper { ---- ==== -.Enum mapping method result, and +.Enum mapping method result, `` and `` ==== [source, java, linenums] [subs="verbatim,attributes"] @@ -137,6 +141,55 @@ public class SpecialOrderMapperImpl implements SpecialOrderMapper { *Note:* MapStruct would have refrained from mapping the `RETAIL` and `B2B` when `` was used instead of ``. +.Enum mapping method with `` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface SpecialOrderMapper { + + SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class ); + + @ValueMappings({ + @ValueMapping( source = "STANDARD", target = "DEFAULT" ), + @ValueMapping( source = "C2C", target = MappingConstants.THROW_EXCEPTION ) + }) + ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); +} +---- +==== + +.Enum mapping method with `` result +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class SpecialOrderMapperImpl implements SpecialOrderMapper { + + @Override + public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType; + + switch ( orderType ) { + case STANDARD: externalOrderType = ExternalOrderType.DEFAULT; + break; + case C2C: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType ); + default: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType ); + } + + return externalOrderType; + } +} +---- +==== + + [WARNING] ==== The mapping of enum to enum via the `@Mapping` annotation is *DEPRECATED*. It will be removed from future versions of MapStruct. Please adapt existing enum mapping methods to make use of `@ValueMapping` instead. @@ -152,6 +205,7 @@ MapStruct supports enum to a String mapping along the same lines as is described 2. Similarity: ` stops after handling defined mapping and proceeds to the switch/default clause value. 3. Difference: `` will result in an error. It acts on the premise that there is name similarity between enum constants in source and target which does not make sense for a String type. 4. Difference: Given 1. and 3. there will never be unmapped values. +5. Similarity: `THROW_EXCEPTION` can be used for throwing an exception for particular enum values. *`String` to enum* @@ -159,6 +213,7 @@ MapStruct supports enum to a String mapping along the same lines as is described 2. Similarity: ` stops after handling defined mapping and proceeds to the switch/default clause value. 3. Similarity: `` will create a mapping for each target enum constant and proceed to the switch/default clause value. 4. Difference: A switch/default value needs to be provided to have a determined outcome (enum has a limited set of values, `String` has unlimited options). Failing to specify `` or ` will result in a warning. +5. Similarity: `THROW_EXCEPTION` can be used for throwing an exception for any arbitrary `String` value. === Custom name transformation From 2be1f306a1179b437e850f8ac99ceb492373dced Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 17 Mar 2023 11:23:53 +0100 Subject: [PATCH 143/363] #3135 BeanMapping#mappingControl should be inherited by forged methods --- .../model/source/BeanMappingOptions.java | 11 ++++++++++ .../model/source/MappingMethodOptions.java | 2 +- .../model/source/SelectionParameters.java | 4 ++++ .../CloningBeanMappingMapper.java | 21 +++++++++++++++++++ .../mappingcontrol/MappingControlTest.java | 15 +++++++++++++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningBeanMappingMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index 90ad0aabb4..24811768e7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -54,6 +54,17 @@ public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping) return options; } + public static BeanMappingOptions forForgedMethods(BeanMappingOptions beanMapping) { + BeanMappingOptions options = new BeanMappingOptions( + beanMapping.selectionParameters != null ? + SelectionParameters.withoutResultType( beanMapping.selectionParameters ) : null, + Collections.emptyList(), + beanMapping.beanMapping, + beanMapping + ); + return options; + } + public static BeanMappingOptions empty(DelegatingOptions delegatingOptions) { return new BeanMappingOptions( null, Collections.emptyList(), null, delegatingOptions ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java index a1a64872a9..9f5da7b8de 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -365,7 +365,7 @@ public static MappingMethodOptions getForgedMethodInheritedOptions(MappingMethod options.mappings, options.iterableMapping, options.mapMapping, - BeanMappingOptions.empty( options.beanMapping.next() ), + BeanMappingOptions.forForgedMethods( options.beanMapping ), options.enumMappingOptions, options.valueMappings, Collections.emptySet(), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java index fb76c13629..7237668e2c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java @@ -39,6 +39,10 @@ public class SelectionParameters { * @return the selection parameters based on the given ones */ public static SelectionParameters forInheritance(SelectionParameters selectionParameters) { + return withoutResultType( selectionParameters ); + } + + public static SelectionParameters withoutResultType(SelectionParameters selectionParameters) { return new SelectionParameters( selectionParameters.qualifiers, selectionParameters.qualifyingNames, diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningBeanMappingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningBeanMappingMapper.java new file mode 100644 index 0000000000..a5c05d974d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningBeanMappingMapper.java @@ -0,0 +1,21 @@ +/* + * 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.mappingcontrol; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.control.DeepClone; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CloningBeanMappingMapper { + + CloningBeanMappingMapper INSTANCE = Mappers.getMapper( CloningBeanMappingMapper.class ); + + @BeanMapping(mappingControl = DeepClone.class) + FridgeDTO clone(FridgeDTO in); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java index d542801799..c9c2e095f5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java @@ -69,6 +69,21 @@ public void testDeepCloning() { assertThat( out.getShelve().getCoolBeer().getBeerCount() ).isEqualTo( "5" ); } + @ProcessorTest + @IssueKey("3135") + @WithClasses(CloningBeanMappingMapper.class) + public void testDeepCloningViaBeanMapping() { + + FridgeDTO in = createFridgeDTO(); + FridgeDTO out = CloningBeanMappingMapper.INSTANCE.clone( in ); + + assertThat( out ).isNotNull(); + assertThat( out.getShelve() ).isNotNull(); + assertThat( out.getShelve() ).isNotSameAs( in.getShelve() ); + assertThat( out.getShelve().getCoolBeer() ).isNotSameAs( in.getShelve().getCoolBeer() ); + assertThat( out.getShelve().getCoolBeer().getBeerCount() ).isEqualTo( "5" ); + } + /** * Test the deep cloning annotation with lists */ From 6e9fa87ba99bb8637ae7f525636610fc77ad6e22 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 13 Apr 2023 21:52:15 +0200 Subject: [PATCH 144/363] #3142 Nested forged methods should declare throws from lifecycle methods --- .../ap/internal/model/BeanMappingMethod.java | 8 ++++ .../test/bugs/_3142/Issue3142Exception.java | 16 +++++++ .../ap/test/bugs/_3142/Issue3142Test.java | 48 +++++++++++++++++++ ...ue3142WithAfterMappingExceptionMapper.java | 32 +++++++++++++ ...e3142WithBeforeMappingExceptionMapper.java | 32 +++++++++++++ .../mapstruct/ap/test/bugs/_3142/Source.java | 33 +++++++++++++ .../mapstruct/ap/test/bugs/_3142/Target.java | 42 ++++++++++++++++ 7 files changed, 211 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Exception.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithAfterMappingExceptionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithBeforeMappingExceptionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Target.java 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 29d27c8d68..8942c62789 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 @@ -340,6 +340,14 @@ else if ( !method.isUpdateMethod() ) { if ( factoryMethod != null ) { forgedMethod.addThrownTypes( factoryMethod.getThrownTypes() ); } + for ( LifecycleCallbackMethodReference beforeMappingMethod : beforeMappingMethods ) { + forgedMethod.addThrownTypes( beforeMappingMethod.getThrownTypes() ); + } + + for ( LifecycleCallbackMethodReference afterMappingMethod : afterMappingMethods ) { + forgedMethod.addThrownTypes( afterMappingMethod.getThrownTypes() ); + } + for ( PropertyMapping propertyMapping : propertyMappings ) { if ( propertyMapping.getAssignment() != null ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Exception.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Exception.java new file mode 100644 index 0000000000..54ed903ee7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Exception.java @@ -0,0 +1,16 @@ +/* + * 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._3142; + +/** + * @author Filip Hrisafov + */ +public class Issue3142Exception extends Exception { + + public Issue3142Exception(String message) { + super( message ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Test.java new file mode 100644 index 0000000000..a8b44874bb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Test.java @@ -0,0 +1,48 @@ +/* + * 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._3142; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue3142Exception.class, + Source.class, + Target.class, +}) +@IssueKey("3142") +class Issue3142Test { + + @ProcessorTest + @WithClasses({ + Issue3142WithBeforeMappingExceptionMapper.class + }) + void exceptionThrownInBeforeMapping() { + assertThatThrownBy( () -> Issue3142WithBeforeMappingExceptionMapper.INSTANCE.map( + new Source( new Source.Nested( "throwException" ) ), "test" ) + ) + .isInstanceOf( Issue3142Exception.class ) + .hasMessage( "Source nested exception" ); + } + + @ProcessorTest + @WithClasses({ + Issue3142WithAfterMappingExceptionMapper.class + }) + void exceptionThrownInAfterMapping() { + assertThatThrownBy( () -> Issue3142WithAfterMappingExceptionMapper.INSTANCE.map( + new Source( new Source.Nested( "throwException" ) ), "test" ) + ) + .isInstanceOf( Issue3142Exception.class ) + .hasMessage( "Source nested exception" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithAfterMappingExceptionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithAfterMappingExceptionMapper.java new file mode 100644 index 0000000000..e188626424 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithAfterMappingExceptionMapper.java @@ -0,0 +1,32 @@ +/* + * 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._3142; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3142WithAfterMappingExceptionMapper { + + Issue3142WithAfterMappingExceptionMapper INSTANCE = + Mappers.getMapper( Issue3142WithAfterMappingExceptionMapper.class ); + + Target map(Source source, String id) throws Issue3142Exception; + + @AfterMapping + default void afterMappingValidation(Object source) throws Issue3142Exception { + if ( source instanceof Source.Nested ) { + Source.Nested nested = (Source.Nested) source; + if ( "throwException".equals( nested.getValue() ) ) { + throw new Issue3142Exception( "Source nested exception" ); + } + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithBeforeMappingExceptionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithBeforeMappingExceptionMapper.java new file mode 100644 index 0000000000..ee27d87b7f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithBeforeMappingExceptionMapper.java @@ -0,0 +1,32 @@ +/* + * 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._3142; + +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3142WithBeforeMappingExceptionMapper { + + Issue3142WithBeforeMappingExceptionMapper INSTANCE = + Mappers.getMapper( Issue3142WithBeforeMappingExceptionMapper.class ); + + Target map(Source source, String id) throws Issue3142Exception; + + @BeforeMapping + default void preMappingValidation(Object source) throws Issue3142Exception { + if ( source instanceof Source.Nested ) { + Source.Nested nested = (Source.Nested) source; + if ( "throwException".equals( nested.getValue() ) ) { + throw new Issue3142Exception( "Source nested exception" ); + } + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Source.java new file mode 100644 index 0000000000..2b78ffa022 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Source.java @@ -0,0 +1,33 @@ +/* + * 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._3142; + +/** + * @author Filip Hrisafov + */ +public class Source { + private final Nested nested; + + public Source(Nested nested) { + this.nested = nested; + } + + public Nested getNested() { + return nested; + } + + public static class Nested { + private final String value; + + public Nested(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Target.java new file mode 100644 index 0000000000..6f832b6ecf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Target.java @@ -0,0 +1,42 @@ +/* + * 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._3142; + +/** + * @author Filip Hrisafov + */ +public class Target { + private String id; + private Nested nested; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public static class Nested { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} From 03563d8ffea0ca5ff4eadb297795f2407001c5dd Mon Sep 17 00:00:00 2001 From: Ben Zegveld Date: Sat, 25 Feb 2023 21:29:23 +0100 Subject: [PATCH 145/363] #3174 Also allow multiple SubclassMapping annotations on an annotation @interface. --- .../java/org/mapstruct/SubclassMappings.java | 2 +- .../CompositeSubclassMappingAnnotation.java | 19 ++++++++ .../SubclassCompositeMapper.java | 29 ++++++++++++ .../subclassmapping/SubclassMappingTest.java | 45 +++++++++++++++++++ 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/CompositeSubclassMappingAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassCompositeMapper.java diff --git a/core/src/main/java/org/mapstruct/SubclassMappings.java b/core/src/main/java/org/mapstruct/SubclassMappings.java index 938b836f77..2ddafaf516 100644 --- a/core/src/main/java/org/mapstruct/SubclassMappings.java +++ b/core/src/main/java/org/mapstruct/SubclassMappings.java @@ -45,7 +45,7 @@ * @author Ben Zegveld * @since 1.5 */ -@Target(ElementType.METHOD) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.CLASS) @Experimental public @interface SubclassMappings { diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/CompositeSubclassMappingAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/CompositeSubclassMappingAnnotation.java new file mode 100644 index 0000000000..a121c08799 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/CompositeSubclassMappingAnnotation.java @@ -0,0 +1,19 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Bike; +import org.mapstruct.ap.test.subclassmapping.mappables.BikeDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.CarDto; + +@SubclassMapping( source = Car.class, target = CarDto.class ) +@SubclassMapping( source = Bike.class, target = BikeDto.class ) +@Mapping( source = "vehicleManufacturingCompany", target = "maker") +public @interface CompositeSubclassMappingAnnotation { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassCompositeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassCompositeMapper.java new file mode 100644 index 0000000000..c660cd2390 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassCompositeMapper.java @@ -0,0 +1,29 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollection; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleCollectionDto; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SubclassCompositeMapper { + SubclassCompositeMapper INSTANCE = Mappers.getMapper( SubclassCompositeMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + @CompositeSubclassMappingAnnotation + VehicleDto map(Vehicle vehicle); + + VehicleCollection mapInverse(VehicleCollectionDto vehicles); + + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java index 6f9b6e160c..5a5ce1ef99 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java @@ -143,6 +143,51 @@ void subclassMappingOverridesInverseInheritsMapping() { .containsExactly( Car.class ); } + @ProcessorTest + @WithClasses( { SubclassCompositeMapper.class, CompositeSubclassMappingAnnotation.class }) + void mappingIsDoneUsingSubclassMappingWithCompositeMapping() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Bike() ); + + VehicleCollectionDto result = SubclassCompositeMapper.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( CarDto.class, BikeDto.class ); + } + + @ProcessorTest + @WithClasses( { SubclassCompositeMapper.class, CompositeSubclassMappingAnnotation.class }) + void inverseMappingIsDoneUsingSubclassMappingWithCompositeMapping() { + VehicleCollectionDto vehicles = new VehicleCollectionDto(); + vehicles.getVehicles().add( new CarDto() ); + vehicles.getVehicles().add( new BikeDto() ); + + VehicleCollection result = SubclassCompositeMapper.INSTANCE.mapInverse( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( Car.class, Bike.class ); + } + + @ProcessorTest + @WithClasses( { SubclassCompositeMapper.class, CompositeSubclassMappingAnnotation.class }) + void subclassMappingInheritsInverseMappingWithCompositeMapping() { + VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); + CarDto carDto = new CarDto(); + carDto.setMaker( "BenZ" ); + vehiclesDto.getVehicles().add( carDto ); + + VehicleCollection result = SubclassCompositeMapper.INSTANCE.mapInverse( vehiclesDto ); + + assertThat( result.getVehicles() ) + .extracting( Vehicle::getVehicleManufacturingCompany ) + .containsExactly( "BenZ" ); + } + @ProcessorTest @WithClasses({ HatchBack.class, From 162fdb44f402e47d549778d57f369db144ce5e0d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 16 Apr 2023 10:17:32 +0200 Subject: [PATCH 146/363] #3195 Update the location for asking questions --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 58d1c515bb..4807b5fba0 100644 --- a/readme.md +++ b/readme.md @@ -126,7 +126,7 @@ If you don't work with a dependency management tool, you can obtain a distributi ## Documentation and getting help -To learn more about MapStruct, refer to the [project homepage](http://mapstruct.org). The [reference documentation](http://mapstruct.org/documentation/reference-guide/) covers all provided functionality in detail. If you need help, come and join the [mapstruct-users](https://groups.google.com/forum/?hl=en#!forum/mapstruct-users) group. +To learn more about MapStruct, refer to the [project homepage](http://mapstruct.org). The [reference documentation](http://mapstruct.org/documentation/reference-guide/) covers all provided functionality in detail. If you need help please ask it in the [Discussions](https://github.com/mapstruct/mapstruct/discussions). ## Building from Source From 3c81d36810caf052f1d8bc73e30d9e3a8c8622f4 Mon Sep 17 00:00:00 2001 From: Johnny Lim Date: Mon, 17 Apr 2023 04:51:33 +0900 Subject: [PATCH 147/363] Polish links in docs (#3214) --- core/src/main/java/org/mapstruct/Mapper.java | 2 +- core/src/main/java/org/mapstruct/MapperConfig.java | 2 +- core/src/main/java/org/mapstruct/package-info.java | 2 +- distribution/pom.xml | 2 +- parent/pom.xml | 2 +- readme.md | 14 +++++++------- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index 0436c0cf69..60725cc441 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -298,7 +298,7 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default * Can be configured by the {@link MapperConfig#disableSubMappingMethodsGeneration()} as well. *

      * Note: If you need to use {@code disableSubMappingMethodsGeneration} please contact the MapStruct team at - * mapstruct.org or + * mapstruct.org or * github.com/mapstruct/mapstruct to share what problem you * are facing with the automatic sub-mapping generation. * diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index 893801cdaa..915f3dd120 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -269,7 +269,7 @@ MappingInheritanceStrategy mappingInheritanceStrategy() * Can be overridden by {@link Mapper#disableSubMappingMethodsGeneration()} *

      * Note: If you need to use {@code disableSubMappingMethodsGeneration} please contact the MapStruct team at - * mapstruct.org or + * mapstruct.org or * github.com/mapstruct/mapstruct to share what problem you * are facing with the automatic sub-mapping generation. * diff --git a/core/src/main/java/org/mapstruct/package-info.java b/core/src/main/java/org/mapstruct/package-info.java index 02f2b31a42..5a53b5a6d3 100644 --- a/core/src/main/java/org/mapstruct/package-info.java +++ b/core/src/main/java/org/mapstruct/package-info.java @@ -13,6 +13,6 @@ * This package contains several annotations which allow to configure how mapper interfaces are generated. *

      * - * @see MapStruct reference documentation + * @see MapStruct reference documentation */ package org.mapstruct; diff --git a/distribution/pom.xml b/distribution/pom.xml index be82f52b3c..bcfa1f3db0 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -100,7 +100,7 @@ MapStruct ${project.version} MapStruct ${project.version} - MapStruct Authors; All rights reserved. Released under the Apache Software License 2.0.]]> + MapStruct Authors; All rights reserved. Released under the Apache Software License 2.0.]]> diff --git a/parent/pom.xml b/parent/pom.xml index e87db14551..b306446b65 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -16,7 +16,7 @@ MapStruct Parent An annotation processor for generating type-safe bean mappers - http://mapstruct.org/ + https://mapstruct.org/ 2012 diff --git a/readme.md b/readme.md index 4807b5fba0..4e0c2f6ad4 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.3.Final-blue.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct%20AND%20v%3A1.*.Final) -[![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](http://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.mapstruct) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.3.Final-blue.svg)](https://search.maven.org/search?q=g:org.mapstruct%20AND%20v:1.*.Final) +[![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://search.maven.org/search?q=g:org.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/master/LICENSE.txt) [![Build Status](https://github.com/mapstruct/mapstruct/workflows/CI/badge.svg?branch=master)](https://github.com/mapstruct/mapstruct/actions?query=branch%3Amaster+workflow%3ACI) @@ -22,7 +22,7 @@ ## What is MapStruct? -MapStruct is a Java [annotation processor](http://docs.oracle.com/javase/6/docs/technotes/guides/apt/index.html) for the generation of type-safe and performant mappers for Java bean classes. It saves you from writing mapping code by hand, which is a tedious and error-prone task. The generator comes with sensible defaults and many built-in type conversions, but it steps out of your way when it comes to configuring or implementing special behavior. +MapStruct is a Java [annotation processor](https://docs.oracle.com/javase/6/docs/technotes/guides/apt/index.html) for the generation of type-safe and performant mappers for Java bean classes. It saves you from writing mapping code by hand, which is a tedious and error-prone task. The generator comes with sensible defaults and many built-in type conversions, but it steps out of your way when it comes to configuring or implementing special behavior. Compared to mapping frameworks working at runtime, MapStruct offers the following advantages: @@ -126,7 +126,7 @@ If you don't work with a dependency management tool, you can obtain a distributi ## Documentation and getting help -To learn more about MapStruct, refer to the [project homepage](http://mapstruct.org). The [reference documentation](http://mapstruct.org/documentation/reference-guide/) covers all provided functionality in detail. If you need help please ask it in the [Discussions](https://github.com/mapstruct/mapstruct/discussions). +To learn more about MapStruct, refer to the [project homepage](https://mapstruct.org). The [reference documentation](https://mapstruct.org/documentation/reference-guide/) covers all provided functionality in detail. If you need help please ask it in the [Discussions](https://github.com/mapstruct/mapstruct/discussions). ## Building from Source @@ -154,13 +154,13 @@ Make sure that you have the [m2e_apt](https://marketplace.eclipse.org/content/m2 ## Links -* [Homepage](http://mapstruct.org) +* [Homepage](https://mapstruct.org) * [Source code](https://github.com/mapstruct/mapstruct/) -* [Downloads](https://sourceforge.net/projects/mapstruct/files/) +* [Downloads](https://github.com/mapstruct/mapstruct/releases) * [Issue tracker](https://github.com/mapstruct/mapstruct/issues) * [User group](https://groups.google.com/forum/?hl=en#!forum/mapstruct-users) * [CI build](https://github.com/mapstruct/mapstruct/actions/) ## Licensing -MapStruct is licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +MapStruct is licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0. From 00f891be5834da6d81db330cde7ddaab6abe6bf8 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 22 Apr 2023 17:14:46 +0200 Subject: [PATCH 148/363] #3112 Add missing brackets --- .../src/main/asciidoc/chapter-8-mapping-values.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc index b9cb308d4f..965479541e 100644 --- a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc +++ b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc @@ -205,7 +205,7 @@ MapStruct supports enum to a String mapping along the same lines as is described 2. Similarity: ` stops after handling defined mapping and proceeds to the switch/default clause value. 3. Difference: `` will result in an error. It acts on the premise that there is name similarity between enum constants in source and target which does not make sense for a String type. 4. Difference: Given 1. and 3. there will never be unmapped values. -5. Similarity: `THROW_EXCEPTION` can be used for throwing an exception for particular enum values. +5. Similarity: `` can be used for throwing an exception for particular enum values. *`String` to enum* @@ -213,7 +213,7 @@ MapStruct supports enum to a String mapping along the same lines as is described 2. Similarity: ` stops after handling defined mapping and proceeds to the switch/default clause value. 3. Similarity: `` will create a mapping for each target enum constant and proceed to the switch/default clause value. 4. Difference: A switch/default value needs to be provided to have a determined outcome (enum has a limited set of values, `String` has unlimited options). Failing to specify `` or ` will result in a warning. -5. Similarity: `THROW_EXCEPTION` can be used for throwing an exception for any arbitrary `String` value. +5. Similarity: `` can be used for throwing an exception for any arbitrary `String` value. === Custom name transformation From 9adcb06c3486ea53ab37020b3080f776efe9fb48 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 22 Apr 2023 17:22:26 +0200 Subject: [PATCH 149/363] #3236 Add missing jakarta-cdi to the documentation --- documentation/src/main/asciidoc/chapter-2-set-up.asciidoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index 12c399c1f5..666954c4b1 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -215,10 +215,11 @@ suppressGeneratorVersionInfoComment` Supported values are: * `default`: the mapper uses no component model, instances are typically retrieved via `Mappers#getMapper(Class)` -* `cdi`: the generated mapper is an application-scoped CDI bean and can be retrieved via `@Inject` +* `cdi`: the generated mapper is an application-scoped (from javax.enterprise.context or jakarta.enterprise.context, depending on which one is available with javax.inject having priority) CDI bean and can be retrieved via `@Inject` * `spring`: the generated mapper is a singleton-scoped Spring bean and can be retrieved via `@Autowired` * `jsr330`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject` (from javax.inject or jakarta.inject, depending which one is available with javax.inject having priority), e.g. using Spring * `jakarta`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject` (from jakarta.inject), e.g. using Spring +* `jakarta-cdi`: the generated mapper is an application-scoped (from jakarta.enterprise.context) CDI bean and can be retrieved via `@Inject` If a component model is given for a specific mapper via `@Mapper#componentModel()`, the value from the annotation takes precedence. |`default` From b1034e67035fea9329b13e288aa4503533f6912c Mon Sep 17 00:00:00 2001 From: Claudio Nave Date: Sat, 18 Mar 2023 13:01:49 +0100 Subject: [PATCH 150/363] #3202 Improve line location report for invalid qualifier for SubclassMapping --- .../ap/internal/model/BeanMappingMethod.java | 2 +- .../model/source/SubclassMappingOptions.java | 40 +++++++++++-------- .../ErroneousSubclassQualifiedByMapper.java | 1 - .../SubclassQualifierMapperTest.java | 5 ++- 4 files changed, 27 insertions(+), 21 deletions(-) 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 8942c62789..c8fbe0205b 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 @@ -420,7 +420,7 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap FormattingParameters.EMPTY, criteria, rightHandSide, - null, + subclassMappingOptions.getMirror(), () -> forgeSubclassMapping( rightHandSide, sourceType, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java index 6ed1117ebd..11b4b283c2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java @@ -7,7 +7,10 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeMirror; @@ -34,14 +37,16 @@ public class SubclassMappingOptions extends DelegatingOptions { private final TypeMirror target; private final TypeUtils typeUtils; private final SelectionParameters selectionParameters; + private final SubclassMappingGem subclassMapping; public SubclassMappingOptions(TypeMirror source, TypeMirror target, TypeUtils typeUtils, DelegatingOptions next, - SelectionParameters selectionParameters) { + SelectionParameters selectionParameters, SubclassMappingGem subclassMapping) { super( next ); this.source = source; this.target = target; this.typeUtils = typeUtils; this.selectionParameters = selectionParameters; + this.subclassMapping = subclassMapping; } @Override @@ -124,14 +129,18 @@ public SelectionParameters getSelectionParameters() { return selectionParameters; } + public AnnotationMirror getMirror() { + return Optional.ofNullable( subclassMapping ).map( SubclassMappingGem::mirror ).orElse( null ); + } + public static void addInstances(SubclassMappingsGem gem, ExecutableElement method, BeanMappingOptions beanMappingOptions, FormattingMessager messager, TypeUtils typeUtils, Set mappings, List sourceParameters, Type resultType, SubclassValidator subclassValidator) { - for ( SubclassMappingGem subclassMappingGem : gem.value().get() ) { + for ( SubclassMappingGem subclassMapping : gem.value().get() ) { addInstance( - subclassMappingGem, + subclassMapping, method, beanMappingOptions, messager, @@ -175,25 +184,22 @@ public static void addInstance(SubclassMappingGem subclassMapping, ExecutableEle targetSubclass, typeUtils, beanMappingOptions, - selectionParameters + selectionParameters, + subclassMapping ) ); } - public static List copyForInverseInheritance(Set subclassMappings, + public static List copyForInverseInheritance(Set mappings, BeanMappingOptions beanMappingOptions) { // we are not interested in keeping it unique at this point. - List mappings = new ArrayList<>(); - for ( SubclassMappingOptions subclassMapping : subclassMappings ) { - mappings.add( - new SubclassMappingOptions( - subclassMapping.target, - subclassMapping.source, - subclassMapping.typeUtils, - beanMappingOptions, - subclassMapping.selectionParameters - ) ); - } - return mappings; + return mappings.stream().map( mapping -> new SubclassMappingOptions( + mapping.target, + mapping.source, + mapping.typeUtils, + beanMappingOptions, + mapping.selectionParameters, + mapping.subclassMapping + ) ).collect( Collectors.toCollection( ArrayList::new ) ); } @Override diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java index 26cdd4715e..fb5dcca11c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/ErroneousSubclassQualifiedByMapper.java @@ -11,6 +11,5 @@ @Mapper(uses = { RossiniMapper.class, VivaldiMapper.class }) public interface ErroneousSubclassQualifiedByMapper { @SubclassMapping(source = Rossini.class, target = RossiniDto.class, qualifiedBy = NonExistent.class) - @SubclassMapping(source = Vivaldi.class, target = VivaldiDto.class) ComposerDto toDto(Composer composer); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java index df67c27013..2b301d8981 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/qualifier/SubclassQualifierMapperTest.java @@ -230,7 +230,7 @@ void subclassQualifiedByNameDoesNotContainUnused() { diagnostics = @Diagnostic( type = ErroneousSubclassQualifiedByMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 15, + line = 13, message = "Qualifier error. No method found annotated with: [ @NonExistent ]. " + "See https://mapstruct.org/faq/#qualifier for more info." ) @@ -245,7 +245,8 @@ void subclassQualifiedByErroneous() { diagnostics = @Diagnostic( type = ErroneousSubclassQualifiedByNameMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 15, + line = 13, + alternativeLine = 15, message = "Qualifier error. No method found annotated with @Named#value: [ non-existent ]. " + "See https://mapstruct.org/faq/#qualifier for more info." ) From d10d48ccff8e3715b44ddfd3ad3f5a54e1238479 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 22 Apr 2023 18:36:42 +0200 Subject: [PATCH 151/363] #3248 BeanMapping#ignoreUnmappedSourceProperties should be inherited for `@InheritConfiguration` --- .../model/source/BeanMappingOptions.java | 5 +- .../model/source/MappingMethodOptions.java | 2 +- .../ap/test/bugs/_3248/Issue3248Mapper.java | 52 +++++++++++++++++++ .../ap/test/bugs/_3248/Issue3248Test.java | 25 +++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index 24811768e7..e8f19f91f6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -41,13 +41,14 @@ public class BeanMappingOptions extends DelegatingOptions { * creates a mapping for inheritance. Will set * * @param beanMapping the bean mapping options that should be used + * @param isInverse whether the inheritance is inverse * * @return new mapping */ - public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping) { + public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping, boolean isInverse) { BeanMappingOptions options = new BeanMappingOptions( SelectionParameters.forInheritance( beanMapping.selectionParameters ), - Collections.emptyList(), + isInverse ? Collections.emptyList() : beanMapping.ignoreUnmappedSourceProperties, beanMapping.beanMapping, beanMapping ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java index 9f5da7b8de..2124dd974d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -167,7 +167,7 @@ public void applyInheritedOptions(SourceMethod sourceMethod, SourceMethod templa } if ( !getBeanMapping().hasAnnotation() && templateOptions.getBeanMapping().hasAnnotation() ) { - setBeanMapping( BeanMappingOptions.forInheritance( templateOptions.getBeanMapping( ) ) ); + setBeanMapping( BeanMappingOptions.forInheritance( templateOptions.getBeanMapping( ), isInverse ) ); } if ( !getEnumMappingOptions().hasAnnotation() && templateOptions.getEnumMappingOptions().hasAnnotation() ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Mapper.java new file mode 100644 index 0000000000..e0cbba40f7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Mapper.java @@ -0,0 +1,52 @@ +/* + * 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._3248; + +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface Issue3248Mapper { + + @BeanMapping(ignoreUnmappedSourceProperties = "otherValue") + Target map(Source source); + + @InheritConfiguration + Target secondMap(Source source); + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public String getOtherValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Test.java new file mode 100644 index 0000000000..ad8cf2e499 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Test.java @@ -0,0 +1,25 @@ +/* + * 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._3248; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3248") +@WithClasses({ + Issue3248Mapper.class +}) +class Issue3248Test { + + @ProcessorTest + void shouldCompileCode() { + + } +} From c6ea69eaf9bba177a52f6ddebebc1378797b0eb2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 15 Apr 2023 23:18:32 +0200 Subject: [PATCH 152/363] #3186 Do not use conversions in 2-step mapping when they are disabled --- .../creation/MappingResolverImpl.java | 54 ++++++----- .../ErroneousBuiltInAndBuiltInMapper.java | 44 +++++++++ .../ErroneousBuiltInAndMethodMapper.java | 50 +++++++++++ .../ErroneousConversionAndMethodMapper.java | 48 ++++++++++ .../ErroneousMethodAndBuiltInMapper.java | 49 ++++++++++ .../ErroneousMethodAndConversionMapper.java | 48 ++++++++++ .../mappingcontrol/MappingControlTest.java | 90 +++++++++++++++++++ .../ap/test/mappingcontrol/NoConversion.java | 20 +++++ 8 files changed, 380 insertions(+), 23 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndBuiltInMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndMethodMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionAndMethodMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndBuiltInMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndConversionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/NoConversion.java 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 12b345d238..0c8e2cc4c4 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 @@ -295,20 +295,24 @@ && allowDirect( sourceType, targetType ) ) { } // 2 step method, then: method(conversion(source)) - assignment = ConversionMethod.getBestMatch( this, sourceType, targetType ); - if ( assignment != null ) { - usedSupportedMappings.addAll( supportingMethodCandidates ); - return assignment; + if ( allowConversion() ) { + assignment = ConversionMethod.getBestMatch( this, sourceType, targetType ); + if ( assignment != null ) { + usedSupportedMappings.addAll( supportingMethodCandidates ); + return assignment; + } } // stop here when looking for update methods. selectionCriteria.setPreferUpdateMapping( false ); // 2 step method, finally: conversion(method(source)) - assignment = MethodConversion.getBestMatch( this, sourceType, targetType ); - if ( assignment != null ) { - usedSupportedMappings.addAll( supportingMethodCandidates ); - return assignment; + if ( allowConversion() ) { + assignment = MethodConversion.getBestMatch( this, sourceType, targetType ); + if ( assignment != null ) { + usedSupportedMappings.addAll( supportingMethodCandidates ); + return assignment; + } } } @@ -763,22 +767,26 @@ static Assignment getBestMatch(ResolvingAttempt att, Type sourceType, Type targe return mmAttempt.result; } } - MethodMethod mbAttempt = - new MethodMethod<>( att, att.methods, att.builtIns, att::toMethodRef, att::toBuildInRef ) - .getBestMatch( sourceType, targetType ); - if ( mbAttempt.hasResult ) { - return mbAttempt.result; - } - MethodMethod bmAttempt = - new MethodMethod<>( att, att.builtIns, att.methods, att::toBuildInRef, att::toMethodRef ) - .getBestMatch( sourceType, targetType ); - if ( bmAttempt.hasResult ) { - return bmAttempt.result; + if ( att.allowConversion() ) { + MethodMethod mbAttempt = + new MethodMethod<>( att, att.methods, att.builtIns, att::toMethodRef, att::toBuildInRef ) + .getBestMatch( sourceType, targetType ); + if ( mbAttempt.hasResult ) { + return mbAttempt.result; + } + MethodMethod bmAttempt = + new MethodMethod<>( att, att.builtIns, att.methods, att::toBuildInRef, att::toMethodRef ) + .getBestMatch( sourceType, targetType ); + if ( bmAttempt.hasResult ) { + return bmAttempt.result; + } + MethodMethod bbAttempt = + new MethodMethod<>( att, att.builtIns, att.builtIns, att::toBuildInRef, att::toBuildInRef ) + .getBestMatch( sourceType, targetType ); + return bbAttempt.result; } - MethodMethod bbAttempt = - new MethodMethod<>( att, att.builtIns, att.builtIns, att::toBuildInRef, att::toBuildInRef ) - .getBestMatch( sourceType, targetType ); - return bbAttempt.result; + + return null; } MethodMethod(ResolvingAttempt attempt, List xMethods, List yMethods, diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndBuiltInMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndBuiltInMapper.java new file mode 100644 index 0000000000..eb9f7ea9ce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndBuiltInMapper.java @@ -0,0 +1,44 @@ +/* + * 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.mappingcontrol; + +import java.time.ZonedDateTime; +import java.util.Date; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(mappingControl = NoConversion.class) +public interface ErroneousBuiltInAndBuiltInMapper { + + Target map(Source source); + + class Source { + private final ZonedDateTime time; + + public Source(ZonedDateTime time) { + this.time = time; + } + + public ZonedDateTime getTime() { + return time; + } + } + + class Target { + private final Date time; + + public Target(Date time) { + this.time = time; + } + + public Date getTime() { + return time; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndMethodMapper.java new file mode 100644 index 0000000000..fdd7545da7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndMethodMapper.java @@ -0,0 +1,50 @@ +/* + * 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.mappingcontrol; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Calendar; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(mappingControl = NoConversion.class) +public interface ErroneousBuiltInAndMethodMapper { + + Target map(Source source); + + default ZonedDateTime fromInt(int time) { + return ZonedDateTime.ofInstant( Instant.ofEpochMilli( time ), ZoneOffset.UTC ); + } + + class Source { + private final int time; + + public Source(int time) { + this.time = time; + } + + public int getTime() { + return time; + } + } + + class Target { + private Calendar time; + + public Target(Calendar time) { + this.time = time; + } + + public Calendar getTime() { + return time; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionAndMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionAndMethodMapper.java new file mode 100644 index 0000000000..91a94aeca2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionAndMethodMapper.java @@ -0,0 +1,48 @@ +/* + * 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.mappingcontrol; + +import java.time.Instant; +import java.util.Date; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(mappingControl = NoConversion.class) +public interface ErroneousConversionAndMethodMapper { + + Target map(Source source); + + default Instant fromDate(int time) { + return Instant.ofEpochMilli( time ); + } + + class Source { + private final int time; + + public Source(int time) { + this.time = time; + } + + public int getTime() { + return time; + } + } + + class Target { + private Date time; + + public Target(Date time) { + this.time = time; + } + + public Date getTime() { + return time; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndBuiltInMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndBuiltInMapper.java new file mode 100644 index 0000000000..d7798161c1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndBuiltInMapper.java @@ -0,0 +1,49 @@ +/* + * 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.mappingcontrol; + +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.Date; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(mappingControl = NoConversion.class) +public interface ErroneousMethodAndBuiltInMapper { + + Target map(Source source); + + default Date fromCalendar(Calendar calendar) { + return calendar != null ? calendar.getTime() : null; + } + + class Source { + private final ZonedDateTime time; + + public Source(ZonedDateTime time) { + this.time = time; + } + + public ZonedDateTime getTime() { + return time; + } + } + + class Target { + private final Date time; + + public Target(Date time) { + this.time = time; + } + + public Date getTime() { + return time; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndConversionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndConversionMapper.java new file mode 100644 index 0000000000..5a372addd9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndConversionMapper.java @@ -0,0 +1,48 @@ +/* + * 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.mappingcontrol; + +import java.time.Instant; +import java.util.Date; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(mappingControl = NoConversion.class) +public interface ErroneousMethodAndConversionMapper { + + Target map(Source source); + + default long fromInstant(Instant value) { + return value != null ? value.getEpochSecond() : null; + } + + class Source { + private final Date time; + + public Source(Date time) { + this.time = time; + } + + public Date getTime() { + return time; + } + } + + class Target { + private long time; + + public Target(long time) { + this.time = time; + } + + public long getTime() { + return time; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java index c9c2e095f5..171fe4bc6a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java @@ -240,6 +240,96 @@ public void complexSelectionNotAllowed() { public void complexSelectionNotAllowedWithConfig() { } + @ProcessorTest + @IssueKey("3186") + @WithClasses({ + ErroneousMethodAndBuiltInMapper.class, + NoConversion.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMethodAndBuiltInMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"ZonedDateTime time\" to \"Date time\". " + + "Consider to declare/implement a mapping method: \"Date map(ZonedDateTime value)\"." + ) + }) + public void conversionSelectionNotAllowedInTwoStepMethodBuiltIdConversion() { + } + + @ProcessorTest + @IssueKey("3186") + @WithClasses({ + ErroneousBuiltInAndBuiltInMapper.class, + NoConversion.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousBuiltInAndBuiltInMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 19, + message = "Can't map property \"ZonedDateTime time\" to \"Date time\". " + + "Consider to declare/implement a mapping method: \"Date map(ZonedDateTime value)\"." + ) + }) + public void conversionSelectionNotAllowedInTwoStepBuiltInBuiltInConversion() { + } + + @ProcessorTest + @IssueKey("3186") + @WithClasses({ + ErroneousBuiltInAndMethodMapper.class, + NoConversion.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousBuiltInAndMethodMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 21, + message = "Can't map property \"int time\" to \"Calendar time\". " + + "Consider to declare/implement a mapping method: \"Calendar map(int value)\"." + ) + }) + public void conversionSelectionNotAllowedInTwoStepBuiltInMethodConversion() { + } + + @ProcessorTest + @IssueKey("3186") + @WithClasses({ + ErroneousMethodAndConversionMapper.class, + NoConversion.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMethodAndConversionMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 19, + message = "Can't map property \"Date time\" to \"long time\". " + + "Consider to declare/implement a mapping method: \"long map(Date value)\"." + ) + }) + public void conversionSelectionNotAllowedInTwoStepMethodConversionConversion() { + } + + @ProcessorTest + @IssueKey("3186") + @WithClasses({ + ErroneousConversionAndMethodMapper.class, + NoConversion.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousConversionAndMethodMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 19, + message = "Can't map property \"int time\" to \"Date time\". " + + "Consider to declare/implement a mapping method: \"Date map(int value)\"." + ) + }) + public void conversionSelectionNotAllowedInTwoStepConversionMethodConversion() { + } + private FridgeDTO createFridgeDTO() { FridgeDTO fridgeDTO = new FridgeDTO(); ShelveDTO shelveDTO = new ShelveDTO(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/NoConversion.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/NoConversion.java new file mode 100644 index 0000000000..6f05c16bb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/NoConversion.java @@ -0,0 +1,20 @@ +/* + * 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.mappingcontrol; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.control.MappingControl; +import org.mapstruct.util.Experimental; + +@Retention(RetentionPolicy.CLASS) +@Experimental +@MappingControl( MappingControl.Use.DIRECT ) +@MappingControl( MappingControl.Use.MAPPING_METHOD ) +@MappingControl( MappingControl.Use.COMPLEX_MAPPING ) +public @interface NoConversion { +} From e69843f46e47332ea6899717e530266b853cee8f Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 2 Apr 2023 19:47:08 +0200 Subject: [PATCH 153/363] #3158 BeanMapping#ignoreByDefault should work properly for constructor properties --- .../ap/internal/model/BeanMappingMethod.java | 10 ++++- .../ap/test/bugs/_2149/Issue2149Test.java | 6 --- .../ap/test/bugs/_3158/Issue3158Mapper.java | 43 +++++++++++++++++++ .../ap/test/bugs/_3158/Issue3158Test.java | 31 +++++++++++++ 4 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Test.java 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 c8fbe0205b..58b4640c95 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 @@ -285,7 +285,11 @@ else if ( !method.isUpdateMethod() ) { return null; } - if ( !mappingReferences.isRestrictToDefinedMappings() ) { + boolean applyImplicitMappings = !mappingReferences.isRestrictToDefinedMappings(); + if ( applyImplicitMappings ) { + applyImplicitMappings = beanMapping == null || !beanMapping.isignoreByDefault(); + } + if ( applyImplicitMappings ) { // apply name based mapping from a source reference applyTargetThisMapping(); @@ -1522,6 +1526,10 @@ private ReportingPolicyGem getUnmappedTargetPolicy() { if ( mappingReferences.isForForgedMethods() ) { return ReportingPolicyGem.IGNORE; } + // If we have ignoreByDefault = true, unprocessed target properties are not an issue. + if ( method.getOptions().getBeanMapping().isignoreByDefault() ) { + return ReportingPolicyGem.IGNORE; + } if ( method.getOptions().getBeanMapping() != null ) { return method.getOptions().getBeanMapping().unmappedTargetPolicy(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Issue2149Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Issue2149Test.java index 8c19e2e090..39c415f340 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Issue2149Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Issue2149Test.java @@ -30,12 +30,6 @@ public class Issue2149Test { line = 18, message = "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not " + "allowed. You'll need to explicitly ignore the target properties that should be ignored instead." - ), - @Diagnostic( - type = Erroneous2149Mapper.class, - kind = javax.tools.Diagnostic.Kind.WARNING, - line = 20, - message = "Unmapped target property: \"address\"." ) } ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Mapper.java new file mode 100644 index 0000000000..25af6b3d44 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Mapper.java @@ -0,0 +1,43 @@ +/* + * 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._3158; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3158Mapper { + + Issue3158Mapper INSTANCE = Mappers.getMapper( Issue3158Mapper.class ); + + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "name") + Target map(Target target); + + class Target { + private final String name; + private final String email; + + public Target(String name, String email) { + this.name = name; + this.email = email; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Test.java new file mode 100644 index 0000000000..e4832edfa9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Test.java @@ -0,0 +1,31 @@ +/* + * 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._3158; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3158Mapper.class) +@IssueKey("3158") +class Issue3158Test { + + @ProcessorTest + void beanMappingIgnoreByDefaultShouldBeRespectedForConstructorProperties() { + Issue3158Mapper.Target target = Issue3158Mapper.INSTANCE.map( new Issue3158Mapper.Target( + "tester", + "tester@test.com" + ) ); + + assertThat( target.getName() ).isEqualTo( "tester" ); + assertThat( target.getEmail() ).isNull(); + } +} From 979b35a2f46e75358af7029f0cd1ee7bbf40d8a7 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 2 Apr 2023 19:06:45 +0200 Subject: [PATCH 154/363] #3153 Do not use compress directive to strip whitespaces for value mapping switch method When using the compress directive it is going to strip whitespaces from the templates as well (i.e. what the user defined in `ValueMapping#source` and / or `ValueMapping#target --- .../ap/internal/model/ValueMappingMethod.ftl | 30 ++++++++--------- .../ap/test/bugs/_3153/Issue3153Mapper.java | 33 +++++++++++++++++++ .../ap/test/bugs/_3153/Issue3153Test.java | 30 +++++++++++++++++ 3 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Test.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl index 016cfcd182..104fd98437 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl @@ -48,26 +48,22 @@ } <#macro writeSource source=""> - <@compress single_line=true> - <#if sourceParameter.type.enumType> - ${source} - <#elseif sourceParameter.type.string> - "${source}" - - + <#if sourceParameter.type.enumType> + ${source}<#t> + <#elseif sourceParameter.type.string> + "${source}"<#t> + <#macro writeTarget target=""> - <@compress single_line=true> - <#if target?has_content> - <#if returnType.enumType> - <@includeModel object=returnType/>.${target} - <#elseif returnType.string> - "${target}" - - <#else> - null + <#if target?has_content> + <#if returnType.enumType> + <@includeModel object=returnType/>.${target}<#t> + <#elseif returnType.string> + "${target}"<#t> - + <#else> + null<#t> + <#macro throws> <#if (thrownTypes?size > 0)><#lt> throws <@compress single_line=true> diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Mapper.java new file mode 100644 index 0000000000..af54d23a8d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Mapper.java @@ -0,0 +1,33 @@ +/* + * 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._3153; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +interface Issue3153Mapper { + + Issue3153Mapper INSTANCE = Mappers.getMapper( Issue3153Mapper.class ); + + @ValueMapping(source = " PR", target = "PR") + @ValueMapping(source = " PR", target = "PR") + @ValueMapping(source = " PR", target = "PR") + @ValueMapping(source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL) + Target mapToEnum(String value); + + @ValueMapping(source = "PR", target = " PR") + String mapFromEnum(Target value); + + enum Target { + PR, + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Test.java new file mode 100644 index 0000000000..658a497086 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Test.java @@ -0,0 +1,30 @@ +/* + * 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._3153; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3153Mapper.class) +@IssueKey("3153") +class Issue3153Test { + + @ProcessorTest + void shouldNotTrimStringValueSource() { + assertThat( Issue3153Mapper.INSTANCE.mapToEnum( "PR" ) ).isEqualTo( Issue3153Mapper.Target.PR ); + assertThat( Issue3153Mapper.INSTANCE.mapToEnum( " PR" ) ).isEqualTo( Issue3153Mapper.Target.PR ); + assertThat( Issue3153Mapper.INSTANCE.mapToEnum( " PR" ) ).isEqualTo( Issue3153Mapper.Target.PR ); + assertThat( Issue3153Mapper.INSTANCE.mapToEnum( " PR" ) ).isEqualTo( Issue3153Mapper.Target.PR ); + + assertThat( Issue3153Mapper.INSTANCE.mapFromEnum( Issue3153Mapper.Target.PR ) ).isEqualTo( " PR" ); + } +} From 1bc3436c5c64077503a1656eb8ef516c69d0e3b4 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 22 Apr 2023 22:30:52 +0200 Subject: [PATCH 155/363] #3239 Mapping composition is no longer experimental --- .../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index e4a77f5391..8609ac4f77 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -126,7 +126,7 @@ Collection-typed attributes with the same element type will be copied by creatin MapStruct takes all public properties of the source and target types into account. This includes properties declared on super-types. [[mapping-composition]] -=== Mapping Composition (experimental) +=== Mapping Composition MapStruct supports the use of meta annotations. The `@Mapping` annotation supports now `@Target` with `ElementType#ANNOTATION_TYPE` in addition to `ElementType#METHOD`. This allows `@Mapping` to be used on other (user defined) annotations for re-use purposes. For example: ==== @@ -164,7 +164,7 @@ public interface StorageMapper { Still, they do have some properties in common. The `@ToEntity` assumes both target beans `ShelveEntity` and `BoxEntity` have properties: `"id"`, `"creationDate"` and `"name"`. It furthermore assumes that the source beans `ShelveDto` and `BoxDto` always have a property `"groupName"`. This concept is also known as "duck-typing". In other words, if it quacks like duck, walks like a duck its probably a duck. -This feature is still experimental. Error messages are not mature yet: the method on which the problem occurs is displayed, as well as the concerned values in the `@Mapping` annotation. However, the composition aspect is not visible. The messages are "as if" the `@Mapping` would be present on the concerned method directly. +Error messages are not mature yet: the method on which the problem occurs is displayed, as well as the concerned values in the `@Mapping` annotation. However, the composition aspect is not visible. The messages are "as if" the `@Mapping` would be present on the concerned method directly. Therefore, the user should use this feature with care, especially when uncertain when a property is always present. A more typesafe (but also more verbose) way would be to define base classes / interfaces on the target bean and the source bean and use `@InheritConfiguration` to achieve the same result (see <>). From 86a668661a45037adc795f003e6134f89faa3c3c Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 22 Apr 2023 23:33:59 +0200 Subject: [PATCH 156/363] #3159 Do null value check for collections with default expression --- .../ap/internal/model/PropertyMapping.java | 8 ++- .../ap/test/bugs/_3159/Issue3159Mapper.java | 64 +++++++++++++++++++ .../ap/test/bugs/_3159/Issue3159Test.java | 27 ++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Test.java 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 ad788de43b..7114ccf57b 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 @@ -510,7 +510,7 @@ private boolean setterWrapperNeedsSourceNullCheck(Assignment rhs, Type targetTyp return true; } - if ( defaultValue != null || defaultJavaExpression != null ) { + if ( hasDefaultValueOrDefaultExpression() ) { // If there is default value defined then a check is needed return true; } @@ -518,6 +518,10 @@ private boolean setterWrapperNeedsSourceNullCheck(Assignment rhs, Type targetTyp return false; } + private boolean hasDefaultValueOrDefaultExpression() { + return defaultValue != null || defaultJavaExpression != null; + } + private Assignment assignToPlainViaAdder( Assignment rightHandSide) { Assignment result = rightHandSide; @@ -555,7 +559,7 @@ private Assignment assignToCollection(Type targetType, AccessorType targetAccess .targetAccessorType( targetAccessorType ) .rightHandSide( rightHandSide ) .assignment( rhs ) - .nullValueCheckStrategy( nvcs ) + .nullValueCheckStrategy( hasDefaultValueOrDefaultExpression() ? ALWAYS : nvcs ) .nullValuePropertyMappingStrategy( nvpms ) .build(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Mapper.java new file mode 100644 index 0000000000..42c77c71dd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Mapper.java @@ -0,0 +1,64 @@ +/* + * 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._3159; + +import java.util.Collection; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3159Mapper { + + Issue3159Mapper INSTANCE = Mappers.getMapper( Issue3159Mapper.class ); + + @Mapping(target = "elements", defaultExpression = "java(new ArrayList<>())") + Target map(Source source); + + default String elementName(Element element) { + return element != null ? element.getName() : null; + } + + class Target { + private final Collection elements; + + public Target(Collection elements) { + this.elements = elements; + } + + public Collection getElements() { + return elements; + } + } + + class Source { + private final Collection elements; + + public Source(Collection elements) { + this.elements = elements; + } + + public Collection getElements() { + return elements; + } + } + + class Element { + private final String name; + + public Element(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Test.java new file mode 100644 index 0000000000..77feba151c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Test.java @@ -0,0 +1,27 @@ +/* + * 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._3159; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3159") +@WithClasses(Issue3159Mapper.class) +class Issue3159Test { + + @ProcessorTest + void shouldUseDefaultExpressionForCollection() { + Issue3159Mapper.Target target = Issue3159Mapper.INSTANCE.map( new Issue3159Mapper.Source( null ) ); + + assertThat( target.getElements() ).isEmpty(); + } +} From 1ab5db6a2774a58a6c41b5e4c2bfb23b2fa1bab7 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 23 Apr 2023 22:20:05 +0200 Subject: [PATCH 157/363] Update latest release version to 1.5.5.Final --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 4e0c2f6ad4..8b8af9a69f 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.3.Final-blue.svg)](https://search.maven.org/search?q=g:org.mapstruct%20AND%20v:1.*.Final) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.5.Final-blue.svg)](https://search.maven.org/search?q=g:org.mapstruct%20AND%20v:1.*.Final) [![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://search.maven.org/search?q=g:org.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/master/LICENSE.txt) @@ -68,7 +68,7 @@ For Maven-based projects, add the following to your POM file in order to use Map ```xml ... - 1.5.3.Final + 1.5.5.Final ... @@ -114,10 +114,10 @@ plugins { dependencies { ... - implementation 'org.mapstruct:mapstruct:1.5.3.Final' + implementation 'org.mapstruct:mapstruct:1.5.5.Final' - annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final' - testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final' // if you are using mapstruct in test code + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' + testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' // if you are using mapstruct in test code } ... ``` From 2f78d3f4e2f493239ed15e6fe2bb85daab4bfed8 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sun, 30 Apr 2023 16:33:00 +0200 Subject: [PATCH 158/363] #3125: Allow subclassmapping inheritance for methods with identical signature Co-authored-by: Ben Zegveld --- .../model/source/MappingMethodOptions.java | 36 ++++++++++++++++-- .../model/source/SubclassMappingOptions.java | 17 +++++++++ .../InheritedSubclassMapper.java | 38 +++++++++++++++++++ .../subclassmapping/SubclassMappingTest.java | 20 +++++++++- 4 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InheritedSubclassMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java index 2124dd974d..73474a8478 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -5,12 +5,15 @@ */ package org.mapstruct.ap.internal.model.source; +import static org.mapstruct.ap.internal.model.source.MappingOptions.getMappingTargetNamesBy; + import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; + import javax.lang.model.element.AnnotationMirror; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; @@ -20,8 +23,6 @@ import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.accessor.Accessor; -import static org.mapstruct.ap.internal.model.source.MappingOptions.getMappingTargetNamesBy; - /** * Encapsulates all options specifiable on a mapping method * @@ -205,12 +206,19 @@ public void applyInheritedOptions(SourceMethod sourceMethod, SourceMethod templa } if ( isInverse ) { - // normal inheritence of subclass mappings will result runtime in infinite loops. List inheritedMappings = SubclassMappingOptions.copyForInverseInheritance( templateOptions.getSubclassMappings(), getBeanMapping() ); addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings ); } + else if ( methodsHaveIdenticalSignature( templateMethod, sourceMethod ) ) { + List inheritedMappings = + SubclassMappingOptions + .copyForInheritance( + templateOptions.getSubclassMappings(), + getBeanMapping() ); + addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings ); + } Set newMappings = new LinkedHashSet<>(); for ( MappingOptions mapping : templateOptions.getMappings() ) { @@ -232,6 +240,28 @@ public void applyInheritedOptions(SourceMethod sourceMethod, SourceMethod templa } } + private boolean methodsHaveIdenticalSignature(SourceMethod templateMethod, SourceMethod sourceMethod) { + return parametersAreOfIdenticalTypeAndOrder( templateMethod, sourceMethod ) + && resultTypeIsTheSame( templateMethod, sourceMethod ); + } + + private boolean parametersAreOfIdenticalTypeAndOrder(SourceMethod templateMethod, SourceMethod sourceMethod) { + if (templateMethod.getParameters().size() != sourceMethod.getParameters().size()) { + return false; + } + for ( int i = 0; i < templateMethod.getParameters().size(); i++ ) { + if (!templateMethod.getParameters().get( i ).getType().equals( + sourceMethod.getParameters().get( i ).getType() ) ) { + return false; + } + } + return true; + } + + private boolean resultTypeIsTheSame(SourceMethod templateMethod, SourceMethod sourceMethod) { + return templateMethod.getResultType().equals( sourceMethod.getResultType() ); + } + private void addAllNonRedefined(SourceMethod sourceMethod, AnnotationMirror annotationMirror, List inheritedMappings) { Set redefinedSubclassMappings = new HashSet<>( subclassMappings ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java index 11b4b283c2..2b0e6dd13e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java @@ -202,6 +202,23 @@ public static List copyForInverseInheritance(Set copyForInheritance(Set subclassMappings, + BeanMappingOptions beanMappingOptions) { + // we are not interested in keeping it unique at this point. + List mappings = new ArrayList<>(); + for ( SubclassMappingOptions subclassMapping : subclassMappings ) { + mappings.add( + new SubclassMappingOptions( + subclassMapping.source, + subclassMapping.target, + subclassMapping.typeUtils, + beanMappingOptions, + subclassMapping.selectionParameters, + subclassMapping.subclassMapping ) ); + } + return mappings; + } + @Override public boolean equals(Object obj) { if ( obj == null || !( obj instanceof SubclassMappingOptions ) ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InheritedSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InheritedSubclassMapper.java new file mode 100644 index 0000000000..6e4aca98ba --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/InheritedSubclassMapper.java @@ -0,0 +1,38 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Bike; +import org.mapstruct.ap.test.subclassmapping.mappables.BikeDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.CarDto; +import org.mapstruct.ap.test.subclassmapping.mappables.HatchBack; +import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto; +import org.mapstruct.factory.Mappers; + +@Mapper( unmappedTargetPolicy = ReportingPolicy.IGNORE ) +public interface InheritedSubclassMapper { + InheritedSubclassMapper INSTANCE = Mappers.getMapper( InheritedSubclassMapper.class ); + + @SubclassMapping( source = HatchBack.class, target = CarDto.class ) + @SubclassMapping( source = Car.class, target = CarDto.class ) + @SubclassMapping( source = Bike.class, target = BikeDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker" ) + VehicleDto map(Vehicle vehicle); + + @InheritConfiguration( name = "map" ) + VehicleDto mapInherited(Vehicle dto); + + @SubclassMapping( source = Bike.class, target = CarDto.class ) + @InheritConfiguration( name = "map" ) + VehicleDto mapInheritedOverride(Vehicle dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java index 5a5ce1ef99..ba64d2cc7f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java @@ -130,7 +130,7 @@ void subclassMappingInheritsInverseMapping() { HatchBack.class, InverseOrderSubclassMapper.class } ) - void subclassMappingOverridesInverseInheritsMapping() { + void subclassMappingOverridesInverseInheritedMapping() { VehicleCollectionDto vehicleDtos = new VehicleCollectionDto(); CarDto carDto = new CarDto(); carDto.setMaker( "BenZ" ); @@ -143,6 +143,24 @@ void subclassMappingOverridesInverseInheritsMapping() { .containsExactly( Car.class ); } + @IssueKey( "3125" ) + @ProcessorTest + @WithClasses( { + HatchBack.class, + InheritedSubclassMapper.class + } ) + void subclassMappingOverridesInheritedMapping() { + Vehicle bike = new Bike(); + + VehicleDto result = InheritedSubclassMapper.INSTANCE.map( bike ); + VehicleDto resultInherited = InheritedSubclassMapper.INSTANCE.mapInherited( bike ); + VehicleDto resultOverride = InheritedSubclassMapper.INSTANCE.mapInheritedOverride( bike ); + + assertThat( result ).isInstanceOf( BikeDto.class ); + assertThat( resultInherited ).isInstanceOf( BikeDto.class ); + assertThat( resultOverride ).isInstanceOf( CarDto.class ); + } + @ProcessorTest @WithClasses( { SubclassCompositeMapper.class, CompositeSubclassMappingAnnotation.class }) void mappingIsDoneUsingSubclassMappingWithCompositeMapping() { From 931591a385a352795090a8bf9db96778d9325a3b Mon Sep 17 00:00:00 2001 From: ro0sterjam Date: Sun, 30 Apr 2023 11:02:39 -0400 Subject: [PATCH 159/363] #3071 Support defining custom processor options by custom SPI --- .../chapter-13-using-mapstruct-spi.asciidoc | 51 +++++++++++ .../org/mapstruct/ap/MappingProcessor.java | 84 ++++++++++++++++++- .../util/AnnotationProcessorContext.java | 9 +- .../mapstruct/ap/internal/util/Services.java | 4 + .../AdditionalSupportedOptionsProvider.java | 23 +++++ .../spi/MapStructProcessingEnvironment.java | 9 ++ ...dditionalSupportedOptionsProviderTest.java | 56 +++++++++++++ ...tomAdditionalSupportedOptionsProvider.java | 22 +++++ .../EmptyMapper.java | 12 +++ ...lidAdditionalSupportedOptionsProvider.java | 20 +++++ .../test/additionalsupportedoptions/Pet.java | 14 ++++ .../PetWithMissing.java | 15 ++++ .../UnknownEnumMappingStrategy.java | 29 +++++++ .../UnknownEnumMappingStrategyMapper.java | 17 ++++ 14 files changed, 361 insertions(+), 4 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java diff --git a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc index 1095d41656..ce9b288428 100644 --- a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc +++ b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc @@ -370,4 +370,55 @@ A nice example is to provide support for a custom transformation strategy. ---- include::{processor-ap-test}/value/nametransformation/CustomEnumTransformationStrategy.java[tag=documentation] ---- +==== + +[[additional-supported-options-provider]] +=== Additional Supported Options Provider + +SPI name: `org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider` + +MapStruct offers the ability to pass through declared compiler args (or "options") provided to the MappingProcessor +to the individual SPIs, by implementing `AdditionalSupportedOptionsProvider` via the Service Provider Interface (SPI). + +.Custom Additional Supported Options Provider that declares `myorg.custom.defaultNullEnumConstant` as an option to pass through +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +include::{processor-ap-test}/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java[tag=documentation] +---- +==== + +The value of this option is provided by including an `arg` to the `compilerArgs` tag when defining your custom SPI +implementation. + +.Example maven configuration with additional options +==== +[source, maven, linenums] +[subs="verbatim,attributes"] +---- + + + + org.myorg + custom-spi-impl + ${project.version} + + + + -Amyorg.custom.defaultNullEnumConstant=MISSING + + +---- +==== + +Your custom SPI implementations can then access this configured value via `MapStructProcessingEnvironment#getOptions()`. + +.Accessing your custom options +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +include::{processor-ap-test}/additionalsupportedoptions/UnknownEnumMappingStrategy.java[tag=documentation] +---- ==== \ No newline at end of file diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 8c842f007b..407166f6fa 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -8,6 +8,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; @@ -33,17 +34,19 @@ import javax.lang.model.util.ElementKindVisitor6; import javax.tools.Diagnostic.Kind; +import org.mapstruct.ap.internal.gem.MapperGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ReportingPolicyGem; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.option.Options; -import org.mapstruct.ap.internal.gem.MapperGem; -import org.mapstruct.ap.internal.gem.ReportingPolicyGem; import org.mapstruct.ap.internal.processor.DefaultModelElementProcessorContext; import org.mapstruct.ap.internal.processor.ModelElementProcessor; import org.mapstruct.ap.internal.processor.ModelElementProcessor.ProcessorContext; import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.AnnotationProcessorContext; import org.mapstruct.ap.internal.util.RoundContext; +import org.mapstruct.ap.internal.util.Services; +import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider; import org.mapstruct.ap.spi.TypeHierarchyErroneousException; import static javax.lang.model.element.ElementKind.CLASS; @@ -113,6 +116,9 @@ public class MappingProcessor extends AbstractProcessor { protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy"; protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy"; + private final Set additionalSupportedOptions; + private final String additionalSupportedOptionsError; + private Options options; private AnnotationProcessorContext annotationProcessorContext; @@ -128,6 +134,21 @@ public class MappingProcessor extends AbstractProcessor { */ private Set deferredMappers = new HashSet<>(); + public MappingProcessor() { + Set additionalSupportedOptions; + String additionalSupportedOptionsError; + try { + additionalSupportedOptions = resolveAdditionalSupportedOptions(); + additionalSupportedOptionsError = null; + } + catch ( IllegalStateException ex ) { + additionalSupportedOptions = Collections.emptySet(); + additionalSupportedOptionsError = ex.getMessage(); + } + this.additionalSupportedOptions = additionalSupportedOptions; + this.additionalSupportedOptionsError = additionalSupportedOptionsError; + } + @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init( processingEnv ); @@ -138,8 +159,13 @@ public synchronized void init(ProcessingEnvironment processingEnv) { processingEnv.getTypeUtils(), processingEnv.getMessager(), options.isDisableBuilders(), - options.isVerbose() + options.isVerbose(), + resolveAdditionalOptions( processingEnv.getOptions() ) ); + + if ( additionalSupportedOptionsError != null ) { + processingEnv.getMessager().printMessage( Kind.ERROR, additionalSupportedOptionsError ); + } } private Options createOptions() { @@ -225,6 +251,17 @@ else if ( !deferredMappers.isEmpty() ) { return ANNOTATIONS_CLAIMED_EXCLUSIVELY; } + @Override + public Set getSupportedOptions() { + Set supportedOptions = super.getSupportedOptions(); + if ( additionalSupportedOptions.isEmpty() ) { + return supportedOptions; + } + Set allSupportedOptions = new HashSet<>( supportedOptions ); + allSupportedOptions.addAll( additionalSupportedOptions ); + return allSupportedOptions; + } + /** * Gets fresh copies of all mappers deferred from previous rounds (the originals may contain references to * erroneous source/target type elements). @@ -407,6 +444,35 @@ public TypeElement visitTypeAsClass(TypeElement e, Void p) { ); } + /** + * Fetch the additional supported options provided by the SPI {@link AdditionalSupportedOptionsProvider}. + * + * @return the additional supported options + */ + private static Set resolveAdditionalSupportedOptions() { + Set additionalSupportedOptions = null; + for ( AdditionalSupportedOptionsProvider optionsProvider : + Services.all( AdditionalSupportedOptionsProvider.class ) ) { + if ( additionalSupportedOptions == null ) { + additionalSupportedOptions = new HashSet<>(); + } + Set providerOptions = optionsProvider.getAdditionalSupportedOptions(); + + for ( String providerOption : providerOptions ) { + // Ensure additional options are not in the mapstruct namespace + if ( providerOption.startsWith( "mapstruct" ) ) { + throw new IllegalStateException( + "Additional SPI options cannot start with \"mapstruct\". Provider " + optionsProvider + + " provided option " + providerOption ); + } + additionalSupportedOptions.add( providerOption ); + } + + } + + return additionalSupportedOptions == null ? Collections.emptySet() : additionalSupportedOptions; + } + private static class ProcessorComparator implements Comparator> { @Override @@ -425,4 +491,16 @@ private DeferredMapper(TypeElement deferredMapperElement, Element erroneousEleme this.erroneousElement = erroneousElement; } } + + /** + * Filters only the options belonging to the declared additional supported options. + * + * @param options all processor environment options + * @return filtered options + */ + private Map resolveAdditionalOptions(Map options) { + return options.entrySet().stream() + .filter( entry -> additionalSupportedOptions.contains( entry.getKey() ) ) + .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java index 7421dfde7a..81640848d0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java @@ -55,8 +55,10 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen private boolean disableBuilder; private boolean verbose; + private Map options; + public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messager messager, boolean disableBuilder, - boolean verbose) { + boolean verbose, Map options) { astModifyingAnnotationProcessors = java.util.Collections.unmodifiableList( findAstModifyingAnnotationProcessors( messager ) ); this.elementUtils = elementUtils; @@ -64,6 +66,7 @@ public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messag this.messager = messager; this.disableBuilder = disableBuilder; this.verbose = verbose; + this.options = java.util.Collections.unmodifiableMap( options ); } /** @@ -270,4 +273,8 @@ public Map getEnumTransformationStrategies() initialize(); return enumTransformationStrategies; } + + public Map getOptions() { + return this.options; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java index 2d37c46db8..292512ff80 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java @@ -18,6 +18,10 @@ public class Services { private Services() { } + public static Iterable all(Class serviceType) { + return ServiceLoader.load( serviceType, Services.class.getClassLoader() ); + } + public static T get(Class serviceType, T defaultValue) { Iterator services = ServiceLoader.load( serviceType, Services.class.getClassLoader() ).iterator(); diff --git a/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java new file mode 100644 index 0000000000..3638f70320 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java @@ -0,0 +1,23 @@ +/* + * 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.spi; + +import java.util.Set; + +/** + * Provider for any additional supported options required for custom SPI implementations. + * The resolved values are retrieved from {@link MapStructProcessingEnvironment#getOptions()}. + */ +public interface AdditionalSupportedOptionsProvider { + + /** + * Returns the supported options required for custom SPI implementations. + * + * @return the additional supported options. + */ + Set getAdditionalSupportedOptions(); + +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java index 0471108a51..0cea4369eb 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java @@ -7,6 +7,7 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import java.util.Map; /** * MapStruct will provide the implementations of its SPIs with on object implementing this interface so they can use @@ -36,4 +37,12 @@ public interface MapStructProcessingEnvironment { */ Types getTypeUtils(); + /** + * Returns the resolved options specified by the impl of + * {@link AdditionalSupportedOptionsProvider}. + * + * @return resolved options + */ + Map getOptions(); + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java new file mode 100644 index 0000000000..ff2c85139f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java @@ -0,0 +1,56 @@ +/* + * 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.additionalsupportedoptions; + +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.mapstruct.ap.spi.EnumMappingStrategy; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +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.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.runner.Compiler; + +import static org.assertj.core.api.Assertions.assertThat; + +@Execution( ExecutionMode.CONCURRENT ) +public class AdditionalSupportedOptionsProviderTest { + + @ProcessorTest + @WithClasses({ + Pet.class, + PetWithMissing.class, + UnknownEnumMappingStrategyMapper.class + }) + @WithServiceImplementation(CustomAdditionalSupportedOptionsProvider.class) + @WithServiceImplementation(value = UnknownEnumMappingStrategy.class, provides = EnumMappingStrategy.class) + @ProcessorOption(name = "myorg.custom.defaultNullEnumConstant", value = "MISSING") + public void shouldUseConfiguredPrefix() { + assertThat( UnknownEnumMappingStrategyMapper.INSTANCE.map( null ) ) + .isEqualTo( PetWithMissing.MISSING ); + } + + @ProcessorTest(Compiler.JDK) // The eclipse compiler does not parse the error message properly + @WithClasses({ + EmptyMapper.class + }) + @WithServiceImplementation(InvalidAdditionalSupportedOptionsProvider.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + messageRegExp = "Additional SPI options cannot start with \"mapstruct\". Provider " + + "org.mapstruct.ap.test.additionalsupportedoptions.InvalidAdditionalSupportedOptionsProvider@.*" + + " provided option mapstruct.custom.test" + ) + ) + public void shouldFailWhenOptionsProviderUsesMapstructPrefix() { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java new file mode 100644 index 0000000000..87a994bb4e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java @@ -0,0 +1,22 @@ +/* + * 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.additionalsupportedoptions; + +// tag::documentation[] +import java.util.Collections; +import java.util.Set; + +import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider; + +public class CustomAdditionalSupportedOptionsProvider implements AdditionalSupportedOptionsProvider { + + @Override + public Set getAdditionalSupportedOptions() { + return Collections.singleton( "myorg.custom.defaultNullEnumConstant" ); + } + +} +// end::documentation[] diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java new file mode 100644 index 0000000000..13a6f0aece --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java @@ -0,0 +1,12 @@ +/* + * 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.additionalsupportedoptions; + +import org.mapstruct.Mapper; + +@Mapper +public interface EmptyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java new file mode 100644 index 0000000000..1a387abba2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java @@ -0,0 +1,20 @@ +/* + * 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.additionalsupportedoptions; + +import java.util.Collections; +import java.util.Set; + +import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider; + +public class InvalidAdditionalSupportedOptionsProvider implements AdditionalSupportedOptionsProvider { + + @Override + public Set getAdditionalSupportedOptions() { + return Collections.singleton( "mapstruct.custom.test" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java new file mode 100644 index 0000000000..8fe9b39558 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java @@ -0,0 +1,14 @@ +/* + * 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.additionalsupportedoptions; + +public enum Pet { + + DOG, + CAT, + BEAR + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java new file mode 100644 index 0000000000..21d5f4a649 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java @@ -0,0 +1,15 @@ +/* + * 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.additionalsupportedoptions; + +public enum PetWithMissing { + + DOG, + CAT, + BEAR, + MISSING + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java new file mode 100644 index 0000000000..ca4403925e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java @@ -0,0 +1,29 @@ +/* + * 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.additionalsupportedoptions; + +// tag::documentation[] +import javax.lang.model.element.TypeElement; + +import org.mapstruct.ap.spi.DefaultEnumMappingStrategy; +import org.mapstruct.ap.spi.MapStructProcessingEnvironment; + +public class UnknownEnumMappingStrategy extends DefaultEnumMappingStrategy { + + private String defaultNullEnumConstant; + + @Override + public void init(MapStructProcessingEnvironment processingEnvironment) { + super.init( processingEnvironment ); + defaultNullEnumConstant = processingEnvironment.getOptions().get( "myorg.custom.defaultNullEnumConstant" ); + } + + @Override + public String getDefaultNullEnumConstant(TypeElement enumType) { + return defaultNullEnumConstant; + } +} +// end::documentation[] diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java new file mode 100644 index 0000000000..be8c74e227 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java @@ -0,0 +1,17 @@ +/* + * 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.additionalsupportedoptions; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface UnknownEnumMappingStrategyMapper { + + UnknownEnumMappingStrategyMapper INSTANCE = Mappers.getMapper( UnknownEnumMappingStrategyMapper.class ); + + PetWithMissing map(Pet pet); +} From d3b4a168b7abef6c31fea4f3506996ffe23ad718 Mon Sep 17 00:00:00 2001 From: Bragolgirith Date: Mon, 1 May 2023 09:11:05 +0200 Subject: [PATCH 160/363] #3199 Add support for implicit conversion between java.time.LocalDate and java.time.LocalDateTime --- .../chapter-5-data-type-conversions.asciidoc | 2 + .../internal/conversion/ConversionUtils.java | 12 +++++ .../ap/internal/conversion/Conversions.java | 5 +- ...avaLocalDateTimeToLocalDateConversion.java | 46 +++++++++++++++++++ .../java8time/Java8TimeConversionTest.java | 15 ++++++ .../ap/test/conversion/java8time/Source.java | 10 ++++ .../ap/test/conversion/java8time/Target.java | 11 +++++ .../java8time/SourceTargetMapperImpl.java | 36 +++++++++++++++ 8 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index 51ccb21a46..258c7085f2 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -107,6 +107,8 @@ public interface CarMapper { * Between `java.time.Instant` from Java 8 Date-Time package and `java.util.Date`. +* Between `java.time.LocalDateTime` from Java 8 Date-Time package and `java.time.LocalDate` from the same package. + * Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Calendar`. * Between `java.sql.Date` and `java.util.Date` diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java index e88bb93b86..496d11676f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java @@ -12,6 +12,7 @@ import java.sql.Timestamp; import java.text.DecimalFormat; import java.text.SimpleDateFormat; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; @@ -190,6 +191,17 @@ public static String zoneId(ConversionContext conversionContext) { return typeReferenceName( conversionContext, ZoneId.class ); } + /** + * Name for {@link java.time.LocalDate}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String localDate(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, LocalDate.class ); + } + /** * Name for {@link java.time.LocalDateTime}. * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index e947c78571..9a4085df83 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -233,12 +233,15 @@ private void registerJava8TimeConversions() { register( Period.class, String.class, new StaticParseToStringConversion() ); register( Duration.class, String.class, new StaticParseToStringConversion() ); - // Java 8 to Date + // Java 8 time to Date register( ZonedDateTime.class, Date.class, new JavaZonedDateTimeToDateConversion() ); register( LocalDateTime.class, Date.class, new JavaLocalDateTimeToDateConversion() ); register( LocalDate.class, Date.class, new JavaLocalDateToDateConversion() ); register( Instant.class, Date.class, new JavaInstantToDateConversion() ); + // Java 8 time + register( LocalDateTime.class, LocalDate.class, new JavaLocalDateTimeToLocalDateConversion() ); + } private void registerJavaTimeSqlConversions() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java new file mode 100644 index 0000000000..ae571ad6b0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java @@ -0,0 +1,46 @@ +/* + * 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.internal.conversion; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +/** + * SimpleConversion for mapping {@link LocalDateTime} to + * {@link LocalDate} and vice versa. + */ + +public class JavaLocalDateTimeToLocalDateConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toLocalDate()"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( LocalDate.class ) + ); + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return ".atStartOfDay()"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( + conversionContext.getTypeFactory().getType( LocalDateTime.class ) + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8TimeConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8TimeConversionTest.java index aa30865425..0059630532 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8TimeConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8TimeConversionTest.java @@ -252,6 +252,21 @@ public void testInstantToDateMapping() { assertThat( source.getForDateConversionWithInstant() ).isEqualTo( instant ); } + @ProcessorTest + public void testLocalDateTimeToLocalDateMapping() { + LocalDate localDate = LocalDate.of( 2014, 1, 1 ); + + Source source = new Source(); + source.setForLocalDateTimeConversionWithLocalDate( localDate ); + Target target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + LocalDateTime localDateTime = target.getForLocalDateTimeConversionWithLocalDate(); + assertThat( localDateTime ).isNotNull(); + assertThat( localDateTime ).isEqualTo( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); + + source = SourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForLocalDateTimeConversionWithLocalDate() ).isEqualTo( localDate ); + } + @ProcessorTest @DefaultTimeZone("Australia/Melbourne") public void testLocalDateTimeToDateMapping() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java index 91298617cd..93479f8a86 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java @@ -38,6 +38,8 @@ public class Source { private Instant forDateConversionWithInstant; + private LocalDate forLocalDateTimeConversionWithLocalDate; + private Instant forInstantConversionWithString; private Period forPeriodConversionWithString; @@ -124,6 +126,14 @@ public void setForDateConversionWithInstant(Instant forDateConversionWithInstant this.forDateConversionWithInstant = forDateConversionWithInstant; } + public LocalDate getForLocalDateTimeConversionWithLocalDate() { + return forLocalDateTimeConversionWithLocalDate; + } + + public void setForLocalDateTimeConversionWithLocalDate(LocalDate forLocalDateTimeConversionWithLocalDate) { + this.forLocalDateTimeConversionWithLocalDate = forLocalDateTimeConversionWithLocalDate; + } + public Instant getForInstantConversionWithString() { return forInstantConversionWithString; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java index 188a6d0e20..d2a4878731 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.conversion.java8time; +import java.time.LocalDateTime; import java.util.Calendar; import java.util.Date; @@ -33,6 +34,8 @@ public class Target { private Date forDateConversionWithInstant; + private LocalDateTime forLocalDateTimeConversionWithLocalDate; + private String forInstantConversionWithString; private String forPeriodConversionWithString; @@ -119,6 +122,14 @@ public void setForDateConversionWithInstant(Date forDateConversionWithInstant) { this.forDateConversionWithInstant = forDateConversionWithInstant; } + public LocalDateTime getForLocalDateTimeConversionWithLocalDate() { + return forLocalDateTimeConversionWithLocalDate; + } + + public void setForLocalDateTimeConversionWithLocalDate(LocalDateTime forLocalDateTimeConversionWithLocalDate) { + this.forLocalDateTimeConversionWithLocalDate = forLocalDateTimeConversionWithLocalDate; + } + public String getForInstantConversionWithString() { return forInstantConversionWithString; } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapperImpl.java index 58beaa19dd..3e0f5f84fe 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapperImpl.java @@ -68,6 +68,9 @@ public Target sourceToTarget(Source source) { if ( source.getForDateConversionWithInstant() != null ) { target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) ); } + if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) { + target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() ); + } if ( source.getForInstantConversionWithString() != null ) { target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() ); } @@ -117,6 +120,9 @@ public Target sourceToTargetDefaultMapping(Source source) { if ( source.getForDateConversionWithInstant() != null ) { target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) ); } + if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) { + target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() ); + } if ( source.getForInstantConversionWithString() != null ) { target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() ); } @@ -166,6 +172,9 @@ public Target sourceToTargetDateTimeMapped(Source source) { if ( source.getForDateConversionWithInstant() != null ) { target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) ); } + if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) { + target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() ); + } if ( source.getForInstantConversionWithString() != null ) { target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() ); } @@ -215,6 +224,9 @@ public Target sourceToTargetLocalDateTimeMapped(Source source) { if ( source.getForDateConversionWithInstant() != null ) { target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) ); } + if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) { + target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() ); + } if ( source.getForInstantConversionWithString() != null ) { target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() ); } @@ -264,6 +276,9 @@ public Target sourceToTargetLocalDateMapped(Source source) { if ( source.getForDateConversionWithInstant() != null ) { target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) ); } + if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) { + target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() ); + } if ( source.getForInstantConversionWithString() != null ) { target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() ); } @@ -313,6 +328,9 @@ public Target sourceToTargetLocalTimeMapped(Source source) { if ( source.getForDateConversionWithInstant() != null ) { target.setForDateConversionWithInstant( Date.from( source.getForDateConversionWithInstant() ) ); } + if ( source.getForLocalDateTimeConversionWithLocalDate() != null ) { + target.setForLocalDateTimeConversionWithLocalDate( source.getForLocalDateTimeConversionWithLocalDate().atStartOfDay() ); + } if ( source.getForInstantConversionWithString() != null ) { target.setForInstantConversionWithString( source.getForInstantConversionWithString().toString() ); } @@ -362,6 +380,9 @@ public Source targetToSource(Target target) { if ( target.getForDateConversionWithInstant() != null ) { source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() ); } + if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) { + source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() ); + } if ( target.getForInstantConversionWithString() != null ) { source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) ); } @@ -411,6 +432,9 @@ public Source targetToSourceDateTimeMapped(Target target) { if ( target.getForDateConversionWithInstant() != null ) { source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() ); } + if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) { + source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() ); + } if ( target.getForInstantConversionWithString() != null ) { source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) ); } @@ -460,6 +484,9 @@ public Source targetToSourceLocalDateTimeMapped(Target target) { if ( target.getForDateConversionWithInstant() != null ) { source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() ); } + if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) { + source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() ); + } if ( target.getForInstantConversionWithString() != null ) { source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) ); } @@ -509,6 +536,9 @@ public Source targetToSourceLocalDateMapped(Target target) { if ( target.getForDateConversionWithInstant() != null ) { source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() ); } + if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) { + source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() ); + } if ( target.getForInstantConversionWithString() != null ) { source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) ); } @@ -558,6 +588,9 @@ public Source targetToSourceLocalTimeMapped(Target target) { if ( target.getForDateConversionWithInstant() != null ) { source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() ); } + if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) { + source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() ); + } if ( target.getForInstantConversionWithString() != null ) { source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) ); } @@ -607,6 +640,9 @@ public Source targetToSourceDefaultMapping(Target target) { if ( target.getForDateConversionWithInstant() != null ) { source.setForDateConversionWithInstant( target.getForDateConversionWithInstant().toInstant() ); } + if ( target.getForLocalDateTimeConversionWithLocalDate() != null ) { + source.setForLocalDateTimeConversionWithLocalDate( target.getForLocalDateTimeConversionWithLocalDate().toLocalDate() ); + } if ( target.getForInstantConversionWithString() != null ) { source.setForInstantConversionWithString( Instant.parse( target.getForInstantConversionWithString() ) ); } From 970984785d5d2714fc398255c5938ee539a0d8c0 Mon Sep 17 00:00:00 2001 From: todzhang's cloudsdocker Date: Mon, 1 May 2023 17:12:23 +1000 Subject: [PATCH 161/363] Update one typo in JavaDoc (#2938) --- core/src/main/java/org/mapstruct/Mapping.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 92ae29eb54..42384152d0 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -287,7 +287,7 @@ /** * Whether the property specified via {@link #target()} should be ignored by the generated mapping method or not. - * This can be useful when certain attributes should not be propagated from source or target or when properties in + * This can be useful when certain attributes should not be propagated from source to target or when properties in * the target object are populated using a decorator and thus would be reported as unmapped target property by * default. * From a8df94cc2049099dce5f42ead520965da563e9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Campanero=20Ortiz?= Date: Mon, 1 May 2023 09:22:59 +0200 Subject: [PATCH 162/363] #2987 Support for defining Javadoc in the generated mapper implementation --- core/src/main/java/org/mapstruct/Javadoc.java | 115 ++++++++++++++++++ core/src/main/java/org/mapstruct/Mapper.java | 1 + .../chapter-3-defining-a-mapper.asciidoc | 100 +++++++++++++++ .../ap/internal/gem/GemGenerator.java | 2 + .../ap/internal/model/GeneratedType.java | 4 + .../mapstruct/ap/internal/model/Javadoc.java | 92 ++++++++++++++ .../mapstruct/ap/internal/model/Mapper.java | 19 ++- .../processor/MapperCreationProcessor.java | 31 +++++ .../mapstruct/ap/internal/util/Message.java | 2 + .../ap/internal/model/GeneratedType.ftl | 1 + .../mapstruct/ap/internal/model/Javadoc.ftl | 25 ++++ .../test/javadoc/ErroneousJavadocMapper.java | 17 +++ .../JavadocAnnotatedWithAttributesMapper.java | 21 ++++ .../JavadocAnnotatedWithValueMapper.java | 22 ++++ .../ap/test/javadoc/JavadocTest.java | 54 ++++++++ ...adocAnnotatedWithAttributesMapperImpl.java | 27 ++++ .../JavadocAnnotatedWithValueMapperImpl.java | 27 ++++ 17 files changed, 558 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/org/mapstruct/Javadoc.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/Javadoc.ftl create mode 100644 processor/src/test/java/org/mapstruct/ap/test/javadoc/ErroneousJavadocMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocTest.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapperImpl.java diff --git a/core/src/main/java/org/mapstruct/Javadoc.java b/core/src/main/java/org/mapstruct/Javadoc.java new file mode 100644 index 0000000000..4b5d2fb839 --- /dev/null +++ b/core/src/main/java/org/mapstruct/Javadoc.java @@ -0,0 +1,115 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Allows the definition of Javadoc comments in the MapStruct Mapper generated class. + * + *

      The annotation provides support for the usual Javadoc comments elements by defining analogous attributes.

      + * + * + *

      Please, note that at least one of these attributes must be specified.

      + * + *

      + * For instance, the following definition; + *

      + *
      
      + * @Javadoc(
      + *     value = "This is the description",
      + *     authors = { "author1", "author2" },
      + *     deprecated = "Use {@link OtherMapper} instead",
      + *     since = "0.1"
      + * )
      + * 
      + * + *

      + * will generate: + *

      + * + *
      
      + * /**
      + * * This is the description
      + * *
      + * * @author author1
      + * * @author author2
      + * *
      + * * @deprecated Use {@link OtherMapper} instead
      + * * @since 0.1
      + * */
      + * 
      + * + *

      + * The entire Javadoc comment block can be passed directly: + *

      + *
      
      + * @Javadoc("This is the description\n"
      + *            + "\n"
      + *            + "@author author1\n"
      + *            + "@author author2\n"
      + *            + "\n"
      + *            + "@deprecated Use {@link OtherMapper} instead\n"
      + *            + "@since 0.1\n"
      + * )
      + * 
      + * + *
      
      + * // or using Text Blocks
      + * @Javadoc(
      + *     """
      + *     This is the description
      + *
      + *     @author author1
      + *     @author author2
      + *
      + *     @deprecated Use {@link OtherMapper} instead
      + *     @since 0.1
      + *     """
      + * )
      + * 
      + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface Javadoc { + /** + * Main Javadoc comment text block. + * + * @return Main Javadoc comment text block. + */ + String value() default ""; + + /** + * List of authors of the code that it is being documented. + *

      + * It will generate a list of the Javadoc tool comment element @author + * with the different values and in the order provided. + * + * @return array of javadoc authors. + */ + String[] authors() default { }; + + /** + * Specifies that the functionality that is being documented is deprecated. + *

      + * Corresponds to the @deprecated Javadoc tool comment element. + * + * @return Deprecation message about the documented functionality + */ + String deprecated() default ""; + + /** + * Specifies the version since the functionality that is being documented is available. + *

      + * Corresponds to the @since Javadoc tool comment element. + * + * @return Version since the functionality is available + */ + String since() default ""; +} diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index 60725cc441..8a6f48dad9 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -74,6 +74,7 @@ *

      * * @author Gunnar Morling + * @see Javadoc */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index 8609ac4f77..eb3d5a80ec 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -761,3 +761,103 @@ public class MyConverterImpl implements MyConverter { } ---- ==== + + +[[javadoc]] +=== Adding Javadoc comments + +MapStruct provides support for defining Javadoc comments in the generated mapper implementation using the +`org.mapstruct.Javadoc` annotation. + +This functionality could be relevant especially in situations where certain Javadoc standards need to be met or +to deal with Javadoc validation constraints. + +The `@Javadoc` annotation defines attributes for the different Javadoc elements. + +Consider the following example: + +.Javadoc annotation example +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +@Javadoc( + value = "This is the description", + authors = { "author1", "author2" }, + deprecated = "Use {@link OtherMapper} instead", + since = "0.1" +) +public interface MyAnnotatedWithJavadocMapper { + //... +} +---- +==== + +.Javadoc annotated generated mapper +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +/** +* This is the description +* +* @author author1 +* @author author2 +* +* @deprecated Use {@link OtherMapper} instead +* @since 0.1 +*/ +public class MyAnnotatedWithJavadocMapperImpl implements MyAnnotatedWithJavadocMapper { + //... +} +---- +==== + +The entire Javadoc comment block can be provided directly as well. + +.Javadoc annotation example with the entire Javadoc comment block provided directly +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +@Javadoc( + "This is the description\n" + + "\n" + + "@author author1\n" + + "@author author2\n" + + "\n" + + "@deprecated Use {@link OtherMapper} instead\n" + + "@since 0.1\n" +) +public interface MyAnnotatedWithJavadocMapper { + //... +} +---- +==== + +Or using Text blocks: + +.Javadoc annotation example with the entire Javadoc comment block provided directly using Text blocks +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +@Javadoc( + """ + This is the description + + @author author1 + @author author2 + + @deprecated Use {@link OtherMapper} instead + @since 0.1 + """ +) +public interface MyAnnotatedWithJavadocMapper { + //... +} +---- +==== \ No newline at end of file diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index 5caea8a008..9ac13184cd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -21,6 +21,7 @@ import org.mapstruct.InheritConfiguration; import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.IterableMapping; +import org.mapstruct.Javadoc; import org.mapstruct.MapMapping; import org.mapstruct.Mapper; import org.mapstruct.MapperConfig; @@ -75,6 +76,7 @@ @GemDefinition(Context.class) @GemDefinition(Builder.class) @GemDefinition(Condition.class) +@GemDefinition(Javadoc.class) @GemDefinition(MappingControl.class) @GemDefinition(MappingControls.class) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index df6ed9b2b1..8348ecd84b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -253,6 +253,10 @@ public void removeConstructor() { constructor = null; } + public Javadoc getJavadoc() { + return null; + } + protected void addIfImportRequired(Collection collection, Type typeToAdd) { if ( typeToAdd == null ) { return; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java new file mode 100644 index 0000000000..a1efc8c022 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java @@ -0,0 +1,92 @@ +/* + * 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.internal.model; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Represents the javadoc information that should be generated for a {@link Mapper}. + * + * @author Jose Carlos Campanero Ortiz + */ +public class Javadoc extends ModelElement { + + public static class Builder { + + private String value; + private List authors; + private String deprecated; + private String since; + + public Builder value(String value) { + this.value = value; + return this; + } + + public Builder authors(List authors) { + this.authors = authors; + return this; + } + + public Builder deprecated(String deprecated) { + this.deprecated = deprecated; + return this; + } + + public Builder since(String since) { + this.since = since; + return this; + } + + public Javadoc build() { + return new Javadoc( + value, + authors, + deprecated, + since + ); + } + } + + private final String value; + private final List authors; + private final String deprecated; + private final String since; + + private Javadoc(String value, List authors, String deprecated, String since) { + this.value = value; + this.authors = authors != null ? Collections.unmodifiableList( authors ) : Collections.emptyList(); + this.deprecated = deprecated; + this.since = since; + } + + public String getValue() { + return value; + } + + public List getAuthors() { + return authors; + } + + public String getDeprecated() { + return deprecated; + } + + public String getSince() { + return since; + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java index 9b7729e8fa..cd092ca4f4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java @@ -43,6 +43,7 @@ public static class Builder extends GeneratedTypeBuilder { private boolean customPackage; private boolean suppressGeneratorTimestamp; private Set customAnnotations; + private Javadoc javadoc; public Builder() { super( Builder.class ); @@ -90,6 +91,11 @@ public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) { return this; } + public Builder javadoc(Javadoc javadoc) { + this.javadoc = javadoc; + return this; + } + public Mapper build() { String implementationName = implName.replace( CLASS_NAME_PLACEHOLDER, getFlatName( element ) ) + ( decorator == null ? "" : "_" ); @@ -119,7 +125,8 @@ public Mapper build() { fields, constructor, decorator, - extraImportedTypes + extraImportedTypes, + javadoc ); } @@ -128,6 +135,7 @@ public Mapper build() { private final boolean customPackage; private final boolean customImplName; private Decorator decorator; + private final Javadoc javadoc; @SuppressWarnings( "checkstyle:parameternumber" ) private Mapper(TypeFactory typeFactory, String packageName, String name, @@ -136,7 +144,7 @@ private Mapper(TypeFactory typeFactory, String packageName, String name, List methods, Options options, VersionInformation versionInformation, boolean suppressGeneratorTimestamp, Accessibility accessibility, List fields, Constructor constructor, - Decorator decorator, SortedSet extraImportedTypes ) { + Decorator decorator, SortedSet extraImportedTypes, Javadoc javadoc ) { super( typeFactory, @@ -157,6 +165,8 @@ private Mapper(TypeFactory typeFactory, String packageName, String name, customAnnotations.forEach( this::addAnnotation ); this.decorator = decorator; + + this.javadoc = javadoc; } public Decorator getDecorator() { @@ -171,6 +181,11 @@ public boolean hasCustomImplementation() { return customImplName || customPackage; } + @Override + public Javadoc getJavadoc() { + return javadoc; + } + @Override protected String getTemplateName() { return getTemplateNameForClass( GeneratedType.class ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index b69ba388b0..24dd528f6b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -28,6 +28,7 @@ import org.mapstruct.ap.internal.gem.DecoratedWithGem; import org.mapstruct.ap.internal.gem.InheritConfigurationGem; import org.mapstruct.ap.internal.gem.InheritInverseConfigurationGem; +import org.mapstruct.ap.internal.gem.JavadocGem; import org.mapstruct.ap.internal.gem.MapperGem; import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; @@ -40,6 +41,7 @@ import org.mapstruct.ap.internal.model.DelegatingMethod; import org.mapstruct.ap.internal.model.Field; import org.mapstruct.ap.internal.model.IterableMappingMethod; +import org.mapstruct.ap.internal.model.Javadoc; import org.mapstruct.ap.internal.model.MapMappingMethod; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.MapperReference; @@ -212,6 +214,7 @@ private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List< .implPackage( mapperOptions.implementationPackage() ) .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) .additionalAnnotations( additionalAnnotationsBuilder.getProcessedAnnotations( element ) ) + .javadoc( getJavadoc( element ) ) .build(); if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) { @@ -441,6 +444,23 @@ else if ( method.isStreamMapping() ) { return mappingMethods; } + private Javadoc getJavadoc(TypeElement element) { + JavadocGem javadocGem = JavadocGem.instanceOn( element ); + + if ( javadocGem == null || !isConsistent( javadocGem, element, messager ) ) { + return null; + } + + Javadoc javadoc = new Javadoc.Builder() + .value( javadocGem.value().getValue() ) + .authors( javadocGem.authors().getValue() ) + .deprecated( javadocGem.deprecated().getValue() ) + .since( javadocGem.since().getValue() ) + .build(); + + return javadoc; + } + private Type getUserDesiredReturnType(SourceMethod method) { SelectionParameters selectionParameters = method.getOptions().getBeanMapping().getSelectionParameters(); if ( selectionParameters != null && selectionParameters.getResultType() != null ) { @@ -810,4 +830,15 @@ private void reportErrorWhenNonMatchingName(SourceMethod onlyCandidate, SourceMe onlyCandidate.getName() ); } + + private boolean isConsistent( JavadocGem gem, TypeElement element, FormattingMessager messager ) { + if ( !gem.value().hasValue() + && !gem.authors().hasValue() + && !gem.deprecated().hasValue() + && !gem.since().hasValue() ) { + messager.printMessage( element, gem.mirror(), Message.JAVADOC_NO_ELEMENTS ); + return false; + } + return true; + } } 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 29600489fb..6f72f720f1 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 @@ -130,6 +130,8 @@ public enum Message { DECORATOR_NO_SUBTYPE( "Specified decorator type is no subtype of the annotated mapper type." ), DECORATOR_CONSTRUCTOR( "Specified decorator type has no default constructor nor a constructor with a single parameter accepting the decorated mapper type." ), + JAVADOC_NO_ELEMENTS( "'value', 'authors', 'deprecated' and 'since' are undefined in @Javadoc, define at least one of them." ), + GENERAL_CANNOT_IMPLEMENT_PRIVATE_MAPPER("Cannot create an implementation for mapper %s, because it is a private %s."), GENERAL_NO_IMPLEMENTATION( "No implementation type is registered for return type %s." ), GENERAL_ABSTRACT_RETURN_TYPE( "The return type %s is an abstract class or interface. Provide a non abstract / non interface result type or a factory method." ), diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl index 4981882403..c65b3e5f85 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl @@ -14,6 +14,7 @@ package ${packageName}; import ${importedType}; +<#if javadoc??><#nt><@includeModel object=javadoc/> <#if !generatedTypeAvailable>/* @Generated( value = "org.mapstruct.ap.MappingProcessor"<#if suppressGeneratorTimestamp == false>, diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Javadoc.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Javadoc.ftl new file mode 100644 index 0000000000..89ac57b9ef --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/Javadoc.ftl @@ -0,0 +1,25 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Javadoc" --> +/** +<#list value?split("\n") as line><#nt>*<#if line?has_content> ${line?trim} + +<#if !authors.isEmpty()> +* +<#list authors as author> <#nt>* @author ${author?trim} + + +<#if deprecated?has_content> +* +<#nt>* @deprecated ${deprecated?trim} + +<#if since?has_content> +* +<#nt>* @since ${since?trim} + +<#nt> */ \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/ErroneousJavadocMapper.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/ErroneousJavadocMapper.java new file mode 100644 index 0000000000..39f6da2890 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/ErroneousJavadocMapper.java @@ -0,0 +1,17 @@ +/* + * 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.javadoc; + +import org.mapstruct.Javadoc; +import org.mapstruct.Mapper; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@Mapper +@Javadoc +public interface ErroneousJavadocMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapper.java new file mode 100644 index 0000000000..eb6285ff13 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapper.java @@ -0,0 +1,21 @@ +/* + * 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.javadoc; + +import org.mapstruct.Javadoc; +import org.mapstruct.Mapper; + +@Mapper +@Javadoc( + value = "This is the description", + authors = { "author1", "author2" }, + deprecated = "Use {@link OtherMapper} instead", + since = "0.1" +) +@Deprecated +public interface JavadocAnnotatedWithAttributesMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapper.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapper.java new file mode 100644 index 0000000000..150d7e5f76 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapper.java @@ -0,0 +1,22 @@ +/* + * 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.javadoc; + +import org.mapstruct.Javadoc; +import org.mapstruct.Mapper; + +@Mapper +@Javadoc("This is the description\n" + + "\n" + + "@author author1\n" + + "@author author2\n" + + "\n" + + "@deprecated Use {@link OtherMapper} instead\n" + + "@since 0.1\n") +@Deprecated +public interface JavadocAnnotatedWithValueMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocTest.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocTest.java new file mode 100644 index 0000000000..316114389f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocTest.java @@ -0,0 +1,54 @@ +/* + * 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.javadoc; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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.GeneratedSource; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@IssueKey("2987") +class JavadocTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses( { JavadocAnnotatedWithValueMapper.class } ) + void javadocAnnotatedWithValueMapper() { + generatedSource.addComparisonToFixtureFor( JavadocAnnotatedWithValueMapper.class ); + } + + @ProcessorTest + @WithClasses( { JavadocAnnotatedWithAttributesMapper.class } ) + void javadocAnnotatedWithAttributesMapper() { + generatedSource.addComparisonToFixtureFor( JavadocAnnotatedWithAttributesMapper.class ); + } + + @ProcessorTest + @IssueKey("2987") + @WithClasses({ ErroneousJavadocMapper.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousJavadocMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 15, + message = "'value', 'authors', 'deprecated' and 'since' are undefined in @Javadoc, " + + "define at least one of them.") + } + ) + void shouldFailOnEmptyJavadocAnnotation() { + } + +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapperImpl.java new file mode 100644 index 0000000000..53b9a08e92 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapperImpl.java @@ -0,0 +1,27 @@ +/* + * 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.javadoc; + +import javax.annotation.processing.Generated; + +/** +* This is the description +* +* @author author1 +* @author author2 +* +* @deprecated Use {@link OtherMapper} instead +* +* @since 0.1 +*/ +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2023-04-30T17:36:38+0200", + comments = "version: , compiler: javac, environment: Java 11.0.18 (Ubuntu)" +) +@Deprecated +public class JavadocAnnotatedWithAttributesMapperImpl implements JavadocAnnotatedWithAttributesMapper { +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapperImpl.java new file mode 100644 index 0000000000..7bcc54ca44 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapperImpl.java @@ -0,0 +1,27 @@ +/* + * 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.javadoc; + +import javax.annotation.processing.Generated; + +/** +* This is the description +* +* @author author1 +* @author author2 +* +* @deprecated Use {@link OtherMapper} instead +* @since 0.1 +* +*/ +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2023-04-30T17:38:45+0200", + comments = "version: , compiler: javac, environment: Java 11.0.18 (Ubuntu)" +) +@Deprecated +public class JavadocAnnotatedWithValueMapperImpl implements JavadocAnnotatedWithValueMapper { +} From 4843123e6ef98589831a8cc8b7cc4330c7f9ed64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etien=20Ro=C5=BEnik?= <12816736+eroznik@users.noreply.github.com> Date: Mon, 1 May 2023 09:42:58 +0200 Subject: [PATCH 163/363] #3165 Support adders for array / iterable to collection --- .../ap/internal/model/PropertyMapping.java | 2 +- .../model/assignment/AdderWrapper.java | 10 ++- .../ap/internal/model/common/SourceRHS.java | 6 ++ .../ap/test/bugs/_3165/Issue3165Mapper.java | 65 +++++++++++++++++++ .../test/bugs/_3165/Issue3165MapperTest.java | 32 +++++++++ 5 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165MapperTest.java 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 7114ccf57b..bedb37cc8f 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 @@ -527,7 +527,7 @@ private Assignment assignToPlainViaAdder( Assignment rightHandSide) { Assignment result = rightHandSide; String adderIteratorName = sourcePropertyName == null ? targetPropertyName : sourcePropertyName; - if ( result.getSourceType().isCollectionType() ) { + if ( result.getSourceType().isIterableType() ) { result = new AdderWrapper( result, method.getThrownTypes(), isFieldAssignment(), adderIteratorName ); } else if ( result.getSourceType().isStreamType() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java index c5778628e9..13dfe832c9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java @@ -39,7 +39,15 @@ public AdderWrapper( Assignment rhs, // localVar is iteratorVariable String desiredName = Nouns.singularize( adderIteratorName ); rhs.setSourceLoopVarName( rhs.createUniqueVarName( desiredName ) ); - adderType = first( getSourceType().determineTypeArguments( Collection.class ) ); + if ( getSourceType().isCollectionType() ) { + adderType = first( getSourceType().determineTypeArguments( Collection.class ) ); + } + else if ( getSourceType().isArrayType() ) { + adderType = getSourceType().getComponentType(); + } + else { // iterable + adderType = first( getSourceType().determineTypeArguments( Iterable.class ) ); + } } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/SourceRHS.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/SourceRHS.java index b73d1ecf44..b4d422c797 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/SourceRHS.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/SourceRHS.java @@ -149,6 +149,12 @@ public Type getSourceTypeForMatching() { else if ( sourceType.isStreamType() ) { return first( sourceType.determineTypeArguments( Stream.class ) ); } + else if ( sourceType.isArrayType() ) { + return sourceType.getComponentType(); + } + else if ( sourceType.isIterableType() ) { + return first( sourceType.determineTypeArguments( Iterable.class ) ); + } } return sourceType; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165Mapper.java new file mode 100644 index 0000000000..e065871ea3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165Mapper.java @@ -0,0 +1,65 @@ +/* + * 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._3165; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.ArrayList; +import java.util.List; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface Issue3165Mapper { + + Issue3165Mapper INSTANCE = Mappers.getMapper( Issue3165Mapper.class ); + + Target toTarget(Source source); + + class Source { + private String[] pets; + private Iterable cats; + + public Source(String[] pets, Iterable cats) { + this.pets = pets; + this.cats = cats; + } + + public String[] getPets() { + return pets; + } + + public Iterable getCats() { + return cats; + } + } + + class Target { + private List pets; + private List cats; + + Target() { + this.pets = new ArrayList<>(); + this.cats = new ArrayList<>(); + } + + public List getPets() { + return pets; + } + + public void addPet(String pet) { + pets.add( pet ); + } + + public List getCats() { + return cats; + } + + public void addCat(String cat) { + cats.add( cat ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165MapperTest.java new file mode 100644 index 0000000000..4fb9e0ab3a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165MapperTest.java @@ -0,0 +1,32 @@ +/* + * 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._3165; + +import java.util.Arrays; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Issue3165Mapper.class +}) +@IssueKey("3165") +class Issue3165MapperTest { + + @ProcessorTest + void supportsAdderWhenMappingArrayAndIterableToCollection() { + Issue3165Mapper.Source src = new Issue3165Mapper.Source( + new String[] { "cat", "dog", "mouse" }, + Arrays.asList( "ivy", "flu", "freya" ) + ); + Issue3165Mapper.Target target = Issue3165Mapper.INSTANCE.toTarget( src ); + assertThat( target.getPets() ).containsExactly( "cat", "dog", "mouse" ); + assertThat( target.getCats() ).containsExactly( "ivy", "flu", "freya" ); + } +} From f3dac94701b316e5a62df3a34e52b3d75c483fe7 Mon Sep 17 00:00:00 2001 From: MengxingYuan Date: Mon, 1 May 2023 16:28:51 +0800 Subject: [PATCH 164/363] #2781 Remove unmapped source properties when source parameter is directly mapped --- .../ap/internal/model/BeanMappingMethod.java | 36 ++++++++----- .../ap/test/bugs/_2781/Issue2781Mapper.java | 53 +++++++++++++++++++ .../ap/test/bugs/_2781/Issue2781Test.java | 24 +++++++++ 3 files changed, 100 insertions(+), 13 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Test.java 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 58b4640c95..84cfd18285 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 @@ -1295,8 +1295,16 @@ else if ( mapping.getJavaExpression() != null ) { .options( mapping ) .build(); handledTargets.add( targetPropertyName ); - unprocessedSourceParameters.remove( sourceRef.getParameter() ); - unprocessedSourceProperties.remove( sourceRef.getShallowestPropertyName() ); + Parameter sourceParameter = sourceRef.getParameter(); + unprocessedSourceParameters.remove( sourceParameter ); + // If the source parameter was directly mapped + if ( sourceRef.getPropertyEntries().isEmpty() ) { + // Ignore all of its source properties completely + ignoreSourceProperties( sourceParameter ); + } + else { + unprocessedSourceProperties.remove( sourceRef.getShallowestPropertyName() ); + } } else { errorOccured = true; @@ -1471,23 +1479,25 @@ private void applyParameterNameBasedMapping() { sourceParameters.remove(); unprocessedDefinedTargets.remove( targetProperty.getKey() ); unprocessedSourceProperties.remove( targetProperty.getKey() ); - - // The source parameter was directly mapped so ignore all of its source properties completely - if ( !sourceParameter.getType().isPrimitive() && !sourceParameter.getType().isArrayType() ) { - // We explicitly ignore source properties from primitives or array types - Map readAccessors = sourceParameter.getType() - .getPropertyReadAccessors(); - for ( String sourceProperty : readAccessors.keySet() ) { - unprocessedSourceProperties.remove( sourceProperty ); - } - } - unprocessedConstructorProperties.remove( targetProperty.getKey() ); + ignoreSourceProperties( sourceParameter ); } } } } + private void ignoreSourceProperties(Parameter sourceParameter) { + // The source parameter was directly mapped so ignore all of its source properties completely + if ( !sourceParameter.getType().isPrimitive() && !sourceParameter.getType().isArrayType() ) { + // We explicitly ignore source properties from primitives or array types + Map readAccessors = sourceParameter.getType() + .getPropertyReadAccessors(); + for ( String sourceProperty : readAccessors.keySet() ) { + unprocessedSourceProperties.remove( sourceProperty ); + } + } + } + private SourceReference getSourceRefByTargetName(Parameter sourceParameter, String targetPropertyName) { SourceReference sourceRef = null; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Mapper.java new file mode 100644 index 0000000000..9abdf560c4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Mapper.java @@ -0,0 +1,53 @@ +/* + * 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._2781; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Mengxing Yuan + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface Issue2781Mapper { + + Issue2781Mapper INSTANCE = Mappers.getMapper( Issue2781Mapper.class ); + + @Mapping(target = "nested", source = "source") + Target map(Source source); + + class Target { + private Source nested; + + public Source getNested() { + return nested; + } + + public void setNested(Source nested) { + this.nested = nested; + } + } + + class Source { + private String field1; + private String field2; + + public Source(String field1, String field2) { + this.field1 = field1; + this.field2 = field2; + } + + public String getField1() { + return field1; + } + + public String getField2() { + return field2; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Test.java new file mode 100644 index 0000000000..80ac4f5ce3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Test.java @@ -0,0 +1,24 @@ +/* + * 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._2781; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Mengxing Yuan + */ +@IssueKey("2781") +@WithClasses({ + Issue2781Mapper.class +}) +class Issue2781Test { + + @ProcessorTest + void shouldCompileWithoutErrors() { + } +} From be94569791e8b587c8ef3a33c1c7e55db4a90ead Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Oct 2022 23:28:54 +0000 Subject: [PATCH 165/363] Bump protobuf-java from 3.21.2 to 3.21.7 in /parent Bumps [protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.21.2 to 3.21.7. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/generate_changelog.py) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.21.2...v3.21.7) --- updated-dependencies: - dependency-name: com.google.protobuf:protobuf-java dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index b306446b65..6e9c2836b6 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -41,7 +41,7 @@ The processor module needs at least Java 11. --> 1.8 - 3.21.2 + 3.21.7 2.3.2 From bc5a8771217365b32782f160f132d1dd2a1c4fa1 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Mon, 1 May 2023 11:54:24 +0200 Subject: [PATCH 166/363] #3054: Allow abstract return type when all directly sealed subtypes are covered by subclass mappings Co-authored-by: Ben Zegveld --- .../itest/tests/MavenIntegrationTest.java | 5 + .../test/resources/sealedSubclassTest/pom.xml | 102 ++++++++++++++++++ .../mapstruct/itest/sealedsubclass/Bike.java | 18 ++++ .../itest/sealedsubclass/BikeDto.java | 18 ++++ .../mapstruct/itest/sealedsubclass/Car.java | 19 ++++ .../itest/sealedsubclass/CarDto.java | 18 ++++ .../itest/sealedsubclass/Davidson.java | 18 ++++ .../itest/sealedsubclass/DavidsonDto.java | 18 ++++ .../itest/sealedsubclass/Harley.java | 18 ++++ .../itest/sealedsubclass/HarleyDto.java | 18 ++++ .../mapstruct/itest/sealedsubclass/Motor.java | 18 ++++ .../itest/sealedsubclass/MotorDto.java | 18 ++++ .../sealedsubclass/SealedSubclassMapper.java | 31 ++++++ .../itest/sealedsubclass/Vehicle.java | 27 +++++ .../sealedsubclass/VehicleCollection.java | 17 +++ .../sealedsubclass/VehicleCollectionDto.java | 17 +++ .../itest/sealedsubclass/VehicleDto.java | 27 +++++ .../sealedsubclass/SealedSubclassTest.java | 59 ++++++++++ .../ap/internal/model/BeanMappingMethod.java | 35 +++++- .../ap/internal/model/common/Type.java | 38 +++++++ 20 files changed, 537 insertions(+), 2 deletions(-) create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/pom.xml create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Bike.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/BikeDto.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Car.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/CarDto.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Davidson.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/DavidsonDto.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Harley.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/HarleyDto.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Motor.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/MotorDto.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/SealedSubclassMapper.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Vehicle.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollection.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollectionDto.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleDto.java create mode 100644 integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 75513dd6c6..7e9175dd92 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -112,6 +112,11 @@ void namingStrategyTest() { void protobufBuilderTest() { } + @ProcessorTest(baseDir = "sealedSubclassTest") + @EnabledForJreRange(min = JRE.JAVA_17) + void sealedSubclassTest() { + } + @ProcessorTest(baseDir = "recordsTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }) diff --git a/integrationtest/src/test/resources/sealedSubclassTest/pom.xml b/integrationtest/src/test/resources/sealedSubclassTest/pom.xml new file mode 100644 index 0000000000..0706425e01 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/pom.xml @@ -0,0 +1,102 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + sealedSubclassTest + jar + + + + generate-via-compiler-plugin + + false + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + \${compiler-id} + --enable-preview + + + + org.eclipse.tycho + tycho-compiler-jdt + ${org.eclipse.tycho.compiler-jdt.version} + + + + + + + + ${project.groupId} + mapstruct-processor + ${mapstruct.version} + provided + + + + + debug-forked-javac + + false + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + + true + + --enable-preview + -J-Xdebug + -J-Xnoagent + -J-Djava.compiler=NONE + -J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 + + + + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + --enable-preview + + + + + + diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Bike.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Bike.java new file mode 100644 index 0000000000..5b68f52e64 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Bike.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public final class Bike extends Vehicle { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/BikeDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/BikeDto.java new file mode 100644 index 0000000000..d51e95633b --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/BikeDto.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public final class BikeDto extends VehicleDto { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Car.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Car.java new file mode 100644 index 0000000000..0ed238e2a5 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Car.java @@ -0,0 +1,19 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public final class Car extends Vehicle { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } + +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/CarDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/CarDto.java new file mode 100644 index 0000000000..800bd23d39 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/CarDto.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public final class CarDto extends VehicleDto { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Davidson.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Davidson.java new file mode 100644 index 0000000000..e883c14be3 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Davidson.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public final class Davidson extends Motor { + private int numberOfExhausts; + + public int getNumberOfExhausts() { + return numberOfExhausts; + } + + public void setNumberOfExhausts(int numberOfExhausts) { + this.numberOfExhausts = numberOfExhausts; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/DavidsonDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/DavidsonDto.java new file mode 100644 index 0000000000..e975226e3e --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/DavidsonDto.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public final class DavidsonDto extends MotorDto { + private int numberOfExhausts; + + public int getNumberOfExhausts() { + return numberOfExhausts; + } + + public void setNumberOfExhausts(int numberOfExhausts) { + this.numberOfExhausts = numberOfExhausts; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Harley.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Harley.java new file mode 100644 index 0000000000..87a48034c6 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Harley.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public final class Harley extends Motor { + private int engineDb; + + public int getEngineDb() { + return engineDb; + } + + public void setEngineDb(int engineDb) { + this.engineDb = engineDb; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/HarleyDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/HarleyDto.java new file mode 100644 index 0000000000..2090ee7450 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/HarleyDto.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public final class HarleyDto extends MotorDto { + private int engineDb; + + public int getEngineDb() { + return engineDb; + } + + public void setEngineDb(int engineDb) { + this.engineDb = engineDb; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Motor.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Motor.java new file mode 100644 index 0000000000..fcd5f4e4dc --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Motor.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public sealed abstract class Motor extends Vehicle permits Harley, Davidson { + private int cc; + + public int getCc() { + return cc; + } + + public void setCc(int cc) { + this.cc = cc; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/MotorDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/MotorDto.java new file mode 100644 index 0000000000..bd74eb9296 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/MotorDto.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public sealed abstract class MotorDto extends VehicleDto permits HarleyDto, DavidsonDto { + private int cc; + + public int getCc() { + return cc; + } + + public void setCc(int cc) { + this.cc = cc; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/SealedSubclassMapper.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/SealedSubclassMapper.java new file mode 100644 index 0000000000..b37f623686 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/SealedSubclassMapper.java @@ -0,0 +1,31 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SealedSubclassMapper { + SealedSubclassMapper INSTANCE = Mappers.getMapper( SealedSubclassMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + @SubclassMapping( source = Car.class, target = CarDto.class ) + @SubclassMapping( source = Bike.class, target = BikeDto.class ) + @SubclassMapping( source = Harley.class, target = HarleyDto.class ) + @SubclassMapping( source = Davidson.class, target = DavidsonDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker") + VehicleDto map(Vehicle vehicle); + + VehicleCollection mapInverse(VehicleCollectionDto vehicles); + + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Vehicle.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Vehicle.java new file mode 100644 index 0000000000..2a4e7560f6 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/Vehicle.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public abstract sealed class Vehicle permits Bike, Car, Motor { + private String name; + private String vehicleManufacturingCompany; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVehicleManufacturingCompany() { + return vehicleManufacturingCompany; + } + + public void setVehicleManufacturingCompany(String vehicleManufacturingCompany) { + this.vehicleManufacturingCompany = vehicleManufacturingCompany; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollection.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollection.java new file mode 100644 index 0000000000..1ada92a298 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollection.java @@ -0,0 +1,17 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollection { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollectionDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollectionDto.java new file mode 100644 index 0000000000..0cae412177 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleCollectionDto.java @@ -0,0 +1,17 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollectionDto { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleDto.java b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleDto.java new file mode 100644 index 0000000000..8c50bdcad9 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/main/java/org/mapstruct/itest/sealedsubclass/VehicleDto.java @@ -0,0 +1,27 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +public abstract sealed class VehicleDto permits CarDto, BikeDto, MotorDto { + private String name; + private String maker; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMaker() { + return maker; + } + + public void setMaker(String maker) { + this.maker = maker; + } +} diff --git a/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java b/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java new file mode 100644 index 0000000000..379341ff66 --- /dev/null +++ b/integrationtest/src/test/resources/sealedSubclassTest/src/test/java/org/mapstruct/itest/sealedsubclass/SealedSubclassTest.java @@ -0,0 +1,59 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.sealedsubclass; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public class SealedSubclassTest { + + @Test + public void mappingIsDoneUsingSubclassMapping() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Bike() ); + vehicles.getVehicles().add( new Harley() ); + vehicles.getVehicles().add( new Davidson() ); + + VehicleCollectionDto result = SealedSubclassMapper.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( CarDto.class, BikeDto.class, HarleyDto.class, DavidsonDto.class ); + } + + @Test + public void inverseMappingIsDoneUsingSubclassMapping() { + VehicleCollectionDto vehicles = new VehicleCollectionDto(); + vehicles.getVehicles().add( new CarDto() ); + vehicles.getVehicles().add( new BikeDto() ); + vehicles.getVehicles().add( new HarleyDto() ); + vehicles.getVehicles().add( new DavidsonDto() ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( Car.class, Bike.class, Harley.class, Davidson.class ); + } + + @Test + public void subclassMappingInheritsInverseMapping() { + VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); + CarDto carDto = new CarDto(); + carDto.setMaker( "BenZ" ); + vehiclesDto.getVehicles().add( carDto ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehiclesDto ); + + assertThat( result.getVehicles() ) + .extracting( Vehicle::getVehicleManufacturingCompany ) + .containsExactly( "BenZ" ); + } +} 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 84cfd18285..cf3e23db9e 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 @@ -446,8 +446,39 @@ private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMap } private boolean isAbstractReturnTypeAllowed() { - return method.getOptions().getBeanMapping().getSubclassExhaustiveStrategy().isAbstractReturnTypeAllowed() - && !method.getOptions().getSubclassMappings().isEmpty(); + return !method.getOptions().getSubclassMappings().isEmpty() + && ( method.getOptions().getBeanMapping().getSubclassExhaustiveStrategy().isAbstractReturnTypeAllowed() + || isCorrectlySealed() ); + } + + private boolean isCorrectlySealed() { + Type mappingSourceType = method.getMappingSourceType(); + return isCorrectlySealed( mappingSourceType ); + } + + private boolean isCorrectlySealed(Type mappingSourceType) { + if ( mappingSourceType.isSealed() ) { + List unusedPermittedSubclasses = + new ArrayList<>( mappingSourceType.getPermittedSubclasses() ); + method.getOptions().getSubclassMappings().forEach( subClassOption -> { + for (Iterator iterator = unusedPermittedSubclasses.iterator(); + iterator.hasNext(); ) { + if ( ctx.getTypeUtils().isSameType( iterator.next(), subClassOption.getSource() ) ) { + iterator.remove(); + } + } + } ); + for ( Iterator iterator = unusedPermittedSubclasses.iterator(); + iterator.hasNext(); ) { + TypeMirror typeMirror = iterator.next(); + Type type = ctx.getTypeFactory().getType( typeMirror ); + if ( type.isAbstract() && isCorrectlySealed( type ) ) { + iterator.remove(); + } + } + return unusedPermittedSubclasses.isEmpty(); + } + return false; } private void initializeMappingReferencesIfNeeded(Type resultTypeToMap) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 6894906266..4254913d83 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.internal.model.common; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; @@ -53,6 +55,7 @@ import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; +import static java.util.Collections.emptyList; import static org.mapstruct.ap.internal.util.Collections.first; /** @@ -67,6 +70,18 @@ * @author Filip Hrisafov */ public class Type extends ModelElement implements Comparable { + private static final Method SEALED_PERMITTED_SUBCLASSES_METHOD; + + static { + Method permittedSubclassesMethod; + try { + permittedSubclassesMethod = TypeElement.class.getMethod( "getPermittedSubclasses" ); + } + catch ( NoSuchMethodException e ) { + permittedSubclassesMethod = null; + } + SEALED_PERMITTED_SUBCLASSES_METHOD = permittedSubclassesMethod; + } private final TypeUtils typeUtils; private final ElementUtils elementUtils; @@ -1661,4 +1676,27 @@ public boolean isEnumSet() { return "java.util.EnumSet".equals( getFullyQualifiedName() ); } + /** + * return true if this type is a java 17+ sealed class + */ + public boolean isSealed() { + return typeElement.getModifiers().stream().map( Modifier::name ).anyMatch( "SEALED"::equals ); + } + + /** + * return the list of permitted TypeMirrors for the java 17+ sealed class + */ + @SuppressWarnings( "unchecked" ) + public List getPermittedSubclasses() { + if (SEALED_PERMITTED_SUBCLASSES_METHOD == null) { + return emptyList(); + } + try { + return (List) SEALED_PERMITTED_SUBCLASSES_METHOD.invoke( typeElement ); + } + catch ( IllegalAccessException | IllegalArgumentException | InvocationTargetException e ) { + return emptyList(); + } + } + } From d0e4c48228dc03722d34deb5506ec159ac38514d Mon Sep 17 00:00:00 2001 From: Jason Bodnar Date: Mon, 8 May 2023 15:23:03 -0500 Subject: [PATCH 167/363] #3172 Add mapping between Locale and String --- .../chapter-5-data-type-conversions.asciidoc | 3 ++ .../ap/internal/conversion/Conversions.java | 2 + .../conversion/LocaleToStringConversion.java | 37 +++++++++++++++ .../locale/LocaleConversionTest.java | 46 +++++++++++++++++++ .../test/conversion/locale/LocaleMapper.java | 19 ++++++++ .../test/conversion/locale/LocaleSource.java | 23 ++++++++++ .../test/conversion/locale/LocaleTarget.java | 21 +++++++++ 7 files changed, 151 insertions(+) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/LocaleToStringConversion.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleTarget.java diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index 258c7085f2..30430fe97e 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -130,6 +130,9 @@ public interface CarMapper { * Between `java.net.URL` and `String`. ** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/URL[URL] otherwise a `MalformedURLException` is thrown. +* Between `java.util.Locale` and `String`. +** When converting from a `Locale`, the resulting `String` will be a well-formed IETF BCP 47 language tag representing the locale. When converting from a `String`, the locale that best represents the language tag will be returned. See https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#forLanguageTag-java.lang.String-[Locale.forLanguageTag()] and https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toLanguageTag--[Locale.toLanguageTag()] for more information. + [[mapping-object-references]] === Mapping object references diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 9a4085df83..6acb69492a 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -21,6 +21,7 @@ import java.util.Currency; import java.util.Date; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -198,6 +199,7 @@ public Conversions(TypeFactory typeFactory) { register( Currency.class, String.class, new CurrencyToStringConversion() ); register( UUID.class, String.class, new UUIDToStringConversion() ); + register( Locale.class, String.class, new LocaleToStringConversion() ); registerURLConversion(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/LocaleToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/LocaleToStringConversion.java new file mode 100644 index 0000000000..05a6da0b19 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/LocaleToStringConversion.java @@ -0,0 +1,37 @@ +/* + * 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.internal.conversion; + +import java.util.Locale; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Collections; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; + +/** + * Conversion between {@link java.util.Locale} and {@link String}. + * + * @author Jason Bodnar + */ +public class LocaleToStringConversion extends SimpleConversion { + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toLanguageTag()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return locale( conversionContext ) + ".forLanguageTag( )"; + } + + @Override + protected Set getFromConversionImportTypes(final ConversionContext conversionContext) { + return Collections.asSet( conversionContext.getTypeFactory().getType( Locale.class ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleConversionTest.java new file mode 100644 index 0000000000..cba7785512 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleConversionTest.java @@ -0,0 +1,46 @@ +/* + * 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.conversion.locale; + +import java.util.Locale; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests conversions between {@link Locale} and String. + * + * @author Jason Bodnar + */ +@IssueKey("3172") +@WithClasses({ LocaleSource.class, LocaleTarget.class, LocaleMapper.class }) +public class LocaleConversionTest { + + @ProcessorTest + public void shouldApplyLocaleConversion() { + LocaleSource source = new LocaleSource(); + source.setLocaleA( Locale.getDefault() ); + + LocaleTarget target = LocaleMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getLocaleA() ).isEqualTo( source.getLocaleA().toLanguageTag() ); + } + + @ProcessorTest + public void shouldApplyReverseLocaleConversion() { + LocaleTarget target = new LocaleTarget(); + target.setLocaleA( Locale.getDefault().toLanguageTag() ); + + LocaleSource source = LocaleMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getLocaleA() ).isEqualTo( Locale.forLanguageTag( target.getLocaleA() ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleMapper.java new file mode 100644 index 0000000000..3da3fc4ae4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleMapper.java @@ -0,0 +1,19 @@ +/* + * 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.conversion.locale; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface LocaleMapper { + + LocaleMapper INSTANCE = Mappers.getMapper( LocaleMapper.class ); + + LocaleTarget sourceToTarget(LocaleSource source); + + LocaleSource targetToSource(LocaleTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleSource.java new file mode 100644 index 0000000000..69ea5bda2d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleSource.java @@ -0,0 +1,23 @@ +/* + * 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.conversion.locale; + +import java.util.Locale; + +/** + * @author Jason Bodnar + */ +public class LocaleSource { + private Locale localeA; + + public Locale getLocaleA() { + return localeA; + } + + public void setLocaleA(Locale localeA) { + this.localeA = localeA; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleTarget.java new file mode 100644 index 0000000000..000ff86b05 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleTarget.java @@ -0,0 +1,21 @@ +/* + * 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.conversion.locale; + +/** + * @author Jason Bodnar + */ +public class LocaleTarget { + private String localeA; + + public String getLocaleA() { + return localeA; + } + + public void setLocaleA(String localeA) { + this.localeA = localeA; + } +} From a89c34f00c58e4b74bb81a102f7e615405c38692 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 1 May 2023 09:44:05 +0200 Subject: [PATCH 168/363] #3238 Compile error instead of null pointer exception for invalid ignore with target this --- .../internal/model/source/MappingOptions.java | 3 ++ .../mapstruct/ap/internal/util/Message.java | 1 + .../bugs/_3238/ErroneousIssue3238Mapper.java | 42 +++++++++++++++++++ .../ap/test/bugs/_3238/Issue3238Test.java | 37 ++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/ErroneousIssue3238Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/Issue3238Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index 5046ce8b29..2af1c95f71 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -254,6 +254,9 @@ else if ( gem.nullValuePropertyMappingStrategy().hasValue() && gem.ignore().hasValue() && gem.ignore().getValue() ) { message = Message.PROPERTYMAPPING_IGNORE_AND_NVPMS; } + else if ( ".".equals( gem.target().get() ) && gem.ignore().hasValue() && gem.ignore().getValue() ) { + message = Message.PROPERTYMAPPING_TARGET_THIS_AND_IGNORE; + } if ( message == null ) { return true; 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 6f72f720f1..249825ec50 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 @@ -68,6 +68,7 @@ public enum Message { PROPERTYMAPPING_CONSTANT_VALUE_AND_NVPMS( "Constant and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a constant or an nullValuePropertyMappingStrategy." ), PROPERTYMAPPING_DEFAULT_EXPERSSION_AND_NVPMS( "DefaultExpression and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a defaultExpression or an nullValuePropertyMappingStrategy." ), PROPERTYMAPPING_IGNORE_AND_NVPMS( "Ignore and nullValuePropertyMappingStrategy are both defined in @Mapping, either define ignore or an nullValuePropertyMappingStrategy." ), + PROPERTYMAPPING_TARGET_THIS_AND_IGNORE( "Using @Mapping( target = \".\", ignore = true ) is not allowed. You need to use @BeanMapping( ignoreByDefault = true ) if you would like to ignore all non explicitly mapped target properties." ), PROPERTYMAPPING_EXPRESSION_AND_QUALIFIER_BOTH_DEFINED("Expression and a qualifier both defined in @Mapping, either define an expression or a qualifier."), PROPERTYMAPPING_INVALID_EXPRESSION( "Value for expression must be given in the form \"java()\"." ), PROPERTYMAPPING_INVALID_DEFAULT_EXPRESSION( "Value for default expression must be given in the form \"java()\"." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/ErroneousIssue3238Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/ErroneousIssue3238Mapper.java new file mode 100644 index 0000000000..fd0d4672a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/ErroneousIssue3238Mapper.java @@ -0,0 +1,42 @@ +/* + * 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._3238; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface ErroneousIssue3238Mapper { + + @Mapping(target = ".", ignore = true) + Target map(Source source); + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/Issue3238Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/Issue3238Test.java new file mode 100644 index 0000000000..6648495a70 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/Issue3238Test.java @@ -0,0 +1,37 @@ +/* + * 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._3238; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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; + +/** + * @author Filip Hrisafov + */ +@WithClasses(ErroneousIssue3238Mapper.class) +@IssueKey("3238") +class Issue3238Test { + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = @Diagnostic( type = ErroneousIssue3238Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 14, + message = "Using @Mapping( target = \".\", ignore = true ) is not allowed." + + " You need to use @BeanMapping( ignoreByDefault = true ) if you would like to ignore" + + " all non explicitly mapped target properties." + ) + ) + void shouldGenerateValidCompileError() { + + } + +} From efaa67aadf6f24c8f12804a1f18c1218d929d56f Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 1 May 2023 10:18:23 +0200 Subject: [PATCH 169/363] #3104 Update methods with NullValuePropertyMappingStrategy.IGNORE should use SetterWrapperForCollectionsAndMapsWithNullCheck --- .../model/CollectionAssignmentBuilder.java | 11 +++ .../ap/test/bugs/_3104/Issue3104Mapper.java | 77 +++++++++++++++++++ .../ap/test/bugs/_3104/Issue3104Test.java | 38 +++++++++ 3 files changed, 126 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Test.java 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 e3bc85342a..9a0a025845 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 @@ -31,6 +31,7 @@ import org.mapstruct.ap.internal.util.accessor.AccessorType; import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT; import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_NULL; @@ -177,6 +178,16 @@ else if ( method.isUpdateMethod() && !targetImmutable ) { targetAccessorType.isFieldAssignment() ); } + else if ( method.isUpdateMethod() && nvpms == IGNORE ) { + + result = new SetterWrapperForCollectionsAndMapsWithNullCheck( + result, + method.getThrownTypes(), + targetType, + ctx.getTypeFactory(), + targetAccessorType.isFieldAssignment() + ); + } else if ( setterWrapperNeedsSourceNullCheck( result ) && canBeMappedOrDirectlyAssigned( result ) ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Mapper.java new file mode 100644 index 0000000000..e561f40bab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Mapper.java @@ -0,0 +1,77 @@ +/* + * 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._3104; + +import java.util.Collections; +import java.util.List; + +import org.mapstruct.BeanMapping; +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE) +public interface Issue3104Mapper { + + Issue3104Mapper INSTANCE = Mappers.getMapper( Issue3104Mapper.class ); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void update(@MappingTarget Target target, Source source); + + class Target { + private List children = Collections.emptyList(); + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + if ( children == null ) { + throw new IllegalArgumentException( "children is null" ); + } + this.children = Collections.unmodifiableList( children ); + } + } + + class Child { + private String myField; + + public String getMyField() { + return myField; + } + + public void setMyField(String myField) { + this.myField = myField; + } + } + + class Source { + private final List children; + + public Source(List children) { + this.children = children; + } + + public List getChildren() { + return children; + } + + } + + class ChildSource { + private final String myField; + + public ChildSource(String myField) { + this.myField = myField; + } + + public String getMyField() { + return myField; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Test.java new file mode 100644 index 0000000000..5f3a9e160b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Test.java @@ -0,0 +1,38 @@ +/* + * 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._3104; + +import java.util.Collections; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3104") +@WithClasses(Issue3104Mapper.class) +class Issue3104Test { + + @ProcessorTest + void shouldCorrectlyMapUpdateMappingWithTargetImmutableCollectionStrategy() { + Issue3104Mapper.Target target = new Issue3104Mapper.Target(); + Issue3104Mapper.INSTANCE.update( target, new Issue3104Mapper.Source( null ) ); + + assertThat( target.getChildren() ).isEmpty(); + + Issue3104Mapper.INSTANCE.update( + target, + new Issue3104Mapper.Source( Collections.singletonList( new Issue3104Mapper.ChildSource( "tester" ) ) ) + ); + assertThat( target.getChildren() ) + .extracting( Issue3104Mapper.Child::getMyField ) + .containsExactly( "tester" ); + } +} From 7c90592d051c828cc20af0818f899c6fc5adc0e4 Mon Sep 17 00:00:00 2001 From: paparadva Date: Sun, 30 Oct 2022 13:11:26 +0300 Subject: [PATCH 170/363] #2863 Add validation of String type to @TargetPropertyName --- .../processor/MethodRetrievalProcessor.java | 15 ++++++++++-- .../mapstruct/ap/internal/util/Message.java | 1 + ...sNonStringTargetPropertyNameParameter.java | 21 +++++++++++++++++ .../TargetPropertyNameTest.java | 23 +++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java 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 4ad531d334..6f1febae9d 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 @@ -28,6 +28,7 @@ import org.mapstruct.ap.internal.gem.ObjectFactoryGem; import org.mapstruct.ap.internal.gem.SubclassMappingGem; import org.mapstruct.ap.internal.gem.SubclassMappingsGem; +import org.mapstruct.ap.internal.gem.TargetPropertyNameGem; import org.mapstruct.ap.internal.gem.ValueMappingGem; import org.mapstruct.ap.internal.gem.ValueMappingsGem; import org.mapstruct.ap.internal.model.common.Parameter; @@ -230,7 +231,7 @@ private SourceMethod getMethod(TypeElement usedMapper, // otherwise add reference to existing mapper method else if ( isValidReferencedMethod( parameters ) || isValidFactoryMethod( method, parameters, returnType ) || isValidLifecycleCallbackMethod( method ) - || isValidPresenceCheckMethod( method, returnType ) ) { + || isValidPresenceCheckMethod( method, parameters, returnType ) ) { return getReferencedMethod( usedMapper, methodType, method, mapperToImplement, parameters ); } else { @@ -407,7 +408,17 @@ private boolean hasFactoryAnnotation(ExecutableElement method) { return ObjectFactoryGem.instanceOn( method ) != null; } - private boolean isValidPresenceCheckMethod(ExecutableElement method, Type returnType) { + private boolean isValidPresenceCheckMethod(ExecutableElement method, List parameters, Type returnType) { + for ( Parameter param : parameters ) { + if ( param.isTargetPropertyName() && !param.getType().isString() ) { + messager.printMessage( + param.getElement(), + TargetPropertyNameGem.instanceOn( param.getElement() ).mirror(), + Message.RETRIEVAL_TARGET_PROPERTY_NAME_WRONG_TYPE + ); + return false; + } + } return isBoolean( returnType ) && hasConditionAnnotation( method ); } 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 249825ec50..a24a43036c 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 @@ -176,6 +176,7 @@ public enum Message { RETRIEVAL_MAPPER_USES_CYCLE( "The mapper %s is referenced itself in Mapper#uses.", Diagnostic.Kind.WARNING ), RETRIEVAL_AFTER_METHOD_NOT_IMPLEMENTED( "@AfterMapping can only be applied to an implemented method." ), RETRIEVAL_BEFORE_METHOD_NOT_IMPLEMENTED( "@BeforeMapping can only be applied to an implemented method." ), + RETRIEVAL_TARGET_PROPERTY_NAME_WRONG_TYPE( "@TargetPropertyName can only by applied to a String parameter." ), INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ), INHERITINVERSECONFIGURATION_INVALID_NAME( "None of the candidates %s() matches given name: \"%s\"." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java new file mode 100644 index 0000000000..ec545a058b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java @@ -0,0 +1,21 @@ +/* + * 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.conditional.targetpropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.TargetPropertyName; + +@Mapper +public interface ErroneousNonStringTargetPropertyNameParameter { + + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @TargetPropertyName int propName) { + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java index 3d96e66664..91e4b77de2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java @@ -9,6 +9,9 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; 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.GeneratedSource; import java.util.Collections; @@ -278,4 +281,24 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { "addresses.street" ); } + + @IssueKey("2863") + @ProcessorTest + @WithClasses({ + ErroneousNonStringTargetPropertyNameParameter.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousNonStringTargetPropertyNameParameter.class, + line = 18, + message = "@TargetPropertyName can only by applied to a String parameter." + ) + } + ) + public void nonStringTargetPropertyNameParameter() { + + } } From 6d205e5bc46365444774609e860f60819818ef4b Mon Sep 17 00:00:00 2001 From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com> Date: Sun, 21 May 2023 22:49:41 +0200 Subject: [PATCH 171/363] #1454 Support for lifecycle methods on type being built with builders Add missing support for lifecycle methods with builders: * `@BeforeMapping` with `@TargetType` the type being build * `@AftereMapping` with `@TargetType` the type being build * `@AfterMapping` with `@MappingTarget` the type being build --- .../resources/build-config/checkstyle.xml | 4 +- .../chapter-12-customizing-mapping.asciidoc | 14 ++-- .../chapter-13-using-mapstruct-spi.asciidoc | 2 +- .../ap/internal/model/BeanMappingMethod.java | 72 ++++++++++++++++++- .../ap/internal/model/MappingMethod.java | 4 +- .../ap/internal/model/BeanMappingMethod.ftl | 21 +++++- .../BuilderLifecycleCallbacksTest.java | 6 +- .../builder/lifecycle/MappingContext.java | 10 ++- 8 files changed, 119 insertions(+), 14 deletions(-) diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml index a4591c39de..a1ff4af23a 100644 --- a/build-config/src/main/resources/build-config/checkstyle.xml +++ b/build-config/src/main/resources/build-config/checkstyle.xml @@ -29,7 +29,9 @@ - + + + diff --git a/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc b/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc index 0c873eac09..dc07b30e62 100644 --- a/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc +++ b/documentation/src/main/asciidoc/chapter-12-customizing-mapping.asciidoc @@ -248,9 +248,8 @@ All before/after-mapping methods that *can* be applied to a mapping method *will The order of the method invocation is determined primarily by their variant: -1. `@BeforeMapping` methods without an `@MappingTarget` parameter are called before any null-checks on source -parameters and constructing a new target bean. -2. `@BeforeMapping` methods with an `@MappingTarget` parameter are called after constructing a new target bean. +1. `@BeforeMapping` methods without parameters, a `@MappingTarget` parameter or a `@TargetType` parameter are called before any null-checks on source parameters and constructing a new target bean. +2. `@BeforeMapping` methods with a `@MappingTarget` parameter are called after constructing a new target bean. 3. `@AfterMapping` methods are called at the end of the mapping method before the last `return` statement. Within those groups, the method invocations are ordered by their location of definition: @@ -262,4 +261,11 @@ Within those groups, the method invocations are ordered by their location of def *Important:* the order of methods declared within one type can not be guaranteed, as it depends on the compiler and the processing environment implementation. -*Important:* when using a builder, the `@AfterMapping` annotated method must have the builder as `@MappingTarget` annotated parameter so that the method is able to modify the object going to be build. The `build` method is called when the `@AfterMapping` annotated method scope finishes. MapStruct will not call the `@AfterMapping` annotated method if the real target is used as `@MappingTarget` annotated parameter. \ No newline at end of file +[NOTE] +==== +Before/After-mapping methods can also be used with builders: + +* `@BeforeMapping` methods with a `@MappingTarget` parameter of the real target will not be invoked because it is only available after the mapping was already performed. +* To be able to modify the object that is going to be built, the `@AfterMapping` annotated method must have the builder as `@MappingTarget` annotated parameter. The `build` method is called when the `@AfterMapping` annotated method scope finishes. +* The `@AfterMapping` annotated method can also have the real target as `@TargetType` or `@MappingTarget`. It will be invoked after the real target was built (first the methods annotated with `@TargetType`, then the methods annotated with `@MappingTarget`) +==== \ No newline at end of file diff --git a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc index ce9b288428..51b2bbff3c 100644 --- a/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc +++ b/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc @@ -71,7 +71,7 @@ public class GolfPlayerDto { public GolfPlayerDto withName(String name) { this.name = name; - return this + return this; } } ---- 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 cf3e23db9e..b6afce74d2 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 @@ -94,6 +94,9 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { private final Type returnTypeToConstruct; private final BuilderType returnTypeBuilder; private final MethodReference finalizerMethod; + private final String finalizedResultName; + private final List beforeMappingReferencesWithFinalizedReturnType; + private final List afterMappingReferencesWithFinalizedReturnType; private final MappingReferences mappingReferences; @@ -368,8 +371,35 @@ else if ( !method.isUpdateMethod() ) { MethodReference finalizeMethod = null; + List beforeMappingReferencesWithFinalizedReturnType = new ArrayList<>(); + List afterMappingReferencesWithFinalizedReturnType = new ArrayList<>(); if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) { finalizeMethod = getFinalizerMethod(); + + Type actualReturnType = method.getReturnType(); + + beforeMappingReferencesWithFinalizedReturnType.addAll( filterMappingTarget( + LifecycleMethodResolver.beforeMappingMethods( + method, + actualReturnType, + selectionParameters, + ctx, + existingVariableNames + ), + false + ) ); + + afterMappingReferencesWithFinalizedReturnType.addAll( LifecycleMethodResolver.afterMappingMethods( + method, + actualReturnType, + selectionParameters, + ctx, + existingVariableNames + ) ); + + // remove methods without parameters as they are already being invoked + removeMappingReferencesWithoutSourceParameters( beforeMappingReferencesWithFinalizedReturnType ); + removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType ); } return new BeanMappingMethod( @@ -383,12 +413,18 @@ else if ( !method.isUpdateMethod() ) { returnTypeBuilder, beforeMappingMethods, afterMappingMethods, + beforeMappingReferencesWithFinalizedReturnType, + afterMappingReferencesWithFinalizedReturnType, finalizeMethod, mappingReferences, subclasses ); } + private void removeMappingReferencesWithoutSourceParameters(List references) { + references.removeIf( r -> r.getSourceParameters().isEmpty() && r.getReturnType().isVoid() ); + } + private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) { return !isAbstractReturnTypeAllowed() && canReturnTypeBeConstructed( returnTypeImpl ); @@ -706,7 +742,6 @@ private boolean isReturnTypeAbstractOrCanBeConstructed(Type returnType) { * Find a factory method for a return type or for a builder. * @param returnTypeImpl the return type implementation to construct * @param @selectionParameters - * @return */ private void initializeFactoryMethod(Type returnTypeImpl, SelectionParameters selectionParameters) { List> matchingFactoryMethods = @@ -1380,7 +1415,7 @@ else if ( mapping.getJavaExpression() != null ) { *

      * When a target property matches its name with the (nested) source property, it is added to the list if and * only if it is an unprocessed target property. - * + *

      * duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)} */ private void applyTargetThisMapping() { @@ -1766,6 +1801,8 @@ private BeanMappingMethod(Method method, BuilderType returnTypeBuilder, List beforeMappingReferences, List afterMappingReferences, + List beforeMappingReferencesWithFinalizedReturnType, + List afterMappingReferencesWithFinalizedReturnType, MethodReference finalizerMethod, MappingReferences mappingReferences, List subclassMappings) { @@ -1783,9 +1820,20 @@ private BeanMappingMethod(Method method, this.propertyMappings = propertyMappings; this.returnTypeBuilder = returnTypeBuilder; this.finalizerMethod = finalizerMethod; + if ( this.finalizerMethod != null ) { + this.finalizedResultName = + Strings.getSafeVariableName( getResultName() + "Result", existingVariableNames ); + existingVariableNames.add( this.finalizedResultName ); + } + else { + this.finalizedResultName = null; + } this.mappingReferences = mappingReferences; - // intialize constant mappings as all mappings, but take out the ones that can be contributed to a + this.beforeMappingReferencesWithFinalizedReturnType = beforeMappingReferencesWithFinalizedReturnType; + this.afterMappingReferencesWithFinalizedReturnType = afterMappingReferencesWithFinalizedReturnType; + + // initialize constant mappings as all mappings, but take out the ones that can be contributed to a // parameter mapping. this.mappingsByParameter = new HashMap<>(); this.constantMappings = new ArrayList<>( propertyMappings.size() ); @@ -1830,6 +1878,18 @@ public List getSubclassMappings() { return subclassMappings; } + public String getFinalizedResultName() { + return finalizedResultName; + } + + public List getBeforeMappingReferencesWithFinalizedReturnType() { + return beforeMappingReferencesWithFinalizedReturnType; + } + + public List getAfterMappingReferencesWithFinalizedReturnType() { + return afterMappingReferencesWithFinalizedReturnType; + } + public List propertyMappingsByParameter(Parameter parameter) { // issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value return mappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() ); @@ -1882,6 +1942,12 @@ public Set getImportTypes() { if ( returnTypeBuilder != null ) { types.add( returnTypeBuilder.getOwningType() ); } + for ( LifecycleCallbackMethodReference reference : beforeMappingReferencesWithFinalizedReturnType ) { + types.addAll( reference.getImportTypes() ); + } + for ( LifecycleCallbackMethodReference reference : afterMappingReferencesWithFinalizedReturnType ) { + types.addAll( reference.getImportTypes() ); + } return types; } 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 5b9eda644b..4c59216e09 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 @@ -186,8 +186,8 @@ public String toString() { return returnType + " " + getName() + "(" + join( parameters, ", " ) + ")"; } - private List filterMappingTarget(List methods, - boolean mustHaveMappingTargetParameter) { + protected static List filterMappingTarget( + List methods, boolean mustHaveMappingTargetParameter) { if ( methods == null ) { return Collections.emptyList(); } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 1b402a57f0..a0fbe24b3e 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -21,6 +21,12 @@ + <#list beforeMappingReferencesWithFinalizedReturnType as callback> + <@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/> + <#if !callback_has_next> + + + <#if !mapNullToDefault> if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && ) { return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /><#else>null; @@ -129,7 +135,20 @@ <#if returnType.name != "void"> <#if finalizerMethod??> - return ${resultName}.<@includeModel object=finalizerMethod />; + <#if (afterMappingReferencesWithFinalizedReturnType?size > 0)> + ${returnType.name} ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; + + <#list afterMappingReferencesWithFinalizedReturnType as callback> + <#if callback_index = 0> + + + <@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/> + + + return ${finalizedResultName}; + <#else> + return ${resultName}.<@includeModel object=finalizerMethod />; + <#else> return ${resultName}; diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/BuilderLifecycleCallbacksTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/BuilderLifecycleCallbacksTest.java index e3892f2154..3d5fb7e533 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/BuilderLifecycleCallbacksTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/BuilderLifecycleCallbacksTest.java @@ -43,12 +43,16 @@ public void lifecycleMethodsShouldBeInvoked() { assertThat( context.getInvokedMethods() ) .contains( "beforeWithoutParameters", + "beforeWithTargetType", "beforeWithBuilderTargetType", "beforeWithBuilderTarget", "afterWithoutParameters", "afterWithBuilderTargetType", "afterWithBuilderTarget", - "afterWithBuilderTargetReturningTarget" + "afterWithBuilderTargetReturningTarget", + "afterWithTargetType", + "afterWithTarget", + "afterWithTargetReturningTarget" ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java index 96b9b30db6..079be90a81 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java @@ -74,7 +74,15 @@ public void afterWithBuilderTarget(OrderDto source, @MappingTarget Order.Builder public Order afterWithBuilderTargetReturningTarget(@MappingTarget Order.Builder orderBuilder) { invokedMethods.add( "afterWithBuilderTargetReturningTarget" ); - return orderBuilder.create(); + // return null, so that @AfterMapping methods on the finalized object will be called in the tests + return null; + } + + @AfterMapping + public Order afterWithTargetReturningTarget(@MappingTarget Order order) { + invokedMethods.add( "afterWithTargetReturningTarget" ); + + return order; } public List getInvokedMethods() { From 84c443df9c141d9947253a19facbb952cbda816e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Campanero=20Ortiz?= Date: Wed, 24 May 2023 05:44:36 +0200 Subject: [PATCH 172/363] #3245 Remove redundant null checks in nested properties --- .../model/NestedPropertyMappingMethod.ftl | 9 ++-- .../test/bugs/_1561/Issue1561MapperImpl.java | 9 +--- .../ap/test/bugs/_1685/UserMapperImpl.java | 45 +++---------------- .../ap/test/bugs/_2245/TestMapperImpl.java | 9 +--- .../nestedsource/ArtistToChartEntryImpl.java | 27 ++--------- .../nestedtarget/ChartEntryToArtistImpl.java | 45 +++---------------- .../nestedbeans/mixed/FishTankMapperImpl.java | 27 ++--------- .../ArtistToChartEntryImpl.java | 27 ++--------- .../ChartEntryToArtistImpl.java | 45 +++---------------- 9 files changed, 30 insertions(+), 213 deletions(-) diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl index ce6dab0714..8eabe23a16 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl @@ -7,15 +7,16 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.NestedPropertyMappingMethod" --> <#lt>private <@includeModel object=returnType.typeBound/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { - if ( ${sourceParameter.name} == null ) { - return ${returnType.null}; - } <#list propertyEntries as entry> <#if entry.presenceChecker?? > if ( <#if entry_index != 0>${entry.previousPropertyName} == null || !<@includeModel object=entry.presenceChecker /> ) { return ${returnType.null}; } + <#if !entry_has_next> + return ${entry.previousPropertyName}.${entry.accessorName}; + + <#if entry_has_next> <@includeModel object=entry.type.typeBound/> ${entry.name} = ${entry.previousPropertyName}.${entry.accessorName}; <#if !entry.presenceChecker?? > <#if !entry.type.primitive> @@ -24,8 +25,6 @@ } - <#if !entry_has_next> - return ${entry.name}; } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1561/Issue1561MapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1561/Issue1561MapperImpl.java index dde54d65b7..83cebe2bde 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1561/Issue1561MapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1561/Issue1561MapperImpl.java @@ -61,17 +61,10 @@ protected NestedTarget sourceToNestedTarget(Source source) { } private Stream targetNestedTargetProperties(Target target) { - if ( target == null ) { - return null; - } NestedTarget nestedTarget = target.getNestedTarget(); if ( nestedTarget == null ) { return null; } - Stream properties = nestedTarget.getProperties(); - if ( properties == null ) { - return null; - } - return properties; + return nestedTarget.getProperties(); } } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java index b8d3e9db4b..3c019a0dd8 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_1685/UserMapperImpl.java @@ -168,77 +168,42 @@ protected ContactDataDTO userToContactDataDTO(User user) { } private String userDTOContactDataDTOEmail(UserDTO userDTO) { - if ( userDTO == null ) { - return null; - } ContactDataDTO contactDataDTO = userDTO.getContactDataDTO(); if ( contactDataDTO == null ) { return null; } - String email = contactDataDTO.getEmail(); - if ( email == null ) { - return null; - } - return email; + return contactDataDTO.getEmail(); } private String userDTOContactDataDTOPhone(UserDTO userDTO) { - if ( userDTO == null ) { - return null; - } ContactDataDTO contactDataDTO = userDTO.getContactDataDTO(); if ( contactDataDTO == null ) { return null; } - String phone = contactDataDTO.getPhone(); - if ( phone == null ) { - return null; - } - return phone; + return contactDataDTO.getPhone(); } private String userDTOContactDataDTOAddress(UserDTO userDTO) { - if ( userDTO == null ) { - return null; - } ContactDataDTO contactDataDTO = userDTO.getContactDataDTO(); if ( contactDataDTO == null ) { return null; } - String address = contactDataDTO.getAddress(); - if ( address == null ) { - return null; - } - return address; + return contactDataDTO.getAddress(); } private List userDTOContactDataDTOPreferences(UserDTO userDTO) { - if ( userDTO == null ) { - return null; - } ContactDataDTO contactDataDTO = userDTO.getContactDataDTO(); if ( contactDataDTO == null ) { return null; } - List preferences = contactDataDTO.getPreferences(); - if ( preferences == null ) { - return null; - } - return preferences; + return contactDataDTO.getPreferences(); } private String[] userDTOContactDataDTOSettings(UserDTO userDTO) { - if ( userDTO == null ) { - return null; - } ContactDataDTO contactDataDTO = userDTO.getContactDataDTO(); if ( contactDataDTO == null ) { return null; } - String[] settings = contactDataDTO.getSettings(); - if ( settings == null ) { - return null; - } - return settings; + return contactDataDTO.getSettings(); } } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2245/TestMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2245/TestMapperImpl.java index c3ecc02911..b7a18048a0 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2245/TestMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_2245/TestMapperImpl.java @@ -34,17 +34,10 @@ public Tenant map(TenantDTO tenant) { } private String tenantInnerId(TenantDTO tenantDTO) { - if ( tenantDTO == null ) { - return null; - } Inner inner = tenantDTO.getInner(); if ( inner == null ) { return null; } - String id = inner.getId(); - if ( id == null ) { - return null; - } - return id; + return inner.getId(); } } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryImpl.java index 09f96b4b3a..40c16c6086 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryImpl.java @@ -96,24 +96,14 @@ public ChartEntry map(Chart name) { } private String songArtistName(Song song) { - if ( song == null ) { - return null; - } Artist artist = song.getArtist(); if ( artist == null ) { return null; } - String name = artist.getName(); - if ( name == null ) { - return null; - } - return name; + return artist.getName(); } private String songArtistLabelStudioName(Song song) { - if ( song == null ) { - return null; - } Artist artist = song.getArtist(); if ( artist == null ) { return null; @@ -126,17 +116,10 @@ private String songArtistLabelStudioName(Song song) { if ( studio == null ) { return null; } - String name = studio.getName(); - if ( name == null ) { - return null; - } - return name; + return studio.getName(); } private String songArtistLabelStudioCity(Song song) { - if ( song == null ) { - return null; - } Artist artist = song.getArtist(); if ( artist == null ) { return null; @@ -149,10 +132,6 @@ private String songArtistLabelStudioCity(Song song) { if ( studio == null ) { return null; } - String city = studio.getCity(); - if ( city == null ) { - return null; - } - return city; + return studio.getCity(); } } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtistImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtistImpl.java index 0413ef5b79..cbd125366e 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtistImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtistImpl.java @@ -132,24 +132,14 @@ protected Song chartEntryToSong(ChartEntry chartEntry) { } private String chartSongTitle(Chart chart) { - if ( chart == null ) { - return null; - } Song song = chart.getSong(); if ( song == null ) { return null; } - String title = song.getTitle(); - if ( title == null ) { - return null; - } - return title; + return song.getTitle(); } private String chartSongArtistName(Chart chart) { - if ( chart == null ) { - return null; - } Song song = chart.getSong(); if ( song == null ) { return null; @@ -158,17 +148,10 @@ private String chartSongArtistName(Chart chart) { if ( artist == null ) { return null; } - String name = artist.getName(); - if ( name == null ) { - return null; - } - return name; + return artist.getName(); } private String chartSongArtistLabelStudioName(Chart chart) { - if ( chart == null ) { - return null; - } Song song = chart.getSong(); if ( song == null ) { return null; @@ -185,17 +168,10 @@ private String chartSongArtistLabelStudioName(Chart chart) { if ( studio == null ) { return null; } - String name = studio.getName(); - if ( name == null ) { - return null; - } - return name; + return studio.getName(); } private String chartSongArtistLabelStudioCity(Chart chart) { - if ( chart == null ) { - return null; - } Song song = chart.getSong(); if ( song == null ) { return null; @@ -212,25 +188,14 @@ private String chartSongArtistLabelStudioCity(Chart chart) { if ( studio == null ) { return null; } - String city = studio.getCity(); - if ( city == null ) { - return null; - } - return city; + return studio.getCity(); } private List chartSongPositions(Chart chart) { - if ( chart == null ) { - return null; - } Song song = chart.getSong(); if ( song == null ) { return null; } - List positions = song.getPositions(); - if ( positions == null ) { - return null; - } - return positions; + return song.getPositions(); } } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java index 3642d15340..dc70a28ce8 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java @@ -159,18 +159,11 @@ protected WaterQualityDto waterQualityToWaterQualityDto(WaterQuality waterQualit } private Ornament sourceInteriorOrnament(FishTank fishTank) { - if ( fishTank == null ) { - return null; - } Interior interior = fishTank.getInterior(); if ( interior == null ) { return null; } - Ornament ornament = interior.getOrnament(); - if ( ornament == null ) { - return null; - } - return ornament; + return interior.getOrnament(); } protected OrnamentDto ornamentToOrnamentDto(Ornament ornament) { @@ -295,18 +288,11 @@ protected Interior fishTankDtoToInterior(FishTankDto fishTankDto) { } private String waterQualityReportDtoOrganisationName(WaterQualityReportDto waterQualityReportDto) { - if ( waterQualityReportDto == null ) { - return null; - } WaterQualityOrganisationDto organisation = waterQualityReportDto.getOrganisation(); if ( organisation == null ) { return null; } - String name = organisation.getName(); - if ( name == null ) { - return null; - } - return name; + return organisation.getName(); } protected WaterQualityReport waterQualityReportDtoToWaterQualityReport(WaterQualityReportDto waterQualityReportDto) { @@ -335,18 +321,11 @@ protected WaterQuality waterQualityDtoToWaterQuality(WaterQualityDto waterQualit } private MaterialTypeDto sourceMaterialMaterialType(FishTankDto fishTankDto) { - if ( fishTankDto == null ) { - return null; - } MaterialDto material = fishTankDto.getMaterial(); if ( material == null ) { return null; } - MaterialTypeDto materialType = material.getMaterialType(); - if ( materialType == null ) { - return null; - } - return materialType; + return material.getMaterialType(); } protected MaterialType materialTypeDtoToMaterialType(MaterialTypeDto materialTypeDto) { diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryImpl.java index 131566ad4f..e43534dc84 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryImpl.java @@ -74,24 +74,14 @@ public ChartEntry map(Chart name) { } private String songArtistName(Song song) { - if ( song == null ) { - return null; - } Artist artist = song.getArtist(); if ( artist == null ) { return null; } - String name = artist.getName(); - if ( name == null ) { - return null; - } - return name; + return artist.getName(); } private String songArtistLabelStudioName(Song song) { - if ( song == null ) { - return null; - } Artist artist = song.getArtist(); if ( artist == null ) { return null; @@ -104,17 +94,10 @@ private String songArtistLabelStudioName(Song song) { if ( studio == null ) { return null; } - String name = studio.getName(); - if ( name == null ) { - return null; - } - return name; + return studio.getName(); } private String songArtistLabelStudioCity(Song song) { - if ( song == null ) { - return null; - } Artist artist = song.getArtist(); if ( artist == null ) { return null; @@ -127,10 +110,6 @@ private String songArtistLabelStudioCity(Song song) { if ( studio == null ) { return null; } - String city = studio.getCity(); - if ( city == null ) { - return null; - } - return city; + return studio.getCity(); } } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistImpl.java index 8877bd421f..725f5b8efe 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistImpl.java @@ -198,24 +198,14 @@ protected void chartEntryToSong2(ChartEntry chartEntry, Song mappingTarget) { } private String chartSongTitle(Chart chart) { - if ( chart == null ) { - return null; - } Song song = chart.getSong(); if ( song == null ) { return null; } - String title = song.getTitle(); - if ( title == null ) { - return null; - } - return title; + return song.getTitle(); } private String chartSongArtistName(Chart chart) { - if ( chart == null ) { - return null; - } Song song = chart.getSong(); if ( song == null ) { return null; @@ -224,17 +214,10 @@ private String chartSongArtistName(Chart chart) { if ( artist == null ) { return null; } - String name = artist.getName(); - if ( name == null ) { - return null; - } - return name; + return artist.getName(); } private String chartSongArtistLabelStudioName(Chart chart) { - if ( chart == null ) { - return null; - } Song song = chart.getSong(); if ( song == null ) { return null; @@ -251,17 +234,10 @@ private String chartSongArtistLabelStudioName(Chart chart) { if ( studio == null ) { return null; } - String name = studio.getName(); - if ( name == null ) { - return null; - } - return name; + return studio.getName(); } private String chartSongArtistLabelStudioCity(Chart chart) { - if ( chart == null ) { - return null; - } Song song = chart.getSong(); if ( song == null ) { return null; @@ -278,25 +254,14 @@ private String chartSongArtistLabelStudioCity(Chart chart) { if ( studio == null ) { return null; } - String city = studio.getCity(); - if ( city == null ) { - return null; - } - return city; + return studio.getCity(); } private List chartSongPositions(Chart chart) { - if ( chart == null ) { - return null; - } Song song = chart.getSong(); if ( song == null ) { return null; } - List positions = song.getPositions(); - if ( positions == null ) { - return null; - } - return positions; + return song.getPositions(); } } From 51f4e7eba9c65d9078491711d616c7b5b38ecc1a Mon Sep 17 00:00:00 2001 From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com> Date: Wed, 24 May 2023 06:04:13 +0200 Subject: [PATCH 173/363] #3231 Prefer record constructor annotated with `@Default` --- .../org/mapstruct/itest/records/Default.java | 21 ++++++++++++++++++ .../org/mapstruct/itest/records/Task.java | 15 +++++++++++++ .../org/mapstruct/itest/records/TaskDto.java | 20 +++++++++++++++++ .../mapstruct/itest/records/TaskMapper.java | 22 +++++++++++++++++++ .../mapstruct/itest/records/RecordsTest.java | 12 ++++++++++ .../ap/internal/model/BeanMappingMethod.java | 18 ++++++++++++++- 6 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Default.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Task.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskDto.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskMapper.java diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Default.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Default.java new file mode 100644 index 0000000000..b845bdd730 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Default.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Filip Hrisafov + */ +@Documented +@Target(ElementType.CONSTRUCTOR) +@Retention(RetentionPolicy.SOURCE) +public @interface Default { +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Task.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Task.java new file mode 100644 index 0000000000..3f990fc89a --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/Task.java @@ -0,0 +1,15 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records; + +import java.util.List; + +/** + * @author Oliver Erhart + */ +public record Task( String id, Long number ) { + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskDto.java new file mode 100644 index 0000000000..1ba6eb2be9 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskDto.java @@ -0,0 +1,20 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records; + +import java.util.List; + +/** + * @author Oliver Erhart + */ +public record TaskDto(String id, Long number) { + + @Default + TaskDto(String id) { + this( id, 1L ); + } + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskMapper.java new file mode 100644 index 0000000000..8d9da767cd --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/TaskMapper.java @@ -0,0 +1,22 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Oliver Erhart + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface TaskMapper { + + TaskMapper INSTANCE = Mappers.getMapper( TaskMapper.class ); + + TaskDto toRecord(Task source); + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java index e98df8797e..2f77e8d49f 100644 --- a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java +++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/RecordsTest.java @@ -83,4 +83,16 @@ public void shouldMapIntoMemberRecord() { assertThat( value.isActive() ).isEqualTo( false ); assertThat( value.premium() ).isEqualTo( true ); } + + @Test + public void shouldUseDefaultConstructor() { + Task entity = new Task( "some-id", 1000L ); + + TaskDto value = TaskMapper.INSTANCE.toRecord( entity ); + + assertThat( value ).isNotNull(); + assertThat( value.id() ).isEqualTo( "some-id" ); + assertThat( value.number() ).isEqualTo( 1L ); + } + } 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 b6afce74d2..9d0acea91b 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 @@ -791,7 +791,23 @@ private ConstructorAccessor getConstructorAccessor(Type type) { } if ( type.isRecord() ) { - // If the type is a record then just get the record components and use then + + List constructors = ElementFilter.constructorsIn( type.getTypeElement() + .getEnclosedElements() ); + + for ( ExecutableElement constructor : constructors ) { + if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + continue; + } + + // prefer constructor annotated with @Default + if ( hasDefaultAnnotationFromAnyPackage( constructor ) ) { + return getConstructorAccessor( type, constructor ); + } + } + + + // Other than that, just get the record components and use them List recordComponents = type.getRecordComponents(); List parameterBindings = new ArrayList<>( recordComponents.size() ); Map constructorAccessors = new LinkedHashMap<>(); From c2eed45df12802cacaec158420656431c061471c Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 21 May 2023 23:01:58 +0200 Subject: [PATCH 174/363] #3126 Apply target this references in the BeanMappingMethod --- .../ap/internal/model/BeanMappingMethod.java | 14 ++- .../model/beanmapping/MappingReferences.java | 30 +----- .../ap/test/bugs/_3126/Issue3126Mapper.java | 92 +++++++++++++++++++ .../ap/test/bugs/_3126/Issue3126Test.java | 28 ++++++ .../nestedbeans/mixed/FishTankMapperImpl.java | 41 +-------- 5 files changed, 137 insertions(+), 68 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Test.java 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 9d0acea91b..361d04b390 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 @@ -117,6 +117,7 @@ public static class Builder extends AbstractMappingMethodBuilder> unprocessedDefinedTargets = new LinkedHashMap<>(); private MappingReferences mappingReferences; + private List targetThisReferences; private MethodReference factoryMethod; private boolean hasFactoryMethod; @@ -1068,6 +1069,14 @@ private boolean handleDefinedMappings(Type resultTypeToMap) { for ( MappingReference mapping : mappingReferences.getMappingReferences() ) { if ( mapping.isValid() ) { String target = mapping.getTargetReference().getShallowestPropertyName(); + if ( target == null ) { + // When the shallowest property name is null then it is for @Mapping(target = ".") + if ( this.targetThisReferences == null ) { + this.targetThisReferences = new ArrayList<>(); + } + this.targetThisReferences.add( mapping ); + continue; + } if ( !handledTargets.contains( target ) ) { if ( handleDefinedMapping( mapping, resultTypeToMap, handledTargets ) ) { errorOccurred = true; @@ -1435,8 +1444,11 @@ else if ( mapping.getJavaExpression() != null ) { * duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)} */ private void applyTargetThisMapping() { + if ( this.targetThisReferences == null ) { + return; + } Set handledTargetProperties = new HashSet<>(); - for ( MappingReference targetThis : mappingReferences.getTargetThisReferences() ) { + for ( MappingReference targetThis : this.targetThisReferences ) { // handle all prior unprocessed target properties, but let duplicates fall through List sourceRefs = targetThis diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java index 569c75e0eb..ced945bb90 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java @@ -5,10 +5,8 @@ */ package org.mapstruct.ap.internal.model.beanmapping; -import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; -import java.util.List; import java.util.Objects; import java.util.Set; @@ -23,7 +21,6 @@ public class MappingReferences { private static final MappingReferences EMPTY = new MappingReferences( Collections.emptySet(), false ); private final Set mappingReferences; - private final List targetThisReferences; private final boolean restrictToDefinedMappings; private final boolean forForgedMethods; @@ -38,7 +35,6 @@ public static MappingReferences forSourceMethod(SourceMethod sourceMethod, TypeFactory typeFactory) { Set references = new LinkedHashSet<>(); - List targetThisReferences = new ArrayList<>( ); for ( MappingOptions mapping : sourceMethod.getOptions().getMappings() ) { @@ -61,30 +57,16 @@ public static MappingReferences forSourceMethod(SourceMethod sourceMethod, // add when inverse is also valid MappingReference mappingReference = new MappingReference( mapping, targetReference, sourceReference ); if ( isValidWhenInversed( mappingReference ) ) { - if ( ".".equals( mapping.getTargetName() ) ) { - targetThisReferences.add( mappingReference ); - } - else { - references.add( mappingReference ); - } + references.add( mappingReference ); } } - return new MappingReferences( references, targetThisReferences, false ); - } - - public MappingReferences(Set mappingReferences, List targetThisReferences, - boolean restrictToDefinedMappings) { - this.mappingReferences = mappingReferences; - this.restrictToDefinedMappings = restrictToDefinedMappings; - this.forForgedMethods = restrictToDefinedMappings; - this.targetThisReferences = targetThisReferences; + return new MappingReferences( references, false ); } public MappingReferences(Set mappingReferences, boolean restrictToDefinedMappings) { this.mappingReferences = mappingReferences; this.restrictToDefinedMappings = restrictToDefinedMappings; this.forForgedMethods = restrictToDefinedMappings; - this.targetThisReferences = Collections.emptyList(); } public MappingReferences(Set mappingReferences, boolean restrictToDefinedMappings, @@ -92,7 +74,6 @@ public MappingReferences(Set mappingReferences, boolean restri this.mappingReferences = mappingReferences; this.restrictToDefinedMappings = restrictToDefinedMappings; this.forForgedMethods = forForgedMethods; - this.targetThisReferences = Collections.emptyList(); } public Set getMappingReferences() { @@ -136,10 +117,6 @@ public boolean hasNestedTargetReferences() { return false; } - public List getTargetThisReferences() { - return targetThisReferences; - } - @Override public boolean equals(Object o) { if ( this == o ) { @@ -160,9 +137,6 @@ public boolean equals(Object o) { if ( !Objects.equals( mappingReferences, that.mappingReferences ) ) { return false; } - if ( Objects.equals( targetThisReferences, that.targetThisReferences ) ) { - return false; - } return true; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Mapper.java new file mode 100644 index 0000000000..e680db9317 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Mapper.java @@ -0,0 +1,92 @@ +/* + * 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._3126; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface Issue3126Mapper { + + Issue3126Mapper INSTANCE = Mappers.getMapper( Issue3126Mapper.class ); + + @SubclassMapping(target = HomeAddressDto.class, source = HomeAddress.class) + @SubclassMapping(target = OfficeAddressDto.class, source = OfficeAddress.class) + @Mapping(target = ".", source = "auditable") + AddressDto map(Address address); + + interface AddressDto { + + } + + class HomeAddressDto implements AddressDto { + private String createdBy; + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + } + + class OfficeAddressDto implements AddressDto { + private String createdBy; + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + } + + class HomeAddress extends Address { + + public HomeAddress(Auditable auditable) { + super( auditable ); + } + } + + class OfficeAddress extends Address { + + public OfficeAddress(Auditable auditable) { + super( auditable ); + } + } + + abstract class Address { + + private final Auditable auditable; + + protected Address(Auditable auditable) { + this.auditable = auditable; + } + + public Auditable getAuditable() { + return auditable; + } + } + + class Auditable { + + private final String createdBy; + + public Auditable(String createdBy) { + this.createdBy = createdBy; + } + + public String getCreatedBy() { + return createdBy; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Test.java new file mode 100644 index 0000000000..35e3eb873c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Test.java @@ -0,0 +1,28 @@ +/* + * 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._3126; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3126") +@WithClasses(Issue3126Mapper.class) +class Issue3126Test { + + @ProcessorTest + void shouldCompile() { + Issue3126Mapper.Auditable auditable = new Issue3126Mapper.Auditable( "home-user" ); + Issue3126Mapper.Address address = new Issue3126Mapper.HomeAddress( auditable ); + Issue3126Mapper.AddressDto addressDto = Issue3126Mapper.INSTANCE.map( address ); + + assertThat( addressDto ).isInstanceOfSatisfying( Issue3126Mapper.HomeAddressDto.class, homeAddress -> { + assertThat( homeAddress.getCreatedBy() ).isEqualTo( "home-user" ); + } ); + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java index dc70a28ce8..b98223fa6f 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedbeans/mixed/FishTankMapperImpl.java @@ -57,9 +57,9 @@ public FishTankDto mapAsWell(FishTank source) { FishTankDto fishTankDto = new FishTankDto(); - fishTankDto.setFish( fishToFishDto1( source.getFish() ) ); + fishTankDto.setFish( fishToFishDto( source.getFish() ) ); fishTankDto.setMaterial( fishTankToMaterialDto1( source ) ); - fishTankDto.setQuality( waterQualityToWaterQualityDto1( source.getQuality() ) ); + fishTankDto.setQuality( waterQualityToWaterQualityDto( source.getQuality() ) ); fishTankDto.setOrnament( ornamentToOrnamentDto( sourceInteriorOrnament( source ) ) ); fishTankDto.setPlant( waterPlantToWaterPlantDto( source.getPlant() ) ); fishTankDto.setName( source.getName() ); @@ -190,18 +190,6 @@ protected WaterPlantDto waterPlantToWaterPlantDto(WaterPlant waterPlant) { return waterPlantDto; } - protected FishDto fishToFishDto1(Fish fish) { - if ( fish == null ) { - return null; - } - - FishDto fishDto = new FishDto(); - - fishDto.setKind( fish.getType() ); - - return fishDto; - } - protected MaterialDto fishTankToMaterialDto1(FishTank fishTank) { if ( fishTank == null ) { return null; @@ -226,31 +214,6 @@ protected WaterQualityOrganisationDto waterQualityReportToWaterQualityOrganisati return waterQualityOrganisationDto; } - protected WaterQualityReportDto waterQualityReportToWaterQualityReportDto1(WaterQualityReport waterQualityReport) { - if ( waterQualityReport == null ) { - return null; - } - - WaterQualityReportDto waterQualityReportDto = new WaterQualityReportDto(); - - waterQualityReportDto.setOrganisation( waterQualityReportToWaterQualityOrganisationDto1( waterQualityReport ) ); - waterQualityReportDto.setVerdict( waterQualityReport.getVerdict() ); - - return waterQualityReportDto; - } - - protected WaterQualityDto waterQualityToWaterQualityDto1(WaterQuality waterQuality) { - if ( waterQuality == null ) { - return null; - } - - WaterQualityDto waterQualityDto = new WaterQualityDto(); - - waterQualityDto.setReport( waterQualityReportToWaterQualityReportDto1( waterQuality.getReport() ) ); - - return waterQualityDto; - } - protected Fish fishDtoToFish(FishDto fishDto) { if ( fishDto == null ) { return null; From 62d1bd3490974468494853a46ec79f6bd5904bdd Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 27 May 2023 15:04:34 +0200 Subject: [PATCH 175/363] #3280 Refactor method selection and use a context to be able to more easily access information --- .../model/LifecycleMethodResolver.java | 12 +- .../model/ObjectFactoryMethodResolver.java | 10 +- .../model/PresenceCheckMethodResolver.java | 12 +- .../selector/CreateOrUpdateSelector.java | 11 +- .../selector/FactoryParameterSelector.java | 9 +- .../source/selector/InheritanceSelector.java | 14 +- .../source/selector/MethodFamilySelector.java | 10 +- .../model/source/selector/MethodSelector.java | 15 +- .../source/selector/MethodSelectors.java | 28 +- .../MostSpecificResultTypeSelector.java | 8 +- .../source/selector/QualifierSelector.java | 9 +- .../source/selector/SelectionContext.java | 240 ++++++++++++++++++ .../source/selector/SourceRhsSelector.java | 8 +- .../source/selector/TargetTypeSelector.java | 10 +- .../model/source/selector/TypeSelector.java | 112 +------- .../selector/XmlElementDeclSelector.java | 12 +- .../creation/MappingResolverImpl.java | 10 +- 17 files changed, 302 insertions(+), 228 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java index 8b44dee254..b713fa5f0c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -6,7 +6,6 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -19,7 +18,7 @@ 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.model.source.selector.SelectionContext; /** * Factory for creating lists of appropriate {@link LifecycleCallbackMethodReference}s @@ -134,15 +133,12 @@ private static List collectLifecycleCallbackMe MappingBuilderContext ctx, Set existingVariableNames) { MethodSelectors selectors = - new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() ); + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() ); List> matchingMethods = selectors.getMatchingMethods( - method, callbackMethods, - Collections.emptyList(), - targetType, - method.getResultType(), - SelectionCriteria.forLifecycleMethods( selectionParameters ) ); + SelectionContext.forLifecycleMethods( method, targetType, selectionParameters, ctx.getTypeFactory() ) + ); return toLifecycleCallbackMethodRefs( method, 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 index aded4cbe5f..4cf653b46b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -23,7 +23,7 @@ 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.model.source.selector.SelectionContext; import org.mapstruct.ap.internal.util.Message; /** @@ -126,15 +126,11 @@ public static List> getMatchingFactoryMethods( Meth MappingBuilderContext ctx) { MethodSelectors selectors = - new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() ); + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() ); return selectors.getMatchingMethods( - method, getAllAvailableMethods( method, ctx.getSourceModel() ), - java.util.Collections.emptyList(), - alternativeTarget, - alternativeTarget, - SelectionCriteria.forFactoryMethods( selectionParameters ) + SelectionContext.forFactoryMethods( method, alternativeTarget, selectionParameters, ctx.getTypeFactory() ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 9d4910bdce..a5c873743d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -6,20 +6,18 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; -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.model.source.selector.SelectionContext; import org.mapstruct.ap.internal.util.Message; /** @@ -60,18 +58,12 @@ private static SelectedMethod findMatchingPresenceCheckMethod( MethodSelectors selectors = new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), - ctx.getTypeFactory(), ctx.getMessager() ); - Type booleanType = ctx.getTypeFactory().getType( Boolean.class ); List> matchingMethods = selectors.getMatchingMethods( - method, getAllAvailableMethods( method, ctx.getSourceModel() ), - Collections.emptyList(), - booleanType, - booleanType, - SelectionCriteria.forPresenceCheckMethods( selectionParameters ) + SelectionContext.forPresenceCheckMethods( method, selectionParameters, ctx.getTypeFactory() ) ); if ( matchingMethods.isEmpty() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java index ed1c72ab2a..03a671de57 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.List; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -29,13 +28,9 @@ public class CreateOrUpdateSelector implements MethodSelector { @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type mappingTargetType, - Type returnType, - SelectionCriteria criteria) { - + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired() || criteria.isPresenceCheckRequired() ) { return methods; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java index 41d37e8b69..50896195f1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.List; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -21,11 +20,9 @@ public class FactoryParameterSelector implements MethodSelector { @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type mappingTargetType, Type returnType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); if ( !criteria.isObjectFactoryRequired() || methods.size() <= 1 ) { return methods; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java index a624c1accb..297dcaa4a3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java @@ -22,18 +22,14 @@ public class InheritanceSelector implements MethodSelector { @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type mappingTargetType, Type returnType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { - if ( sourceTypes.size() != 1 ) { + Type sourceType = context.getSourceType(); + if ( sourceType == null ) { return methods; } - Type singleSourceType = first( sourceTypes ); - List> candidatesWithBestMatchingSourceType = new ArrayList<>(); int bestMatchingSourceTypeDistance = Integer.MAX_VALUE; @@ -41,7 +37,7 @@ public List> getMatchingMethods(Method mapp for ( SelectedMethod method : methods ) { Parameter singleSourceParam = first( method.getMethod().getSourceParameters() ); - int sourceTypeDistance = singleSourceType.distanceTo( singleSourceParam.getType() ); + int sourceTypeDistance = sourceType.distanceTo( singleSourceParam.getType() ); bestMatchingSourceTypeDistance = addToCandidateListIfMinimal( candidatesWithBestMatchingSourceType, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java index 37502ec56d..d81269421f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.List; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -20,12 +19,9 @@ public class MethodFamilySelector implements MethodSelector { @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type mappingTargetType, - Type returnType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); List> result = new ArrayList<>( methods.size() ); for ( SelectedMethod method : methods ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java index 86d35bb3aa..375d5a9bf8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java @@ -7,7 +7,6 @@ import java.util.List; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -23,18 +22,10 @@ interface MethodSelector { * Selects those methods which match the given types and other criteria * * @param either SourceMethod or BuiltInMethod - * @param mappingMethod mapping method, defined in Mapper for which this selection is carried out * @param candidates list of available methods - * @param sourceTypes parameter type(s) that should be matched - * @param mappingTargetType mappingTargetType that should be matched - * @param returnType return type that should be matched - * @param criteria criteria used in the selection process + * @param context the context for the matching * @return list of methods that passes the matching process */ - List> getMatchingMethods(Method mappingMethod, - List> candidates, - List sourceTypes, - Type mappingTargetType, - Type returnType, - SelectionCriteria criteria); + List> getMatchingMethods(List> candidates, + SelectionContext context); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java index 74fa1a33a3..c14729a90f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java @@ -9,8 +9,6 @@ import java.util.Arrays; import java.util.List; -import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; @@ -25,11 +23,11 @@ public class MethodSelectors { private final List selectors; - public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, TypeFactory typeFactory, + public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, FormattingMessager messager) { selectors = Arrays.asList( new MethodFamilySelector(), - new TypeSelector( typeFactory, messager ), + new TypeSelector( messager ), new QualifierSelector( typeUtils, elementUtils ), new TargetTypeSelector( typeUtils ), new JavaxXmlElementDeclSelector( typeUtils ), @@ -46,20 +44,12 @@ public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, TypeFacto * Selects those methods which match the given types and other criteria * * @param either SourceMethod or BuiltInMethod - * @param mappingMethod mapping method, defined in Mapper for which this selection is carried out * @param methods list of available methods - * @param sourceTypes parameter type(s) that should be matched - * @param mappingTargetType the mapping target type that should be matched - * @param returnType return type that should be matched - * @param criteria criteria used in the selection process + * @param context the selection context that should be used in the matching process * @return list of methods that passes the matching process */ - public List> getMatchingMethods(Method mappingMethod, - List methods, - List sourceTypes, - Type mappingTargetType, - Type returnType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List methods, + SelectionContext context) { List> candidates = new ArrayList<>( methods.size() ); for ( T method : methods ) { @@ -67,13 +57,7 @@ public List> getMatchingMethods(Method mapp } for ( MethodSelector selector : selectors ) { - candidates = selector.getMatchingMethods( - mappingMethod, - candidates, - sourceTypes, - mappingTargetType, - returnType, - criteria ); + candidates = selector.getMatchingMethods( candidates, context ); } return candidates; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java index 6e86a30bc7..23af0a55f9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java @@ -17,10 +17,10 @@ public class MostSpecificResultTypeSelector implements MethodSelector { @Override - public List> getMatchingMethods(Method mappingMethod, - List> candidates, - List sourceTypes, Type mappingTargetType, - Type returnType, SelectionCriteria criteria) { + public List> getMatchingMethods(List> candidates, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); + Type mappingTargetType = context.getMappingTargetType(); if ( candidates.size() < 2 || !criteria.isForMapping() || criteria.getQualifyingResultType() != null) { return candidates; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java index 210e462e3e..f2f5b591d6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java @@ -49,12 +49,9 @@ public QualifierSelector(TypeUtils typeUtils, ElementUtils elementUtils ) { } @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type mappingTargetType, - Type returnType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); int numberOfQualifiersToMatch = 0; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java new file mode 100644 index 0000000000..9e82dae25d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java @@ -0,0 +1,240 @@ +/* + * 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.internal.model.source.selector; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.common.SourceRHS; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; + +/** + * Context passed to the selectors to get the information they need. + * + * @author Filip Hrisafov + */ +public class SelectionContext { + + private final Type sourceType; + private final SelectionCriteria selectionCriteria; + private final Method mappingMethod; + private final Type mappingTargetType; + private final Type returnType; + private final Supplier> parameterBindingsProvider; + private List parameterBindings; + + private SelectionContext(Type sourceType, SelectionCriteria selectionCriteria, Method mappingMethod, + Type mappingTargetType, Type returnType, + Supplier> parameterBindingsProvider) { + this.sourceType = sourceType; + this.selectionCriteria = selectionCriteria; + this.mappingMethod = mappingMethod; + this.mappingTargetType = mappingTargetType; + this.returnType = returnType; + this.parameterBindingsProvider = parameterBindingsProvider; + } + + /** + * @return the source type that should be matched + */ + public Type getSourceType() { + return sourceType; + } + + /** + * @return the criteria used in the selection process + */ + public SelectionCriteria getSelectionCriteria() { + return selectionCriteria; + } + + /** + * @return the mapping target type that should be matched + */ + public Type getMappingTargetType() { + return mappingTargetType; + } + + /** + * @return the return type that should be matched + */ + public Type getReturnType() { + return returnType; + } + + /** + * @return the available parameter bindings for the matching + */ + public List getAvailableParameterBindings() { + if ( this.parameterBindings == null ) { + this.parameterBindings = this.parameterBindingsProvider.get(); + } + return parameterBindings; + } + + /** + * @return the mapping method, defined in Mapper for which this selection is carried out + */ + public Method getMappingMethod() { + return mappingMethod; + } + + public static SelectionContext forMappingMethods(Method mappingMethod, Type source, Type target, + SelectionCriteria criteria, TypeFactory typeFactory) { + return new SelectionContext( + source, + criteria, + mappingMethod, + target, + target, + () -> getAvailableParameterBindingsFromSourceType( + source, + target, + mappingMethod, + typeFactory + ) + ); + } + + public static SelectionContext forLifecycleMethods(Method mappingMethod, Type targetType, + SelectionParameters selectionParameters, + TypeFactory typeFactory) { + SelectionCriteria criteria = SelectionCriteria.forLifecycleMethods( selectionParameters ); + return new SelectionContext( + null, + criteria, + mappingMethod, + targetType, + mappingMethod.getResultType(), + () -> getAvailableParameterBindingsFromMethod( + mappingMethod, + targetType, + criteria.getSourceRHS(), + typeFactory + ) + ); + } + + public static SelectionContext forFactoryMethods(Method mappingMethod, Type alternativeTarget, + SelectionParameters selectionParameters, + TypeFactory typeFactory) { + SelectionCriteria criteria = SelectionCriteria.forFactoryMethods( selectionParameters ); + return new SelectionContext( + null, + criteria, + mappingMethod, + alternativeTarget, + alternativeTarget, + () -> getAvailableParameterBindingsFromMethod( + mappingMethod, + alternativeTarget, + criteria.getSourceRHS(), + typeFactory + ) + ); + } + + public static SelectionContext forPresenceCheckMethods(Method mappingMethod, + SelectionParameters selectionParameters, + TypeFactory typeFactory) { + SelectionCriteria criteria = SelectionCriteria.forPresenceCheckMethods( selectionParameters ); + Type booleanType = typeFactory.getType( Boolean.class ); + return new SelectionContext( + null, + criteria, + mappingMethod, + booleanType, + booleanType, + () -> getAvailableParameterBindingsFromMethod( + mappingMethod, + booleanType, + criteria.getSourceRHS(), + typeFactory + ) + ); + } + + private static List getAvailableParameterBindingsFromMethod(Method method, Type targetType, + SourceRHS sourceRHS, + TypeFactory typeFactory) { + List availableParams = new ArrayList<>( method.getParameters().size() + 3 ); + + if ( sourceRHS != null ) { + availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); + availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) ); + } + else { + availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); + } + + addTargetRelevantBindings( availableParams, targetType, typeFactory ); + + return availableParams; + } + + private static List getAvailableParameterBindingsFromSourceType(Type sourceType, + Type targetType, + Method mappingMethod, + TypeFactory typeFactory) { + + List availableParams = new ArrayList<>(); + + availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) ); + + for ( Parameter param : mappingMethod.getParameters() ) { + if ( param.isMappingContext() ) { + availableParams.add( ParameterBinding.fromParameter( param ) ); + } + } + + addTargetRelevantBindings( availableParams, targetType, typeFactory ); + + return availableParams; + } + + /** + * Adds default parameter bindings for the mapping-target and target-type if not already available. + * + * @param availableParams Already available params, new entries will be added to this list + * @param targetType Target type + */ + private static void addTargetRelevantBindings(List availableParams, Type targetType, + TypeFactory typeFactory) { + boolean mappingTargetAvailable = false; + boolean targetTypeAvailable = false; + boolean targetPropertyNameAvailable = false; + + // search available parameter bindings if mapping-target and/or target-type is available + for ( ParameterBinding pb : availableParams ) { + if ( pb.isMappingTarget() ) { + mappingTargetAvailable = true; + } + else if ( pb.isTargetType() ) { + targetTypeAvailable = true; + } + else if ( pb.isTargetPropertyName() ) { + targetPropertyNameAvailable = true; + } + } + + if ( !mappingTargetAvailable ) { + availableParams.add( ParameterBinding.forMappingTargetBinding( targetType ) ); + } + if ( !targetTypeAvailable ) { + availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) ); + } + if ( !targetPropertyNameAvailable ) { + availableParams.add( ParameterBinding.forTargetPropertyNameBinding( typeFactory.getType( String.class ) ) ); + } + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java index 930ce2d403..fb797f5808 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java @@ -9,7 +9,6 @@ import java.util.List; import org.mapstruct.ap.internal.model.common.ParameterBinding; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -20,10 +19,9 @@ public class SourceRhsSelector implements MethodSelector { @Override - public List> getMatchingMethods(Method mappingMethod, - List> candidates, - List sourceTypes, Type mappingTargetType, - Type returnType, SelectionCriteria criteria) { + public List> getMatchingMethods(List> candidates, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); if ( candidates.size() < 2 || criteria.getSourceRHS() == null ) { return candidates; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java index 38a907aedb..0afae641db 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java @@ -11,7 +11,6 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.util.TypeUtils; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -31,12 +30,9 @@ public TargetTypeSelector( TypeUtils typeUtils ) { } @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type mappingTargetType, - Type returnType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); TypeMirror qualifyingTypeMirror = criteria.getQualifyingResultType(); if ( qualifyingTypeMirror != null && !criteria.isLifecycleCallbackRequired() ) { 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 8acdd327c4..24275f594b 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 @@ -11,9 +11,7 @@ import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.ParameterBinding; -import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.MethodMatcher; import org.mapstruct.ap.internal.util.FormattingMessager; @@ -29,44 +27,24 @@ */ public class TypeSelector implements MethodSelector { - private TypeFactory typeFactory; private FormattingMessager messager; - public TypeSelector(TypeFactory typeFactory, FormattingMessager messager) { - this.typeFactory = typeFactory; + public TypeSelector(FormattingMessager messager) { this.messager = messager; } @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type mappingTargetType, - Type returnType, - SelectionCriteria criteria) { - + public List> getMatchingMethods(List> methods, + SelectionContext context) { if ( methods.isEmpty() ) { return methods; } + Type returnType = context.getReturnType(); + List> result = new ArrayList<>(); - List availableBindings; - if ( sourceTypes.isEmpty() ) { - // if no source types are given, we have a factory or lifecycle method - availableBindings = getAvailableParameterBindingsFromMethod( - mappingMethod, - mappingTargetType, - criteria.getSourceRHS() - ); - } - else { - availableBindings = getAvailableParameterBindingsFromSourceTypes( - sourceTypes, - mappingTargetType, - mappingMethod - ); - } + List availableBindings = context.getAvailableParameterBindings(); for ( SelectedMethod method : methods ) { List> parameterBindingPermutations = @@ -74,7 +52,7 @@ public List> getMatchingMethods(Method mapp if ( parameterBindingPermutations != null ) { SelectedMethod matchingMethod = - getMatchingParameterBinding( returnType, mappingMethod, method, parameterBindingPermutations ); + getMatchingParameterBinding( returnType, context, method, parameterBindingPermutations ); if ( matchingMethod != null ) { result.add( matchingMethod ); @@ -84,80 +62,8 @@ public List> getMatchingMethods(Method mapp return result; } - private List getAvailableParameterBindingsFromMethod(Method method, Type targetType, - SourceRHS sourceRHS) { - List availableParams = new ArrayList<>( method.getParameters().size() + 3 ); - - if ( sourceRHS != null ) { - availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); - availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) ); - } - else { - availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); - } - - addTargetRelevantBindings( availableParams, targetType ); - - return availableParams; - } - - private List getAvailableParameterBindingsFromSourceTypes(List sourceTypes, - Type targetType, Method mappingMethod) { - - List availableParams = new ArrayList<>( sourceTypes.size() + 2 ); - - for ( Type sourceType : sourceTypes ) { - availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) ); - } - - for ( Parameter param : mappingMethod.getParameters() ) { - if ( param.isMappingContext() ) { - availableParams.add( ParameterBinding.fromParameter( param ) ); - } - } - - addTargetRelevantBindings( availableParams, targetType ); - - return availableParams; - } - - /** - * Adds default parameter bindings for the mapping-target and target-type if not already available. - * - * @param availableParams Already available params, new entries will be added to this list - * @param targetType Target type - */ - private void addTargetRelevantBindings(List availableParams, Type targetType) { - boolean mappingTargetAvailable = false; - boolean targetTypeAvailable = false; - boolean targetPropertyNameAvailable = false; - - // search available parameter bindings if mapping-target and/or target-type is available - for ( ParameterBinding pb : availableParams ) { - if ( pb.isMappingTarget() ) { - mappingTargetAvailable = true; - } - else if ( pb.isTargetType() ) { - targetTypeAvailable = true; - } - else if ( pb.isTargetPropertyName() ) { - targetPropertyNameAvailable = true; - } - } - - if ( !mappingTargetAvailable ) { - availableParams.add( ParameterBinding.forMappingTargetBinding( targetType ) ); - } - if ( !targetTypeAvailable ) { - availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) ); - } - if ( !targetPropertyNameAvailable ) { - availableParams.add( ParameterBinding.forTargetPropertyNameBinding( typeFactory.getType( String.class ) ) ); - } - } - private SelectedMethod getMatchingParameterBinding(Type returnType, - Method mappingMethod, SelectedMethod selectedMethodInfo, + SelectionContext context, SelectedMethod selectedMethodInfo, List> parameterAssignmentVariants) { List> matchingParameterAssignmentVariants = new ArrayList<>( @@ -200,7 +106,7 @@ else if ( matchingParameterAssignmentVariants.size() == 1 ) { messager.printMessage( selectedMethod.getExecutable(), Message.LIFECYCLEMETHOD_AMBIGUOUS_PARAMETERS, - mappingMethod + context.getMappingMethod() ); return null; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java index 91b4b5ca10..a32cb250d5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java @@ -45,18 +45,16 @@ abstract class XmlElementDeclSelector implements MethodSelector { } @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type mappingTargetType, - Type returnType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + Type resultType = context.getMappingMethod().getResultType(); + String targetPropertyName = context.getSelectionCriteria().getTargetPropertyName(); List> nameMatches = new ArrayList<>(); List> scopeMatches = new ArrayList<>(); List> nameAndScopeMatches = new ArrayList<>(); XmlElementRefInfo xmlElementRefInfo = - findXmlElementRef( mappingMethod.getResultType(), criteria.getTargetPropertyName() ); + findXmlElementRef( resultType, targetPropertyName ); for ( SelectedMethod candidate : methods ) { if ( !( candidate.getMethod() instanceof SourceMethod ) ) { 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 0c8e2cc4c4..586465d5f1 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 @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.internal.processor.creation; -import static java.util.Collections.singletonList; import static org.mapstruct.ap.internal.util.Collections.first; import static org.mapstruct.ap.internal.util.Collections.firstKey; import static org.mapstruct.ap.internal.util.Collections.firstValue; @@ -57,6 +56,7 @@ import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; 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.SelectionContext; import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.internal.util.ElementUtils; @@ -116,7 +116,7 @@ public MappingResolverImpl(FormattingMessager messager, ElementUtils elementUtil this.conversions = new Conversions( typeFactory ); this.builtInMethods = new BuiltInMappingMethods( typeFactory ); - this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, typeFactory, messager ); + this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, messager ); this.verboseLogging = verboseLogging; } @@ -491,12 +491,8 @@ private boolean isUpdateMethodForMapping(Method methodCandidate) { private List> getBestMatch(List methods, Type source, Type target) { return methodSelectors.getMatchingMethods( - mappingMethod, methods, - singletonList( source ), - target, - target, - selectionCriteria + SelectionContext.forMappingMethods( mappingMethod, source, target, selectionCriteria, typeFactory ) ); } From d075d9a5b69a2377069dc0a9de86613dff8c2619 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 24 May 2023 06:09:57 +0200 Subject: [PATCH 176/363] Upgrade Freemarker to 2.3.32 --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index 6e9c2836b6..f9531590f6 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -109,7 +109,7 @@ org.freemarker freemarker - 2.3.31 + 2.3.32 org.assertj From 86919c637f228ec3a668f8140b9b68e2092f5189 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 28 May 2023 09:55:40 +0200 Subject: [PATCH 177/363] #3144 Map to Bean should only be possible for single source mappings and if explicitly used in multi source mappings --- .../ap/internal/model/BeanMappingMethod.java | 16 +++-- .../NestedTargetPropertyMappingHolder.java | 5 +- .../model/beanmapping/SourceReference.java | 23 +++++-- .../ap/internal/model/common/Type.java | 4 +- .../ap/test/bugs/_3144/Issue3144Mapper.java | 66 +++++++++++++++++++ .../ap/test/bugs/_3144/Issue3144Test.java | 63 ++++++++++++++++++ .../MapToBeanUsingMappingMethodMapper.java | 2 +- 7 files changed, 165 insertions(+), 14 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Test.java 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 361d04b390..903dfa5cc5 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 @@ -586,7 +586,7 @@ private void handleUnprocessedDefinedTargets() { .build(); ReadAccessor targetPropertyReadAccessor = - method.getResultType().getReadAccessor( propertyName ); + method.getResultType().getReadAccessor( propertyName, forceUpdateMethod ); MappingReferences mappingRefs = extractMappingReferences( propertyName, true ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) @@ -1160,7 +1160,10 @@ private boolean handleDefinedMapping(MappingReference mappingRef, Type resultTyp } Accessor targetWriteAccessor = unprocessedTargetProperties.get( targetPropertyName ); - ReadAccessor targetReadAccessor = resultTypeToMap.getReadAccessor( targetPropertyName ); + ReadAccessor targetReadAccessor = resultTypeToMap.getReadAccessor( + targetPropertyName, + method.getSourceParameters().size() == 1 + ); if ( targetWriteAccessor == null ) { if ( targetReadAccessor == null ) { @@ -1513,7 +1516,8 @@ private void applyPropertyNameBasedMapping(List sourceReference } ReadAccessor targetPropertyReadAccessor = - method.getResultType().getReadAccessor( targetPropertyName ); + method.getResultType() + .getReadAccessor( targetPropertyName, method.getSourceParameters().size() == 1 ); MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) .sourceMethod( method ) @@ -1556,7 +1560,8 @@ private void applyParameterNameBasedMapping() { .build(); ReadAccessor targetPropertyReadAccessor = - method.getResultType().getReadAccessor( targetProperty.getKey() ); + method.getResultType() + .getReadAccessor( targetProperty.getKey(), method.getSourceParameters().size() == 1 ); MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) @@ -1600,7 +1605,8 @@ private SourceReference getSourceRefByTargetName(Parameter sourceParameter, Stri return sourceRef; } - ReadAccessor sourceReadAccessor = sourceParameter.getType().getReadAccessor( targetPropertyName ); + ReadAccessor sourceReadAccessor = sourceParameter.getType() + .getReadAccessor( targetPropertyName, method.getSourceParameters().size() == 1 ); if ( sourceReadAccessor != null ) { // property mapping PresenceCheckAccessor sourcePresenceChecker = diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 61e3926047..b7bfc69780 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -643,7 +643,10 @@ private PropertyMapping createPropertyMappingForNestedTarget(MappingReferences m boolean forceUpdateMethod) { Accessor targetWriteAccessor = targetPropertiesWriteAccessors.get( targetPropertyName ); - ReadAccessor targetReadAccessor = targetType.getReadAccessor( targetPropertyName ); + ReadAccessor targetReadAccessor = targetType.getReadAccessor( + targetPropertyName, + method.getSourceParameters().size() == 1 + ); if ( targetWriteAccessor == null ) { Set readAccessors = targetType.getPropertyReadAccessors().keySet(); String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 4e7dad220c..73ec84b6b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -152,15 +152,27 @@ public SourceReference build() { private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) { boolean foundEntryMatch; + boolean allowedMapToBean = false; + if ( segments.length > 0 ) { + if ( parameter.getType().isMapType() ) { + // When the parameter type is a map and the parameter name matches the first segment + // then the first segment should not be treated as a property of the map + allowedMapToBean = !segments[0].equals( parameter.getName() ); + } + } String[] propertyNames = segments; - List entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames ); + List entries = matchWithSourceAccessorTypes( + parameter.getType(), + propertyNames, + allowedMapToBean + ); foundEntryMatch = ( entries.size() == propertyNames.length ); if ( !foundEntryMatch ) { //Lets see if the expression contains the parameterName, so parameterName.propName1.propName2 if ( getSourceParameterFromMethodOrTemplate( segments[0] ) != null ) { propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); - entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames ); + entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames, true ); foundEntryMatch = ( entries.size() == propertyNames.length ); } else { @@ -193,7 +205,7 @@ private SourceReference buildFromMultipleSourceParameters(String[] segments, Par if ( segments.length > 1 && parameter != null ) { propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); - entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames ); + entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames, true ); foundEntryMatch = ( entries.size() == propertyNames.length ); } else { @@ -306,13 +318,14 @@ private void reportErrorOnNoMatch( Parameter parameter, String[] propertyNames, } } - private List matchWithSourceAccessorTypes(Type type, String[] entryNames) { + private List matchWithSourceAccessorTypes(Type type, String[] entryNames, + boolean allowedMapToBean) { List sourceEntries = new ArrayList<>(); Type newType = type; for ( int i = 0; i < entryNames.length; i++ ) { boolean matchFound = false; Type noBoundsType = newType.withoutBounds(); - ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i] ); + ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i], i > 0 || allowedMapToBean ); if ( readAccessor != null ) { PresenceCheckAccessor presenceChecker = noBoundsType.getPresenceChecker( entryNames[i] ); newType = typeFactory.getReturnType( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 4254913d83..93358c2a27 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -652,8 +652,8 @@ public Type asRawType() { } } - public ReadAccessor getReadAccessor(String propertyName) { - if ( hasStringMapSignature() ) { + public ReadAccessor getReadAccessor(String propertyName, boolean allowedMapToBean) { + if ( allowedMapToBean && hasStringMapSignature() ) { ExecutableElement getMethod = getAllMethods() .stream() .filter( m -> m.getSimpleName().contentEquals( "get" ) ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Mapper.java new file mode 100644 index 0000000000..df5b767876 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Mapper.java @@ -0,0 +1,66 @@ +/* + * 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._3144; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface Issue3144Mapper { + + Issue3144Mapper INSTANCE = Mappers.getMapper( Issue3144Mapper.class ); + + @Mapping(target = "map", source = "sourceMap") + Target mapExplicitDefined(Map sourceMap); + + @Mapping(target = "map", ignore = true) + Target map(Map sourceMap); + + Target mapMultiParameters(Source source, Map map); + + @Mapping(target = "value", source = "map.testValue") + Target mapMultiParametersDefinedMapping(Source source, Map map); + + class Source { + private final String sourceValue; + + public Source(String sourceValue) { + this.sourceValue = sourceValue; + } + + public String getSourceValue() { + return sourceValue; + } + } + + class Target { + private String value; + private Map map; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Test.java new file mode 100644 index 0000000000..13f349414a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Test.java @@ -0,0 +1,63 @@ +/* + * 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._3144; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3144Mapper.class) +@IssueKey("3144") +class Issue3144Test { + + @ProcessorTest + void shouldCorrectlyHandleMapBeanMapping() { + Map map = new HashMap<>(); + map.put( "value", "Map Value" ); + map.put( "testValue", "Map Test Value" ); + + Issue3144Mapper.Target target = Issue3144Mapper.INSTANCE.mapExplicitDefined( map ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "Map Value" ); + assertThat( target.getMap() ) + .containsOnly( + entry( "value", "Map Value" ), + entry( "testValue", "Map Test Value" ) + ); + + target = Issue3144Mapper.INSTANCE.map( map ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "Map Value" ); + assertThat( target.getMap() ).isNull(); + + target = Issue3144Mapper.INSTANCE.mapMultiParameters( null, map ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + assertThat( target.getMap() ) + .containsOnly( + entry( "value", "Map Value" ), + entry( "testValue", "Map Test Value" ) + ); + + target = Issue3144Mapper.INSTANCE.mapMultiParametersDefinedMapping( null, map ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "Map Test Value" ); + assertThat( target.getMap() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java index 9a6344ff15..225ca00adc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java @@ -19,7 +19,7 @@ public interface MapToBeanUsingMappingMethodMapper { MapToBeanUsingMappingMethodMapper INSTANCE = Mappers.getMapper( MapToBeanUsingMappingMethodMapper.class ); - @Mapping(target = "normalInt", source = "number") + @Mapping(target = "normalInt", source = "source.number") Target toTarget(Map source); default String mapIntegerToString( Integer input ) { From 1b1325da6d27ecc995aa659551c98dcf4ee764a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 22:28:38 +0000 Subject: [PATCH 178/363] Bump guava from 29.0-jre to 32.0.0-jre in /parent Bumps [guava](https://github.com/google/guava) from 29.0-jre to 32.0.0-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) --- updated-dependencies: - dependency-name: com.google.guava:guava dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index f9531590f6..32f7e2897a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -119,7 +119,7 @@ com.google.guava guava - 29.0-jre + 32.0.0-jre org.mapstruct.tools.gem From 04434af17a1195268af14b9775c93d48936f7e6e Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sat, 8 Jul 2023 18:03:56 +0200 Subject: [PATCH 179/363] #3296: Skip default and static methods when determining prototype methods --- .../processor/MethodRetrievalProcessor.java | 5 +++ .../mapstruct/ap/test/bugs/_3296/Entity.java | 18 ++++++++ .../ap/test/bugs/_3296/Issue3296Test.java | 43 +++++++++++++++++++ .../MapperConfigWithPayloadArgument.java | 24 +++++++++++ .../MapperConfigWithoutPayloadArgument.java | 23 ++++++++++ .../bugs/_3296/MapperExtendingConfig.java | 13 ++++++ .../bugs/_3296/MapperNotExtendingConfig.java | 13 ++++++ .../mapstruct/ap/test/bugs/_3296/Payload.java | 19 ++++++++ 8 files changed, 158 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Entity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Issue3296Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithPayloadArgument.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithoutPayloadArgument.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperExtendingConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperNotExtendingConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Payload.java 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 6f1febae9d..faf7a786fd 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 @@ -124,6 +124,11 @@ private List retrievePrototypeMethods(TypeElement mapperTypeElemen List methods = new ArrayList<>(); for ( ExecutableElement executable : elementUtils.getAllEnclosedExecutableElements( typeElement ) ) { + if ( executable.isDefault() || executable.getModifiers().contains( Modifier.STATIC ) ) { + // skip the default and static methods because these are not prototypes. + continue; + } + ExecutableType methodType = typeFactory.getMethodType( mapperAnnotation.mapperConfigType(), executable ); List parameters = typeFactory.getParameters( methodType, executable ); boolean containsTargetTypeParameter = SourceMethod.containsTargetTypeParameter( parameters ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Entity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Entity.java new file mode 100644 index 0000000000..cfd038b03c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Entity.java @@ -0,0 +1,18 @@ +/* + * 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._3296; + +public class Entity { + String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Issue3296Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Issue3296Test.java new file mode 100644 index 0000000000..941eee9a70 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Issue3296Test.java @@ -0,0 +1,43 @@ +/* + * 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._3296; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@IssueKey( "3296" ) +@WithClasses( { Entity.class, Payload.class } ) +public class Issue3296Test { + + @ProcessorTest + @WithClasses( { MapperExtendingConfig.class, MapperConfigWithPayloadArgument.class } ) + public void shouldNotRaiseErrorForDefaultAfterMappingMethodImplementation() { + Payload payload = new Payload(); + payload.setName( "original" ); + + Entity entity = Mappers.getMapper( MapperExtendingConfig.class ).toEntity( payload ); + + assertThat( entity.getName() ).isEqualTo( "AfterMapping called" ); + } + + @ProcessorTest + @WithClasses( { MapperNotExtendingConfig.class, MapperConfigWithoutPayloadArgument.class } ) + public void shouldNotRaiseErrorRequiringArgumentsForDefaultMethods() { + Payload payload = new Payload(); + payload.setName( "original" ); + + Entity entity = Mappers.getMapper( MapperNotExtendingConfig.class ).toEntity( payload ); + + assertThat( entity.getName() ).isEqualTo( "original" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithPayloadArgument.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithPayloadArgument.java new file mode 100644 index 0000000000..69826aa10e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithPayloadArgument.java @@ -0,0 +1,24 @@ +/* + * 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._3296; + +import org.mapstruct.AfterMapping; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingTarget; + +@MapperConfig +public interface MapperConfigWithPayloadArgument { + + @AfterMapping + default void afterMapping(@MappingTarget Entity entity, Payload unused) { + staticMethod( entity ); + } + + static void staticMethod(Entity entity) { + entity.setName( "AfterMapping called" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithoutPayloadArgument.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithoutPayloadArgument.java new file mode 100644 index 0000000000..36cc046d12 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithoutPayloadArgument.java @@ -0,0 +1,23 @@ +/* + * 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._3296; + +import org.mapstruct.AfterMapping; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingTarget; + +@MapperConfig +public interface MapperConfigWithoutPayloadArgument { + + @AfterMapping + default void afterMapping(@MappingTarget Entity entity) { + staticMethod( entity ); + } + + static void staticMethod(Entity entity) { + entity.setName( "AfterMapping called" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperExtendingConfig.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperExtendingConfig.java new file mode 100644 index 0000000000..157bd051cb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperExtendingConfig.java @@ -0,0 +1,13 @@ +/* + * 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._3296; + +import org.mapstruct.Mapper; + +@Mapper( config = MapperConfigWithPayloadArgument.class ) +public interface MapperExtendingConfig extends MapperConfigWithPayloadArgument { + Entity toEntity(Payload payload); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperNotExtendingConfig.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperNotExtendingConfig.java new file mode 100644 index 0000000000..2ceb18c253 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperNotExtendingConfig.java @@ -0,0 +1,13 @@ +/* + * 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._3296; + +import org.mapstruct.Mapper; + +@Mapper( config = MapperConfigWithoutPayloadArgument.class ) +public interface MapperNotExtendingConfig { + Entity toEntity(Payload payload); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Payload.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Payload.java new file mode 100644 index 0000000000..2502e5e4a2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Payload.java @@ -0,0 +1,19 @@ +/* + * 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._3296; + +public class Payload { + + String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} From 53c73324ff85e0b718d2a8f2f64bcdbb9256632b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 8 Jul 2023 17:45:00 +0200 Subject: [PATCH 180/363] #3310 Make sure that adders work properly when they are in a generic class --- .../ap/internal/model/common/Type.java | 5 +- .../ap/test/bugs/_3310/Issue3310Mapper.java | 61 +++++++++++++++++++ .../ap/test/bugs/_3310/Issue3310Test.java | 31 ++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 93358c2a27..933f1f6ce0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -958,9 +958,8 @@ private List getAccessorCandidates(Type property, Class superclass) List adderList = getAdders(); List candidateList = new ArrayList<>(); for ( Accessor adder : adderList ) { - ExecutableElement executable = (ExecutableElement) adder.getElement(); - VariableElement arg = executable.getParameters().get( 0 ); - if ( typeUtils.isSameType( boxed( arg.asType() ), boxed( typeArg ) ) ) { + TypeMirror adderParameterType = determineTargetType( adder ).getTypeMirror(); + if ( typeUtils.isSameType( boxed( adderParameterType ), boxed( typeArg ) ) ) { candidateList.add( adder ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Mapper.java new file mode 100644 index 0000000000..2975a5de8a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Mapper.java @@ -0,0 +1,61 @@ +/* + * 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._3310; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface Issue3310Mapper { + + Issue3310Mapper INSTANCE = Mappers.getMapper( Issue3310Mapper.class ); + + Target map(Source source); + + abstract class BaseClass { + + private List items; + + public List getItems() { + return items; + } + + public void setItems(List items) { + throw new UnsupportedOperationException( "adder should be used instead" ); + } + + public void addItem(T item) { + if ( items == null ) { + items = new ArrayList<>(); + } + items.add( item ); + } + } + + class Target extends BaseClass { + + } + + class Source { + + private final List items; + + public Source(List items) { + this.items = items; + } + + public List getItems() { + return items; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Test.java new file mode 100644 index 0000000000..a00526985f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Test.java @@ -0,0 +1,31 @@ +/* + * 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._3310; + +import java.util.Collections; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3310") +@WithClasses(Issue3310Mapper.class) +class Issue3310Test { + + @ProcessorTest + void shouldUseAdderWithGenericBaseClass() { + Issue3310Mapper.Source source = new Issue3310Mapper.Source( Collections.singletonList( "test" ) ); + Issue3310Mapper.Target target = Issue3310Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getItems() ).containsExactly( "test" ); + } +} From 4abf2d42029e8b5e7b767a6f4ac74050e188170a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 8 Jul 2023 18:32:10 +0200 Subject: [PATCH 181/363] #3317 Do not generate source parameter if check for only primitives --- .../ap/internal/model/BeanMappingMethod.ftl | 2 +- .../ap/test/bugs/_3317/Issue3317Mapper.java | 39 +++++++++++++++++++ .../ap/test/bugs/_3317/Issue3317Test.java | 28 +++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Test.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index a0fbe24b3e..0f9b96bb66 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -27,7 +27,7 @@ - <#if !mapNullToDefault> + <#if !mapNullToDefault && !sourceParametersExcludingPrimitives.empty> if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && ) { return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /><#else>null; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Mapper.java new file mode 100644 index 0000000000..33a40b1dfb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Mapper.java @@ -0,0 +1,39 @@ +/* + * 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._3317; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3317Mapper { + + Issue3317Mapper INSTANCE = Mappers.getMapper( Issue3317Mapper.class ); + + Target map(int id, long value); + + class Target { + + private final int id; + private final long value; + + public Target(int id, long value) { + this.id = id; + this.value = value; + } + + public int getId() { + return id; + } + + public long getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Test.java new file mode 100644 index 0000000000..218a1f8979 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Test.java @@ -0,0 +1,28 @@ +/* + * 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._3317; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3317") +@WithClasses(Issue3317Mapper.class) +class Issue3317Test { + + @ProcessorTest + void shouldGenerateValidCode() { + Issue3317Mapper.Target target = Issue3317Mapper.INSTANCE.map( 10, 42L ); + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( 10 ); + assertThat( target.getValue() ).isEqualTo( 42L ); + } +} From 230e84efd1dde2f06e3032200051f69932402424 Mon Sep 17 00:00:00 2001 From: Roberto Oliveira Date: Tue, 25 Jul 2023 18:10:23 +0200 Subject: [PATCH 182/363] #3340 Update tarLongFileMode to use POSIX --- distribution/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/pom.xml b/distribution/pom.xml index bcfa1f3db0..afa8e31002 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -162,7 +162,7 @@ ${basedir}/src/main/assembly/dist.xml mapstruct-${project.version} - gnu + posix From 0460c373c06d771e7766dd17aaaad7d1a28928a1 Mon Sep 17 00:00:00 2001 From: Lucas Resch Date: Sun, 30 Jul 2023 10:38:24 +0200 Subject: [PATCH 183/363] #3229: Implement InjectionStrategy.SETTER --- .../java/org/mapstruct/InjectionStrategy.java | 6 +- .../chapter-4-retrieving-a-mapper.asciidoc | 6 +- .../ap/internal/gem/InjectionStrategyGem.java | 3 +- .../ap/internal/model/AnnotatedSetter.java | 59 +++++++++ .../ap/internal/model/GeneratedType.java | 8 +- .../internal/model/GeneratedTypeMethod.java | 15 +++ .../ap/internal/model/MappingMethod.java | 3 +- ...nnotationBasedComponentModelProcessor.java | 70 +++++++++-- .../ap/internal/model/AnnotatedSetter.ftl | 14 +++ .../decorator/spring/setter/PersonMapper.java | 26 ++++ .../spring/setter/PersonMapperDecorator.java | 29 +++++ .../spring/setter/SpringDecoratorTest.java | 88 +++++++++++++ .../setter/CustomerJakartaSetterMapper.java | 26 ++++ .../setter/GenderJakartaSetterMapper.java | 25 ++++ .../setter/JakartaSetterMapperTest.java | 57 +++++++++ .../jakarta/setter/SetterJakartaConfig.java | 18 +++ .../setter/CustomerJsr330SetterMapper.java | 26 ++++ .../setter/GenderJsr330SetterMapper.java | 25 ++++ .../jsr330/setter/Jsr330SetterMapperTest.java | 98 +++++++++++++++ .../jsr330/setter/SetterJsr330Config.java | 18 +++ .../CustomerRecordSpringSetterMapper.java | 23 ++++ .../setter/CustomerSpringSetterMapper.java | 25 ++++ .../setter/GenderSpringSetterMapper.java | 25 ++++ .../spring/setter/SetterSpringConfig.java | 17 +++ .../spring/setter/SpringSetterMapperTest.java | 119 ++++++++++++++++++ 25 files changed, 813 insertions(+), 16 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedSetter.ftl create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/SpringDecoratorTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/CustomerJakartaSetterMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/GenderJakartaSetterMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/JakartaSetterMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/SetterJakartaConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/CustomerJsr330SetterMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/GenderJsr330SetterMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/SetterJsr330Config.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerRecordSpringSetterMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerSpringSetterMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/GenderSpringSetterMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SetterSpringConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SpringSetterMapperTest.java diff --git a/core/src/main/java/org/mapstruct/InjectionStrategy.java b/core/src/main/java/org/mapstruct/InjectionStrategy.java index b841667671..f5029e2246 100644 --- a/core/src/main/java/org/mapstruct/InjectionStrategy.java +++ b/core/src/main/java/org/mapstruct/InjectionStrategy.java @@ -10,6 +10,7 @@ * JSR330 / Jakarta. * * @author Kevin Grüneberg + * @author Lucas Resch */ public enum InjectionStrategy { @@ -17,5 +18,8 @@ public enum InjectionStrategy { FIELD, /** Annotations are written on the constructor **/ - CONSTRUCTOR + CONSTRUCTOR, + + /** A dedicated setter method is created */ + SETTER } diff --git a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc index 67db28c7c0..35afc93a51 100644 --- a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc @@ -102,7 +102,7 @@ A mapper which uses other mapper classes (see <>) will o [[injection-strategy]] === Injection strategy -When using <>, you can choose between field and constructor injection. +When using <>, you can choose between constructor, field, or setter injection. This can be done by either providing the injection strategy via `@Mapper` or `@MapperConfig` annotation. .Using constructor injection @@ -120,9 +120,13 @@ public interface CarMapper { The generated mapper will inject classes defined in the **uses** attribute if MapStruct has detected that it needs to use an instance of it for a mapping. When `InjectionStrategy#CONSTRUCTOR` is used, the constructor will have the appropriate annotation and the fields won't. When `InjectionStrategy#FIELD` is used, the annotation is on the field itself. +When `InjectionStrategy#SETTER` is used the annotation is on a generated setter method. For now, the default injection strategy is field injection, but it can be configured with <>. It is recommended to use constructor injection to simplify testing. +When you define mappers in Spring with circular dependencies compilation may fail. +In that case utilize the `InjectionStrategy#SETTER` strategy. + [TIP] ==== For abstract classes or decorators setter injection should be used. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/InjectionStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/InjectionStrategyGem.java index f8ed93eeb4..621d07fd09 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/InjectionStrategyGem.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/InjectionStrategyGem.java @@ -13,5 +13,6 @@ public enum InjectionStrategyGem { FIELD, - CONSTRUCTOR; + CONSTRUCTOR, + SETTER; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java new file mode 100644 index 0000000000..5614376ea4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java @@ -0,0 +1,59 @@ +/* + * 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.internal.model; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Type; + +/** + * @author Lucas Resch + */ +public class AnnotatedSetter extends GeneratedTypeMethod { + + private final Field field; + private final Collection methodAnnotations; + private final Collection parameterAnnotations; + + public AnnotatedSetter(Field field, Collection methodAnnotations, + Collection parameterAnnotations) { + this.field = field; + this.methodAnnotations = methodAnnotations; + this.parameterAnnotations = parameterAnnotations; + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>( field.getImportTypes() ); + for ( Annotation annotation : methodAnnotations ) { + importTypes.addAll( annotation.getImportTypes() ); + } + + for ( Annotation annotation : parameterAnnotations ) { + importTypes.addAll( annotation.getImportTypes() ); + } + + return importTypes; + } + + public Type getType() { + return field.getType(); + } + + public String getFieldName() { + return field.getVariableName(); + } + + public Collection getMethodAnnotations() { + return methodAnnotations; + } + + public Collection getParameterAnnotations() { + return parameterAnnotations; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index 8348ecd84b..0bc3ec7bf0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -82,7 +82,7 @@ public T methods(List methods) { private final Type mapperDefinitionType; private final List annotations; - private final List methods; + private final List methods; private final SortedSet extraImportedTypes; private final boolean suppressGeneratorTimestamp; @@ -110,7 +110,7 @@ protected GeneratedType(TypeFactory typeFactory, String packageName, String name this.extraImportedTypes = extraImportedTypes; this.annotations = new ArrayList<>(); - this.methods = methods; + this.methods = new ArrayList<>(methods); this.fields = fields; this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; @@ -161,7 +161,7 @@ public void addAnnotation(Annotation annotation) { annotations.add( annotation ); } - public List getMethods() { + public List getMethods() { return methods; } @@ -204,7 +204,7 @@ public SortedSet getImportTypes() { addIfImportRequired( importedTypes, mapperDefinitionType ); - for ( MappingMethod mappingMethod : methods ) { + for ( GeneratedTypeMethod mappingMethod : methods ) { for ( Type type : mappingMethod.getImportTypes() ) { addIfImportRequired( importedTypes, type ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java new file mode 100644 index 0000000000..f02cc16b2c --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java @@ -0,0 +1,15 @@ +/* + * 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.internal.model; + +import org.mapstruct.ap.internal.model.common.ModelElement; + +/** + * @author Filip Hrisafov + */ +public abstract class GeneratedTypeMethod extends ModelElement { + +} 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 4c59216e09..599ddf1d35 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 @@ -14,7 +14,6 @@ import java.util.Set; import org.mapstruct.ap.internal.model.common.Accessibility; -import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; @@ -27,7 +26,7 @@ * * @author Gunnar Morling */ -public abstract class MappingMethod extends ModelElement { +public abstract class MappingMethod extends GeneratedTypeMethod { private final String name; private final List parameters; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java index d68936f545..014ceda58e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java @@ -11,10 +11,11 @@ import java.util.List; import java.util.ListIterator; import java.util.Set; - +import java.util.stream.Collectors; import javax.lang.model.element.TypeElement; import org.mapstruct.ap.internal.model.AnnotatedConstructor; +import org.mapstruct.ap.internal.model.AnnotatedSetter; import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.AnnotationMapperReference; import org.mapstruct.ap.internal.model.Decorator; @@ -77,6 +78,9 @@ else if ( mapper.getDecorator() != null ) { if ( injectionStrategy == InjectionStrategyGem.CONSTRUCTOR ) { buildConstructors( mapper ); } + else if ( injectionStrategy == InjectionStrategyGem.SETTER ) { + buildSetters( mapper ); + } return mapper; } @@ -110,6 +114,42 @@ private List toMapperReferences(List fields) { return mapperReferences; } + private void buildSetters(Mapper mapper) { + List mapperReferences = toMapperReferences( mapper.getFields() ); + for ( MapperReference mapperReference : mapperReferences ) { + if ( mapperReference.isUsed() ) { + AnnotatedSetter setter = new AnnotatedSetter( + mapperReference, + getMapperReferenceAnnotations(), + Collections.emptyList() + ); + mapper.getMethods().add( setter ); + } + } + + Decorator decorator = mapper.getDecorator(); + if ( decorator != null ) { + List mapperReferenceAnnotations = getMapperReferenceAnnotations(); + Set mapperReferenceAnnotationsTypes = mapperReferenceAnnotations + .stream() + .map( Annotation::getType ) + .collect( Collectors.toSet() ); + for ( Field field : decorator.getFields() ) { + if ( field instanceof AnnotationMapperReference ) { + + List fieldAnnotations = ( (AnnotationMapperReference) field ).getAnnotations(); + + List qualifiers = extractMissingAnnotations( + fieldAnnotations, + mapperReferenceAnnotationsTypes + ); + + decorator.getMethods().add( new AnnotatedSetter( field, mapperReferenceAnnotations, qualifiers ) ); + } + } + } + } + private void buildConstructors(Mapper mapper) { if ( !toMapperReferences( mapper.getFields() ).isEmpty() ) { AnnotatedConstructor annotatedConstructor = buildAnnotatedConstructorForMapper( mapper ); @@ -197,17 +237,33 @@ private void removeDuplicateAnnotations(List annotati AnnotationMapperReference annotationMapperReference = mapperReferenceIterator.next(); mapperReferenceIterator.remove(); - List qualifiers = new ArrayList<>(); - for ( Annotation annotation : annotationMapperReference.getAnnotations() ) { - if ( !mapperReferenceAnnotationsTypes.contains( annotation.getType() ) ) { - qualifiers.add( annotation ); - } - } + List qualifiers = extractMissingAnnotations( + annotationMapperReference.getAnnotations(), + mapperReferenceAnnotationsTypes + ); mapperReferenceIterator.add( annotationMapperReference.withNewAnnotations( qualifiers ) ); } } + /** + * Extract all annotations from {@code annotations} that do not have a type in {@code annotationTypes}. + * + * @param annotations the annotations from which we need to extract information + * @param annotationTypes the annotation types to ignore + * @return the annotations that are not in the {@code annotationTypes} + */ + private List extractMissingAnnotations(List annotations, + Set annotationTypes) { + List qualifiers = new ArrayList<>(); + for ( Annotation annotation : annotations ) { + if ( !annotationTypes.contains( annotation.getType() ) ) { + qualifiers.add( annotation ); + } + } + return qualifiers; + } + protected boolean additionalPublicEmptyConstructor() { return false; } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedSetter.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedSetter.ftl new file mode 100644 index 0000000000..74d4241fa5 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedSetter.ftl @@ -0,0 +1,14 @@ + <#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> + <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.AnnotatedSetter" --> + <#list methodAnnotations as annotation> + <#nt><@includeModel object=annotation/> + +public void set${fieldName?cap_first}(<#list parameterAnnotations as annotation><#nt><@includeModel object=annotation/> <@includeModel object=type/> ${fieldName}) { + this.${fieldName} = ${fieldName}; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapper.java new file mode 100644 index 0000000000..20ac9d874c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapper.java @@ -0,0 +1,26 @@ +/* + * 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.decorator.spring.setter; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.SETTER) +@DecoratedWith(PersonMapperDecorator.class) +public interface PersonMapper { + + @Mapping( target = "name", ignore = true ) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapperDecorator.java new file mode 100644 index 0000000000..8f306284a1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapperDecorator.java @@ -0,0 +1,29 @@ +/* + * 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.decorator.spring.setter; + +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +public abstract class PersonMapperDecorator implements PersonMapper { + + private PersonMapper decoratorDelegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = decoratorDelegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + + return dto; + } + + @Autowired + public void setDecoratorDelegate(@Qualifier("delegate") PersonMapper decoratorDelegate) { + this.decoratorDelegate = decoratorDelegate; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/SpringDecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/SpringDecoratorTest.java new file mode 100644 index 0000000000..c03b05395a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/SpringDecoratorTest.java @@ -0,0 +1,88 @@ +/* + * 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.decorator.spring.setter; + +import java.util.Calendar; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + PersonMapper.class, + PersonMapperDecorator.class +}) +@WithSpring +@IssueKey("3229") +@ComponentScan(basePackageClasses = SpringDecoratorTest.class) +@Configuration +public class SpringDecoratorTest { + + @Autowired + private PersonMapper personMapper; + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldInvokeDecoratorMethods() { + //given + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + //when + PersonDto personDto = personMapper.personToPersonDto( person ); + + //then + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest + public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { + //given + Address address = new Address( "42 Ocean View Drive" ); + + //when + AddressDto addressDto = personMapper.addressToAddressDto( address ); + + //then + assertThat( addressDto ).isNotNull(); + assertThat( addressDto.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/CustomerJakartaSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/CustomerJakartaSetterMapper.java new file mode 100644 index 0000000000..d422442791 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/CustomerJakartaSetterMapper.java @@ -0,0 +1,26 @@ +/* + * 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.injectionstrategy.jakarta.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper( componentModel = MappingConstants.ComponentModel.JAKARTA, + uses = GenderJakartaSetterMapper.class, + injectionStrategy = InjectionStrategy.SETTER ) +public interface CustomerJakartaSetterMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/GenderJakartaSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/GenderJakartaSetterMapper.java new file mode 100644 index 0000000000..f0d9b851e9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/GenderJakartaSetterMapper.java @@ -0,0 +1,25 @@ +/* + * 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.injectionstrategy.jakarta.setter; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = SetterJakartaConfig.class) +public interface GenderJakartaSetterMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/JakartaSetterMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/JakartaSetterMapperTest.java new file mode 100644 index 0000000000..13eba2e866 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/JakartaSetterMapperTest.java @@ -0,0 +1,57 @@ +/* + * 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.injectionstrategy.jakarta.setter; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaSetterMapper.class, + GenderJakartaSetterMapper.class, + SetterJakartaConfig.class +}) +@IssueKey("3229") +@Configuration +@WithJakartaInject +public class JakartaSetterMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveSetterInjection() { + String method = "@Inject" + lineSeparator() + + " public void setGenderJakartaSetterMapper(GenderJakartaSetterMapper genderJakartaSetterMapper) {" + + lineSeparator() + " this.genderJakartaSetterMapper = genderJakartaSetterMapper;" + + lineSeparator() + " }"; + generatedSource.forMapper( CustomerJakartaSetterMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "private GenderJakartaSetterMapper genderJakartaSetterMapper;" ) + .doesNotContain( "@Inject" + lineSeparator() + " private GenderJakartaSetterMapper" ) + .contains( method ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/SetterJakartaConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/SetterJakartaConfig.java new file mode 100644 index 0000000000..a189244936 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/SetterJakartaConfig.java @@ -0,0 +1,18 @@ +/* + * 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.injectionstrategy.jakarta.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = MappingConstants.ComponentModel.JAKARTA, + injectionStrategy = InjectionStrategy.SETTER) +public interface SetterJakartaConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/CustomerJsr330SetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/CustomerJsr330SetterMapper.java new file mode 100644 index 0000000000..ab269f83f8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/CustomerJsr330SetterMapper.java @@ -0,0 +1,26 @@ +/* + * 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.injectionstrategy.jsr330.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper( componentModel = MappingConstants.ComponentModel.JSR330, + uses = GenderJsr330SetterMapper.class, + injectionStrategy = InjectionStrategy.SETTER ) +public interface CustomerJsr330SetterMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/GenderJsr330SetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/GenderJsr330SetterMapper.java new file mode 100644 index 0000000000..d9b2f8ea9c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/GenderJsr330SetterMapper.java @@ -0,0 +1,25 @@ +/* + * 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.injectionstrategy.jsr330.setter; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = SetterJsr330Config.class) +public interface GenderJsr330SetterMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java new file mode 100644 index 0000000000..558206e51c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java @@ -0,0 +1,98 @@ +/* + * 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.injectionstrategy.jsr330.setter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJsr330SetterMapper.class, + GenderJsr330SetterMapper.class, + SetterJsr330Config.class +}) +@IssueKey("3229") +@ComponentScan(basePackageClasses = CustomerJsr330SetterMapper.class) +@Configuration +@WithJavaxInject +public class Jsr330SetterMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerJsr330SetterMapper customerMapper; + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldConvertToTarget() { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + + // when + CustomerDto customerDto = customerMapper.asTarget( customerEntity ); + + // then + assertThat( customerDto ).isNotNull(); + assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); + assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); + } + + @ProcessorTest + public void shouldHaveSetterInjection() { + String method = "@Inject" + lineSeparator() + + " public void setGenderJsr330SetterMapper(GenderJsr330SetterMapper genderJsr330SetterMapper) {" + + lineSeparator() + " this.genderJsr330SetterMapper = genderJsr330SetterMapper;" + + lineSeparator() + " }"; + generatedSource.forMapper( CustomerJsr330SetterMapper.class ) + .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) + .contains( "private GenderJsr330SetterMapper genderJsr330SetterMapper;" ) + .doesNotContain( "@Inject" + lineSeparator() + " private GenderJsr330SetterMapper" ) + .contains( method ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/SetterJsr330Config.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/SetterJsr330Config.java new file mode 100644 index 0000000000..2b13a56feb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/SetterJsr330Config.java @@ -0,0 +1,18 @@ +/* + * 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.injectionstrategy.jsr330.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = MappingConstants.ComponentModel.JSR330, + injectionStrategy = InjectionStrategy.SETTER) +public interface SetterJsr330Config { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerRecordSpringSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerRecordSpringSetterMapper.java new file mode 100644 index 0000000000..1d09bdda03 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerRecordSpringSetterMapper.java @@ -0,0 +1,23 @@ +/* + * 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.injectionstrategy.spring.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordEntity; + +/** + * @author Lucas Resch + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = { CustomerSpringSetterMapper.class, GenderSpringSetterMapper.class }, + injectionStrategy = InjectionStrategy.SETTER) +public interface CustomerRecordSpringSetterMapper { + + CustomerRecordDto asTarget(CustomerRecordEntity customerRecordEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerSpringSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerSpringSetterMapper.java new file mode 100644 index 0000000000..67dbab5e9c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerSpringSetterMapper.java @@ -0,0 +1,25 @@ +/* + * 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.injectionstrategy.spring.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Lucas Resch + */ +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, + uses = GenderSpringSetterMapper.class, + injectionStrategy = InjectionStrategy.SETTER ) +public interface CustomerSpringSetterMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/GenderSpringSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/GenderSpringSetterMapper.java new file mode 100644 index 0000000000..6243465ab5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/GenderSpringSetterMapper.java @@ -0,0 +1,25 @@ +/* + * 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.injectionstrategy.spring.setter; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Lucas Resch + */ +@Mapper(config = SetterSpringConfig.class) +public interface GenderSpringSetterMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SetterSpringConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SetterSpringConfig.java new file mode 100644 index 0000000000..18227f315a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SetterSpringConfig.java @@ -0,0 +1,17 @@ +/* + * 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.injectionstrategy.spring.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; + +/** + * @author Lucas Resch + */ +@MapperConfig(componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.SETTER) +public interface SetterSpringConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SpringSetterMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SpringSetterMapperTest.java new file mode 100644 index 0000000000..4dcf098a9e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SpringSetterMapperTest.java @@ -0,0 +1,119 @@ +/* + * 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.injectionstrategy.spring.setter; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.DefaultTimeZone; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test setter injection for component model spring. + * + * @author Lucas Resch + */ +@WithClasses( { + CustomerRecordDto.class, + CustomerRecordEntity.class, + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerRecordSpringSetterMapper.class, + CustomerSpringSetterMapper.class, + GenderSpringSetterMapper.class, + SetterSpringConfig.class +} ) +@IssueKey( "3229" ) +@ComponentScan(basePackageClasses = CustomerSpringSetterMapper.class) +@Configuration +@WithSpring +@DefaultTimeZone("Europe/Berlin") +public class SpringSetterMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerRecordSpringSetterMapper customerRecordMapper; + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldConvertToTarget() throws Exception { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + CustomerRecordEntity customerRecordEntity = new CustomerRecordEntity(); + customerRecordEntity.setCustomer( customerEntity ); + customerRecordEntity.setRegistrationDate( createDate( "31-08-1982 10:20:56" ) ); + + // when + CustomerRecordDto customerRecordDto = customerRecordMapper.asTarget( customerRecordEntity ); + + // then + assertThat( customerRecordDto ).isNotNull(); + assertThat( customerRecordDto.getCustomer() ).isNotNull(); + assertThat( customerRecordDto.getCustomer().getName() ).isEqualTo( "Samuel" ); + assertThat( customerRecordDto.getCustomer().getGender() ).isEqualTo( GenderDto.M ); + assertThat( customerRecordDto.getRegistrationDate() ).isNotNull(); + assertThat( customerRecordDto.getRegistrationDate() ).hasToString( "1982-08-31T10:20:56.000+02:00" ); + } + + @ProcessorTest + public void shouldHaveSetterInjection() { + String method = "@Autowired" + lineSeparator() + + " public void setGenderSpringSetterMapper(GenderSpringSetterMapper genderSpringSetterMapper) {" + + lineSeparator() + " this.genderSpringSetterMapper = genderSpringSetterMapper;" + + lineSeparator() + " }"; + generatedSource.forMapper( CustomerSpringSetterMapper.class ) + .content() + .contains( "private GenderSpringSetterMapper genderSpringSetterMapper;" ) + .contains( method ); + } + + private Date createDate(String date) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" ); + return sdf.parse( date ); + } + +} From 28d827a724bab212c8069903bd1bac3679370ad6 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 29 Jul 2023 14:52:24 +0200 Subject: [PATCH 184/363] Add test case for subclass mapping and bean mapping ignore by default --- .../SubclassIgnoreByDefaultMapper.java | 29 +++++++++++++++++++ .../subclassmapping/SubclassMappingTest.java | 14 +++++++++ 2 files changed, 43 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassIgnoreByDefaultMapper.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassIgnoreByDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassIgnoreByDefaultMapper.java new file mode 100644 index 0000000000..d138eb4ec3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassIgnoreByDefaultMapper.java @@ -0,0 +1,29 @@ +/* + * 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.subclassmapping; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.ap.test.subclassmapping.mappables.Bike; +import org.mapstruct.ap.test.subclassmapping.mappables.BikeDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Car; +import org.mapstruct.ap.test.subclassmapping.mappables.CarDto; +import org.mapstruct.ap.test.subclassmapping.mappables.Vehicle; +import org.mapstruct.ap.test.subclassmapping.mappables.VehicleDto; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SubclassIgnoreByDefaultMapper { + SubclassIgnoreByDefaultMapper INSTANCE = Mappers.getMapper( SubclassIgnoreByDefaultMapper.class ); + + @BeanMapping(ignoreByDefault = true) + @SubclassMapping(source = Car.class, target = CarDto.class) + @SubclassMapping(source = Bike.class, target = BikeDto.class) + @Mapping(target = "name") + VehicleDto map(Vehicle vehicle); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java index ba64d2cc7f..60e274ef19 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/SubclassMappingTest.java @@ -277,4 +277,18 @@ void erroneousMethodWithSourceTargetType() { }) void inverseSubclassMappingNotPossible() { } + + @ProcessorTest + @WithClasses(SubclassIgnoreByDefaultMapper.class) + void beanMappingIgnoreByDefaultShouldBePropagated() { + Car car = new Car(); + car.setName( "Test car" ); + car.setManual( true ); + VehicleDto target = SubclassIgnoreByDefaultMapper.INSTANCE.map( car ); + assertThat( target ) + .isInstanceOfSatisfying( CarDto.class, carDto -> { + assertThat( carDto.getName() ).isEqualTo( "Test car" ); + assertThat( carDto.isManual() ).isFalse(); + } ); + } } From 279ab2248291cba77391cae38a4b2ea40fbeae7c Mon Sep 17 00:00:00 2001 From: Venkatesh Prasad Kannan Date: Tue, 1 Aug 2023 08:48:20 +0100 Subject: [PATCH 185/363] #3309 Add `BeanMapping#unmappedSourcePolicy` --- .../main/java/org/mapstruct/BeanMapping.java | 11 +++ .../ap/internal/model/BeanMappingMethod.java | 2 +- .../model/source/BeanMappingOptions.java | 10 +++ .../unmappedsource/UnmappedSourceTest.java | 1 + ...eanMappingSourcePolicyErroneousMapper.java | 18 +++++ .../BeanMappingSourcePolicyMapper.java | 25 +++++++ .../unmappedsource/beanmapping/Source.java | 30 +++++++++ .../unmappedsource/beanmapping/Target.java | 20 ++++++ .../beanmapping/UnmappedSourceTest.java | 67 +++++++++++++++++++ 9 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/BeanMappingSourcePolicyErroneousMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/BeanMappingSourcePolicyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/UnmappedSourceTest.java diff --git a/core/src/main/java/org/mapstruct/BeanMapping.java b/core/src/main/java/org/mapstruct/BeanMapping.java index be3df0d29d..a03546b07d 100644 --- a/core/src/main/java/org/mapstruct/BeanMapping.java +++ b/core/src/main/java/org/mapstruct/BeanMapping.java @@ -152,6 +152,17 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() */ String[] ignoreUnmappedSourceProperties() default {}; + /** + * How unmapped properties of the source type of a mapping should be reported. + * If no policy is configured, the policy given via {@link MapperConfig#unmappedSourcePolicy()} or + * {@link Mapper#unmappedSourcePolicy()} will be applied, using {@link ReportingPolicy#IGNORE} by default. + * + * @return The reporting policy for unmapped source properties. + * + * @since 1.6 + */ + ReportingPolicy unmappedSourcePolicy() default ReportingPolicy.IGNORE; + /** * How unmapped properties of the target type of a mapping should be reported. * If no policy is configured, the policy given via {@link MapperConfig#unmappedTargetPolicy()} or 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 903dfa5cc5..11245b3376 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 @@ -1740,7 +1740,7 @@ private ReportingPolicyGem getUnmappedSourcePolicy() { if ( method.getOptions().getBeanMapping().isignoreByDefault() ) { return ReportingPolicyGem.IGNORE; } - return method.getOptions().getMapper().unmappedSourcePolicy(); + return method.getOptions().getBeanMapping().unmappedSourcePolicy(); } private void reportErrorForUnmappedSourcePropertiesIfRequired() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index e8f19f91f6..6f36238a35 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -113,6 +113,7 @@ private static boolean isConsistent(BeanMappingGem gem, ExecutableElement method && !gem.nullValueMappingStrategy().hasValue() && !gem.subclassExhaustiveStrategy().hasValue() && !gem.unmappedTargetPolicy().hasValue() + && !gem.unmappedSourcePolicy().hasValue() && !gem.ignoreByDefault().hasValue() && !gem.builder().hasValue() ) { @@ -179,6 +180,15 @@ public ReportingPolicyGem unmappedTargetPolicy() { .orElse( next().unmappedTargetPolicy() ); } + @Override + public ReportingPolicyGem unmappedSourcePolicy() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::unmappedSourcePolicy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( ReportingPolicyGem::valueOf ) + .orElse( next().unmappedSourcePolicy() ); + } + @Override public BuilderGem getBuilder() { return Optional.ofNullable( beanMapping ).map( BeanMappingGem::builder ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/UnmappedSourceTest.java b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/UnmappedSourceTest.java index 9b244592a2..75231f3f53 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/UnmappedSourceTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/UnmappedSourceTest.java @@ -110,4 +110,5 @@ public void shouldRaiseErrorDueToUnsetSourcePropertyWithPolicySetViaProcessorOpt ) public void shouldLeaveUnmappedSourcePropertyUnsetWithWarnPolicySetViaProcessorOption() { } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/BeanMappingSourcePolicyErroneousMapper.java b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/BeanMappingSourcePolicyErroneousMapper.java new file mode 100644 index 0000000000..eab3f8f425 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/BeanMappingSourcePolicyErroneousMapper.java @@ -0,0 +1,18 @@ +/* + * 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.unmappedsource.beanmapping; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +@Mapper(unmappedSourcePolicy = ReportingPolicy.WARN) +public interface BeanMappingSourcePolicyErroneousMapper { + + @BeanMapping(unmappedSourcePolicy = ReportingPolicy.ERROR) + Target map(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/BeanMappingSourcePolicyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/BeanMappingSourcePolicyMapper.java new file mode 100644 index 0000000000..11e1f07132 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/BeanMappingSourcePolicyMapper.java @@ -0,0 +1,25 @@ +/* + * 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.unmappedsource.beanmapping; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface BeanMappingSourcePolicyMapper { + + BeanMappingSourcePolicyMapper MAPPER = + Mappers.getMapper( BeanMappingSourcePolicyMapper.class ); + + @BeanMapping(unmappedSourcePolicy = ReportingPolicy.WARN) + Target map(Source source); + + @BeanMapping(unmappedSourcePolicy = ReportingPolicy.IGNORE) + Target map2(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/Source.java b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/Source.java new file mode 100644 index 0000000000..fc92d1a5c1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/Source.java @@ -0,0 +1,30 @@ +/* + * 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.unmappedsource.beanmapping; + +public class Source { + + private int foo; + + private int bar; + + public int getFoo() { + return foo; + } + + public void setFoo(int foo) { + this.foo = foo; + } + + public int getBar() { + return bar; + } + + public void setBar(int bar) { + this.bar = bar; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/Target.java b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/Target.java new file mode 100644 index 0000000000..5696455b46 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/Target.java @@ -0,0 +1,20 @@ +/* + * 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.unmappedsource.beanmapping; + +public class Target { + + private int foo; + + public int getFoo() { + return foo; + } + + public void setFoo(int foo) { + this.foo = foo; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/UnmappedSourceTest.java b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/UnmappedSourceTest.java new file mode 100644 index 0000000000..6eff388774 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/unmappedsource/beanmapping/UnmappedSourceTest.java @@ -0,0 +1,67 @@ +/* + * 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.unmappedsource.beanmapping; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.tools.Diagnostic.Kind; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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; + +@IssueKey("3309") +class UnmappedSourceTest { + + @ProcessorTest + @WithClasses({ Source.class, Target.class, BeanMappingSourcePolicyMapper.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = BeanMappingSourcePolicyMapper.class, + kind = Kind.WARNING, + line = 20, + message = "Unmapped source property: \"bar\".") + } + ) + public void shouldCompileWithUnmappedSourcePolicySetToWarnWithBeanMapping() { + Source source = new Source(); + Source source2 = new Source(); + + source.setFoo( 10 ); + source.setBar( 20 ); + + source2.setFoo( 1 ); + source2.setBar( 2 ); + + Target target = BeanMappingSourcePolicyMapper.MAPPER.map( source ); + Target target2 = BeanMappingSourcePolicyMapper.MAPPER.map2( source2 ); + + assertThat( target ).isNotNull(); + assertThat( target.getFoo() ).isEqualTo( 10 ); + + assertThat( target2 ).isNotNull(); + assertThat( target2.getFoo() ).isEqualTo( 1 ); + } + + @ProcessorTest + @WithClasses({ Source.class, Target.class, BeanMappingSourcePolicyErroneousMapper.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = BeanMappingSourcePolicyErroneousMapper.class, + kind = Kind.ERROR, + line = 16, + message = "Unmapped source property: \"bar\".") + } + ) + public void shouldErrorWithUnmappedSourcePolicySetToErrorWithBeanMapping() { + } + +} From b2dc64136d39547dec3bf3386837be8de7130e0c Mon Sep 17 00:00:00 2001 From: Anton Erofeev Date: Tue, 1 Aug 2023 14:17:50 +0200 Subject: [PATCH 186/363] #3292 Simplify expressions, remove redundant expressions --- .../FullFeatureCompilationExclusionCliEnhancer.java | 2 +- .../itest/tests/GradleIncrementalCompilationTest.java | 2 +- .../conversion/BigDecimalToStringConversion.java | 2 +- .../conversion/BigIntegerToStringConversion.java | 2 +- .../ap/internal/model/AbstractMappingMethodBuilder.java | 4 +--- .../mapstruct/ap/internal/model/BeanMappingMethod.java | 6 ++---- .../model/NestedTargetPropertyMappingHolder.java | 2 +- .../org/mapstruct/ap/internal/model/PropertyMapping.java | 2 +- .../model/common/DateFormatValidatorFactory.java | 9 ++------- .../org/mapstruct/ap/internal/model/common/Type.java | 3 +-- .../ap/test/builder/lifecycle/MappingContext.java | 2 +- .../test/builder/parentchild/ParentChildBuilderTest.java | 2 +- .../ap/test/builder/simple/SimpleImmutablePerson.java | 4 ++-- .../java/org/mapstruct/ap/test/callbacks/BaseMapper.java | 2 +- .../mapstruct/ap/test/callbacks/CallbackMethodTest.java | 2 +- .../ap/test/callbacks/ClassContainingCallbacks.java | 2 +- .../returning/CallbacksWithReturnValuesTest.java | 7 +------ .../ap/test/callbacks/returning/NodeMapperContext.java | 6 +++--- .../ap/test/collection/adder/_target/Target2.java | 2 +- .../collection/defaultimplementation/NoSetterTarget.java | 4 ++-- .../java/org/mapstruct/ap/test/java8stream/Target.java | 2 +- .../java/org/mapstruct/ap/test/nestedbeans/HouseDto.java | 6 ++++-- .../ap/test/nestedbeans/maps/AntonymsDictionaryDto.java | 4 ++-- .../selection/resulttype/InheritanceSelectionTest.java | 2 +- .../org/mapstruct/ap/test/source/constants/Target.java | 2 +- .../mapstruct/ap/testutil/assertions/JavaFileAssert.java | 2 +- .../testutil/compilation/model/DiagnosticDescriptor.java | 4 ++-- .../ap/testutil/runner/JdkCompilingExtension.java | 2 +- .../ap/testutil/runner/ModifiableURLClassLoader.java | 2 +- 29 files changed, 40 insertions(+), 53 deletions(-) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index e64b207990..c0ea96fd5c 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -44,7 +44,7 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces default: } - Collection result = new ArrayList( additionalExcludes.size() ); + Collection result = new ArrayList<>(additionalExcludes.size()); for ( int i = 0; i < additionalExcludes.size(); i++ ) { result.add( "-DadditionalExclude" + i + "=" + additionalExcludes.get( i ) ); } diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java index 0162582468..3d496906c0 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java @@ -62,7 +62,7 @@ private void replaceInFile(File file, CharSequence target, CharSequence replacem } private GradleRunner getRunner(String... additionalArguments) { - List fullArguments = new ArrayList( compileArgs ); + List fullArguments = new ArrayList<>(compileArgs); fullArguments.addAll( Arrays.asList( additionalArguments ) ); return runner.withArguments( fullArguments ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java index d696cf5bb5..92a93e3893 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java @@ -45,7 +45,7 @@ public String getToExpression(ConversionContext conversionContext) { public String getFromExpression(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { StringBuilder sb = new StringBuilder(); - sb.append( "(" + bigDecimal( conversionContext ) + ") " ); + sb.append( "(" ).append( bigDecimal( conversionContext ) ).append( ") " ); appendDecimalFormatter( sb, conversionContext ); sb.append( ".parse( )" ); return sb.toString(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java index 9cb1cf41cb..df0a48a673 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java @@ -47,7 +47,7 @@ public String getToExpression(ConversionContext conversionContext) { public String getFromExpression(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { StringBuilder sb = new StringBuilder(); - sb.append( "( (" + bigDecimal( conversionContext ) + ") " ); + sb.append( "( (" ).append( bigDecimal( conversionContext ) ).append( ") " ); appendDecimalFormatter( sb, conversionContext ); sb.append( ".parse( )" ); sb.append( " ).toBigInteger()" ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index c23dd50430..7329df5ab7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -130,9 +130,7 @@ public List getMethodAnnotations() { ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() ); - List annotations = new ArrayList<>(); - annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); - return annotations; + return new ArrayList<>( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); } } 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 11245b3376..00e1d9f9f9 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 @@ -266,9 +266,7 @@ else if ( !method.isUpdateMethod() ) { Map readAccessors = sourceParameter.getType().getPropertyReadAccessors(); - for ( Entry entry : readAccessors.entrySet() ) { - unprocessedSourceProperties.put( entry.getKey(), entry.getValue() ); - } + unprocessedSourceProperties.putAll( readAccessors ); } // get bean mapping (when specified as annotation ) @@ -499,7 +497,7 @@ private boolean isCorrectlySealed(Type mappingSourceType) { new ArrayList<>( mappingSourceType.getPermittedSubclasses() ); method.getOptions().getSubclassMappings().forEach( subClassOption -> { for (Iterator iterator = unusedPermittedSubclasses.iterator(); - iterator.hasNext(); ) { + iterator.hasNext(); ) { if ( ctx.getTypeUtils().isSameType( iterator.next(), subClassOption.getSource() ) ) { iterator.remove(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index b7bfc69780..20825c2dd9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -607,7 +607,7 @@ else if ( mappingsKeyedByProperty.isEmpty() && nonNested.isEmpty() ) { * @param hasNoMappings parameter indicating whether there were any extracted mappings for this target property * @param sourceParameter the source parameter for which the grouping is being done * - * @return a list with valid single target references + * @return a set with valid single target references */ private Set extractSingleTargetReferencesToUseAndPopulateSourceParameterMappings( Set singleTargetReferences, Set sourceParameterMappings, 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 bedb37cc8f..b81cf55447 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 @@ -1115,7 +1115,7 @@ private PropertyMapping(String name, String sourceBeanName, String targetWriteAc this.targetType = targetType; this.assignment = assignment; - this.dependsOn = dependsOn != null ? dependsOn : Collections.emptySet(); + this.dependsOn = dependsOn != null ? dependsOn : Collections.emptySet(); this.defaultValueAssignment = defaultValueAssignment; this.constructorMapping = constructorMapping; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java index 09e4957aad..4d54f3c3e9 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java @@ -61,13 +61,8 @@ else if ( isJodaDateTimeSupposed( sourceType, targetType ) ) { dateFormatValidator = new JodaTimeDateFormatValidator(); } else { - dateFormatValidator = new DateFormatValidator() { - @Override - public DateFormatValidationResult validate(String dateFormat) { - return new DateFormatValidationResult( true, Message.GENERAL_UNSUPPORTED_DATE_FORMAT_CHECK, - sourceType, targetType ); - } - }; + dateFormatValidator = dateFormat -> new DateFormatValidationResult( + true, Message.GENERAL_UNSUPPORTED_DATE_FORMAT_CHECK, sourceType, targetType ); } return dateFormatValidator; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 933f1f6ce0..a6d942748d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -684,10 +684,9 @@ public PresenceCheckAccessor getPresenceChecker(String propertyName) { */ public Map getPropertyReadAccessors() { if ( readAccessors == null ) { - Map modifiableGetters = new LinkedHashMap<>(); Map recordAccessors = filters.recordAccessorsIn( getRecordComponents() ); - modifiableGetters.putAll( recordAccessors ); + Map modifiableGetters = new LinkedHashMap<>(recordAccessors); List getterList = filters.getterMethodsIn( getAllMethods() ); for ( ReadAccessor getter : getterList ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java index 079be90a81..2065bad478 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java @@ -18,7 +18,7 @@ */ public class MappingContext { - private final List invokedMethods = new ArrayList(); + private final List invokedMethods = new ArrayList<>(); @BeforeMapping public void beforeWithoutParameters() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ParentChildBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ParentChildBuilderTest.java index dd45573a59..562770a2bb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ParentChildBuilderTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ParentChildBuilderTest.java @@ -26,7 +26,7 @@ public class ParentChildBuilderTest { public void testParentChildBuilderMapper() { final MutableParent parent = new MutableParent(); parent.setCount( 4 ); - parent.setChildren( new ArrayList() ); + parent.setChildren( new ArrayList<>() ); parent.getChildren().add( new MutableChild( "Phineas" ) ); parent.getChildren().add( new MutableChild( "Ferb" ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java index daa3dfad0b..2fe2c8ded2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java @@ -22,7 +22,7 @@ public class SimpleImmutablePerson { this.job = builder.job; this.city = builder.city; this.address = builder.address; - this.children = new ArrayList( builder.children ); + this.children = new ArrayList<>(builder.children); } public static Builder builder() { @@ -59,7 +59,7 @@ public static class Builder { private String job; private String city; private String address; - private List children = new ArrayList(); + private List children = new ArrayList<>(); public Builder age(int age) { this.age = age; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/BaseMapper.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/BaseMapper.java index bee462accc..4e02045ed5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/BaseMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/BaseMapper.java @@ -24,7 +24,7 @@ public abstract class BaseMapper { @Qualified public abstract Target sourceToTargetQualified(Source source); - private static final List INVOCATIONS = new ArrayList(); + private static final List INVOCATIONS = new ArrayList<>(); @BeforeMapping public void noArgsBeforeMapping() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/CallbackMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/CallbackMethodTest.java index 7026a335d8..825b359152 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/CallbackMethodTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/CallbackMethodTest.java @@ -111,7 +111,7 @@ public void callbackMethodsForEnumMappingCalled() { SourceEnum source = SourceEnum.B; TargetEnum target = SourceTargetMapper.INSTANCE.toTargetEnum( source ); - List invocations = new ArrayList(); + List invocations = new ArrayList<>(); invocations.addAll( allBeforeMappingMethods( source, target, TargetEnum.class ) ); invocations.addAll( allAfterMappingMethods( source, target, TargetEnum.class ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ClassContainingCallbacks.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ClassContainingCallbacks.java index 762247045d..2889a4ad30 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ClassContainingCallbacks.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ClassContainingCallbacks.java @@ -18,7 +18,7 @@ * @author Andreas Gudian */ public class ClassContainingCallbacks { - private static final List INVOCATIONS = new ArrayList(); + private static final List INVOCATIONS = new ArrayList<>(); @BeforeMapping public void noArgsBeforeMapping() { 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 7d6eaacb79..10d2a29cd7 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 @@ -50,12 +50,7 @@ void updatingWithDefaultHandlingRaisesStackOverflowError() { @ProcessorTest void mappingWithContextCorrectlyResolvesCycles() { final AtomicReference contextLevel = new AtomicReference<>( null ); - ContextListener contextListener = new ContextListener() { - @Override - public void methodCalled(Integer level, String method, Object source, Object target) { - contextLevel.set( level ); - } - }; + ContextListener contextListener = (level, method, source, target) -> contextLevel.set( level ); NodeMapperContext.addContextListener( contextListener ); try { diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperContext.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperContext.java index 40a1a63937..325778b85e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperContext.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperContext.java @@ -19,11 +19,11 @@ * @author Pascal Grün */ public class NodeMapperContext { - private static final ThreadLocal LEVEL = new ThreadLocal(); - private static final ThreadLocal> MAPPING = new ThreadLocal>(); + private static final ThreadLocal LEVEL = new ThreadLocal<>(); + private static final ThreadLocal> MAPPING = new ThreadLocal<>(); /** Only for test-inspection */ - private static final List LISTENERS = new CopyOnWriteArrayList(); + private static final List LISTENERS = new CopyOnWriteArrayList<>(); private NodeMapperContext() { // Only allow static access diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target2.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target2.java index 850c6ee94d..bd6e8f9979 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target2.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target2.java @@ -16,7 +16,7 @@ */ public class Target2 { - private List attributes = new ArrayList(); + private List attributes = new ArrayList<>(); public Foo addAttribute( Foo foo ) { attributes.add( foo ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterTarget.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterTarget.java index 562966dfd1..817879fa01 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterTarget.java @@ -15,8 +15,8 @@ * */ public class NoSetterTarget { - private List listValues = new ArrayList(); - private Map mapValues = new HashMap(); + private List listValues = new ArrayList<>(); + private Map mapValues = new HashMap<>(); public List getListValues() { return listValues; diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/Target.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/Target.java index 2d63c3f166..49b23530df 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/Target.java @@ -77,7 +77,7 @@ public Set getColours() { public List getStringListNoSetter() { if ( stringListNoSetter == null ) { - stringListNoSetter = new ArrayList(); + stringListNoSetter = new ArrayList<>(); } return stringListNoSetter; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/HouseDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/HouseDto.java index 1d92eb7e4f..71beedaa93 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/HouseDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/HouseDto.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.nestedbeans; +import java.util.Objects; + public class HouseDto { private String name; @@ -58,10 +60,10 @@ public boolean equals(Object o) { if ( year != houseDto.year ) { return false; } - if ( name != null ? !name.equals( houseDto.name ) : houseDto.name != null ) { + if ( !Objects.equals( name, houseDto.name ) ) { return false; } - return roof != null ? roof.equals( houseDto.roof ) : houseDto.roof == null; + return Objects.equals( roof, houseDto.roof ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionaryDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionaryDto.java index 33ccfbc1cc..9ac7913c9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionaryDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionaryDto.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.nestedbeans.maps; import java.util.Map; +import java.util.Objects; public class AntonymsDictionaryDto { private Map antonyms; @@ -36,8 +37,7 @@ public boolean equals(Object o) { AntonymsDictionaryDto antonymsDictionaryDto = (AntonymsDictionaryDto) o; - return antonyms != null ? antonyms.equals( antonymsDictionaryDto.antonyms ) : - antonymsDictionaryDto.antonyms == null; + return Objects.equals( antonyms, antonymsDictionaryDto.antonyms ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java index 05245bafe0..8cd58f0664 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java @@ -192,7 +192,7 @@ public void testShouldSelectResultTypeInCaseOfAmbiguityForIterable() { }) public void testShouldSelectResultTypeInCaseOfAmbiguityForMap() { - Map source = new HashMap(); + Map source = new HashMap<>(); source.put( new AppleDto( "GoldenDelicious" ), new AppleDto( "AppleDto" ) ); Map result = FruitFamilyMapper.INSTANCE.mapToGoldenDeliciousMap( source ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/Target.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/Target.java index 98ca13eb0b..7897fe2bd6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/constants/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/Target.java @@ -20,7 +20,7 @@ public class Target { private int integerConstant; private Long longWrapperConstant; private Date dateConstant; - private List nameConstants = new ArrayList(); + private List nameConstants = new ArrayList<>(); private CountryEnum country; public String getPropertyThatShouldBeMapped() { diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java b/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java index 2bcfbd94d3..e6eabd9f84 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java @@ -59,7 +59,7 @@ public AbstractCharSequenceAssert content() { return Assertions.assertThat( FileUtils.readFileToString( actual, StandardCharsets.UTF_8 ) ); } catch ( IOException e ) { - failWithMessage( "Unable to read" + actual.toString() + ". Exception: " + e.getMessage() ); + failWithMessage( "Unable to read" + actual + ". Exception: " + e.getMessage() ); } return null; } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java index 94dda74708..ecfec14a16 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java @@ -131,7 +131,7 @@ private static URI getUri(javax.tools.Diagnostic diagn //Make the URI absolute in case it isn't (the case with JDK 6) try { - return new URI( "file:" + uri.toString() ); + return new URI( "file:" + uri ); } catch ( URISyntaxException e ) { throw new RuntimeException( e ); @@ -189,7 +189,7 @@ public boolean equals(Object obj) { if ( kind != other.kind ) { return false; } - if ( line != other.line ) { + if ( !Objects.equals( line, other.line ) ) { return false; } if ( !Objects.equals( message, other.message ) ) { diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index 7b44a4733b..5185bfb7ed 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -135,7 +135,7 @@ protected List filterExpectedDiagnostics(List filtered = new ArrayList( expectedDiagnostics.size() ); + List filtered = new ArrayList<>(expectedDiagnostics.size()); // The JDK 8 compiler only reports the first message of kind ERROR that is reported for one source file line, // so we filter out the surplus diagnostics. The input list is already sorted by file name and line number, diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java index 9cc41fe134..2dd5d5b8e7 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java @@ -31,7 +31,7 @@ final class ModifiableURLClassLoader extends URLClassLoader { tryRegisterAsParallelCapable(); } - private final ConcurrentMap addedURLs = new ConcurrentHashMap(); + private final ConcurrentMap addedURLs = new ConcurrentHashMap<>(); ModifiableURLClassLoader(ClassLoader parent) { super( new URL[] { }, parent ); From 812faeef5100ea28db6081c9f7b5b22b4450bf95 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Tue, 1 Aug 2023 09:53:58 +0200 Subject: [PATCH 187/363] Use presence checks for checking source parameter presence Instead of explicitly doing a null check use a PresenceCheck mechanism for * BeanMappingMethod * ContainerMappingMethod * MapMappingMethod --- .../ap/internal/model/BeanMappingMethod.java | 49 +++++++++++------ .../model/ContainerMappingMethod.java | 26 ++++++++-- .../ap/internal/model/MapMappingMethod.java | 26 ++++++++-- .../model/MethodReferencePresenceCheck.java | 15 ++++++ .../model/common/NegatePresenceCheck.java | 52 +++++++++++++++++++ .../internal/model/common/PresenceCheck.java | 2 + .../AllPresenceChecksPresenceCheck.java | 6 +++ .../presence/JavaExpressionPresenceCheck.java | 6 +++ .../model/presence/NullPresenceCheck.java | 16 ++++++ .../model/presence/SuffixPresenceCheck.java | 16 ++++++ .../ap/internal/model/BeanMappingMethod.ftl | 20 +++---- .../internal/model/IterableMappingMethod.ftl | 2 +- .../ap/internal/model/MapMappingMethod.ftl | 2 +- .../model/MethodReferencePresenceCheck.ftl | 2 +- .../ap/internal/model/StreamMappingMethod.ftl | 2 +- .../model/common/NegatePresenceCheck.ftl | 9 ++++ .../model/presence/NullPresenceCheck.ftl | 2 +- .../model/presence/SuffixPresenceCheck.ftl | 2 +- 18 files changed, 214 insertions(+), 41 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.ftl 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 00e1d9f9f9..ee347ef35e 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 @@ -46,11 +46,13 @@ import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.source.BeanMappingOptions; import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; @@ -88,6 +90,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { private final List propertyMappings; private final Map> mappingsByParameter; private final Map> constructorMappingsByParameter; + private final Map presenceChecksByParameter; private final List constantMappings; private final List constructorConstantMappings; private final List subclassMappings; @@ -1869,11 +1872,19 @@ private BeanMappingMethod(Method method, // parameter mapping. this.mappingsByParameter = new HashMap<>(); this.constantMappings = new ArrayList<>( propertyMappings.size() ); + this.presenceChecksByParameter = new LinkedHashMap<>(); this.constructorMappingsByParameter = new LinkedHashMap<>(); this.constructorConstantMappings = new ArrayList<>(); - Set sourceParameterNames = getSourceParameters().stream() - .map( Parameter::getName ) - .collect( Collectors.toSet() ); + Set sourceParameterNames = new HashSet<>(); + for ( Parameter sourceParameter : getSourceParameters() ) { + sourceParameterNames.add( sourceParameter.getName() ); + if ( !sourceParameter.getType().isPrimitive() ) { + presenceChecksByParameter.put( + sourceParameter.getName(), + new NullPresenceCheck( sourceParameter.getName() ) + ); + } + } for ( PropertyMapping mapping : propertyMappings ) { if ( mapping.isConstructorMapping() ) { if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { @@ -1984,44 +1995,50 @@ public Set getImportTypes() { return types; } - public List getSourceParametersExcludingPrimitives() { - return getSourceParameters().stream() - .filter( parameter -> !parameter.getType().isPrimitive() ) - .collect( Collectors.toList() ); + public Collection getSourcePresenceChecks() { + return presenceChecksByParameter.values(); + } + + public Map getPresenceChecksByParameter() { + return presenceChecksByParameter; + } + + public PresenceCheck getPresenceCheckByParameter(Parameter parameter) { + return presenceChecksByParameter.get( parameter.getName() ); } - public List getSourceParametersNeedingNullCheck() { + public List getSourceParametersNeedingPresenceCheck() { return getSourceParameters().stream() - .filter( this::needsNullCheck ) + .filter( this::needsPresenceCheck ) .collect( Collectors.toList() ); } - public List getSourceParametersNotNeedingNullCheck() { + public List getSourceParametersNotNeedingPresenceCheck() { return getSourceParameters().stream() - .filter( parameter -> !needsNullCheck( parameter ) ) + .filter( parameter -> !needsPresenceCheck( parameter ) ) .collect( Collectors.toList() ); } - private boolean needsNullCheck(Parameter parameter) { - if ( parameter.getType().isPrimitive() ) { + private boolean needsPresenceCheck(Parameter parameter) { + if ( !presenceChecksByParameter.containsKey( parameter.getName() ) ) { return false; } List mappings = propertyMappingsByParameter( parameter ); - if ( mappings.size() == 1 && doesNotNeedNullCheckForSourceParameter( mappings.get( 0 ) ) ) { + if ( mappings.size() == 1 && doesNotNeedPresenceCheckForSourceParameter( mappings.get( 0 ) ) ) { return false; } mappings = constructorPropertyMappingsByParameter( parameter ); - if ( mappings.size() == 1 && doesNotNeedNullCheckForSourceParameter( mappings.get( 0 ) ) ) { + if ( mappings.size() == 1 && doesNotNeedPresenceCheckForSourceParameter( mappings.get( 0 ) ) ) { return false; } return true; } - private boolean doesNotNeedNullCheckForSourceParameter(PropertyMapping mapping) { + private boolean doesNotNeedPresenceCheckForSourceParameter(PropertyMapping mapping) { if ( mapping.getAssignment().isCallingUpdateMethod() ) { // If the mapping assignment is calling an update method then we should do a null check // in the bean mapping diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java index 21e547eea0..7ecb7b0ed1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java @@ -12,7 +12,9 @@ import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.util.Strings; @@ -30,6 +32,8 @@ public abstract class ContainerMappingMethod extends NormalTypeMappingMethod { private final SelectionParameters selectionParameters; private final String index1Name; private final String index2Name; + private final Parameter sourceParameter; + private final PresenceCheck sourceParameterPresenceCheck; private IterableCreation iterableCreation; ContainerMappingMethod(Method method, List annotations, @@ -45,16 +49,30 @@ public abstract class ContainerMappingMethod extends NormalTypeMappingMethod { this.selectionParameters = selectionParameters; this.index1Name = Strings.getSafeVariableName( "i", existingVariables ); this.index2Name = Strings.getSafeVariableName( "j", existingVariables ); - } - public Parameter getSourceParameter() { + Parameter sourceParameter = null; for ( Parameter parameter : getParameters() ) { if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) { - return parameter; + sourceParameter = parameter; + break; } } - throw new IllegalStateException( "Method " + this + " has no source parameter." ); + if ( sourceParameter == null ) { + throw new IllegalStateException( "Method " + this + " has no source parameter." ); + } + + this.sourceParameter = sourceParameter; + this.sourceParameterPresenceCheck = new NullPresenceCheck( this.sourceParameter.getName() ); + + } + + public Parameter getSourceParameter() { + return sourceParameter; + } + + public PresenceCheck getSourceParameterPresenceCheck() { + return sourceParameterPresenceCheck; } public IterableCreation getIterableCreation() { 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 85766e2eca..42dbf826d0 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 @@ -15,8 +15,10 @@ import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; @@ -35,6 +37,8 @@ public class MapMappingMethod extends NormalTypeMappingMethod { private final Assignment keyAssignment; private final Assignment valueAssignment; + private final Parameter sourceParameter; + private final PresenceCheck sourceParameterPresenceCheck; private IterableCreation iterableCreation; public static class Builder extends AbstractMappingMethodBuilder { @@ -235,16 +239,28 @@ private MapMappingMethod(Method method, List annotations, this.keyAssignment = keyAssignment; this.valueAssignment = valueAssignment; - } - - public Parameter getSourceParameter() { + Parameter sourceParameter = null; for ( Parameter parameter : getParameters() ) { if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) { - return parameter; + sourceParameter = parameter; + break; } } - throw new IllegalStateException( "Method " + this + " has no source parameter." ); + if ( sourceParameter == null ) { + throw new IllegalStateException( "Method " + this + " has no source parameter." ); + } + + this.sourceParameter = sourceParameter; + this.sourceParameterPresenceCheck = new NullPresenceCheck( this.sourceParameter.getName() ); + } + + public Parameter getSourceParameter() { + return sourceParameter; + } + + public PresenceCheck getSourceParameterPresenceCheck() { + return sourceParameterPresenceCheck; } public List getSourceElementTypes() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java index e6adceab03..879a76efbe 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java @@ -18,9 +18,15 @@ public class MethodReferencePresenceCheck extends ModelElement implements PresenceCheck { protected final MethodReference methodReference; + protected final boolean negate; public MethodReferencePresenceCheck(MethodReference methodReference) { + this( methodReference, false ); + } + + public MethodReferencePresenceCheck(MethodReference methodReference, boolean negate) { this.methodReference = methodReference; + this.negate = negate; } @Override @@ -32,6 +38,15 @@ public MethodReference getMethodReference() { return methodReference; } + public boolean isNegate() { + return negate; + } + + @Override + public PresenceCheck negate() { + return new MethodReferencePresenceCheck( methodReference, true ); + } + @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java new file mode 100644 index 0000000000..f8844edcdd --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java @@ -0,0 +1,52 @@ +/* + * 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.internal.model.common; + +import java.util.Objects; +import java.util.Set; + +/** + * @author Filip Hrisafov + */ +public class NegatePresenceCheck extends ModelElement implements PresenceCheck { + + private final PresenceCheck presenceCheck; + + public NegatePresenceCheck(PresenceCheck presenceCheck) { + this.presenceCheck = presenceCheck; + } + + public PresenceCheck getPresenceCheck() { + return presenceCheck; + } + + @Override + public Set getImportTypes() { + return presenceCheck.getImportTypes(); + } + + @Override + public PresenceCheck negate() { + return presenceCheck; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + NegatePresenceCheck that = (NegatePresenceCheck) o; + return Objects.equals( presenceCheck, that.presenceCheck ); + } + + @Override + public int hashCode() { + return Objects.hash( presenceCheck ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/PresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/PresenceCheck.java index e77e24f218..ec286480d9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/PresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/PresenceCheck.java @@ -21,4 +21,6 @@ public interface PresenceCheck { */ Set getImportTypes(); + PresenceCheck negate(); + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java index df8b008032..6bb191214f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java @@ -11,6 +11,7 @@ import java.util.Set; import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.NegatePresenceCheck; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; @@ -29,6 +30,11 @@ public Collection getPresenceChecks() { return presenceChecks; } + @Override + public PresenceCheck negate() { + return new NegatePresenceCheck( this ); + } + @Override public Set getImportTypes() { Set importTypes = new HashSet<>(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java index d4f52c9f27..e100be7e79 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java @@ -10,6 +10,7 @@ import java.util.Set; import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.NegatePresenceCheck; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; @@ -28,6 +29,11 @@ public String getJavaExpression() { return javaExpression; } + @Override + public PresenceCheck negate() { + return new NegatePresenceCheck( this ); + } + @Override public Set getImportTypes() { return Collections.emptySet(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java index c9cae61b7c..3496486e1d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java @@ -19,20 +19,36 @@ public class NullPresenceCheck extends ModelElement implements PresenceCheck { private final String sourceReference; + private final boolean negate; public NullPresenceCheck(String sourceReference) { this.sourceReference = sourceReference; + this.negate = false; + } + + public NullPresenceCheck(String sourceReference, boolean negate) { + this.sourceReference = sourceReference; + this.negate = negate; } public String getSourceReference() { return sourceReference; } + public boolean isNegate() { + return negate; + } + @Override public Set getImportTypes() { return Collections.emptySet(); } + @Override + public PresenceCheck negate() { + return new NullPresenceCheck( sourceReference, !negate ); + } + @Override public boolean equals(Object o) { if ( this == o ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java index 3710e97173..0f75fbba69 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java @@ -10,6 +10,7 @@ import java.util.Set; import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.NegatePresenceCheck; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; @@ -20,10 +21,16 @@ public class SuffixPresenceCheck extends ModelElement implements PresenceCheck { private final String sourceReference; private final String suffix; + private final boolean negate; public SuffixPresenceCheck(String sourceReference, String suffix) { + this( sourceReference, suffix, false ); + } + + public SuffixPresenceCheck(String sourceReference, String suffix, boolean negate) { this.sourceReference = sourceReference; this.suffix = suffix; + this.negate = negate; } public String getSourceReference() { @@ -34,6 +41,15 @@ public String getSuffix() { return suffix; } + public boolean isNegate() { + return negate; + } + + @Override + public PresenceCheck negate() { + return new NegatePresenceCheck( this ); + } + @Override public Set getImportTypes() { return Collections.emptySet(); diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 0f9b96bb66..8fb5bfdef0 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -27,8 +27,8 @@ - <#if !mapNullToDefault && !sourceParametersExcludingPrimitives.empty> - if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && ) { + <#if !mapNullToDefault && !sourcePresenceChecks.empty> + if ( <#list sourcePresenceChecks as sourcePresenceCheck><@includeModel object=sourcePresenceCheck.negate() /><#if sourcePresenceCheck_has_next> && ) { return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /><#else>null; } @@ -47,19 +47,19 @@ <#if !existingInstanceMapping> <#if hasConstructorMappings()> <#if (sourceParameters?size > 1)> - <#list sourceParametersNeedingNullCheck as sourceParam> + <#list sourceParametersNeedingPresenceCheck as sourceParam> <#if (constructorPropertyMappingsByParameter(sourceParam)?size > 0)> <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; - if ( ${sourceParam.name} != null ) { + if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) { <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> } - <#list sourceParametersNotNeedingNullCheck as sourceParam> + <#list sourceParametersNotNeedingPresenceCheck as sourceParam> <#if (constructorPropertyMappingsByParameter(sourceParam)?size > 0)> <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; @@ -71,7 +71,7 @@ <#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; - <#if mapNullToDefault>if ( ${sourceParameters[0].name} != null ) { + <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { <#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -100,16 +100,16 @@ <#if (sourceParameters?size > 1)> - <#list sourceParametersNeedingNullCheck as sourceParam> + <#list sourceParametersNeedingPresenceCheck as sourceParam> <#if (propertyMappingsByParameter(sourceParam)?size > 0)> - if ( ${sourceParam.name} != null ) { + if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) { <#list propertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> } - <#list sourceParametersNotNeedingNullCheck as sourceParam> + <#list sourceParametersNotNeedingPresenceCheck as sourceParam> <#if (propertyMappingsByParameter(sourceParam)?size > 0)> <#list propertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -117,7 +117,7 @@ <#else> - <#if mapNullToDefault>if ( ${sourceParameters[0].name} != null ) { + <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { <#list propertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl index 9af6e762bf..4d08c44b38 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl @@ -17,7 +17,7 @@ - if ( ${sourceParameter.name} == null ) { + if ( <@includeModel object=sourceParameterPresenceCheck.negate() /> ) { <#if !mapNullToDefault> return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null; <#else> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl index 0e388da103..1227dbd27a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl @@ -17,7 +17,7 @@ - if ( ${sourceParameter.name} == null ) { + if ( <@includeModel object=sourceParameterPresenceCheck.negate() /> ) { <#if !mapNullToDefault> return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null; <#else> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl index 9a1c7b24ae..0452e1699a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl @@ -6,7 +6,7 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" --> -<@includeModel object=methodReference +<#if isNegate()>!<@includeModel object=methodReference presenceCheck=true targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl index 29977bdd5d..7d0080a1d2 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl @@ -18,7 +18,7 @@ - if ( ${sourceParameter.name} == null ) { + if ( <@includeModel object=sourceParameterPresenceCheck.negate() /> ) { <#if !mapNullToDefault> return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null; <#else> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.ftl new file mode 100644 index 0000000000..6951952487 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.ftl @@ -0,0 +1,9 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.NegatePresenceCheck" --> +!( <@includeModel object=presenceCheck /> ) \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.ftl index 6e5494c735..ebd9f12eca 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.ftl @@ -6,4 +6,4 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.NullPresenceCheck" --> -${sourceReference} != null \ No newline at end of file +${sourceReference} <#if isNegate()>==<#else>!= null \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.ftl index 103b4e7f9c..8eddad3485 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.ftl @@ -6,4 +6,4 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck" --> -${sourceReference}${suffix} \ No newline at end of file +<#if isNegate()>!${sourceReference}${suffix} \ No newline at end of file From 721288140aa1c1210de222224b44f23e82be0c53 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Fri, 4 Aug 2023 10:14:53 +0200 Subject: [PATCH 188/363] Feature/2663 (#3007) #2663 Fix for 2-step mapping with generics. --------- Co-authored-by: Ben Zegveld --- .../ap/internal/model/common/Type.java | 116 +++++++++++++++++- .../creation/MappingResolverImpl.java | 2 +- .../ap/test/bugs/_2663/Issue2663Mapper.java | 31 +++++ .../ap/test/bugs/_2663/Issue2663Test.java | 47 +++++++ .../ap/test/bugs/_2663/JsonNullable.java | 44 +++++++ .../ap/test/bugs/_2663/Nullable.java | 45 +++++++ .../ap/test/bugs/_2663/NullableHelper.java | 23 ++++ .../mapstruct/ap/test/bugs/_2663/Request.java | 44 +++++++ .../ap/test/bugs/_2663/RequestDto.java | 44 +++++++ 9 files changed, 390 insertions(+), 6 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/JsonNullable.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Nullable.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/NullableHelper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Request.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/RequestDto.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index a6d942748d..c18574fcfb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -605,6 +605,21 @@ public Type withoutBounds() { ); } + private Type replaceGeneric(Type oldGenericType, Type newType) { + if ( !typeParameters.contains( oldGenericType ) || newType == null ) { + return this; + } + newType = newType.getBoxedEquivalent(); + TypeMirror[] replacedTypeMirrors = new TypeMirror[typeParameters.size()]; + for ( int i = 0; i < typeParameters.size(); i++ ) { + Type typeParameter = typeParameters.get( i ); + replacedTypeMirrors[i] = + typeParameter.equals( oldGenericType ) ? newType.typeMirror : typeParameter.typeMirror; + } + + return typeFactory.getType( typeUtils.getDeclaredType( typeElement, replacedTypeMirrors ) ); + } + /** * Whether this type is assignable to the given other type, considering the "extends / upper bounds" * as well. @@ -1377,9 +1392,9 @@ public boolean isLiteral() { /** * Steps through the declaredType in order to find a match for this typeVar Type. It aligns with - * the provided parameterized type where this typeVar type is used. - * - * For example: + * the provided parameterized type where this typeVar type is used.
      + *
      + * For example:

            * {@code
            * this: T
            * declaredType: JAXBElement
      @@ -1392,12 +1407,13 @@ public boolean isLiteral() {
            * parameterizedType: Callable
            * return: BigDecimal
            * }
      +     * 
      * * @param declared the type * @param parameterized the parameterized type * - * @return - the same type when this is not a type var in the broadest sense (T, T[], or ? extends T) - * - the matching parameter in the parameterized type when this is a type var when found + * @return - the same type when this is not a type var in the broadest sense (T, T[], or ? extends T)
      + * - the matching parameter in the parameterized type when this is a type var when found
      * - null in all other cases */ public ResolvedPair resolveParameterToType(Type declared, Type parameterized) { @@ -1408,6 +1424,96 @@ public ResolvedPair resolveParameterToType(Type declared, Type parameterized) { return new ResolvedPair( this, this ); } + /** + * Resolves generic types using the declared and parameterized types as input.
      + *
      + * For example: + *
      +     * {@code
      +     * this: T
      +     * declaredType: JAXBElement
      +     * parameterizedType: JAXBElement
      +     * result: Integer
      +     *
      +     * this: List
      +     * declaredType: JAXBElement
      +     * parameterizedType: JAXBElement
      +     * result: List
      +     *
      +     * this: List
      +     * declaredType: JAXBElement
      +     * parameterizedType: JAXBElement
      +     * result: List
      +     *
      +     * this: List>
      +     * declaredType: JAXBElement
      +     * parameterizedType: JAXBElement
      +     * result: List>
      +     * }
      +     * 
      + * It also works for partial matching.
      + *
      + * For example: + *
      +     * {@code
      +     * this: Map
      +     * declaredType: JAXBElement
      +     * parameterizedType: JAXBElement
      +     * result: Map
      +     * }
      +     * 
      + * It also works with multiple parameters at both sides.
      + *
      + * For example when reversing Key/Value for a Map: + *
      +     * {@code
      +     * this: Map
      +     * declaredType: HashMap
      +     * parameterizedType: HashMap
      +     * result: Map
      +     * }
      +     * 
      + * + * Mismatch result examples: + *
      +     * {@code
      +     * this: T
      +     * declaredType: JAXBElement
      +     * parameterizedType: JAXBElement
      +     * result: null
      +     *
      +     * this: List
      +     * declaredType: JAXBElement
      +     * parameterizedType: JAXBElement
      +     * result: List
      +     * }
      +     * 
      + * + * @param declared the type + * @param parameterized the parameterized type + * + * @return - the result of {@link #resolveParameterToType(Type, Type)} when this type itself is a type var.
      + * - the type but then with the matching type parameters replaced.
      + * - the same type when this type does not contain matching type parameters. + */ + public Type resolveGenericTypeParameters(Type declared, Type parameterized) { + if ( isTypeVar() || isArrayTypeVar() || isWildCardBoundByTypeVar() ) { + return resolveParameterToType( declared, parameterized ).getMatch(); + } + Type resultType = this; + for ( Type generic : getTypeParameters() ) { + if ( generic.isTypeVar() || generic.isArrayTypeVar() || generic.isWildCardBoundByTypeVar() ) { + ResolvedPair resolveParameterToType = generic.resolveParameterToType( declared, parameterized ); + resultType = resultType.replaceGeneric( generic, resolveParameterToType.getMatch() ); + } + else { + Type replacementType = generic.resolveParameterToType( declared, parameterized ).getMatch(); + resultType = resultType.replaceGeneric( generic, replacementType ); + } + } + return resultType; + } + public boolean isWildCardBoundByTypeVar() { return ( hasExtendsBound() || hasSuperBound() ) && getTypeBound().isTypeVar(); } 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 586465d5f1..32fa1af931 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 @@ -825,7 +825,7 @@ private MethodMethod getBestMatch(Type sourceType, Type targetType, Best for ( T2 yCandidate : yMethods ) { Type ySourceType = yCandidate.getMappingSourceType(); - ySourceType = ySourceType.resolveParameterToType( targetType, yCandidate.getResultType() ).getMatch(); + ySourceType = ySourceType.resolveGenericTypeParameters( targetType, yCandidate.getResultType() ); Type yTargetType = yCandidate.getResultType(); if ( ySourceType == null || !yTargetType.isRawAssignableTo( targetType ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Mapper.java new file mode 100644 index 0000000000..0ac236372a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Mapper.java @@ -0,0 +1,31 @@ +/* + * 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._2663; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper( uses = NullableHelper.class ) +public interface Issue2663Mapper { + + Issue2663Mapper INSTANCE = Mappers.getMapper( Issue2663Mapper.class ); + + Request map(RequestDto dto); + + default JsonNullable mapJsonNullableChildren(JsonNullable dtos) { + if ( dtos.isPresent() ) { + return JsonNullable.of( mapChild( dtos.get() ) ); + } + else { + return JsonNullable.undefined(); + } + } + + Request.ChildRequest mapChild(RequestDto.ChildRequestDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Test.java new file mode 100644 index 0000000000..efd14b1d36 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Test.java @@ -0,0 +1,47 @@ +/* + * 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._2663; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey( "2663" ) +@WithClasses({ + Issue2663Mapper.class, + JsonNullable.class, + Nullable.class, + NullableHelper.class, + Request.class, + RequestDto.class +}) +public class Issue2663Test { + + @ProcessorTest + public void shouldUnpackGenericsCorrectly() { + RequestDto dto = new RequestDto(); + dto.setName( JsonNullable.of( "Tester" ) ); + + Request request = Issue2663Mapper.INSTANCE.map( dto ); + + assertThat( request.getName() ) + .extracting( Nullable::get ) + .isEqualTo( "Tester" ); + + dto.setName( JsonNullable.undefined() ); + + request = Issue2663Mapper.INSTANCE.map( dto ); + + assertThat( request.getName() ) + .extracting( Nullable::isPresent ) + .isEqualTo( false ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/JsonNullable.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/JsonNullable.java new file mode 100644 index 0000000000..4794aff940 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/JsonNullable.java @@ -0,0 +1,44 @@ +/* + * 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._2663; + +import java.util.NoSuchElementException; + +/** + * @author Filip Hrisafov + */ +public class JsonNullable { + + private static final JsonNullable UNDEFINED = new JsonNullable<>( null, false ); + + private final T value; + private final boolean present; + + private JsonNullable(T value, boolean present) { + this.value = value; + this.present = present; + } + + public T get() { + if (!present) { + throw new NoSuchElementException("Value is undefined"); + } + return value; + } + + public boolean isPresent() { + return present; + } + + @SuppressWarnings("unchecked") + public static JsonNullable undefined() { + return (JsonNullable) UNDEFINED; + } + + public static JsonNullable of(T value) { + return new JsonNullable<>( value, true ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Nullable.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Nullable.java new file mode 100644 index 0000000000..f6309be1c6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Nullable.java @@ -0,0 +1,45 @@ +/* + * 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._2663; + +import java.util.NoSuchElementException; + +/** + * @author Filip Hrisafov + */ +public class Nullable { + + @SuppressWarnings("rawtypes") + private static final Nullable UNDEFINED = new Nullable<>( null, false ); + + private final T value; + private final boolean present; + + private Nullable(T value, boolean present) { + this.value = value; + this.present = present; + } + + public T get() { + if (!present) { + throw new NoSuchElementException("Value is undefined"); + } + return value; + } + + public boolean isPresent() { + return present; + } + + public static Nullable of(T value) { + return new Nullable<>( value, true ); + } + + @SuppressWarnings("unchecked") + public static Nullable undefined() { + return (Nullable) UNDEFINED; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/NullableHelper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/NullableHelper.java new file mode 100644 index 0000000000..ae68300e62 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/NullableHelper.java @@ -0,0 +1,23 @@ +/* + * 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._2663; + +/** + * @author Filip Hrisafov + */ +public class NullableHelper { + + private NullableHelper() { + // Helper class + } + + public static Nullable jsonNullableToNullable(JsonNullable jsonNullable) { + if ( jsonNullable.isPresent() ) { + return Nullable.of( jsonNullable.get() ); + } + return Nullable.undefined(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Request.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Request.java new file mode 100644 index 0000000000..8b58e2e0b5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Request.java @@ -0,0 +1,44 @@ +/* + * 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._2663; + +/** + * @author Filip Hrisafov + */ +public class Request { + + private Nullable name = Nullable.undefined(); + private Nullable child = Nullable.undefined(); + + public Nullable getName() { + return name; + } + + public void setName(Nullable name) { + this.name = name; + } + + public Nullable getChild() { + return child; + } + + public void setChild(Nullable child) { + this.child = child; + } + + public static class ChildRequest { + + private Nullable name = Nullable.undefined(); + + public Nullable getName() { + return name; + } + + public void setName(Nullable name) { + this.name = name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/RequestDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/RequestDto.java new file mode 100644 index 0000000000..d1a93a9bd4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/RequestDto.java @@ -0,0 +1,44 @@ +/* + * 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._2663; + +/** + * @author Filip Hrisafov + */ +public class RequestDto { + + private JsonNullable name = JsonNullable.undefined(); + private JsonNullable child = JsonNullable.undefined(); + + public JsonNullable getName() { + return name; + } + + public void setName(JsonNullable name) { + this.name = name; + } + + public JsonNullable getChild() { + return child; + } + + public void setChild(JsonNullable child) { + this.child = child; + } + + public static class ChildRequestDto { + + private JsonNullable name = JsonNullable.undefined(); + + public JsonNullable getName() { + return name; + } + + public void setName(JsonNullable name) { + this.name = name; + } + } +} From 8cc2bdd0928a11acf2ef656e93a0ebadbb740b90 Mon Sep 17 00:00:00 2001 From: Ben Zegveld Date: Tue, 1 Aug 2023 15:04:15 +0200 Subject: [PATCH 189/363] #3163: Strip wild card when checking for type assignability --- .../ap/internal/model/common/Type.java | 9 +++-- .../ap/test/bugs/_3163/Issue3163Mapper.java | 22 +++++++++++ .../ap/test/bugs/_3163/Issue3163Test.java | 21 ++++++++++ .../mapstruct/ap/test/bugs/_3163/Source.java | 34 +++++++++++++++++ .../mapstruct/ap/test/bugs/_3163/Target.java | 38 +++++++++++++++++++ 5 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Target.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index c18574fcfb..f742e1ba0f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -629,11 +629,14 @@ private Type replaceGeneric(Type oldGenericType, Type newType) { * @return {@code true} if and only if this type is assignable to the given other type. */ public boolean isAssignableTo(Type other) { + TypeMirror otherMirror = other.typeMirror; + if ( otherMirror.getKind() == TypeKind.WILDCARD ) { + otherMirror = typeUtils.erasure( other.typeMirror ); + } if ( TypeKind.WILDCARD == typeMirror.getKind() ) { - return typeUtils.contains( typeMirror, other.typeMirror ); + return typeUtils.contains( typeMirror, otherMirror ); } - - return typeUtils.isAssignable( typeMirror, other.typeMirror ); + return typeUtils.isAssignable( typeMirror, otherMirror ); } /** diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Mapper.java new file mode 100644 index 0000000000..bd44a9d386 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Mapper.java @@ -0,0 +1,22 @@ +/* + * 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._3163; + +import java.util.Optional; + +import org.mapstruct.Mapper; + +@Mapper +public interface Issue3163Mapper { + + Target map(Source value); + + Target.Nested map(Source.Nested value); + + default Optional wrapAsOptional(T value) { + return Optional.ofNullable( value ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Test.java new file mode 100644 index 0000000000..5c16a707f3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Test.java @@ -0,0 +1,21 @@ +/* + * 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._3163; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@WithClasses({ + Issue3163Mapper.class, + Source.class, + Target.class +}) +class Issue3163Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Source.java new file mode 100644 index 0000000000..8f5a57f1bf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Source.java @@ -0,0 +1,34 @@ +/* + * 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._3163; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final Nested nested; + + public Source(Nested nested) { + this.nested = nested; + } + + public Nested getNested() { + return nested; + } + + public static class Nested { + private final String value; + + public Nested(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Target.java new file mode 100644 index 0000000000..301a281d2d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Target.java @@ -0,0 +1,38 @@ +/* + * 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._3163; + +import java.util.Optional; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private Nested nested; + + public Optional getNested() { + return Optional.ofNullable( nested ); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public final void setNested(Optional nested) { + this.nested = nested.orElse( null ); + } + + public static class Nested { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} From ea997f83cefd7d3900f8a6a218af1c20f7a1ef8a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 19 Aug 2023 10:09:36 +0200 Subject: [PATCH 190/363] #2340 Add FUNDING.yml --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..be6d63e5d2 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: mapstruct +open_collective: mapstruct From f61a3acec301de2a781bd46a56cdcf5c83ca159e Mon Sep 17 00:00:00 2001 From: GVladi <47348760+GVladi@users.noreply.github.com> Date: Wed, 30 Aug 2023 22:07:38 +0200 Subject: [PATCH 191/363] #3089 Improve support for Map attributes for Immutables Co-Authored-By: thunderhook <8238759+thunderhook@users.noreply.github.com> --- ...eatureCompilationExclusionCliEnhancer.java | 1 + .../spi/ImmutablesAccessorNamingStrategy.java | 13 +- .../ap/test/bugs/_1801/Issue1801Test.java | 2 +- .../test/bugs/_1801/domain/ImmutableItem.java | 2 +- .../bugs/_3089/Issue3089BuilderProvider.java | 96 +++++ .../ap/test/bugs/_3089/Issue3089Test.java | 61 ++++ .../ap/test/bugs/_3089/ItemMapper.java | 23 ++ .../test/bugs/_3089/domain/ImmutableItem.java | 296 ++++++++++++++++ .../ap/test/bugs/_3089/domain/Item.java | 19 + .../test/bugs/_3089/dto/ImmutableItemDTO.java | 330 ++++++++++++++++++ .../ap/test/bugs/_3089/dto/ItemDTO.java | 18 + 11 files changed, 858 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089BuilderProvider.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/ItemMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/ImmutableItem.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/Item.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ImmutableItemDTO.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ItemDTO.java diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index c0ea96fd5c..15d9e42598 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -26,6 +26,7 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces // SPI not working correctly here.. (not picked up) additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1596/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1801/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/bugs/_3089/*.java" ); switch ( currentJreVersion ) { case JAVA_8: diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java index 100798e063..1df6ebc1b6 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java @@ -21,7 +21,18 @@ public class ImmutablesAccessorNamingStrategy extends DefaultAccessorNamingStrat @Override protected boolean isFluentSetter(ExecutableElement method) { - return super.isFluentSetter( method ) && !method.getSimpleName().toString().equals( "from" ); + return super.isFluentSetter( method ) && + !method.getSimpleName().toString().equals( "from" ) && + !isPutterWithUpperCase4thCharacter( method ); + } + + private boolean isPutterWithUpperCase4thCharacter(ExecutableElement method) { + return isPutterMethod( method ) && Character.isUpperCase( method.getSimpleName().toString().charAt( 3 ) ); + } + + public boolean isPutterMethod(ExecutableElement method) { + String methodName = method.getSimpleName().toString(); + return methodName.startsWith( "put" ) && methodName.length() > 3; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801Test.java index ee6076f823..40b9515305 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801Test.java @@ -38,7 +38,7 @@ public class Issue1801Test { @ProcessorTest - public void shouldIncludeBuildeType() { + public void shouldIncludeBuilderType() { ItemDTO item = ImmutableItemDTO.builder().id( "test" ).build(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/domain/ImmutableItem.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/domain/ImmutableItem.java index 68231f12ef..393dc153d6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/domain/ImmutableItem.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/domain/ImmutableItem.java @@ -124,7 +124,7 @@ public final Builder from(Item instance) { /** * Initializes the value for the {@link Item#getId() id} attribute. - * @param id The value for id + * @param id The value for id * @return {@code this} builder for use in a chained invocation */ public final Builder id(String id) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089BuilderProvider.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089BuilderProvider.java new file mode 100644 index 0000000000..c827d32587 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089BuilderProvider.java @@ -0,0 +1,96 @@ +/* + * 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._3089; + +import java.util.List; +import java.util.Objects; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; + +import org.mapstruct.ap.spi.BuilderInfo; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesBuilderProvider; + +/** + * @author Oliver Erhart + */ +public class Issue3089BuilderProvider extends ImmutablesBuilderProvider implements BuilderProvider { + + @Override + protected BuilderInfo findBuilderInfo(TypeElement typeElement) { + Name name = typeElement.getQualifiedName(); + if ( name.toString().endsWith( ".Item" ) ) { + BuilderInfo info = findBuilderInfoFromInnerBuilderClass( typeElement ); + if ( info != null ) { + return info; + } + } + return super.findBuilderInfo( typeElement ); + } + + /** + * Looks for inner builder class in the Immutable interface / abstract class. + * + * The inner builder class should be be declared with the following line + * + *
      +     *     public static Builder() extends ImmutableItem.Builder { }
      +     * 
      + * + * The Immutable instance should be created with the following line + * + *
      +     *     new Item.Builder().withId("123").build();
      +     * 
      + * + * @see org.mapstruct.ap.test.bugs._3089.domain.Item + * + * @param typeElement + * @return + */ + private BuilderInfo findBuilderInfoFromInnerBuilderClass(TypeElement typeElement) { + if (shouldIgnore( typeElement )) { + return null; + } + + List innerTypes = ElementFilter.typesIn( typeElement.getEnclosedElements() ); + ExecutableElement defaultConstructor = innerTypes.stream() + .filter( this::isBuilderCandidate ) + .map( this::getEmptyArgPublicConstructor ) + .filter( Objects::nonNull ) + .findAny() + .orElse( null ); + + if ( defaultConstructor != null ) { + return new BuilderInfo.Builder() + .builderCreationMethod( defaultConstructor ) + .buildMethod( findBuildMethods( (TypeElement) defaultConstructor.getEnclosingElement(), typeElement ) ) + .build(); + } + return null; + } + + private boolean isBuilderCandidate(TypeElement innerType ) { + TypeElement outerType = (TypeElement) innerType.getEnclosingElement(); + String packageName = this.elementUtils.getPackageOf( outerType ).getQualifiedName().toString(); + Name outerSimpleName = outerType.getSimpleName(); + String builderClassName = packageName + ".Immutable" + outerSimpleName + ".Builder"; + return innerType.getSimpleName().contentEquals( "Builder" ) + && getTypeElement( innerType.getSuperclass() ).getQualifiedName().contentEquals( builderClassName ) + && innerType.getModifiers().contains( Modifier.PUBLIC ); + } + + private ExecutableElement getEmptyArgPublicConstructor(TypeElement builderType) { + return ElementFilter.constructorsIn( builderType.getEnclosedElements() ).stream() + .filter( c -> c.getParameters().isEmpty() ) + .filter( c -> c.getModifiers().contains( Modifier.PUBLIC ) ) + .findAny() + .orElse( null ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089Test.java new file mode 100644 index 0000000000..f7c138c146 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089Test.java @@ -0,0 +1,61 @@ +/* + * 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._3089; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.ap.spi.AccessorNamingStrategy; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; +import org.mapstruct.ap.test.bugs._3089.domain.ImmutableItem; +import org.mapstruct.ap.test.bugs._3089.domain.Item; +import org.mapstruct.ap.test.bugs._3089.dto.ImmutableItemDTO; +import org.mapstruct.ap.test.bugs._3089.dto.ItemDTO; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.WithServiceImplementations; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Oliver Erhart + */ +@WithClasses({ + ItemMapper.class, + Item.class, + ImmutableItem.class, + ItemDTO.class, + ImmutableItemDTO.class +}) +@IssueKey("3089") +@WithServiceImplementations({ + @WithServiceImplementation(provides = BuilderProvider.class, value = Issue3089BuilderProvider.class), + @WithServiceImplementation(provides = AccessorNamingStrategy.class, value = ImmutablesAccessorNamingStrategy.class) +}) +public class Issue3089Test { + + @ProcessorTest + public void shouldIgnorePutterOfMap() { + + Map attributesMap = new HashMap<>(); + attributesMap.put( "a", "b" ); + attributesMap.put( "c", "d" ); + + ItemDTO item = ImmutableItemDTO.builder() + .id( "test" ) + .attributes( attributesMap ) + .build(); + + Item target = ItemMapper.INSTANCE.map( item ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( "test" ); + assertThat( target.getAttributes() ).isEqualTo( attributesMap ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/ItemMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/ItemMapper.java new file mode 100644 index 0000000000..ed06115ae2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/ItemMapper.java @@ -0,0 +1,23 @@ +/* + * 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._3089; + +import org.mapstruct.Builder; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._3089.domain.Item; +import org.mapstruct.ap.test.bugs._3089.dto.ItemDTO; +import org.mapstruct.factory.Mappers; + +/** + * @author Oliver Erhart + */ +@Mapper(builder = @Builder) +public abstract class ItemMapper { + + public static final ItemMapper INSTANCE = Mappers.getMapper( ItemMapper.class ); + + public abstract Item map(ItemDTO itemDTO); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/ImmutableItem.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/ImmutableItem.java new file mode 100644 index 0000000000..ab5a6afdc0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/ImmutableItem.java @@ -0,0 +1,296 @@ +/* + * 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._3089.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable implementation of {@link Item}. + *

      + * Superclass should expose a static subclass of the Builder to create immutable instance + * {@code public static Builder extends ImmutableItem.Builder}. + * + * @author Oliver Erhart + */ +@SuppressWarnings({"all"}) +public final class ImmutableItem extends Item { + private final String id; + private final Map attributes; + + private ImmutableItem(String id, Map attributes) { + this.id = id; + this.attributes = attributes; + } + + /** + * @return The value of the {@code id} attribute + */ + @Override + public String getId() { + return id; + } + + /** + * @return The value of the {@code attributes} attribute + */ + @Override + public Map getAttributes() { + return attributes; + } + + /** + * Copy the current immutable object by setting a value for the {@link Item#getId() id} attribute. + * An equals check used to prevent copying of the same value by returning {@code this}. + * @param value A new value for id + * @return A modified copy of the {@code this} object + */ + public final ImmutableItem withId(String value) { + String newValue = Objects.requireNonNull(value, "id"); + if (this.id.equals(newValue)) return this; + return new ImmutableItem(newValue, this.attributes); + } + + /** + * Copy the current immutable object by replacing the {@link Item#getAttributes() attributes} map with the specified map. + * Nulls are not permitted as keys or values. + * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}. + * @param entries The entries to be added to the attributes map + * @return A modified copy of {@code this} object + */ + public final ImmutableItem withAttributes(Map entries) { + if (this.attributes == entries) return this; + Map newValue = createUnmodifiableMap(true, false, entries); + return new ImmutableItem(this.id, newValue); + } + + /** + * This instance is equal to all instances of {@code ImmutableItem} that have equal attribute values. + * @return {@code true} if {@code this} is equal to {@code another} instance + */ + @Override + public boolean equals(Object another) { + if (this == another) return true; + return another instanceof ImmutableItem + && equalTo((ImmutableItem) another); + } + + private boolean equalTo(ImmutableItem another) { + return id.equals(another.id) + && attributes.equals(another.attributes); + } + + /** + * Computes a hash code from attributes: {@code id}, {@code attributes}. + * @return hashCode value + */ + @Override + public int hashCode() { + int h = 5381; + h += (h << 5) + id.hashCode(); + h += (h << 5) + attributes.hashCode(); + return h; + } + + /** + * Prints the immutable value {@code Item} with attribute values. + * @return A string representation of the value + */ + @Override + public String toString() { + return "Item{" + + "id=" + id + + ", attributes=" + attributes + + "}"; + } + + /** + * Creates an immutable copy of a {@link Item} value. + * Uses accessors to get values to initialize the new immutable instance. + * If an instance is already immutable, it is returned as is. + * @param instance The instance to copy + * @return A copied immutable Item instance + */ + public static ImmutableItem copyOf(Item instance) { + if (instance instanceof ImmutableItem) { + return (ImmutableItem) instance; + } + return ImmutableItem.builder() + .from(instance) + .build(); + } + + /** + * Creates a builder for {@link ImmutableItem ImmutableItem}. + *

      +   * ImmutableItem.builder()
      +   *    .id(String) // required {@link Item#getId() id}
      +   *    .putAttributes|putAllAttributes(String => String) // {@link Item#getAttributes() attributes} mappings
      +   *    .build();
      +   * 
      + * @return A new ImmutableItem builder + */ + public static ImmutableItem.Builder builder() { + return new ImmutableItem.Builder(); + } + + /** + * Builds instances of type {@link ImmutableItem ImmutableItem}. + * Initialize attributes and then invoke the {@link #build()} method to create an + * immutable instance. + *

      {@code Builder} is not thread-safe and generally should not be stored in a field or collection, + * but instead used immediately to create instances. + */ + public static class Builder { + private static final long INIT_BIT_ID = 0x1L; + private long initBits = 0x1L; + + private String id; + private Map attributes = new LinkedHashMap(); + + public Builder() { + } + + /** + * Fill a builder with attribute values from the provided {@code Item} instance. + * Regular attribute values will be replaced with those from the given instance. + * Absent optional values will not replace present values. + * Collection elements and entries will be added, not replaced. + * @param instance The instance from which to copy values + * @return {@code this} builder for use in a chained invocation + */ + public final Builder from(Item instance) { + Objects.requireNonNull(instance, "instance"); + id(instance.getId()); + putAllAttributes(instance.getAttributes()); + return this; + } + + /** + * Initializes the value for the {@link Item#getId() id} attribute. + * @param id The value for id + * @return {@code this} builder for use in a chained invocation + */ + public final Builder id(String id) { + this.id = Objects.requireNonNull(id, "id"); + initBits &= ~INIT_BIT_ID; + return this; + } + + /** + * Put one entry to the {@link Item#getAttributes() attributes} map. + * @param key The key in the attributes map + * @param value The associated value in the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Builder putAttributes(String key, String value) { + this.attributes.put( + Objects.requireNonNull(key, "attributes key"), + Objects.requireNonNull(value, "attributes value")); + return this; + } + + /** + * Put one entry to the {@link Item#getAttributes() attributes} map. Nulls are not permitted + * @param entry The key and value entry + * @return {@code this} builder for use in a chained invocation + */ + public final Builder putAttributes(Map.Entry entry) { + String k = entry.getKey(); + String v = entry.getValue(); + this.attributes.put( + Objects.requireNonNull(k, "attributes key"), + Objects.requireNonNull(v, "attributes value")); + return this; + } + + /** + * Sets or replaces all mappings from the specified map as entries for the {@link Item#getAttributes() attributes} map. Nulls are not permitted + * @param entries The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Builder attributes(Map entries) { + this.attributes.clear(); + return putAllAttributes(entries); + } + + /** + * Put all mappings from the specified map as entries to {@link Item#getAttributes() attributes} map. Nulls are not permitted + * @param entries The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Builder putAllAttributes(Map entries) { + for (Map.Entry e : entries.entrySet()) { + String k = e.getKey(); + String v = e.getValue(); + this.attributes.put( + Objects.requireNonNull(k, "attributes key"), + Objects.requireNonNull(v, "attributes value")); + } + return this; + } + + /** + * Builds a new {@link ImmutableItem ImmutableItem}. + * @return An immutable instance of Item + * @throws java.lang.IllegalStateException if any required attributes are missing + */ + public ImmutableItem build() { + if (initBits != 0) { + throw new IllegalStateException(formatRequiredAttributesMessage()); + } + return new ImmutableItem(id, createUnmodifiableMap(false, false, attributes)); + } + + private String formatRequiredAttributesMessage() { + List attributes = new ArrayList<>(); + if ((initBits & INIT_BIT_ID) != 0) attributes.add("id"); + return "Cannot build Item, some of required attributes are not set " + attributes; + } + } + + private static Map createUnmodifiableMap(boolean checkNulls, boolean skipNulls, Map map) { + switch (map.size()) { + case 0: return Collections.emptyMap(); + case 1: { + Map.Entry e = map.entrySet().iterator().next(); + K k = e.getKey(); + V v = e.getValue(); + if (checkNulls) { + Objects.requireNonNull(k, "key"); + Objects.requireNonNull(v, "value"); + } + if (skipNulls && (k == null || v == null)) { + return Collections.emptyMap(); + } + return Collections.singletonMap(k, v); + } + default: { + Map linkedMap = new LinkedHashMap<>(map.size()); + if (skipNulls || checkNulls) { + for (Map.Entry e : map.entrySet()) { + K k = e.getKey(); + V v = e.getValue(); + if (skipNulls) { + if (k == null || v == null) continue; + } else if (checkNulls) { + Objects.requireNonNull(k, "key"); + Objects.requireNonNull(v, "value"); + } + linkedMap.put(k, v); + } + } else { + linkedMap.putAll(map); + } + return Collections.unmodifiableMap(linkedMap); + } + } + } +} \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/Item.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/Item.java new file mode 100644 index 0000000000..e193432367 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/Item.java @@ -0,0 +1,19 @@ +/* + * 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._3089.domain; + +import java.util.Map; + +/** + * @author Oliver Erhart + */ +public abstract class Item { + public abstract String getId(); + + public abstract Map getAttributes(); + + public static class Builder extends ImmutableItem.Builder { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ImmutableItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ImmutableItemDTO.java new file mode 100644 index 0000000000..de64727bf1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ImmutableItemDTO.java @@ -0,0 +1,330 @@ +/* + * 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._3089.dto; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable implementation of {@link ItemDTO}. + *

      + * Use the builder to create immutable instances: + * {@code ImmutableItemDTO.builder()}. + * + * @author Oliver Erhart + */ +public final class ImmutableItemDTO extends ItemDTO { + private final String id; + private final Map attributes; + + private ImmutableItemDTO(String id, Map attributes) { + this.id = id; + this.attributes = attributes; + } + + /** + * @return The value of the {@code id} attribute + */ + @Override + public String getId() { + return id; + } + + /** + * @return The value of the {@code attributes} attribute + */ + @Override + public Map getAttributes() { + return attributes; + } + + /** + * Copy the current immutable object by setting a value for the {@link ItemDTO#getId() id} attribute. + * An equals check used to prevent copying of the same value by returning {@code this}. + * + * @param value A new value for id + * @return A modified copy of the {@code this} object + */ + public ImmutableItemDTO withId(String value) { + String newValue = Objects.requireNonNull( value, "id" ); + if ( this.id.equals( newValue ) ) { + return this; + } + return new ImmutableItemDTO( newValue, this.attributes ); + } + + /** + * Copy the current immutable object by replacing the {@link ItemDTO#getAttributes() attributes} map with + * the specified map. + * Nulls are not permitted as keys or values. + * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}. + * + * @param entries The entries to be added to the attributes map + * @return A modified copy of {@code this} object + */ + public ImmutableItemDTO withAttributes(Map entries) { + if ( this.attributes == entries ) { + return this; + } + Map newValue = createUnmodifiableMap( true, false, entries ); + return new ImmutableItemDTO( this.id, newValue ); + } + + /** + * This instance is equal to all instances of {@code ImmutableItemDTO} that have equal attribute values. + * + * @return {@code true} if {@code this} is equal to {@code another} instance + */ + @Override + public boolean equals(Object another) { + if ( this == another ) { + return true; + } + return another instanceof ImmutableItemDTO + && equalTo( (ImmutableItemDTO) another ); + } + + private boolean equalTo(ImmutableItemDTO another) { + return id.equals( another.id ) + && attributes.equals( another.attributes ); + } + + /** + * Computes a hash code from attributes: {@code id}, {@code attributes}. + * + * @return hashCode value + */ + @Override + public int hashCode() { + int h = 5381; + h += ( h << 5 ) + id.hashCode(); + h += ( h << 5 ) + attributes.hashCode(); + return h; + } + + /** + * Prints the immutable value {@code Item} with attribute values. + * + * @return A string representation of the value + */ + @Override + public String toString() { + return "Item{" + + "id=" + id + + ", attributes=" + attributes + + "}"; + } + + /** + * Creates an immutable copy of a {@link ItemDTO} value. + * Uses accessors to get values to initialize the new immutable instance. + * If an instance is already immutable, it is returned as is. + * + * @param instance The instance to copy + * @return A copied immutable Item instance + */ + public static ImmutableItemDTO copyOf(ItemDTO instance) { + if ( instance instanceof ImmutableItemDTO ) { + return (ImmutableItemDTO) instance; + } + return ImmutableItemDTO.builder() + .from( instance ) + .build(); + } + + /** + * Creates a builder for {@link ImmutableItemDTO ImmutableItemDTO}. + *

      +     * ImmutableItemDTO.builder()
      +     *    .id(String) // required {@link ItemDTO#getId() id}
      +     *    .putAttributes|putAllAttributes(String => String) // {@link ItemDTO#getAttributes() attributes} mappings
      +     *    .build();
      +     * 
      + * + * @return A new ImmutableItemDTO builder + */ + public static ImmutableItemDTO.Builder builder() { + return new ImmutableItemDTO.Builder(); + } + + /** + * Builds instances of type {@link ImmutableItemDTO ImmutableItemDTO}. + * Initialize attributes and then invoke the {@link #build()} method to create an + * immutable instance. + *

      {@code Builder} is not thread-safe and generally should not be stored in a field or collection, + * but instead used immediately to create instances. + */ + public static class Builder { + private static final long INIT_BIT_ID = 0x1L; + private long initBits = 0x1L; + + private String id; + private Map attributes = new LinkedHashMap(); + + public Builder() { + } + + /** + * Fill a builder with attribute values from the provided {@code Item} instance. + * Regular attribute values will be replaced with those from the given instance. + * Absent optional values will not replace present values. + * Collection elements and entries will be added, not replaced. + * + * @param instance The instance from which to copy values + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder from(ItemDTO instance) { + Objects.requireNonNull( instance, "instance" ); + id( instance.getId() ); + putAllAttributes( instance.getAttributes() ); + return this; + } + + /** + * Initializes the value for the {@link ItemDTO#getId() id} attribute. + * + * @param id The value for id + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder id(String id) { + this.id = Objects.requireNonNull( id, "id" ); + initBits &= ~INIT_BIT_ID; + return this; + } + + /** + * Put one entry to the {@link ItemDTO#getAttributes() attributes} map. + * + * @param key The key in the attributes map + * @param value The associated value in the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder putAttributes(String key, String value) { + this.attributes.put( + Objects.requireNonNull( key, "attributes key" ), + Objects.requireNonNull( value, "attributes value" ) + ); + return this; + } + + /** + * Put one entry to the {@link ItemDTO#getAttributes() attributes} map. Nulls are not permitted + * + * @param entry The key and value entry + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder putAttributes(Map.Entry entry) { + String k = entry.getKey(); + String v = entry.getValue(); + this.attributes.put( + Objects.requireNonNull( k, "attributes key" ), + Objects.requireNonNull( v, "attributes value" ) + ); + return this; + } + + /** + * Sets or replaces all mappings from the specified map as entries for the {@link ItemDTO#getAttributes() + * attributes} map. Nulls are not permitted + * + * @param entries The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder attributes(Map entries) { + this.attributes.clear(); + return putAllAttributes( entries ); + } + + /** + * Put all mappings from the specified map as entries to {@link ItemDTO#getAttributes() attributes} map. + * Nulls are not permitted + * + * @param entries The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder putAllAttributes(Map entries) { + for ( Map.Entry e : entries.entrySet() ) { + String k = e.getKey(); + String v = e.getValue(); + this.attributes.put( + Objects.requireNonNull( k, "attributes key" ), + Objects.requireNonNull( v, "attributes value" ) + ); + } + return this; + } + + /** + * Builds a new {@link ImmutableItemDTO ImmutableItemDTO}. + * + * @return An immutable instance of Item + * @throws java.lang.IllegalStateException if any required attributes are missing + */ + public ImmutableItemDTO build() { + if ( initBits != 0 ) { + throw new IllegalStateException( formatRequiredAttributesMessage() ); + } + return new ImmutableItemDTO( id, createUnmodifiableMap( false, false, attributes ) ); + } + + private String formatRequiredAttributesMessage() { + List attributes = new ArrayList<>(); + if ( ( initBits & INIT_BIT_ID ) != 0 ) { + attributes.add( "id" ); + } + return "Cannot build Item, some of required attributes are not set " + attributes; + } + } + + @SuppressWarnings( "checkstyle:AvoidNestedBlocks" ) + private static Map createUnmodifiableMap(boolean checkNulls, boolean skipNulls, + Map map) { + switch ( map.size() ) { + case 0: + return Collections.emptyMap(); + case 1: { + Map.Entry e = map.entrySet().iterator().next(); + K k = e.getKey(); + V v = e.getValue(); + if ( checkNulls ) { + Objects.requireNonNull( k, "key" ); + Objects.requireNonNull( v, "value" ); + } + if ( skipNulls && ( k == null || v == null ) ) { + return Collections.emptyMap(); + } + return Collections.singletonMap( k, v ); + } + default: { + Map linkedMap = new LinkedHashMap<>( map.size() ); + if ( skipNulls || checkNulls ) { + for ( Map.Entry e : map.entrySet() ) { + K k = e.getKey(); + V v = e.getValue(); + if ( skipNulls ) { + if ( k == null || v == null ) { + continue; + } + } + else if ( checkNulls ) { + Objects.requireNonNull( k, "key" ); + Objects.requireNonNull( v, "value" ); + } + linkedMap.put( k, v ); + } + } + else { + linkedMap.putAll( map ); + } + return Collections.unmodifiableMap( linkedMap ); + } + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ItemDTO.java new file mode 100644 index 0000000000..c38e37f08a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ItemDTO.java @@ -0,0 +1,18 @@ +/* + * 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._3089.dto; + +import java.util.Map; + +/** + * @author Oliver Erhart + */ +public abstract class ItemDTO { + public abstract String getId(); + + public abstract Map getAttributes(); + +} From 032ee4d77adaa23e2012381eb76e9ab94f937cc3 Mon Sep 17 00:00:00 2001 From: Nikolas Charalambidis Date: Sat, 16 Sep 2023 23:13:20 +0700 Subject: [PATCH 192/363] #3374 Lombok compatibility documentation --- .../asciidoc/chapter-14-third-party-api-integration.asciidoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc b/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc index 0f12d67e22..ef2a58f82e 100644 --- a/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc +++ b/documentation/src/main/asciidoc/chapter-14-third-party-api-integration.asciidoc @@ -46,8 +46,10 @@ public @interface Default { MapStruct works together with https://projectlombok.org/[Project Lombok] as of MapStruct 1.2.0.Beta1 and Lombok 1.16.14. MapStruct takes advantage of generated getters, setters, and constructors and uses them to generate the mapper implementations. +Be reminded that the generated code by Lombok might not always be compatible with the expectations from the individual mappings. +In such a case, either Mapstruct mapping must be changed or Lombok must be configured accordingly using https://projectlombok.org/features/configuration[`lombok.config`] for mutual synergy. -[NOTE] +[WARNING] ==== Lombok 1.18.16 introduces a breaking change (https://projectlombok.org/changelog[changelog]). The additional annotation processor `lombok-mapstruct-binding` (https://mvnrepository.com/artifact/org.projectlombok/lombok-mapstruct-binding[Maven]) must be added otherwise MapStruct stops working with Lombok. From 97c389d58bd125c022edf4a6cba1a485c33eb754 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 19:42:54 +0000 Subject: [PATCH 193/363] Bump org.codehaus.plexus:plexus-utils from 3.0.20 to 3.0.24 in /parent Bumps [org.codehaus.plexus:plexus-utils](https://github.com/codehaus-plexus/plexus-utils) from 3.0.20 to 3.0.24. - [Release notes](https://github.com/codehaus-plexus/plexus-utils/releases) - [Commits](https://github.com/codehaus-plexus/plexus-utils/compare/plexus-utils-3.0.20...plexus-utils-3.0.24) --- updated-dependencies: - dependency-name: org.codehaus.plexus:plexus-utils dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index 32f7e2897a..73239c87e3 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -304,7 +304,7 @@ org.codehaus.plexus plexus-utils - 3.0.20 + 3.0.24 commons-io From c59eca2a77a02ea97ab0b87c8a90c48266e106dc Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 30 Sep 2023 21:52:07 +0200 Subject: [PATCH 194/363] #3361 Inheriting mappings should only be applied if the target has been redefined --- .../model/source/MappingMethodOptions.java | 8 +- .../ap/test/bugs/_3361/Issue3361Mapper.java | 80 +++++++++++++++++++ .../ap/test/bugs/_3361/Issue3361Test.java | 34 ++++++++ 3 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java index 73474a8478..d17172211b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -278,20 +278,16 @@ private void addAllNonRedefined(SourceMethod sourceMethod, AnnotationMirror anno } private void addAllNonRedefined(Set inheritedMappings) { - Set redefinedSources = new HashSet<>(); + // We are only adding the targets here since this mappings have already been reversed Set redefinedTargets = new HashSet<>(); for ( MappingOptions redefinedMappings : mappings ) { - if ( redefinedMappings.getSourceName() != null ) { - redefinedSources.add( redefinedMappings.getSourceName() ); - } if ( redefinedMappings.getTargetName() != null ) { redefinedTargets.add( redefinedMappings.getTargetName() ); } } for ( MappingOptions inheritedMapping : inheritedMappings ) { if ( inheritedMapping.isIgnored() - || ( !isRedefined( redefinedSources, inheritedMapping.getSourceName() ) - && !isRedefined( redefinedTargets, inheritedMapping.getTargetName() ) ) + || !isRedefined( redefinedTargets, inheritedMapping.getTargetName() ) ) { mappings.add( inheritedMapping ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Mapper.java new file mode 100644 index 0000000000..a556b63306 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Mapper.java @@ -0,0 +1,80 @@ +/* + * 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._3361; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +@Mapper +public abstract class Issue3361Mapper { + + public static final Issue3361Mapper INSTANCE = Mappers.getMapper( Issue3361Mapper.class ); + + @Mapping(target = "someAttribute", source = "source.attribute") + @Mapping(target = "otherAttribute", source = "otherSource.anotherAttribute") + public abstract Target mapFromSource(Source source, OtherSource otherSource); + + @InheritConfiguration(name = "mapFromSource") + @Mapping(target = "otherAttribute", source = "source", qualifiedByName = "otherMapping") + public abstract Target mapInherited(Source source, OtherSource otherSource); + + @Named("otherMapping") + protected Long otherMapping(Source source) { + return source.getAttribute() != null ? 1L : 0L; + } + + public static class Target { + private String someAttribute; + private Long otherAttribute; + + public String getSomeAttribute() { + return someAttribute; + } + + public Target setSomeAttribute(String someAttribute) { + this.someAttribute = someAttribute; + return this; + } + + public Long getOtherAttribute() { + return otherAttribute; + } + + public Target setOtherAttribute(Long otherAttribute) { + this.otherAttribute = otherAttribute; + return this; + } + } + + public static class Source { + private final String attribute; + + public Source(String attribute) { + this.attribute = attribute; + } + + public String getAttribute() { + return attribute; + } + } + + public static class OtherSource { + + private final Long anotherAttribute; + + public OtherSource(Long anotherAttribute) { + this.anotherAttribute = anotherAttribute; + } + + public Long getAnotherAttribute() { + return anotherAttribute; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Test.java new file mode 100644 index 0000000000..3fa16ec1ae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Test.java @@ -0,0 +1,34 @@ +/* + * 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._3361; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3361") +@WithClasses(Issue3361Mapper.class) +class Issue3361Test { + + @ProcessorTest + void multiSourceShouldInherit() { + Issue3361Mapper.Source source = new Issue3361Mapper.Source( "Test" ); + Issue3361Mapper.OtherSource otherSource = new Issue3361Mapper.OtherSource( 10L ); + + Issue3361Mapper.Target target = Issue3361Mapper.INSTANCE.mapFromSource( source, otherSource ); + assertThat( target.getSomeAttribute() ).isEqualTo( "Test" ); + assertThat( target.getOtherAttribute() ).isEqualTo( 10L ); + + target = Issue3361Mapper.INSTANCE.mapInherited( source, otherSource ); + assertThat( target.getSomeAttribute() ).isEqualTo( "Test" ); + assertThat( target.getOtherAttribute() ).isEqualTo( 1L ); + } +} From 5d39314bd216b8818face21ec8be95d9f7cfff18 Mon Sep 17 00:00:00 2001 From: Xiu Hong Kooi Date: Wed, 1 Nov 2023 06:55:11 +0800 Subject: [PATCH 195/363] #3376 support mapping from iterables to collection --- .../internal/conversion/ConversionUtils.java | 1 + .../ap/internal/model/PropertyMapping.java | 3 +- .../creation/MappingResolverImpl.java | 1 - .../test/collection/iterabletolist/Fruit.java | 27 ++++++++++ .../collection/iterabletolist/FruitSalad.java | 29 +++++++++++ .../iterabletolist/FruitsMapper.java | 24 +++++++++ .../collection/iterabletolist/FruitsMenu.java | 35 +++++++++++++ .../IterableToListMappingTest.java | 47 +++++++++++++++++ .../test/collection/iterabletoset/Fruit.java | 27 ++++++++++ .../collection/iterabletoset/FruitSalad.java | 29 +++++++++++ .../iterabletoset/FruitsMapper.java | 24 +++++++++ .../collection/iterabletoset/FruitsMenu.java | 35 +++++++++++++ .../IterableToSetMappingTest.java | 50 +++++++++++++++++++ 13 files changed, 330 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/Fruit.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitSalad.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMenu.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/IterableToListMappingTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/Fruit.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitSalad.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMenu.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/IterableToSetMappingTest.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java index 496d11676f..aa01a73276 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java @@ -278,4 +278,5 @@ public static String uuid(ConversionContext conversionContext) { public static String url(ConversionContext conversionContext) { return typeReferenceName( conversionContext, URL.class ); } + } 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 b81cf55447..50f23ecbff 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 @@ -301,7 +301,8 @@ else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.get private Assignment forge( ) { Assignment assignment; Type sourceType = rightHandSide.getSourceType(); - if ( (sourceType.isCollectionType() || sourceType.isArrayType()) && targetType.isIterableType() ) { + if ( ( sourceType.isCollectionType() || sourceType.isArrayType()) && targetType.isIterableType() + || ( sourceType.isIterableType() && targetType.isCollectionType() ) ) { assignment = forgeIterableMapping( sourceType, targetType, rightHandSide ); } else if ( sourceType.isMapType() && targetType.isMapType() ) { 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 32fa1af931..b290ff56bc 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 @@ -434,7 +434,6 @@ private boolean allow2Steps() { private ConversionAssignment resolveViaConversion(Type sourceType, Type targetType) { ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType ); - if ( conversionProvider == null ) { return null; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/Fruit.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/Fruit.java new file mode 100644 index 0000000000..c989aa4e19 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/Fruit.java @@ -0,0 +1,27 @@ +/* + * 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.collection.iterabletolist; + +/** + * + * @author Xiu-Hong Kooi + */ +public class Fruit { + + private String type; + + public Fruit(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitSalad.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitSalad.java new file mode 100644 index 0000000000..6a04121044 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitSalad.java @@ -0,0 +1,29 @@ +/* + * 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.collection.iterabletolist; + +import java.util.List; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitSalad { + + private Iterable fruits; + + public FruitSalad(List fruits) { + this.fruits = fruits; + } + + public Iterable getFruits() { + return fruits; + } + + public void setFruits(List fruits) { + this.fruits = fruits; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMapper.java new file mode 100644 index 0000000000..bb4fdc66af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMapper.java @@ -0,0 +1,24 @@ +/* + * 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.collection.iterabletolist; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Xiu-Hong Kooi + */ +@Mapper +public interface FruitsMapper { + + FruitsMapper INSTANCE = Mappers.getMapper( + FruitsMapper.class ); + + FruitsMenu fruitSaladToMenu(FruitSalad salad); + + FruitSalad fruitsMenuToSalad(FruitsMenu menu); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMenu.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMenu.java new file mode 100644 index 0000000000..6f272dc454 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMenu.java @@ -0,0 +1,35 @@ +/* + * 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.collection.iterabletolist; + +import java.util.Iterator; +import java.util.List; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitsMenu implements Iterable { + + private List fruits; + + public FruitsMenu(List fruits) { + this.fruits = fruits; + } + + public List getFruits() { + return fruits; + } + + public void setFruits(List fruits) { + this.fruits = fruits; + } + + @Override + public Iterator iterator() { + return this.fruits.iterator(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/IterableToListMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/IterableToListMappingTest.java new file mode 100644 index 0000000000..915b9e5fd4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/IterableToListMappingTest.java @@ -0,0 +1,47 @@ +/* + * 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.collection.iterabletolist; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Xiu-Hong Kooi + */ +@WithClasses({ FruitsMenu.class, FruitSalad.class, Fruit.class, FruitsMapper.class }) +public class IterableToListMappingTest { + + @ProcessorTest + @IssueKey("3376") + public void shouldMapIterableToList() { + List fruits = Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ); + FruitsMenu menu = new FruitsMenu(fruits); + FruitSalad salad = FruitsMapper.INSTANCE.fruitsMenuToSalad( menu ); + Iterator itr = salad.getFruits().iterator(); + assertThat( itr.next().getType() ).isEqualTo( "mango" ); + assertThat( itr.next().getType() ).isEqualTo( "apple" ); + assertThat( itr.next().getType() ).isEqualTo( "banana" ); + } + + @ProcessorTest + @IssueKey("3376") + public void shouldMapListToIterable() { + List fruits = Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ); + FruitSalad salad = new FruitSalad(fruits); + FruitsMenu menu = FruitsMapper.INSTANCE.fruitSaladToMenu( salad ); + assertThat( menu.getFruits() ).extracting( Fruit::getType ).containsExactly( "mango", "apple", "banana" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/Fruit.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/Fruit.java new file mode 100644 index 0000000000..a15b344908 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/Fruit.java @@ -0,0 +1,27 @@ +/* + * 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.collection.iterabletoset; + +/** + * + * @author Xiu-Hong Kooi + */ +public class Fruit { + + private String type; + + public Fruit(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitSalad.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitSalad.java new file mode 100644 index 0000000000..b92ac3160c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitSalad.java @@ -0,0 +1,29 @@ +/* + * 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.collection.iterabletoset; + +import java.util.List; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitSalad { + + private Iterable fruits; + + public FruitSalad(Iterable fruits) { + this.fruits = fruits; + } + + public Iterable getFruits() { + return fruits; + } + + public void setFruits(List fruits) { + this.fruits = fruits; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMapper.java new file mode 100644 index 0000000000..413dd73617 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMapper.java @@ -0,0 +1,24 @@ +/* + * 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.collection.iterabletoset; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Xiu-Hong Kooi + */ +@Mapper +public interface FruitsMapper { + + FruitsMapper INSTANCE = Mappers.getMapper( + FruitsMapper.class ); + + FruitsMenu fruitSaladToMenu(FruitSalad salad); + + FruitSalad fruitsMenuToSalad(FruitsMenu menu); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMenu.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMenu.java new file mode 100644 index 0000000000..8ae4b8b632 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMenu.java @@ -0,0 +1,35 @@ +/* + * 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.collection.iterabletoset; + +import java.util.Iterator; +import java.util.Set; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitsMenu implements Iterable { + + private Set fruits; + + public FruitsMenu(Set fruits) { + this.fruits = fruits; + } + + public Set getFruits() { + return fruits; + } + + public void setFruits(Set fruits) { + this.fruits = fruits; + } + + @Override + public Iterator iterator() { + return this.fruits.iterator(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/IterableToSetMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/IterableToSetMappingTest.java new file mode 100644 index 0000000000..8b2373d4d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/IterableToSetMappingTest.java @@ -0,0 +1,50 @@ +/* + * 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.collection.iterabletoset; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Xiu-Hong Kooi + */ +@WithClasses({ FruitsMenu.class, FruitSalad.class, Fruit.class, FruitsMapper.class }) +public class IterableToSetMappingTest { + + @ProcessorTest + @IssueKey("3376") + public void shouldMapIterableToSet() { + Set fruits = new HashSet<>( Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ) ); + FruitsMenu menu = new FruitsMenu(fruits); + FruitSalad salad = FruitsMapper.INSTANCE.fruitsMenuToSalad( menu ); + Iterator itr = salad.getFruits().iterator(); + Set fruitTypes = fruits.stream().map( Fruit::getType ).collect( Collectors.toSet() ); + assertThat( fruitTypes.contains( itr.next().getType() ) ); + assertThat( fruitTypes.contains( itr.next().getType() ) ); + assertThat( fruitTypes.contains( itr.next().getType() ) ); + } + + @ProcessorTest + @IssueKey("3376") + public void shouldMapSetToIterable() { + Set fruits = new HashSet<>( Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ) ); + FruitSalad salad = new FruitSalad(fruits); + FruitsMenu menu = FruitsMapper.INSTANCE.fruitSaladToMenu( salad ); + assertThat( menu.getFruits() ).extracting( Fruit::getType ).contains( "mango", "apple", "banana" ); + } +} From b77d321ffb06f42eab9219e120fdf1782bccef1b Mon Sep 17 00:00:00 2001 From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com> Date: Wed, 1 Nov 2023 00:01:45 +0100 Subject: [PATCH 196/363] Added recent contributors (including myself) --- copyright.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/copyright.txt b/copyright.txt index 1b23a0e84a..a111489d01 100644 --- a/copyright.txt +++ b/copyright.txt @@ -44,6 +44,7 @@ Nikolas Charalambidis - https://github.com/Nikolas-Charalambidis Michael Pardo - https://github.com/pardom Mustafa Caylak - https://github.com/luxmeter Oliver Ehrenmüller - https://github.com/greuelpirat +Oliver Erhart - https://github.com/thunderhook Paul Strugnell - https://github.com/ps-powa Pascal Grün - https://github.com/pascalgn Pavel Makhov - https://github.com/streetturtle @@ -67,3 +68,4 @@ Timo Eckhardt - https://github.com/timoe Tomek Gubala - https://github.com/vgtworld Valentin Kulesh - https://github.com/unshare Vincent Alexander Beelte - https://github.com/grandmasterpixel +Xiu Hong Kooi - https://github.com/kooixh From 79f01e2de09c34ee08fef5288f8d8fa686250174 Mon Sep 17 00:00:00 2001 From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com> Date: Sat, 4 Nov 2023 21:35:40 +0100 Subject: [PATCH 197/363] Change master to main branch and fix CI status badge (#3423) --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 8b8af9a69f..21d820dc58 100644 --- a/readme.md +++ b/readme.md @@ -2,10 +2,10 @@ [![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.5.Final-blue.svg)](https://search.maven.org/search?q=g:org.mapstruct%20AND%20v:1.*.Final) [![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://search.maven.org/search?q=g:org.mapstruct) -[![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/master/LICENSE.txt) +[![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/main/LICENSE.txt) -[![Build Status](https://github.com/mapstruct/mapstruct/workflows/CI/badge.svg?branch=master)](https://github.com/mapstruct/mapstruct/actions?query=branch%3Amaster+workflow%3ACI) -[![Coverage Status](https://img.shields.io/codecov/c/github/mapstruct/mapstruct.svg)](https://codecov.io/gh/mapstruct/mapstruct) +[![Build Status](https://github.com/mapstruct/mapstruct/workflows/CI/badge.svg?branch=main)](https://github.com/mapstruct/mapstruct/actions?query=branch%3Amain+workflow%3ACI) +[![Coverage Status](https://img.shields.io/codecov/c/github/mapstruct/mapstruct.svg)](https://codecov.io/gh/mapstruct/mapstruct/tree/main) [![Gitter](https://img.shields.io/gitter/room/mapstruct/mapstruct.svg)](https://gitter.im/mapstruct/mapstruct-users) [![Code Quality: Java](https://img.shields.io/lgtm/grade/java/g/mapstruct/mapstruct.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mapstruct/mapstruct/context:java) [![Total Alerts](https://img.shields.io/lgtm/alerts/g/mapstruct/mapstruct.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mapstruct/mapstruct/alerts) From 0ac0c42dbc481a86879e3796d0201c622da61200 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 4 Nov 2023 23:06:16 +0100 Subject: [PATCH 198/363] [maven-release-plugin] prepare release 1.6.0.Beta1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0bdec734cb..2e244492d9 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 160f963adf..ddf9514f19 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index ac993c73d6..5a6a8f500c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index afa8e31002..e04b31cd73 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 0a62a3ef3c..49f1dc3d6e 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index f888e226ff..f9f43c871c 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 73239c87e3..6701e069f7 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 pom MapStruct Parent @@ -71,7 +71,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.6.0.Beta1 diff --git a/pom.xml b/pom.xml index 25a5ead6bc..4c99f88d0a 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - HEAD + 1.6.0.Beta1 diff --git a/processor/pom.xml b/processor/pom.xml index 12fc615f5f..5ab8d5027f 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta1 ../parent/pom.xml From 04deac2b3ae4b9ffe1c31ce293ee2d6ad3b20418 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 4 Nov 2023 23:06:17 +0100 Subject: [PATCH 199/363] [maven-release-plugin] prepare for next development iteration --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 4 ++-- processor/pom.xml | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 2e244492d9..0bdec734cb 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index ddf9514f19..160f963adf 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 5a6a8f500c..ac993c73d6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index e04b31cd73..afa8e31002 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 49f1dc3d6e..0a62a3ef3c 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index f9f43c871c..f888e226ff 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 6701e069f7..73239c87e3 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT pom MapStruct Parent @@ -71,7 +71,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.6.0.Beta1 + HEAD diff --git a/pom.xml b/pom.xml index 4c99f88d0a..25a5ead6bc 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT parent/pom.xml @@ -54,7 +54,7 @@ scm:git:git://github.com/mapstruct/mapstruct.git scm:git:git@github.com:mapstruct/mapstruct.git https://github.com/mapstruct/mapstruct/ - 1.6.0.Beta1 + HEAD diff --git a/processor/pom.xml b/processor/pom.xml index 5ab8d5027f..12fc615f5f 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta1 + 1.6.0-SNAPSHOT ../parent/pom.xml From 2af291ce2f1a9978c9d3f449d8bb8a9bb3b969f2 Mon Sep 17 00:00:00 2001 From: wandi34 Date: Sat, 11 Nov 2023 21:48:52 +0100 Subject: [PATCH 200/363] Fix Typo Mappper in SubclassMapping Doc --- copyright.txt | 1 + core/src/main/java/org/mapstruct/SubclassMapping.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/copyright.txt b/copyright.txt index a111489d01..d6dc8d1b8a 100644 --- a/copyright.txt +++ b/copyright.txt @@ -68,4 +68,5 @@ Timo Eckhardt - https://github.com/timoe Tomek Gubala - https://github.com/vgtworld Valentin Kulesh - https://github.com/unshare Vincent Alexander Beelte - https://github.com/grandmasterpixel +Winter Andreas - https://github.dev/wandi34 Xiu Hong Kooi - https://github.com/kooixh diff --git a/core/src/main/java/org/mapstruct/SubclassMapping.java b/core/src/main/java/org/mapstruct/SubclassMapping.java index ccf8d4d472..f5463b9026 100644 --- a/core/src/main/java/org/mapstruct/SubclassMapping.java +++ b/core/src/main/java/org/mapstruct/SubclassMapping.java @@ -49,7 +49,7 @@ * } * * Example 2: For parents that can be created. (e.g. normal classes or interfaces with - * @Mappper( uses = ObjectFactory.class ) ) + * @Mapper( uses = ObjectFactory.class ) ) *

      
        * // generates
        * @Override
      
      From 2bb2aefed8957861bf297b8f953b40e8753a8c75 Mon Sep 17 00:00:00 2001
      From: Muhammad Usama 
      Date: Fri, 24 Nov 2023 10:34:15 +0500
      Subject: [PATCH 201/363] #3413 Using Mapping#expression and
       Mapping#conditionaQualifiedBy(Name) should lead to compile error
      
      ---
       copyright.txt                                 |  1 +
       .../internal/model/source/MappingOptions.java |  5 ++-
       .../mapstruct/ap/internal/util/Message.java   |  1 +
       .../test/bugs/_3413/Erroneous3413Mapper.java  | 45 +++++++++++++++++++
       .../ap/test/bugs/_3413/Issue3413Test.java     | 35 +++++++++++++++
       5 files changed, 86 insertions(+), 1 deletion(-)
       create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Erroneous3413Mapper.java
       create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Issue3413Test.java
      
      diff --git a/copyright.txt b/copyright.txt
      index d6dc8d1b8a..71d47a796a 100644
      --- a/copyright.txt
      +++ b/copyright.txt
      @@ -42,6 +42,7 @@ Kevin Grüneberg - https://github.com/kevcodez
       Lukas Lazar - https://github.com/LukeLaz
       Nikolas Charalambidis - https://github.com/Nikolas-Charalambidis
       Michael Pardo - https://github.com/pardom
      +Muhammad Usama - https://github.com/the-mgi
       Mustafa Caylak - https://github.com/luxmeter
       Oliver Ehrenmüller - https://github.com/greuelpirat
       Oliver Erhart - https://github.com/thunderhook
      diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java
      index 2af1c95f71..dd5b2f17b9 100644
      --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java
      +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java
      @@ -207,10 +207,13 @@ private static boolean isConsistent(MappingGem gem, ExecutableElement method,
               if ( gem.source().hasValue() && gem.constant().hasValue() ) {
                   message = Message.PROPERTYMAPPING_SOURCE_AND_CONSTANT_BOTH_DEFINED;
               }
      +        else if ( gem.expression().hasValue() && gem.conditionQualifiedByName().hasValue() ) {
      +            message = Message.PROPERTYMAPPING_EXPRESSION_AND_CONDITION_QUALIFIED_BY_NAME_BOTH_DEFINED;
      +        }
               else if ( gem.source().hasValue() && gem.expression().hasValue() ) {
                   message = Message.PROPERTYMAPPING_SOURCE_AND_EXPRESSION_BOTH_DEFINED;
               }
      -        else if (gem.expression().hasValue() && gem.constant().hasValue() ) {
      +        else if ( gem.expression().hasValue() && gem.constant().hasValue() ) {
                   message = Message.PROPERTYMAPPING_EXPRESSION_AND_CONSTANT_BOTH_DEFINED;
               }
               else if ( gem.expression().hasValue() && gem.defaultValue().hasValue() ) {
      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 a24a43036c..4b43315390 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
      @@ -84,6 +84,7 @@ public enum Message {
           PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PROPERTY_FROM_TARGET("The type of parameter \"%s\" has no property named \"%s\". Please define the source property explicitly."),
           PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET("No property named \"%s\" exists in source parameter(s). Please define the source explicitly."),
           PROPERTYMAPPING_NO_SUITABLE_COLLECTION_OR_MAP_CONSTRUCTOR( "%s does not have an accessible copy or no-args constructor." ),
      +    PROPERTYMAPPING_EXPRESSION_AND_CONDITION_QUALIFIED_BY_NAME_BOTH_DEFINED( "Expression and condition qualified by name are both defined in @Mapping, either define an expression or a condition qualified by name." ),
       
           CONVERSION_LOSSY_WARNING( "%s has a possibly lossy conversion from %s to %s.", Diagnostic.Kind.WARNING ),
           CONVERSION_LOSSY_ERROR( "Can't map %s. It has a possibly lossy conversion from %s to %s." ),
      diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Erroneous3413Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Erroneous3413Mapper.java
      new file mode 100644
      index 0000000000..784e47b8e7
      --- /dev/null
      +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Erroneous3413Mapper.java
      @@ -0,0 +1,45 @@
      +/*
      + * 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._3413;
      +
      +import org.mapstruct.Mapper;
      +import org.mapstruct.Mapping;
      +import org.mapstruct.factory.Mappers;
      +
      +/**
      + * @author Muhammad Usama
      + */
      +@Mapper
      +public interface Erroneous3413Mapper {
      +    Erroneous3413Mapper INSTANCE = Mappers.getMapper( Erroneous3413Mapper.class );
      +
      +    @Mapping(target = "", expression = "", conditionQualifiedByName = "")
      +    ToPOJO map(FromPOJO fromPOJO);
      +
      +    class FromPOJO {
      +        private String value;
      +
      +        public String getValue() {
      +            return value;
      +        }
      +
      +        public void setValue(String value) {
      +            this.value = value;
      +        }
      +    }
      +
      +    class ToPOJO {
      +        private String value;
      +
      +        public String getValue() {
      +            return value;
      +        }
      +
      +        public void setValue(String value) {
      +            this.value = value;
      +        }
      +    }
      +}
      diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Issue3413Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Issue3413Test.java
      new file mode 100644
      index 0000000000..f98ed9cdbd
      --- /dev/null
      +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Issue3413Test.java
      @@ -0,0 +1,35 @@
      +/*
      + * 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._3413;
      +
      +import org.mapstruct.ap.testutil.IssueKey;
      +import org.mapstruct.ap.testutil.ProcessorTest;
      +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;
      +
      +/**
      + * @author Muhammad Usama
      + */
      +@IssueKey("3413")
      +public class Issue3413Test {
      +    @ProcessorTest
      +    @WithClasses(Erroneous3413Mapper.class)
      +    @ExpectedCompilationOutcome(
      +        value = CompilationResult.FAILED,
      +        diagnostics = {
      +            @Diagnostic(
      +                kind = javax.tools.Diagnostic.Kind.ERROR,
      +                line = 19,
      +                message = "Expression and condition qualified by name are both defined in @Mapping, " +
      +                    "either define an expression or a condition qualified by name."
      +            )
      +        }
      +    )
      +    void errorExpectedBecauseExpressionAndConditionQualifiedByNameCannotCoExists() {
      +    }
      +}
      
      From 930f5709b65d6f8a573172247ad02293649c08a9 Mon Sep 17 00:00:00 2001
      From: Ravil Galeyev 
      Date: Wed, 29 Nov 2023 22:25:22 +0100
      Subject: [PATCH 202/363] #3400 Remove unnecessary casts to long and double
      
      ---
       .../java/org/mapstruct/ap/internal/util/NativeTypes.java    | 4 ++--
       .../ap/test/source/constants/ConstantsMapperImpl.java       | 6 +++---
       2 files changed, 5 insertions(+), 5 deletions(-)
      
      diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java
      index 769f7b83f0..5498d4c698 100644
      --- a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java
      +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java
      @@ -284,7 +284,7 @@ void parse(String val, int radix) {
       
               @Override
               public Class getLiteral() {
      -            return float.class;
      +            return double.class;
               }
       
           }
      @@ -363,7 +363,7 @@ else if ( new BigInteger( val, radix ).bitLength() > 64 ) {
       
               @Override
               public Class getLiteral() {
      -            return int.class;
      +            return long.class;
               }
           }
       
      diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/source/constants/ConstantsMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/source/constants/ConstantsMapperImpl.java
      index f5f37142c2..ec13eed3fc 100644
      --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/source/constants/ConstantsMapperImpl.java
      +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/source/constants/ConstantsMapperImpl.java
      @@ -33,12 +33,12 @@ public ConstantsTarget mapFromConstants(String dummy) {
               constantsTarget.setIntValue( -03777777 );
               constantsTarget.setIntBoxed( 15 );
               constantsTarget.setLongValue( 0x7fffffffffffffffL );
      -        constantsTarget.setLongBoxed( (long) 0xCAFEBABEL );
      +        constantsTarget.setLongBoxed( 0xCAFEBABEL );
               constantsTarget.setFloatValue( 1.40e-45f );
               constantsTarget.setFloatBoxed( 3.4028235e38f );
               constantsTarget.setDoubleValue( 1e137 );
      -        constantsTarget.setDoubleBoxed( (double) 0x0.001P-1062d );
      -        constantsTarget.setDoubleBoxedZero( (double) 0.0 );
      +        constantsTarget.setDoubleBoxed( 0x0.001P-1062d );
      +        constantsTarget.setDoubleBoxedZero( 0.0 );
       
               return constantsTarget;
           }
      
      From fa857e9ff46b5bb3e5a6c2c8b1c56009944f5ef3 Mon Sep 17 00:00:00 2001
      From: mosesonline 
      Date: Sun, 10 Dec 2023 15:26:47 +0100
      Subject: [PATCH 203/363] bump some lib versions (#3460)
      
      ---
       parent/pom.xml                                | 16 ++++-----
       .../testutil/assertions/JavaFileAssert.java   | 34 ++++---------------
       2 files changed, 15 insertions(+), 35 deletions(-)
      
      diff --git a/parent/pom.xml b/parent/pom.xml
      index 73239c87e3..1b2c27741d 100644
      --- a/parent/pom.xml
      +++ b/parent/pom.xml
      @@ -22,17 +22,17 @@
           
               UTF-8
               1.0.0.Alpha3
      -        3.0.0-M3
      -        3.0.0-M5
      +        3.4.1
      +        3.2.2
               3.1.0
               5.3.18
               1.6.0
               8.36.1
      -        5.8.0-M1
      -        1.4.2
      +        5.10.1
      +        2.2.0
               
               1
      -        3.17.2
      +        3.24.2
               
               
               jdt_apt
      @@ -226,7 +226,7 @@
                   
                       org.projectlombok
                       lombok
      -                1.18.22
      +                1.18.30
                   
                   
                       org.immutables
      @@ -253,7 +253,7 @@
                   
                       joda-time
                       joda-time
      -                2.9
      +                2.12.5
                   
       
                   
      @@ -309,7 +309,7 @@
                   
                       commons-io
                       commons-io
      -                2.7
      +                2.15.0
                   
       
                   
      diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java b/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java
      index e6eabd9f84..4c6da0d13c 100644
      --- a/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java
      +++ b/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java
      @@ -5,7 +5,11 @@
        */
       package org.mapstruct.ap.testutil.assertions;
       
      -import static java.lang.String.format;
      +import org.assertj.core.api.FileAssert;
      +import org.assertj.core.error.ShouldHaveSameContent;
      +import org.assertj.core.internal.Diff;
      +import org.assertj.core.internal.Failures;
      +import org.assertj.core.util.diff.Delta;
       
       import java.io.File;
       import java.io.IOException;
      @@ -15,14 +19,7 @@
       import java.util.ArrayList;
       import java.util.List;
       
      -import org.apache.commons.io.FileUtils;
      -import org.assertj.core.api.AbstractCharSequenceAssert;
      -import org.assertj.core.api.Assertions;
      -import org.assertj.core.api.FileAssert;
      -import org.assertj.core.error.ShouldHaveSameContent;
      -import org.assertj.core.internal.Diff;
      -import org.assertj.core.internal.Failures;
      -import org.assertj.core.util.diff.Delta;
      +import static java.lang.String.format;
       
       /**
        * Allows to perform assertions on .java source files.
      @@ -39,7 +36,7 @@ public class JavaFileAssert extends FileAssert {
           private static final String IMPORT_GENERATED_ANNOTATION_REGEX = "import javax\\.annotation\\.(processing\\.)?" +
               "Generated;";
       
      -    private Diff diff = new Diff();
      +    private final Diff diff = new Diff();
       
           /**
            * @param actual the actual file
      @@ -48,22 +45,6 @@ public JavaFileAssert(File actual) {
               super( actual );
           }
       
      -    /**
      -     * @return assertion on the file content
      -     */
      -    public AbstractCharSequenceAssert content() {
      -        exists();
      -        isFile();
      -
      -        try {
      -            return Assertions.assertThat( FileUtils.readFileToString( actual, StandardCharsets.UTF_8 ) );
      -        }
      -        catch ( IOException e ) {
      -            failWithMessage( "Unable to read" + actual + ". Exception: " + e.getMessage() );
      -        }
      -        return null;
      -    }
      -
           /**
            * Verifies that the specified class is imported in this Java file
            *
      @@ -118,7 +99,6 @@ public void hasSameMapperContent(File expected) {
            * or if it is a change delta for the date/comments part of a {@code @Generated} annotation.
            *
            * @param delta that needs to be checked
      -     *
            * @return {@code true} if this delta should be ignored, {@code false} otherwise
            */
           private boolean ignoreDelta(Delta delta) {
      
      From 6d99f7b8f312617c8a9526e81fbb2dbf3eaa96a5 Mon Sep 17 00:00:00 2001
      From: Filip Hrisafov 
      Date: Mon, 18 Dec 2023 07:35:26 +0100
      Subject: [PATCH 204/363] #3473 Add Java 21 and EA to build matrix
      
      Fix tests not running green on Java 21
      Update Spring to run correctly on Java 21
      ---
       .github/workflows/java-ea.yml                            | 6 +++---
       .github/workflows/main.yml                               | 2 +-
       parent/pom.xml                                           | 4 ++--
       .../ap/test/conversion/jodatime/JodaConversionTest.java  | 9 ++++-----
       4 files changed, 10 insertions(+), 11 deletions(-)
      
      diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml
      index 3cc488ca7e..d9b018bfb8 100644
      --- a/.github/workflows/java-ea.yml
      +++ b/.github/workflows/java-ea.yml
      @@ -17,9 +17,9 @@ jobs:
             - name: 'Checkout'
               uses: actions/checkout@v3
             - name: 'Set up JDK'
      -        uses: actions/setup-java@v3
      +        uses: oracle-actions/setup-java@v1
               with:
      -          distribution: 'zulu'
      -          java-version: ${{ matrix.java }}
      +          website: jdk.java.net
      +          release: EA
             - name: 'Test'
               run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true
      diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
      index 54cf0b1076..b27dfc075a 100644
      --- a/.github/workflows/main.yml
      +++ b/.github/workflows/main.yml
      @@ -12,7 +12,7 @@ jobs:
           strategy:
             fail-fast: false
             matrix:
      -        java: [13, 17, 18]
      +        java: [17, 21]
           name: 'Linux JDK ${{ matrix.java }}'
           runs-on: ubuntu-latest
           steps:
      diff --git a/parent/pom.xml b/parent/pom.xml
      index 1b2c27741d..e3f0d89627 100644
      --- a/parent/pom.xml
      +++ b/parent/pom.xml
      @@ -25,7 +25,7 @@
               3.4.1
               3.2.2
               3.1.0
      -        5.3.18
      +        5.3.31
               1.6.0
               8.36.1
               5.10.1
      @@ -576,7 +576,7 @@
                       
                           org.jacoco
                           jacoco-maven-plugin
      -                    0.8.8
      +                    0.8.11
                       
                       
                           org.jvnet.jaxb2.maven2
      diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java
      index 1ab73616d5..8574c7a6be 100644
      --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java
      +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java
      @@ -14,7 +14,6 @@
       import org.joda.time.LocalDateTime;
       import org.joda.time.LocalTime;
       import org.junit.jupiter.api.condition.EnabledForJreRange;
      -import org.junit.jupiter.api.condition.EnabledOnJre;
       import org.junit.jupiter.api.condition.JRE;
       import org.junitpioneer.jupiter.DefaultLocale;
       import org.mapstruct.ap.testutil.IssueKey;
      @@ -72,7 +71,7 @@ public void testLocalTimeToString() {
           }
       
           @ProcessorTest
      -    @EnabledOnJre(JRE.JAVA_8)
      +    @EnabledForJreRange(min = JRE.JAVA_21)
           // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+
           public void testSourceToTargetMappingForStrings() {
               Source src = new Source();
      @@ -93,14 +92,14 @@ public void testSourceToTargetMappingForStrings() {
               // and now with default mappings
               target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( src );
               assertThat( target ).isNotNull();
      -        assertThat( target.getDateTime() ).isEqualTo( "1. Januar 2014 00:00:00 UTC" );
      -        assertThat( target.getLocalDateTime() ).isEqualTo( "1. Januar 2014 00:00:00" );
      +        assertThat( target.getDateTime() ).isEqualTo( "1. Januar 2014, 00:00:00 UTC" );
      +        assertThat( target.getLocalDateTime() ).isEqualTo( "1. Januar 2014, 00:00:00" );
               assertThat( target.getLocalDate() ).isEqualTo( "1. Januar 2014" );
               assertThat( target.getLocalTime() ).isEqualTo( "00:00:00" );
           }
       
           @ProcessorTest
      -    @EnabledForJreRange(min = JRE.JAVA_11)
      +    @EnabledForJreRange(min = JRE.JAVA_11, max = JRE.JAVA_17)
           // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+
           public void testSourceToTargetMappingForStringsJdk11() {
               Source src = new Source();
      
      From 7e6fee8714396cb79d81aaecae5f5cd69e178d09 Mon Sep 17 00:00:00 2001
      From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com>
      Date: Wed, 27 Dec 2023 13:40:04 +0100
      Subject: [PATCH 205/363] #1064 Provide a switch to turn off CheckStyle on
       generated test sources
      
      ---
       .../mapstruct/ap/testutil/runner/CompilingExtension.java    | 6 +++++-
       1 file changed, 5 insertions(+), 1 deletion(-)
      
      diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java
      index 7c47d2a2e8..95874e4b3d 100644
      --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java
      +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java
      @@ -182,11 +182,15 @@ private void assertResult(CompilationOutcomeDescriptor actualResult, ExtensionCo
               assertDiagnostics( actualResult.getDiagnostics(), expectedResult.getDiagnostics() );
               assertNotes( actualResult.getNotes(), expectedResult.getNotes() );
       
      -        if ( !findAnnotation( testClass, DisableCheckstyle.class ).isPresent() ) {
      +        if ( !findAnnotation( testClass, DisableCheckstyle.class ).isPresent() && !skipCheckstyleBySystemProperty() ) {
                   assertCheckstyleRules();
               }
           }
       
      +    private static boolean skipCheckstyleBySystemProperty() {
      +        return Boolean.parseBoolean( System.getProperty( "checkstyle.skip" ) );
      +    }
      +
           private void assertCheckstyleRules() throws Exception {
               if ( sourceOutputDir != null ) {
                   Properties properties = new Properties();
      
      From 6cb126cd7cd03aaf8254b3f7f12a29cb6d2ee1b0 Mon Sep 17 00:00:00 2001
      From: Filip Hrisafov 
      Date: Sun, 31 Dec 2023 09:34:34 +0100
      Subject: [PATCH 206/363] #3462 Stream getters should not be treated as
       alternative setter
      
      ---
       .../ap/internal/model/common/Type.java        | 16 ++++--
       .../ap/test/bugs/_3462/Issue3462Mapper.java   | 56 +++++++++++++++++++
       .../ap/test/bugs/_3462/Issue3462Test.java     | 34 +++++++++++
       3 files changed, 100 insertions(+), 6 deletions(-)
       create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Mapper.java
       create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Test.java
      
      diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java
      index f742e1ba0f..54305cffc1 100644
      --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java
      +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java
      @@ -821,12 +821,6 @@ else if ( candidate.getAccessorType() == AccessorType.GETTER ) {
                           candidate = adderMethod;
                       }
       
      -                if ( cmStrategy == CollectionMappingStrategyGem.TARGET_IMMUTABLE
      -                    && candidate.getAccessorType() == AccessorType.GETTER ) {
      -                    // If the collection mapping strategy is target immutable
      -                    // then the getter method cannot be used as a setter
      -                    continue;
      -                }
                   }
                   else if ( candidate.getAccessorType() == AccessorType.FIELD  && ( Executables.isFinal( candidate ) ||
                       result.containsKey( targetPropertyName ) ) ) {
      @@ -834,6 +828,16 @@ else if ( candidate.getAccessorType() == AccessorType.FIELD  && ( Executables.is
                       continue;
                   }
       
      +            if ( candidate.getAccessorType() == AccessorType.GETTER ) {
      +                // When the candidate is a getter then it can't be used in the following cases:
      +                // 1. The collection mapping strategy is target immutable
      +                // 2. The target type is a stream (streams are immutable)
      +                if ( cmStrategy == CollectionMappingStrategyGem.TARGET_IMMUTABLE ||
      +                    targetType != null && targetType.isStreamType() ) {
      +                    continue;
      +                }
      +            }
      +
                   Accessor previousCandidate = result.get( targetPropertyName );
                   if ( previousCandidate == null || preferredType == null || ( targetType != null
                       && typeUtils.isAssignable( preferredType.getTypeMirror(), targetType.getTypeMirror() ) ) ) {
      diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Mapper.java
      new file mode 100644
      index 0000000000..1bf87672c6
      --- /dev/null
      +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Mapper.java
      @@ -0,0 +1,56 @@
      +/*
      + * 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._3462;
      +
      +import java.util.List;
      +import java.util.stream.Stream;
      +
      +import org.mapstruct.Mapper;
      +import org.mapstruct.factory.Mappers;
      +
      +/**
      + * @author Filip Hrisafov
      + */
      +@Mapper
      +public interface Issue3462Mapper {
      +
      +    Issue3462Mapper INSTANCE = Mappers.getMapper( Issue3462Mapper.class );
      +
      +    Target map(Source source);
      +
      +    class Source {
      +        private final List values;
      +
      +        public Source(List values) {
      +            this.values = values;
      +        }
      +
      +        public List getValues() {
      +            return values;
      +        }
      +
      +        public Stream getValuesStream() {
      +            return values != null ? values.stream() : Stream.empty();
      +        }
      +    }
      +
      +    class Target {
      +        private List values;
      +
      +        public List getValues() {
      +            return values;
      +        }
      +
      +        public void setValues(List values) {
      +            this.values = values;
      +        }
      +
      +        public Stream getValuesStream() {
      +            return values != null ? values.stream() : Stream.empty();
      +        }
      +    }
      +
      +}
      diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Test.java
      new file mode 100644
      index 0000000000..16be4f441e
      --- /dev/null
      +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Test.java
      @@ -0,0 +1,34 @@
      +/*
      + * 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._3462;
      +
      +import java.util.Arrays;
      +
      +import org.mapstruct.ap.testutil.IssueKey;
      +import org.mapstruct.ap.testutil.ProcessorTest;
      +import org.mapstruct.ap.testutil.WithClasses;
      +
      +import static org.assertj.core.api.Assertions.assertThat;
      +
      +/**
      + * @author Filip Hrisafov
      + */
      +@IssueKey("3462")
      +@WithClasses(Issue3462Mapper.class)
      +class Issue3462Test {
      +
      +    @ProcessorTest
      +    void shouldNotTreatStreamGettersAsAlternativeSetter() {
      +
      +        Issue3462Mapper.Source source = new Issue3462Mapper.Source( Arrays.asList( "first", "second" ) );
      +        Issue3462Mapper.Target target = Issue3462Mapper.INSTANCE.map( source );
      +
      +        assertThat( target ).isNotNull();
      +        assertThat( target.getValues() ).containsExactly( "first", "second" );
      +        assertThat( target.getValuesStream() ).containsExactly( "first", "second" );
      +
      +    }
      +}
      
      From 60f162ca881076ac0540822026f0bb1c0a65b36e Mon Sep 17 00:00:00 2001
      From: Filip Hrisafov 
      Date: Sun, 28 Jan 2024 17:47:39 +0100
      Subject: [PATCH 207/363] #3463 DefaultBuilderProvider should be able to handle
       methods in parent interfaces
      
      ---
       .../ap/spi/DefaultBuilderProvider.java        | 131 ++++++++++++++----
       .../ap/test/bugs/_3463/EntityBuilder.java     |  14 ++
       .../ap/test/bugs/_3463/Issue3463Mapper.java   |  21 +++
       .../ap/test/bugs/_3463/Issue3463Test.java     |  33 +++++
       .../mapstruct/ap/test/bugs/_3463/Person.java  |  50 +++++++
       .../ap/test/bugs/_3463/PersonDto.java         |  21 +++
       6 files changed, 242 insertions(+), 28 deletions(-)
       create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/EntityBuilder.java
       create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Mapper.java
       create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Test.java
       create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Person.java
       create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/PersonDto.java
      
      diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java
      index f89e99cd17..6395364a35 100644
      --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java
      +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java
      @@ -14,6 +14,7 @@
       import javax.lang.model.element.Modifier;
       import javax.lang.model.element.TypeElement;
       import javax.lang.model.type.DeclaredType;
      +import javax.lang.model.type.ExecutableType;
       import javax.lang.model.type.TypeKind;
       import javax.lang.model.type.TypeMirror;
       import javax.lang.model.util.ElementFilter;
      @@ -107,19 +108,17 @@ public BuilderInfo findBuilderInfo(TypeMirror type) {
            * @throws TypeHierarchyErroneousException if the {@link TypeMirror} is of kind {@link TypeKind#ERROR}
            */
           protected TypeElement getTypeElement(TypeMirror type) {
      -        if ( type.getKind() == TypeKind.ERROR ) {
      -            throw new TypeHierarchyErroneousException( type );
      -        }
      -        DeclaredType declaredType = type.accept(
      -            new SimpleTypeVisitor6() {
      -                @Override
      -                public DeclaredType visitDeclared(DeclaredType t, Void p) {
      -                    return t;
      -                }
      -            },
      -            null
      -        );
      +        DeclaredType declaredType = getDeclaredType( type );
      +        return getTypeElement( declaredType );
      +    }
       
      +    /**
      +     * Find the {@link TypeElement} for the given {@link DeclaredType}.
      +     *
      +     * @param declaredType for which the {@link TypeElement} needs to be found.
      +     * @return the type element or {@code null} if the declared type element is not {@link TypeElement}
      +     */
      +    private TypeElement getTypeElement(DeclaredType declaredType) {
               if ( declaredType == null ) {
                   return null;
               }
      @@ -135,6 +134,28 @@ public TypeElement visitType(TypeElement e, Void p) {
               );
           }
       
      +    /**
      +     * Find the {@link DeclaredType} for the given {@link TypeMirror}.
      +     *
      +     * @param type for which the {@link DeclaredType} needs to be found.
      +     * @return the declared or {@code null} if the {@link TypeMirror} is not a {@link DeclaredType}
      +     * @throws TypeHierarchyErroneousException if the {@link TypeMirror} is of kind {@link TypeKind#ERROR}
      +     */
      +    private DeclaredType getDeclaredType(TypeMirror type) {
      +        if ( type.getKind() == TypeKind.ERROR ) {
      +            throw new TypeHierarchyErroneousException( type );
      +        }
      +        return type.accept(
      +            new SimpleTypeVisitor6() {
      +                @Override
      +                public DeclaredType visitDeclared(DeclaredType t, Void p) {
      +                    return t;
      +                }
      +            },
      +            null
      +        );
      +    }
      +
           /**
            * Find the {@link BuilderInfo} for the given {@code typeElement}.
            * 

      @@ -218,21 +239,32 @@ protected boolean isPossibleBuilderCreationMethod(ExecutableElement method, Type * Searches for a build method for {@code typeElement} within the {@code builderElement}. *

      * The default implementation iterates over each method in {@code builderElement} and uses - * {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, TypeElement)} to check if the method is a - * build method for {@code typeElement}. + * {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, DeclaredType, TypeElement)} + * to check if the method is a build method for {@code typeElement}. *

      * The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the * {@code builderElement} should be ignored, i.e. not checked for build elements. *

      - * If there are multiple methods that satisfy - * {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, TypeElement)} and one of those methods - * is names {@code build} that that method would be considered as a build method. * @param builderElement the element for the builder * @param typeElement the element for the type that is being built * @return the build method for the {@code typeElement} if it exists, or {@code null} if it does not * {@code build} */ protected Collection findBuildMethods(TypeElement builderElement, TypeElement typeElement) { + if ( shouldIgnore( builderElement ) || typeElement == null ) { + return Collections.emptyList(); + } + DeclaredType builderType = getDeclaredType( builderElement.asType() ); + + if ( builderType == null ) { + return Collections.emptyList(); + } + + return findBuildMethods( builderElement, builderType, typeElement ); + } + + private Collection findBuildMethods(TypeElement builderElement, DeclaredType builderType, + TypeElement typeElement) { if ( shouldIgnore( builderElement ) ) { return Collections.emptyList(); } @@ -240,23 +272,57 @@ protected Collection findBuildMethods(TypeElement builderElem List builderMethods = ElementFilter.methodsIn( builderElement.getEnclosedElements() ); List buildMethods = new ArrayList<>(); for ( ExecutableElement buildMethod : builderMethods ) { - if ( isBuildMethod( buildMethod, typeElement ) ) { + if ( isBuildMethod( buildMethod, builderType, typeElement ) ) { buildMethods.add( buildMethod ); } } - if ( buildMethods.isEmpty() ) { - return findBuildMethods( - getTypeElement( builderElement.getSuperclass() ), + if ( !buildMethods.isEmpty() ) { + return buildMethods; + } + + Collection parentClassBuildMethods = findBuildMethods( + getTypeElement( builderElement.getSuperclass() ), + builderType, + typeElement + ); + + if ( !parentClassBuildMethods.isEmpty() ) { + return parentClassBuildMethods; + } + + List interfaces = builderElement.getInterfaces(); + if ( interfaces.isEmpty() ) { + return Collections.emptyList(); + } + + Collection interfaceBuildMethods = new ArrayList<>(); + + for ( TypeMirror builderInterface : interfaces ) { + interfaceBuildMethods.addAll( findBuildMethods( + getTypeElement( builderInterface ), + builderType, typeElement - ); + ) ); } - return buildMethods; + return interfaceBuildMethods; + } + + /** + * @see #isBuildMethod(ExecutableElement, DeclaredType, TypeElement) + * @deprecated use {@link #isBuildMethod(ExecutableElement, DeclaredType, TypeElement)} instead + */ + @Deprecated + protected boolean isBuildMethod(ExecutableElement buildMethod, TypeElement typeElement) { + return buildMethod.getParameters().isEmpty() && + buildMethod.getModifiers().contains( Modifier.PUBLIC ) + && typeUtils.isAssignable( buildMethod.getReturnType(), typeElement.asType() ); } /** - * Checks if the {@code buildMethod} is a method that creates {@code typeElement}. + * Checks if the {@code buildMethod} is a method that creates the {@code typeElement} + * as a member of the {@code builderType}. *

      * The default implementation considers a method to be a build method if the following is satisfied: *

        @@ -266,14 +332,23 @@ protected Collection findBuildMethods(TypeElement builderElem *
      * * @param buildMethod the method that should be checked + * @param builderType the type of the builder in which the {@code buildMethod} is located in * @param typeElement the type element that needs to be built * @return {@code true} if the {@code buildMethod} is a build method for {@code typeElement}, {@code false} * otherwise */ - protected boolean isBuildMethod(ExecutableElement buildMethod, TypeElement typeElement) { - return buildMethod.getParameters().isEmpty() && - buildMethod.getModifiers().contains( Modifier.PUBLIC ) - && typeUtils.isAssignable( buildMethod.getReturnType(), typeElement.asType() ); + protected boolean isBuildMethod(ExecutableElement buildMethod, DeclaredType builderType, TypeElement typeElement) { + if ( !buildMethod.getParameters().isEmpty() ) { + return false; + } + if ( !buildMethod.getModifiers().contains( Modifier.PUBLIC ) ) { + return false; + } + TypeMirror buildMethodType = typeUtils.asMemberOf( builderType, buildMethod ); + if ( buildMethodType instanceof ExecutableType ) { + return typeUtils.isAssignable( ( (ExecutableType) buildMethodType ).getReturnType(), typeElement.asType() ); + } + return false; } /** diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/EntityBuilder.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/EntityBuilder.java new file mode 100644 index 0000000000..6b0b71993e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/EntityBuilder.java @@ -0,0 +1,14 @@ +/* + * 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._3463; + +/** + * @author Filip Hrisafov + */ +public interface EntityBuilder { + + T build(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Mapper.java new file mode 100644 index 0000000000..37ead3dfdf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Mapper.java @@ -0,0 +1,21 @@ +/* + * 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._3463; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3463Mapper { + + Issue3463Mapper INSTANCE = Mappers.getMapper( Issue3463Mapper.class ); + + Person map(PersonDto dto); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Test.java new file mode 100644 index 0000000000..e6cfc3ece7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Test.java @@ -0,0 +1,33 @@ +/* + * 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._3463; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3463") +@WithClasses({ + EntityBuilder.class, + Issue3463Mapper.class, + Person.class, + PersonDto.class +}) +class Issue3463Test { + + @ProcessorTest + void shouldUseInterfaceBuildMethod() { + Person person = Issue3463Mapper.INSTANCE.map( new PersonDto( "Tester" ) ); + + assertThat( person ).isNotNull(); + assertThat( person.getName() ).isEqualTo( "Tester" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Person.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Person.java new file mode 100644 index 0000000000..977d619474 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Person.java @@ -0,0 +1,50 @@ +/* + * 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._3463; + +/** + * @author Filip Hrisafov + */ +public class Person { + + private final String name; + + private Person(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static Builder builder() { + return new BuilderImpl(); + } + + public interface Builder extends EntityBuilder { + + Builder name(String name); + } + + private static final class BuilderImpl implements Builder { + + private String name; + + private BuilderImpl() { + } + + @Override + public Builder name(String name) { + this.name = name; + return this; + } + + @Override + public Person build() { + return new Person( this.name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/PersonDto.java new file mode 100644 index 0000000000..447e4813cc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/PersonDto.java @@ -0,0 +1,21 @@ +/* + * 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._3463; + +/** + * @author Filip Hrisafov + */ +public class PersonDto { + private final String name; + + public PersonDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} From 632213802817629c413bb83c9aebfc9bf25ee245 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:50:33 +0100 Subject: [PATCH 208/363] Add missing generic diamond operator to MappingOptions (#3498) --- .../org/mapstruct/ap/internal/model/source/MappingOptions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index dd5b2f17b9..f60eba1931 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -121,7 +121,7 @@ public static void addInstance(MappingGem mapping, ExecutableElement method, String defaultValue = mapping.defaultValue().getValue(); Set dependsOn = mapping.dependsOn().hasValue() ? - new LinkedHashSet( mapping.dependsOn().getValue() ) : + new LinkedHashSet<>( mapping.dependsOn().getValue() ) : Collections.emptySet(); FormattingParameters formattingParam = new FormattingParameters( From 6830258f77096c12b826ab15851d0dc17cd9a9b4 Mon Sep 17 00:00:00 2001 From: Chanyut Yuvacharuskul Date: Sun, 28 Jan 2024 23:51:57 +0700 Subject: [PATCH 209/363] Fix typo in chapter-10-advanced-mapping-options.asciidoc (#3500) --- .../main/asciidoc/chapter-10-advanced-mapping-options.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 87dffce31c..9d6c93f211 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -281,7 +281,7 @@ First calling a mapping method on the source property is not protected by a null The option `nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS` will always include a null check when source is non primitive, unless a source presence checker is defined on the source bean. -The strategy works in a hierarchical fashion. `@Mapping#nullValueCheckStrategy` will override `@BeanMapping#nullValueCheckStrategy`, `@BeanMapping#nullValueCheckStrategy` will override `@Mapper#nullValueCheckStrategy` and `@Mapper#nullValueCheckStrategy` will override `@MaperConfig#nullValueCheckStrategy`. +The strategy works in a hierarchical fashion. `@Mapping#nullValueCheckStrategy` will override `@BeanMapping#nullValueCheckStrategy`, `@BeanMapping#nullValueCheckStrategy` will override `@Mapper#nullValueCheckStrategy` and `@Mapper#nullValueCheckStrategy` will override `@MapperConfig#nullValueCheckStrategy`. [[source-presence-check]] === Source presence checking From 90a3ce0b464e96a82f6c7d81aaba1fd36fca7b8e Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:06:42 +0100 Subject: [PATCH 210/363] Use primitive types in NativeTypes (#3501) --- .../java/org/mapstruct/ap/internal/util/NativeTypes.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java index 5498d4c698..729cb180ef 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java @@ -270,11 +270,11 @@ public void validate(String s) { @Override void parse(String val, int radix) { - Double d = Double.parseDouble( radix == 16 ? "0x" + val : val ); + double d = Double.parseDouble( radix == 16 ? "0x" + val : val ); if ( doubleHasBecomeZero( d ) ) { throw new NumberFormatException( "floating point number too small" ); } - if ( d.isInfinite() ) { + if ( Double.isInfinite( d ) ) { throw new NumberFormatException( "infinitive is not allowed" ); } } @@ -297,11 +297,11 @@ public void validate(String s) { NumberRepresentation br = new NumberRepresentation( s, false, false, true ) { @Override void parse(String val, int radix) { - Float f = Float.parseFloat( radix == 16 ? "0x" + val : val ); + float f = Float.parseFloat( radix == 16 ? "0x" + val : val ); if ( doubleHasBecomeZero( f ) ) { throw new NumberFormatException( "floating point number too small" ); } - if ( f.isInfinite() ) { + if ( Float.isInfinite( f ) ) { throw new NumberFormatException( "infinitive is not allowed" ); } } From 0a43bc088f2f0bd934438d5ec8ca58b72e09998a Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:07:57 +0100 Subject: [PATCH 211/363] Add missing generic type to Javadoc Builder (#3499) --- .../src/main/java/org/mapstruct/ap/internal/model/Javadoc.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java index a1efc8c022..1afb5258a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java @@ -31,7 +31,7 @@ public Builder value(String value) { return this; } - public Builder authors(List authors) { + public Builder authors(List authors) { this.authors = authors; return this; } From 8191c850e0c210a5215512fe9ff67380125e7120 Mon Sep 17 00:00:00 2001 From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com> Date: Sun, 11 Feb 2024 10:42:23 +0100 Subject: [PATCH 212/363] #3323 Support access to the source property name --- .../main/java/org/mapstruct/Condition.java | 1 + .../org/mapstruct/SourcePropertyName.java | 26 ++ .../org/mapstruct/TargetPropertyName.java | 6 +- ...apter-10-advanced-mapping-options.asciidoc | 22 +- .../ap/internal/gem/GemGenerator.java | 2 + .../ap/internal/model/BeanMappingMethod.java | 1 + .../ap/internal/model/PropertyMapping.java | 17 +- .../ap/internal/model/common/Parameter.java | 21 +- .../model/common/ParameterBinding.java | 33 +- .../internal/model/source/SourceMethod.java | 9 +- .../source/selector/SelectionContext.java | 18 + .../model/source/selector/TypeSelector.java | 1 + .../processor/MethodRetrievalProcessor.java | 11 + .../mapstruct/ap/internal/util/Message.java | 1 + .../ap/internal/model/MethodReference.ftl | 7 +- .../model/MethodReferencePresenceCheck.ftl | 3 +- .../ap/internal/model/PropertyMapping.ftl | 1 + .../ap/internal/model/TypeConversion.ftl | 1 + .../ap/internal/model/macro/CommonMacros.ftl | 2 + .../Address.java | 2 +- .../AddressDto.java | 2 +- .../DomainModel.java | 2 +- .../Employee.java | 2 +- .../EmployeeDto.java | 22 +- ...ollectionMapperWithSourcePropertyName.java | 48 +++ ...onalMethodInMapperWithAllExceptTarget.java | 54 +++ ...nditionalMethodInMapperWithAllOptions.java | 64 ++++ ...lMethodInMapperWithSourcePropertyName.java | 39 +++ ...hodInUsesMapperWithSourcePropertyName.java | 43 +++ ...WithSourcePropertyNameInContextMapper.java | 108 ++++++ ...sNonStringSourcePropertyNameParameter.java | 26 ++ .../SourcePropertyNameTest.java | 312 ++++++++++++++++++ ...ollectionMapperWithTargetPropertyName.java | 11 +- ...onalMethodInMapperWithAllExceptTarget.java | 14 +- ...nditionalMethodInMapperWithAllOptions.java | 26 +- ...lMethodInMapperWithTargetPropertyName.java | 7 +- ...hodInUsesMapperWithTargetPropertyName.java | 7 +- ...WithTargetPropertyNameInContextMapper.java | 33 +- ...sNonStringTargetPropertyNameParameter.java | 7 +- .../TargetPropertyNameTest.java | 67 ++-- 40 files changed, 979 insertions(+), 100 deletions(-) create mode 100644 core/src/main/java/org/mapstruct/SourcePropertyName.java rename processor/src/test/java/org/mapstruct/ap/test/conditional/{targetpropertyname => propertyname}/Address.java (86%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{targetpropertyname => propertyname}/AddressDto.java (86%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{targetpropertyname => propertyname}/DomainModel.java (80%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{targetpropertyname => propertyname}/Employee.java (96%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{targetpropertyname => propertyname}/EmployeeDto.java (75%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java (76%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java (76%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java (61%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java (70%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java (74%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java (70%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java (59%) rename processor/src/test/java/org/mapstruct/ap/test/conditional/{ => propertyname}/targetpropertyname/TargetPropertyNameTest.java (88%) diff --git a/core/src/main/java/org/mapstruct/Condition.java b/core/src/main/java/org/mapstruct/Condition.java index 148ec4879d..37f1553be1 100644 --- a/core/src/main/java/org/mapstruct/Condition.java +++ b/core/src/main/java/org/mapstruct/Condition.java @@ -24,6 +24,7 @@ *
    3. The mapping source parameter
    4. *
    5. {@code @}{@link Context} parameter
    6. *
    7. {@code @}{@link TargetPropertyName} parameter
    8. + *
    9. {@code @}{@link SourcePropertyName} parameter
    10. * * * Note: The usage of this annotation is mandatory diff --git a/core/src/main/java/org/mapstruct/SourcePropertyName.java b/core/src/main/java/org/mapstruct/SourcePropertyName.java new file mode 100644 index 0000000000..a9d036d5d8 --- /dev/null +++ b/core/src/main/java/org/mapstruct/SourcePropertyName.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation marks a presence check method parameter as a source property name parameter. + *

      + * This parameter enables conditional filtering based on source property name at run-time. + * Parameter must be of type {@link String} and can be present only in {@link Condition} method. + *

      + * + * @author Oliver Erhart + * @since 1.6 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.CLASS) +public @interface SourcePropertyName { +} diff --git a/core/src/main/java/org/mapstruct/TargetPropertyName.java b/core/src/main/java/org/mapstruct/TargetPropertyName.java index 17af966fa8..c7ab8d957d 100644 --- a/core/src/main/java/org/mapstruct/TargetPropertyName.java +++ b/core/src/main/java/org/mapstruct/TargetPropertyName.java @@ -11,10 +11,10 @@ import java.lang.annotation.Target; /** - * This annotation marks a presence check method parameter as a property name parameter. + * This annotation marks a presence check method parameter as a target property name parameter. *

      - * This parameter enables conditional filtering based on target property name at run-time. - * Parameter must be of type {@link String} and can be present only in {@link Condition} method. + * This parameter enables conditional filtering based on target property name at run-time. + * Parameter must be of type {@link String} and can be present only in {@link Condition} method. *

      * @author Nikola Ivačič * @since 1.6 diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 9d6c93f211..db13c0548b 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -406,9 +406,9 @@ public class CarMapperImpl implements CarMapper { ---- ==== -Additionally `@TargetPropertyName` of type `java.lang.String` can be used in custom condition check method: +Additionally `@TargetPropertyName` or `@SourcePropertyName` of type `java.lang.String` can be used in custom condition check method: -.Mapper using custom condition check method with `@TargetPropertyName` +.Mapper using custom condition check method with `@TargetPropertyName` and `@SourcePropertyName` ==== [source, java, linenums] [subs="verbatim,attributes"] @@ -416,11 +416,19 @@ Additionally `@TargetPropertyName` of type `java.lang.String` can be used in cus @Mapper public interface CarMapper { + @Mapping(target = "owner", source = "ownerName") CarDto carToCarDto(Car car, @MappingTarget CarDto carDto); @Condition - default boolean isNotEmpty(String value, @TargetPropertyName String name) { - if ( name.equals( "owner" ) { + default boolean isNotEmpty( + String value, + @TargetPropertyName String targetPropertyName, + @SourcePropertyName String sourcePropertyName + ) { + + if ( targetPropertyName.equals( "owner" ) + && sourcePropertyName.equals( "ownerName" ) ) { + return value != null && !value.isEmpty() && !value.equals( value.toLowerCase() ); @@ -431,7 +439,7 @@ public interface CarMapper { ---- ==== -The generated mapper with `@TargetPropertyName` will look like: +The generated mapper with `@TargetPropertyName` and `@SourcePropertyName` will look like: .Custom condition check in generated implementation ==== @@ -447,7 +455,7 @@ public class CarMapperImpl implements CarMapper { return carDto; } - if ( isNotEmpty( car.getOwner(), "owner" ) ) { + if ( isNotEmpty( car.getOwner(), "owner", "ownerName" ) ) { carDto.setOwner( car.getOwner() ); } else { carDto.setOwner( null ); @@ -470,7 +478,7 @@ If there is a custom `@Condition` method applicable for the property it will hav ==== Methods annotated with `@Condition` in addition to the value of the source property can also have the source parameter as an input. -`@TargetPropertyName` parameter can only be used in `@Condition` methods. +`@TargetPropertyName` and `@SourcePropertyName` parameters can only be used in `@Condition` methods. ==== <> is also valid for `@Condition` methods. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index 9ac13184cd..2ed9bd9a09 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -31,6 +31,7 @@ import org.mapstruct.Named; import org.mapstruct.ObjectFactory; import org.mapstruct.Qualifier; +import org.mapstruct.SourcePropertyName; import org.mapstruct.SubclassMapping; import org.mapstruct.SubclassMappings; import org.mapstruct.TargetPropertyName; @@ -57,6 +58,7 @@ @GemDefinition(BeanMapping.class) @GemDefinition(EnumMapping.class) @GemDefinition(MapMapping.class) +@GemDefinition(SourcePropertyName.class) @GemDefinition(SubclassMapping.class) @GemDefinition(SubclassMappings.class) @GemDefinition(TargetType.class) 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 ee347ef35e..be7203507b 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 @@ -1522,6 +1522,7 @@ private void applyPropertyNameBasedMapping(List sourceReference MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) .sourceMethod( method ) + .sourcePropertyName( targetPropertyName ) .target( targetPropertyName, targetPropertyReadAccessor, targetPropertyWriteAccessor ) .sourceReference( sourceRef ) .existingVariableNames( existingVariableNames ) 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 50f23ecbff..2f30957962 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 @@ -70,6 +70,7 @@ public class PropertyMapping extends ModelElement { private final String name; + private final String sourcePropertyName; private final String sourceBeanName; private final String targetWriteAccessorName; private final ReadAccessor targetReadAccessorProvider; @@ -286,6 +287,7 @@ else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.get } return new PropertyMapping( + sourcePropertyName, targetPropertyName, rightHandSide.getSourceParameterName(), targetWriteAccessor.getSimpleName(), @@ -1099,16 +1101,17 @@ private PropertyMapping(String name, String targetWriteAccessorName, ReadAccessor targetReadAccessorProvider, Type targetType, Assignment propertyAssignment, Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { - this( name, null, targetWriteAccessorName, targetReadAccessorProvider, + this( name, null, null, targetWriteAccessorName, targetReadAccessorProvider, targetType, propertyAssignment, dependsOn, defaultValueAssignment, constructorMapping ); } - private PropertyMapping(String name, String sourceBeanName, String targetWriteAccessorName, - ReadAccessor targetReadAccessorProvider, Type targetType, - Assignment assignment, - Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { + private PropertyMapping(String sourcePropertyName, String name, String sourceBeanName, + String targetWriteAccessorName, ReadAccessor targetReadAccessorProvider, Type targetType, + Assignment assignment, + Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { + this.sourcePropertyName = sourcePropertyName; this.name = name; this.sourceBeanName = sourceBeanName; this.targetWriteAccessorName = targetWriteAccessorName; @@ -1128,6 +1131,10 @@ public String getName() { return name; } + public String getSourcePropertyName() { + return sourcePropertyName; + } + public String getSourceBeanName() { return sourceBeanName; } 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 600c8ac337..44ac0eb7f0 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 @@ -14,8 +14,9 @@ import org.mapstruct.ap.internal.gem.ContextGem; import org.mapstruct.ap.internal.gem.MappingTargetGem; -import org.mapstruct.ap.internal.gem.TargetTypeGem; +import org.mapstruct.ap.internal.gem.SourcePropertyNameGem; import org.mapstruct.ap.internal.gem.TargetPropertyNameGem; +import org.mapstruct.ap.internal.gem.TargetTypeGem; import org.mapstruct.ap.internal.util.Collections; /** @@ -32,6 +33,7 @@ public class Parameter extends ModelElement { private final boolean mappingTarget; private final boolean targetType; private final boolean mappingContext; + private final boolean sourcePropertyName; private final boolean targetPropertyName; private final boolean varArgs; @@ -44,12 +46,13 @@ private Parameter(Element element, Type type, boolean varArgs) { this.mappingTarget = MappingTargetGem.instanceOn( element ) != null; this.targetType = TargetTypeGem.instanceOn( element ) != null; this.mappingContext = ContextGem.instanceOn( element ) != null; + this.sourcePropertyName = SourcePropertyNameGem.instanceOn( element ) != null; this.targetPropertyName = TargetPropertyNameGem.instanceOn( element ) != null; this.varArgs = varArgs; } private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext, - boolean targetPropertyName, + boolean sourcePropertyName, boolean targetPropertyName, boolean varArgs) { this.element = null; this.name = name; @@ -58,12 +61,13 @@ private Parameter(String name, Type type, boolean mappingTarget, boolean targetT this.mappingTarget = mappingTarget; this.targetType = targetType; this.mappingContext = mappingContext; + this.sourcePropertyName = sourcePropertyName; this.targetPropertyName = targetPropertyName; this.varArgs = varArgs; } public Parameter(String name, Type type) { - this( name, type, false, false, false, false, false ); + this( name, type, false, false, false, false, false, false ); } public Element getElement() { @@ -99,6 +103,7 @@ private String format() { return ( mappingTarget ? "@MappingTarget " : "" ) + ( targetType ? "@TargetType " : "" ) + ( mappingContext ? "@Context " : "" ) + + ( sourcePropertyName ? "@SourcePropertyName " : "" ) + ( targetPropertyName ? "@TargetPropertyName " : "" ) + "%s " + name; } @@ -120,6 +125,10 @@ public boolean isTargetPropertyName() { return targetPropertyName; } + public boolean isSourcePropertyName() { + return sourcePropertyName; + } + public boolean isVarArgs() { return varArgs; } @@ -165,6 +174,7 @@ public static Parameter forForgedMappingTarget(Type parameterType) { false, false, false, + false, false ); } @@ -206,6 +216,10 @@ public static Parameter getTargetTypeParameter(List parameters) { return parameters.stream().filter( Parameter::isTargetType ).findAny().orElse( null ); } + public static Parameter getSourcePropertyNameParameter(List parameters) { + return parameters.stream().filter( Parameter::isSourcePropertyName ).findAny().orElse( null ); + } + public static Parameter getTargetPropertyNameParameter(List parameters) { return parameters.stream().filter( Parameter::isTargetPropertyName ).findAny().orElse( null ); } @@ -214,6 +228,7 @@ private static boolean isSourceParameter( Parameter parameter ) { return !parameter.isMappingTarget() && !parameter.isTargetType() && !parameter.isMappingContext() && + !parameter.isSourcePropertyName() && !parameter.isTargetPropertyName(); } 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 18274e5492..0791ee626a 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 @@ -22,16 +22,19 @@ public class ParameterBinding { private final boolean targetType; private final boolean mappingTarget; private final boolean mappingContext; + private final boolean sourcePropertyName; private final boolean targetPropertyName; private final SourceRHS sourceRHS; private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType, - boolean mappingContext, boolean targetPropertyName, SourceRHS sourceRHS) { + boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName, + SourceRHS sourceRHS) { this.type = parameterType; this.variableName = variableName; this.targetType = targetType; this.mappingTarget = mappingTarget; this.mappingContext = mappingContext; + this.sourcePropertyName = sourcePropertyName; this.targetPropertyName = targetPropertyName; this.sourceRHS = sourceRHS; } @@ -64,11 +67,18 @@ public boolean isMappingContext() { return mappingContext; } + /** + * @return {@code true}, if the parameter being bound is a {@code @SourcePropertyName} parameter. + */ + public boolean isSourcePropertyName() { + return sourcePropertyName; + } + /** * @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter. */ public boolean isTargetPropertyName() { - return targetPropertyName; + return targetPropertyName; } /** @@ -108,6 +118,7 @@ public static ParameterBinding fromParameter(Parameter parameter) { parameter.isMappingTarget(), parameter.isTargetType(), parameter.isMappingContext(), + parameter.isSourcePropertyName(), parameter.isTargetPropertyName(), null ); @@ -129,6 +140,7 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame false, false, false, + false, null ); } @@ -138,14 +150,21 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame * @return a parameter binding representing a target type parameter */ public static ParameterBinding forTargetTypeBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, true, false, false, null ); + return new ParameterBinding( classTypeOf, null, false, true, false, false, false, null ); } /** * @return a parameter binding representing a target property name parameter */ public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, false, false, true, null ); + return new ParameterBinding( classTypeOf, null, false, false, false, false, true, null ); + } + + /** + * @return a parameter binding representing a source property name parameter + */ + public static ParameterBinding forSourcePropertyNameBinding(Type classTypeOf) { + return new ParameterBinding( classTypeOf, null, false, false, false, true, false, null ); } /** @@ -153,7 +172,7 @@ public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) { * @return a parameter binding representing a mapping target parameter */ public static ParameterBinding forMappingTargetBinding(Type resultType) { - return new ParameterBinding( resultType, null, true, false, false, false, null ); + return new ParameterBinding( resultType, null, true, false, false, false, false, null ); } /** @@ -161,10 +180,10 @@ public static ParameterBinding forMappingTargetBinding(Type resultType) { * @return a parameter binding representing a mapping source type */ public static ParameterBinding forSourceTypeBinding(Type sourceType) { - return new ParameterBinding( sourceType, null, false, false, false, false, null ); + return new ParameterBinding( sourceType, null, false, false, false, false, false, null ); } public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) { - return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, sourceRHS ); + return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, false, sourceRHS ); } } 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 37b57d9025..42c318ca49 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 @@ -47,6 +47,7 @@ public class SourceMethod implements Method { private final List parameters; private final Parameter mappingTargetParameter; private final Parameter targetTypeParameter; + private final Parameter sourcePropertyNameParameter; private final Parameter targetPropertyNameParameter; private final boolean isObjectFactory; private final boolean isPresenceCheck; @@ -249,6 +250,7 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters ); + this.sourcePropertyNameParameter = Parameter.getSourcePropertyNameParameter( parameters ); this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters ); this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null; this.isObjectFactory = determineIfIsObjectFactory(); @@ -265,9 +267,10 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) private boolean determineIfIsObjectFactory() { boolean hasNoSourceParameters = getSourceParameters().isEmpty(); boolean hasNoMappingTargetParam = getMappingTargetParameter() == null; + boolean hasNoSourcePropertyNameParam = getSourcePropertyNameParameter() == null; boolean hasNoTargetPropertyNameParam = getTargetPropertyNameParameter() == null; return !isLifecycleCallbackMethod() && !returnType.isVoid() - && hasNoMappingTargetParam && hasNoTargetPropertyNameParam + && hasNoMappingTargetParam && hasNoSourcePropertyNameParam && hasNoTargetPropertyNameParam && ( hasObjectFactoryAnnotation || hasNoSourceParameters ); } @@ -382,6 +385,10 @@ public Parameter getTargetTypeParameter() { return targetTypeParameter; } + public Parameter getSourcePropertyNameParameter() { + return sourcePropertyNameParameter; + } + public Parameter getTargetPropertyNameParameter() { return targetPropertyNameParameter; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java index 9e82dae25d..800278a4c4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java @@ -171,6 +171,7 @@ private static List getAvailableParameterBindingsFromMethod(Me if ( sourceRHS != null ) { availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) ); + addSourcePropertyNameBindings( availableParams, sourceRHS.getSourceType(), typeFactory ); } else { availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); @@ -189,6 +190,7 @@ private static List getAvailableParameterBindingsFromSourceTyp List availableParams = new ArrayList<>(); availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) ); + addSourcePropertyNameBindings( availableParams, sourceType, typeFactory ); for ( Parameter param : mappingMethod.getParameters() ) { if ( param.isMappingContext() ) { @@ -201,6 +203,22 @@ private static List getAvailableParameterBindingsFromSourceTyp return availableParams; } + private static void addSourcePropertyNameBindings(List availableParams, Type sourceType, + TypeFactory typeFactory) { + + boolean sourcePropertyNameAvailable = false; + for ( ParameterBinding pb : availableParams ) { + if ( pb.isSourcePropertyName() ) { + sourcePropertyNameAvailable = true; + break; + } + } + if ( !sourcePropertyNameAvailable ) { + availableParams.add( ParameterBinding.forSourcePropertyNameBinding( typeFactory.getType( String.class ) ) ); + } + + } + /** * Adds default parameter bindings for the mapping-target and target-type if not already available. * 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 24275f594b..50f834d43e 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 @@ -215,6 +215,7 @@ private static List findCandidateBindingsForParameter(List parameters, Type returnType) { for ( Parameter param : parameters ) { + + if ( param.isSourcePropertyName() && !param.getType().isString() ) { + messager.printMessage( + param.getElement(), + SourcePropertyNameGem.instanceOn( param.getElement() ).mirror(), + Message.RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE + ); + return false; + } + if ( param.isTargetPropertyName() && !param.getType().isString() ) { messager.printMessage( param.getElement(), 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 4b43315390..d8f27791ee 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 @@ -177,6 +177,7 @@ public enum Message { RETRIEVAL_MAPPER_USES_CYCLE( "The mapper %s is referenced itself in Mapper#uses.", Diagnostic.Kind.WARNING ), RETRIEVAL_AFTER_METHOD_NOT_IMPLEMENTED( "@AfterMapping can only be applied to an implemented method." ), RETRIEVAL_BEFORE_METHOD_NOT_IMPLEMENTED( "@BeforeMapping can only be applied to an implemented method." ), + RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE( "@SourcePropertyName can only by applied to a String parameter." ), RETRIEVAL_TARGET_PROPERTY_NAME_WRONG_TYPE( "@TargetPropertyName can only by applied to a String parameter." ), 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 4b45643dc8..63f983df46 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 @@ -44,6 +44,8 @@ <#if ext.targetBeanName??>${ext.targetBeanName}<#else>${param.variableName}<#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}<#t> <#elseif param.mappingContext> ${param.variableName}<#t> + <#elseif param.sourcePropertyName> + "${ext.sourcePropertyName}"<#t> <#elseif param.targetPropertyName> "${ext.targetPropertyName}"<#t> <#elseif param.sourceRHS??> @@ -60,7 +62,7 @@ <#-- macro: assignment - purpose: note: takes its targetyType from the singleSourceParameterType + purpose: note: takes its targetType from the singleSourceParameterType --> <#macro _assignment assignmentToUse> <@includeModel object=assignmentToUse @@ -69,6 +71,7 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName targetType=singleSourceParameterType/> - \ No newline at end of file + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl index 0452e1699a..68b05d84ed 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl @@ -8,5 +8,6 @@ <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" --> <#if isNegate()>!<@includeModel object=methodReference presenceCheck=true + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName - targetType=ext.targetType/> \ No newline at end of file + targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl index e6aef4cecd..f45659cb5d 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl @@ -11,6 +11,7 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=targetReadAccessorName targetWriteAccessorName=targetWriteAccessorName + sourcePropertyName=sourcePropertyName targetPropertyName=name targetType=targetType defaultValueAssignment=defaultValueAssignment /> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl index 4a9d356ce4..a5e5798d1c 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl @@ -14,6 +14,7 @@ ${openExpression}<@_assignment/>${closeExpression} existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index bce28ebe19..278b441aa9 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -17,6 +17,7 @@ <#if sourcePresenceCheckerReference??> if ( <@includeModel object=sourcePresenceCheckerReference targetPropertyName=ext.targetPropertyName + sourcePropertyName=ext.sourcePropertyName targetType=ext.targetType/> ) { <#nested> } @@ -61,6 +62,7 @@ <#if sourcePresenceCheckerReference??> if ( <@includeModel object=sourcePresenceCheckerReference targetType=ext.targetType + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName /> ) { <#if needs_explicit_local_var> <@includeModel object=nullCheckLocalVarType/> ${nullCheckLocalVarName} = <@lib.handleAssignment/>; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.java index 162ed118bb..339af75944 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; /** * @author Nikola Ivačič diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.java index f4cbc71915..c6a63065d4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; /** * @author Nikola Ivačič diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.java similarity index 80% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.java index 4d2c716a96..8e5fa8695d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; /** * Target Property Name test entities diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.java similarity index 96% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.java index 59ee3426e1..497717a592 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; import java.util.List; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.java similarity index 75% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.java index 5d81a9334b..f49f25e4a3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; import java.util.List; @@ -15,14 +15,14 @@ public class EmployeeDto implements DomainModel { private String firstName; private String lastName; private String title; - private String country; + private String originCountry; private boolean active; private int age; private EmployeeDto boss; private AddressDto primaryAddress; - private List addresses; + private List originAddresses; public String getFirstName() { return firstName; @@ -48,12 +48,12 @@ public void setTitle(String title) { this.title = title; } - public String getCountry() { - return country; + public String getOriginCountry() { + return originCountry; } - public void setCountry(String country) { - this.country = country; + public void setOriginCountry(String originCountry) { + this.originCountry = originCountry; } public boolean isActive() { @@ -88,11 +88,11 @@ public void setPrimaryAddress(AddressDto primaryAddress) { this.primaryAddress = primaryAddress; } - public List getAddresses() { - return addresses; + public List getOriginAddresses() { + return originAddresses; } - public void setAddresses(List addresses) { - this.addresses = addresses; + public void setOriginAddresses(List originAddresses) { + this.originAddresses = originAddresses; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java new file mode 100644 index 0000000000..944fe5d146 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java @@ -0,0 +1,48 @@ +/* + * 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.conditional.propertyname.sourcepropertyname; + +import java.util.Collection; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodForCollectionMapperWithSourcePropertyName { + + ConditionalMethodForCollectionMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodForCollectionMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotEmpty(Collection collection, @SourcePropertyName String propName) { + if ( "originAddresses".equalsIgnoreCase( propName ) ) { + return false; + } + return collection != null && !collection.isEmpty(); + } + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java new file mode 100644 index 0000000000..ea6a77d94e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java @@ -0,0 +1,54 @@ +/* + * 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.conditional.propertyname.sourcepropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithAllExceptTarget { + + ConditionalMethodInMapperWithAllExceptTarget INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllExceptTarget.class ); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @SourcePropertyName String propName, + @Context PresenceUtils utils) { + utils.visited.add( propName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + if ( propName.equalsIgnoreCase( "firstName" ) ) { + return true; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java new file mode 100644 index 0000000000..ee3be57648 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java @@ -0,0 +1,64 @@ +/* + * 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.conditional.propertyname.sourcepropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithAllOptions { + + ConditionalMethodInMapperWithAllOptions INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class ); + + class PresenceUtils { + Set visitedSourceNames = new LinkedHashSet<>(); + Set visitedTargetNames = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + Set visitedTargets = new LinkedHashSet<>(); + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + void map(EmployeeDto employeeDto, + @MappingTarget Employee employee, + @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @MappingTarget DomainModel target, + @SourcePropertyName String sourcePropName, + @TargetPropertyName String targetPropName, + @Context PresenceUtils utils) { + utils.visitedSourceNames.add( sourcePropName ); + utils.visitedTargetNames.add( targetPropName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + utils.visitedTargets.add( target.getClass().getSimpleName() ); + if ( sourcePropName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java new file mode 100644 index 0000000000..41ff6c5264 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java @@ -0,0 +1,39 @@ +/* + * 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.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithSourcePropertyName { + + ConditionalMethodInMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java new file mode 100644 index 0000000000..8ba222512f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java @@ -0,0 +1,43 @@ +/* + * 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.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper(uses = ConditionalMethodInUsesMapperWithSourcePropertyName.PresenceUtils.class) +public interface ConditionalMethodInUsesMapperWithSourcePropertyName { + + ConditionalMethodInUsesMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInUsesMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + class PresenceUtils { + + @Condition + public boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java new file mode 100644 index 0000000000..64b6fcbbab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java @@ -0,0 +1,108 @@ +/* + * 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.conditional.propertyname.sourcepropertyname; + +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.TargetType; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodWithSourcePropertyNameInContextMapper { + + ConditionalMethodWithSourcePropertyNameInContextMapper INSTANCE + = Mappers.getMapper( ConditionalMethodWithSourcePropertyNameInContextMapper.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + Address map(AddressDto addressDto, @Context PresenceUtils utils); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean isNotBlank(String value, @SourcePropertyName String propName) { + visited.add( propName ); + return value != null && !value.trim().isEmpty(); + } + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils); + + class PresenceUtilsAllProps { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean collect(@SourcePropertyName String propName) { + visited.add( propName ); + return true; + } + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtilsAllPropsWithSource utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllPropsWithSource utils); + + @BeforeMapping + default void before(DomainModel source, @Context PresenceUtilsAllPropsWithSource utils) { + String lastProp = utils.visitedSegments.peekLast(); + if ( lastProp != null && source != null ) { + utils.path.offerLast( lastProp ); + } + } + + @AfterMapping + default void after(@TargetType Class targetClass, @Context PresenceUtilsAllPropsWithSource utils) { + // intermediate method for collection mapping must not change the path + if (targetClass != List.class) { + utils.path.pollLast(); + } + } + + class PresenceUtilsAllPropsWithSource { + Deque visitedSegments = new LinkedList<>(); + Deque visited = new LinkedList<>(); + Deque path = new LinkedList<>(); + + @Condition + public boolean collect(@SourcePropertyName String propName) { + visitedSegments.offerLast( propName ); + path.offerLast( propName ); + visited.offerLast( String.join( ".", path ) ); + path.pollLast(); + return true; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java new file mode 100644 index 0000000000..5ed118676d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java @@ -0,0 +1,26 @@ +/* + * 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.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; + +@Mapper +public interface ErroneousNonStringSourcePropertyNameParameter { + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName int propName) { + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java new file mode 100644 index 0000000000..2471ed647d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java @@ -0,0 +1,312 @@ +/* + * 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.conditional.propertyname.sourcepropertyname; + +import java.util.Collections; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@IssueKey("3323") +@WithClasses({ + Address.class, + AddressDto.class, + Employee.class, + EmployeeDto.class, + DomainModel.class +}) +public class SourcePropertyNameTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithSourcePropertyName.class + }) + public void conditionalMethodInMapperWithSourcePropertyName() { + ConditionalMethodInMapperWithSourcePropertyName mapper + = ConditionalMethodInMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodForCollectionMapperWithSourcePropertyName.class + }) + public void conditionalMethodForCollectionMapperWithSourcePropertyName() { + ConditionalMethodForCollectionMapperWithSourcePropertyName mapper + = ConditionalMethodForCollectionMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInUsesMapperWithSourcePropertyName.class + }) + public void conditionalMethodInUsesMapperWithSourcePropertyName() { + ConditionalMethodInUsesMapperWithSourcePropertyName mapper + = ConditionalMethodInUsesMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllOptions.class + }) + public void conditionalMethodInMapperWithAllOptions() { + ConditionalMethodInMapperWithAllOptions mapper + = ConditionalMethodInMapperWithAllOptions.INSTANCE; + + ConditionalMethodInMapperWithAllOptions.PresenceUtils utils = + new ConditionalMethodInMapperWithAllOptions.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = new Employee(); + mapper.map( employeeDto, employee, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visitedSourceNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry" ); + assertThat( utils.visitedTargetNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country" ); + assertThat( utils.visitedSources ).containsExactly( "EmployeeDto" ); + assertThat( utils.visitedTargets ).containsExactly( "Employee" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllExceptTarget.class + }) + public void conditionalMethodInMapperWithAllExceptTarget() { + ConditionalMethodInMapperWithAllExceptTarget mapper + = ConditionalMethodInMapperWithAllExceptTarget.INSTANCE; + + ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils utils = + new ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isEqualTo( " " ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry", "street" ); + assertThat( utils.visitedSources ).containsExactlyInAnyOrder( "EmployeeDto", "AddressDto" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithSourcePropertyNameInContextMapper.class + }) + public void conditionalMethodWithSourcePropertyNameInUsesContextMapper() { + ConditionalMethodWithSourcePropertyNameInContextMapper mapper + = ConditionalMethodWithSourcePropertyNameInContextMapper.INSTANCE; + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtils utils = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setLastName( " " ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry", "street" ); + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllProps allPropsUtils = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllProps(); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtils ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( allPropsUtils.visited ) + .containsExactlyInAnyOrder( + "firstName", + "lastName", + "title", + "originCountry", + "active", + "age", + "boss", + "primaryAddress", + "originAddresses", + "street" + ); + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllPropsWithSource allPropsUtilsWithSource = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllPropsWithSource(); + + EmployeeDto bossEmployeeDto = new EmployeeDto(); + bossEmployeeDto.setLastName( "Boss Tester" ); + bossEmployeeDto.setOriginCountry( "US" ); + bossEmployeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( + "Testing St. 10" ) ) ); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setBoss( bossEmployeeDto ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtilsWithSource ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ).isNotEmpty(); + assertThat( employee.getAddresses().get( 0 ).getStreet() ).isEqualTo( "Testing St. 6" ); + assertThat( employee.getBoss() ).isNotNull(); + assertThat( employee.getBoss().getCountry() ).isEqualTo( "US" ); + assertThat( employee.getBoss().getLastName() ).isEqualTo( "Boss Tester" ); + assertThat( employee.getBoss().getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 10" ); + assertThat( allPropsUtilsWithSource.visited ) + .containsExactly( + "originCountry", + "originAddresses", + "originAddresses.street", + "firstName", + "lastName", + "title", + "active", + "age", + "boss", + "boss.originCountry", + "boss.originAddresses", + "boss.originAddresses.street", + "boss.firstName", + "boss.lastName", + "boss.title", + "boss.active", + "boss.age", + "boss.boss", + "boss.primaryAddress", + "primaryAddress" + ); + } + + @ProcessorTest + @WithClasses({ + ErroneousNonStringSourcePropertyNameParameter.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousNonStringSourcePropertyNameParameter.class, + line = 23, + message = "@SourcePropertyName can only by applied to a String parameter." + ) + } + ) + public void nonStringSourcePropertyNameParameter() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java similarity index 76% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java index 71baa0942b..a51a318eb6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java @@ -3,15 +3,18 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.Collection; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.Collection; - /** * @author Nikola Ivačič */ @@ -21,6 +24,8 @@ public interface ConditionalMethodForCollectionMapperWithTargetPropertyName { ConditionalMethodForCollectionMapperWithTargetPropertyName INSTANCE = Mappers.getMapper( ConditionalMethodForCollectionMapperWithTargetPropertyName.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java similarity index 76% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java index 430303d06d..1d0dd3fa51 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java @@ -3,17 +3,21 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; import org.mapstruct.Condition; import org.mapstruct.Context; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.LinkedHashSet; -import java.util.Set; - /** * @author Filip Hrisafov * @author Nikola Ivačič @@ -29,6 +33,8 @@ class PresenceUtils { Set visitedSources = new LinkedHashSet<>(); } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtils utils); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java similarity index 61% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java index a18ba0db73..d3ccb9ba2e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java @@ -3,18 +3,23 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; import org.mapstruct.Condition; import org.mapstruct.Context; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; +import org.mapstruct.SourcePropertyName; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.LinkedHashSet; -import java.util.Set; - /** * @author Filip Hrisafov * @author Nikola Ivačič @@ -26,11 +31,14 @@ public interface ConditionalMethodInMapperWithAllOptions { = Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class ); class PresenceUtils { - Set visited = new LinkedHashSet<>(); + Set visitedSourceNames = new LinkedHashSet<>(); + Set visitedTargetNames = new LinkedHashSet<>(); Set visitedSources = new LinkedHashSet<>(); Set visitedTargets = new LinkedHashSet<>(); } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") void map(EmployeeDto employeeDto, @MappingTarget Employee employee, @Context PresenceUtils utils); @@ -39,12 +47,14 @@ void map(EmployeeDto employeeDto, default boolean isNotBlank(String value, DomainModel source, @MappingTarget DomainModel target, - @TargetPropertyName String propName, + @SourcePropertyName String sourcePropName, + @TargetPropertyName String targetPropName, @Context PresenceUtils utils) { - utils.visited.add( propName ); + utils.visitedSourceNames.add( sourcePropName ); + utils.visitedTargetNames.add( targetPropName ); utils.visitedSources.add( source.getClass().getSimpleName() ); utils.visitedTargets.add( target.getClass().getSimpleName() ); - if ( propName.equalsIgnoreCase( "lastName" ) ) { + if ( targetPropName.equalsIgnoreCase( "lastName" ) ) { return false; } return value != null && !value.trim().isEmpty(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java similarity index 70% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java index 6d27bc9036..d5dc378f32 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java @@ -3,11 +3,14 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; /** @@ -20,6 +23,8 @@ public interface ConditionalMethodInMapperWithTargetPropertyName { ConditionalMethodInMapperWithTargetPropertyName INSTANCE = Mappers.getMapper( ConditionalMethodInMapperWithTargetPropertyName.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java similarity index 74% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java index 7c526884e9..381b99d4f4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java @@ -3,11 +3,14 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; /** @@ -20,6 +23,8 @@ public interface ConditionalMethodInUsesMapperWithTargetPropertyName { ConditionalMethodInUsesMapperWithTargetPropertyName INSTANCE = Mappers.getMapper( ConditionalMethodInUsesMapperWithTargetPropertyName.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); class PresenceUtils { diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java similarity index 70% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java index a7cde220c9..44bc262435 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java @@ -3,21 +3,29 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; import org.mapstruct.AfterMapping; import org.mapstruct.BeforeMapping; import org.mapstruct.Condition; import org.mapstruct.Context; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.TargetType; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.Deque; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.Set; - /** * @author Nikola Ivačič */ @@ -27,6 +35,8 @@ public interface ConditionalMethodWithTargetPropertyNameInContextMapper { ConditionalMethodWithTargetPropertyNameInContextMapper INSTANCE = Mappers.getMapper( ConditionalMethodWithTargetPropertyNameInContextMapper.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtils utils); Address map(AddressDto addressDto, @Context PresenceUtils utils); @@ -41,6 +51,8 @@ public boolean isNotBlank(String value, @TargetPropertyName String propName) { } } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils); Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils); @@ -55,6 +67,8 @@ public boolean collect(@TargetPropertyName String propName) { } } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtilsAllPropsWithSource utils); Address map(AddressDto addressDto, @Context PresenceUtilsAllPropsWithSource utils); @@ -68,8 +82,11 @@ default void before(DomainModel source, @Context PresenceUtilsAllPropsWithSource } @AfterMapping - default void after(@Context PresenceUtilsAllPropsWithSource utils) { - utils.path.pollLast(); + default void after(@TargetType Class targetClass, @Context PresenceUtilsAllPropsWithSource utils) { + // intermediate method for collection mapping must not change the path + if (targetClass != List.class) { + utils.path.pollLast(); + } } class PresenceUtilsAllPropsWithSource { diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java similarity index 59% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java index ec545a058b..d56277abf4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java @@ -3,15 +3,20 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; @Mapper public interface ErroneousNonStringTargetPropertyNameParameter { + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java index 91e4b77de2..bb90c0b069 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java @@ -3,9 +3,16 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.Collections; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -14,8 +21,6 @@ import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.runner.GeneratedSource; -import java.util.Collections; - import static org.assertj.core.api.Assertions.assertThat; /** @@ -46,8 +51,8 @@ public void conditionalMethodInMapperWithTargetPropertyName() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -71,8 +76,8 @@ public void conditionalMethodForCollectionMapperWithTargetPropertyName() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -94,8 +99,8 @@ public void conditionalMethodInUsesMapperWithTargetPropertyName() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -122,8 +127,8 @@ public void conditionalMethodInMapperWithAllOptions() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -135,7 +140,9 @@ public void conditionalMethodInMapperWithAllOptions() { assertThat( employee.getAddresses() ) .extracting( Address::getStreet ) .containsExactly( "Testing St. 6" ); - assertThat( utils.visited ) + assertThat( utils.visitedSourceNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry" ); + assertThat( utils.visitedTargetNames ) .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country" ); assertThat( utils.visitedSources ).containsExactly( "EmployeeDto" ); assertThat( utils.visitedTargets ).containsExactly( "Employee" ); @@ -155,8 +162,8 @@ public void conditionalMethodInMapperWithAllExceptTarget() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -185,8 +192,8 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setLastName( " " ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -204,8 +211,8 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { employeeDto = new EmployeeDto(); employeeDto.setLastName( "Tester" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -234,15 +241,15 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { EmployeeDto bossEmployeeDto = new EmployeeDto(); bossEmployeeDto.setLastName( "Boss Tester" ); - bossEmployeeDto.setCountry( "US" ); - bossEmployeeDto.setAddresses( Collections.singletonList( new AddressDto( + bossEmployeeDto.setOriginCountry( "US" ); + bossEmployeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 10" ) ) ); employeeDto = new EmployeeDto(); employeeDto.setLastName( "Tester" ); - employeeDto.setCountry( "US" ); + employeeDto.setOriginCountry( "US" ); employeeDto.setBoss( bossEmployeeDto ); - employeeDto.setAddresses( + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -259,26 +266,26 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { .containsExactly( "Testing St. 10" ); assertThat( allPropsUtilsWithSource.visited ) .containsExactly( + "country", + "addresses", + "addresses.street", "firstName", "lastName", "title", - "country", "active", "age", "boss", + "boss.country", + "boss.addresses", + "boss.addresses.street", "boss.firstName", "boss.lastName", "boss.title", - "boss.country", "boss.active", "boss.age", "boss.boss", "boss.primaryAddress", - "boss.addresses", - "boss.addresses.street", - "primaryAddress", - "addresses", - "addresses.street" + "primaryAddress" ); } @@ -293,7 +300,7 @@ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { @Diagnostic( kind = javax.tools.Diagnostic.Kind.ERROR, type = ErroneousNonStringTargetPropertyNameParameter.class, - line = 18, + line = 23, message = "@TargetPropertyName can only by applied to a String parameter." ) } From ca1fd0d85dc76f914167c94f1e034e8dfd55bb61 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 11 Feb 2024 12:51:19 +0100 Subject: [PATCH 213/363] #3331 Do not handle defined mappings if the result type is abstract due to runtime exception subclass exhaustive strategy (#3487) --- .../ap/internal/model/BeanMappingMethod.java | 43 +++++++++++-- .../ap/test/bugs/_3331/Issue3331Mapper.java | 27 ++++++++ .../ap/test/bugs/_3331/Issue3331Test.java | 48 ++++++++++++++ .../mapstruct/ap/test/bugs/_3331/Vehicle.java | 64 +++++++++++++++++++ .../ap/test/bugs/_3331/VehicleDto.java | 51 +++++++++++++++ 5 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Vehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/VehicleDto.java 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 be7203507b..8979901f80 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 @@ -284,10 +284,15 @@ else if ( !method.isUpdateMethod() ) { initializeMappingReferencesIfNeeded( resultTypeToMap ); - // map properties with mapping - boolean mappingErrorOccurred = handleDefinedMappings( resultTypeToMap ); - if ( mappingErrorOccurred ) { - return null; + boolean shouldHandledDefinedMappings = shouldHandledDefinedMappings( resultTypeToMap ); + + + if ( shouldHandledDefinedMappings ) { + // map properties with mapping + boolean mappingErrorOccurred = handleDefinedMappings( resultTypeToMap ); + if ( mappingErrorOccurred ) { + return null; + } } boolean applyImplicitMappings = !mappingReferences.isRestrictToDefinedMappings(); @@ -1045,6 +1050,36 @@ private List getValueAsList(AnnotationValue av) { return (List) av.getValue(); } + /** + * Determine whether defined mappings should be handled on the result type. + * They should be, if any of the following is true: + *
        + *
      • The {@code resultTypeToMap} is not abstract
      • + *
      • There is a factory method
      • + *
      • The method is an update method
      • + *
      + * Otherwise, it means that we have reached this because subclass mappings are being used + * and the chosen strategy is runtime exception. + * + * @param resultTypeToMap the type in which the defined target properties are defined + * @return {@code true} if defined mappings should be handled for the result type, {@code false} otherwise + */ + private boolean shouldHandledDefinedMappings(Type resultTypeToMap) { + if ( !resultTypeToMap.isAbstract() ) { + return true; + } + + if ( hasFactoryMethod ) { + return true; + } + + if ( method.isUpdateMethod() ) { + return true; + } + + return false; + } + /** * Iterates over all defined mapping methods ({@code @Mapping(s)}), either directly given or inherited from the * inverse mapping method. diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Mapper.java new file mode 100644 index 0000000000..3011479c65 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Mapper.java @@ -0,0 +1,27 @@ +/* + * 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._3331; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface Issue3331Mapper { + + Issue3331Mapper INSTANCE = Mappers.getMapper( Issue3331Mapper.class ); + + @SubclassMapping(source = Vehicle.Car.class, target = VehicleDto.Car.class) + @SubclassMapping(source = Vehicle.Motorbike.class, target = VehicleDto.Motorbike.class) + @Mapping(target = "name", constant = "noname") + VehicleDto map(Vehicle vehicle); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Test.java new file mode 100644 index 0000000000..3871868226 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Test.java @@ -0,0 +1,48 @@ +/* + * 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._3331; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3331") +@WithClasses({ + Issue3331Mapper.class, + Vehicle.class, + VehicleDto.class, +}) +class Issue3331Test { + + @ProcessorTest + void shouldCorrectCompileAndThrowExceptionOnRuntime() { + VehicleDto target = Issue3331Mapper.INSTANCE.map( new Vehicle.Car( "Test car", 4 ) ); + + assertThat( target.getName() ).isEqualTo( "noname" ); + assertThat( target ) + .isInstanceOfSatisfying( VehicleDto.Car.class, car -> { + assertThat( car.getNumOfDoors() ).isEqualTo( 4 ); + } ); + + target = Issue3331Mapper.INSTANCE.map( new Vehicle.Motorbike( "Test bike", true ) ); + + assertThat( target.getName() ).isEqualTo( "noname" ); + assertThat( target ) + .isInstanceOfSatisfying( VehicleDto.Motorbike.class, bike -> { + assertThat( bike.isAllowedForMinor() ).isTrue(); + } ); + + assertThatThrownBy( () -> Issue3331Mapper.INSTANCE.map( new Vehicle.Truck( "Test truck", 3 ) ) ) + .isInstanceOf( IllegalArgumentException.class ) + .hasMessage( "Not all subclasses are supported for this mapping. Missing for " + Vehicle.Truck.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Vehicle.java new file mode 100644 index 0000000000..3b68a3acc4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Vehicle.java @@ -0,0 +1,64 @@ +/* + * 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._3331; + +/** + * @author Filip Hrisafov + */ +public abstract class Vehicle { + + private final String name; + + protected Vehicle(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static class Car extends Vehicle { + + private final int numOfDoors; + + public Car(String name, int numOfDoors) { + super( name ); + this.numOfDoors = numOfDoors; + } + + public int getNumOfDoors() { + return numOfDoors; + } + } + + public static class Motorbike extends Vehicle { + + private final boolean allowedForMinor; + + public Motorbike(String name, boolean allowedForMinor) { + super( name ); + this.allowedForMinor = allowedForMinor; + } + + public boolean isAllowedForMinor() { + return allowedForMinor; + } + } + + public static class Truck extends Vehicle { + + private final int numOfAxis; + + public Truck(String name, int numOfAxis) { + super( name ); + this.numOfAxis = numOfAxis; + } + + public int getNumOfAxis() { + return numOfAxis; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/VehicleDto.java new file mode 100644 index 0000000000..8c2ef13dd2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/VehicleDto.java @@ -0,0 +1,51 @@ +/* + * 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._3331; + +/** + * @author Filip Hrisafov + */ +public abstract class VehicleDto { + + private final String name; + + protected VehicleDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static class Car extends VehicleDto { + + private final int numOfDoors; + + public Car(String name, int numOfDoors) { + super( name ); + this.numOfDoors = numOfDoors; + } + + public int getNumOfDoors() { + return numOfDoors; + } + } + + public static class Motorbike extends VehicleDto { + + private final boolean allowedForMinor; + + public Motorbike(String name, boolean allowedForMinor) { + super( name ); + this.allowedForMinor = allowedForMinor; + } + + public boolean isAllowedForMinor() { + return allowedForMinor; + } + } + +} From bb1cd6348502c592dfb77aa5ac86cfe62cc0ba32 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sun, 11 Feb 2024 13:29:34 +0100 Subject: [PATCH 214/363] #2788 Improve unmapped source properties message for forged methods --- .../ap/internal/model/BeanMappingMethod.java | 115 +++---- .../mapstruct/ap/internal/util/Message.java | 2 + .../nestedbeans/DottedErrorMessageTest.java | 285 ++++++++++++++---- ...SourceCollectionElementPropertyMapper.java | 17 ++ .../UnmappableSourceDeepListMapper.java | 17 ++ .../UnmappableSourceDeepMapKeyMapper.java | 17 ++ .../UnmappableSourceDeepMapValueMapper.java | 17 ++ .../UnmappableSourceDeepNestingMapper.java | 17 ++ ...r.java => UnmappableSourceEnumMapper.java} | 2 +- .../UnmappableSourceValuePropertyMapper.java | 17 ++ ...argetCollectionElementPropertyMapper.java} | 4 +- ...va => UnmappableTargetDeepListMapper.java} | 4 +- ... => UnmappableTargetDeepMapKeyMapper.java} | 4 +- ...> UnmappableTargetDeepMapValueMapper.java} | 4 +- ...=> UnmappableTargetDeepNestingMapper.java} | 4 +- ... UnmappableTargetValuePropertyMapper.java} | 4 +- ...eWarnCollectionElementPropertyMapper.java} | 7 +- ...> UnmappableSourceWarnDeepListMapper.java} | 7 +- ...UnmappableSourceWarnDeepMapKeyMapper.java} | 7 +- ...mappableSourceWarnDeepMapValueMapper.java} | 7 +- ...nmappableSourceWarnDeepNestingMapper.java} | 7 +- ...appableSourceWarnValuePropertyMapper.java} | 7 +- ...etWarnCollectionElementPropertyMapper.java | 17 ++ .../UnmappableTargetWarnDeepListMapper.java | 17 ++ .../UnmappableTargetWarnDeepMapKeyMapper.java | 17 ++ ...nmappableTargetWarnDeepMapValueMapper.java | 17 ++ ...UnmappableTargetWarnDeepNestingMapper.java | 17 ++ ...mappableTargetWarnValuePropertyMapper.java | 17 ++ 28 files changed, 546 insertions(+), 128 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceCollectionElementPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepListMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepMapKeyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepMapValueMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepNestingMapper.java rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableEnumMapper.java => UnmappableSourceEnumMapper.java} (96%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceValuePropertyMapper.java rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableCollectionElementPropertyMapper.java => UnmappableTargetCollectionElementPropertyMapper.java} (68%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableDeepListMapper.java => UnmappableTargetDeepListMapper.java} (71%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableDeepMapKeyMapper.java => UnmappableTargetDeepMapKeyMapper.java} (71%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableDeepMapValueMapper.java => UnmappableTargetDeepMapValueMapper.java} (70%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableDeepNestingMapper.java => UnmappableTargetDeepNestingMapper.java} (71%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/{UnmappableValuePropertyMapper.java => UnmappableTargetValuePropertyMapper.java} (70%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnCollectionElementPropertyMapper.java => UnmappableSourceWarnCollectionElementPropertyMapper.java} (55%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnDeepListMapper.java => UnmappableSourceWarnDeepListMapper.java} (56%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnDeepMapKeyMapper.java => UnmappableSourceWarnDeepMapKeyMapper.java} (56%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnDeepMapValueMapper.java => UnmappableSourceWarnDeepMapValueMapper.java} (56%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnDeepNestingMapper.java => UnmappableSourceWarnDeepNestingMapper.java} (56%) rename processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/{UnmappableWarnValuePropertyMapper.java => UnmappableSourceWarnValuePropertyMapper.java} (56%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnCollectionElementPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepListMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepMapKeyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepMapValueMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepNestingMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnValuePropertyMapper.java 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 8979901f80..9abed3ec44 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 @@ -1718,54 +1718,22 @@ private void reportErrorForUnmappedTargetPropertiesIfRequired() { } else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) { - if ( !( method instanceof ForgedMethod ) ) { - Message msg = unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ? - Message.BEANMAPPING_UNMAPPED_TARGETS_ERROR : Message.BEANMAPPING_UNMAPPED_TARGETS_WARNING; - Object[] args = new Object[] { - MessageFormat.format( - "{0,choice,1#property|1 unmappedProperties, + Message unmappedPropertiesMsg, + Message unmappedForgedPropertiesMsg) { + if ( !( method instanceof ForgedMethod ) ) { Object[] args = new Object[] { MessageFormat.format( "{0,choice,1#property|1 Date: Sun, 11 Feb 2024 19:53:38 +0100 Subject: [PATCH 215/363] #3360 Do not report unmapped source and target properties when result type is abstract due to runtime exception subclass exhaustive strategy (#3526) --- .../ap/internal/model/BeanMappingMethod.java | 6 +- .../ap/test/bugs/_3360/Issue3360Mapper.java | 34 ++++++++++++ .../ap/test/bugs/_3360/Issue3360Test.java | 43 +++++++++++++++ .../mapstruct/ap/test/bugs/_3360/Vehicle.java | 55 +++++++++++++++++++ .../ap/test/bugs/_3360/VehicleDto.java | 45 +++++++++++++++ 5 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Vehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/VehicleDto.java 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 9abed3ec44..dd3a86e2cf 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 @@ -319,8 +319,10 @@ else if ( !method.isUpdateMethod() ) { handleUnmappedConstructorProperties(); // report errors on unmapped properties - reportErrorForUnmappedTargetPropertiesIfRequired(); - reportErrorForUnmappedSourcePropertiesIfRequired(); + if ( shouldHandledDefinedMappings ) { + reportErrorForUnmappedTargetPropertiesIfRequired(); + reportErrorForUnmappedSourcePropertiesIfRequired(); + } reportErrorForMissingIgnoredSourceProperties(); reportErrorForUnusedSourceParameters(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Mapper.java new file mode 100644 index 0000000000..812d885489 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Mapper.java @@ -0,0 +1,34 @@ +/* + * 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._3360; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper( + subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + unmappedTargetPolicy = ReportingPolicy.ERROR, + unmappedSourcePolicy = ReportingPolicy.ERROR +) +public interface Issue3360Mapper { + + Issue3360Mapper INSTANCE = Mappers.getMapper( Issue3360Mapper.class ); + + @SubclassMapping(target = VehicleDto.Car.class, source = Vehicle.Car.class) + VehicleDto map(Vehicle vehicle); + + @Mapping(target = "model", source = "modelName") + @BeanMapping(ignoreUnmappedSourceProperties = "computedName") + VehicleDto.Car map(Vehicle.Car car); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Test.java new file mode 100644 index 0000000000..307c2b265a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Test.java @@ -0,0 +1,43 @@ +/* + * 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._3360; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3360") +@WithClasses({ + Issue3360Mapper.class, + Vehicle.class, + VehicleDto.class, +}) +class Issue3360Test { + + @ProcessorTest + void shouldCompileWithoutErrorsAndWarnings() { + + Vehicle vehicle = new Vehicle.Car( "Test", "car", 4 ); + + VehicleDto target = Issue3360Mapper.INSTANCE.map( vehicle ); + + assertThat( target.getName() ).isEqualTo( "Test" ); + assertThat( target.getModel() ).isEqualTo( "car" ); + assertThat( target ).isInstanceOfSatisfying( VehicleDto.Car.class, car -> { + assertThat( car.getNumOfDoors() ).isEqualTo( 4 ); + } ); + + assertThatThrownBy( () -> Issue3360Mapper.INSTANCE.map( new Vehicle.Motorbike( "Test", "bike" ) ) ) + .isInstanceOf( IllegalArgumentException.class ) + .hasMessage( "Not all subclasses are supported for this mapping. Missing for " + Vehicle.Motorbike.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Vehicle.java new file mode 100644 index 0000000000..0cc2011bc9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Vehicle.java @@ -0,0 +1,55 @@ +/* + * 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._3360; + +/** + * @author Filip Hrisafov + */ +public abstract class Vehicle { + + private final String name; + private final String modelName; + + protected Vehicle(String name, String modelName) { + this.name = name; + this.modelName = modelName; + } + + public String getName() { + return name; + } + + public String getModelName() { + return modelName; + } + + public String getComputedName() { + return null; + } + + public static class Car extends Vehicle { + + private final int numOfDoors; + + public Car(String name, String modelName, int numOfDoors) { + super( name, modelName ); + this.numOfDoors = numOfDoors; + } + + public int getNumOfDoors() { + return numOfDoors; + } + } + + public static class Motorbike extends Vehicle { + + public Motorbike(String name, String modelName) { + super( name, modelName ); + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/VehicleDto.java new file mode 100644 index 0000000000..fc93a2fcae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/VehicleDto.java @@ -0,0 +1,45 @@ +/* + * 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._3360; + +/** + * @author Filip Hrisafov + */ +public abstract class VehicleDto { + + private String name; + private String model; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public static class Car extends VehicleDto { + + private int numOfDoors; + + public int getNumOfDoors() { + return numOfDoors; + } + + public void setNumOfDoors(int numOfDoors) { + this.numOfDoors = numOfDoors; + } + } + +} From 2c12e75bfcae80aed0aa2674ccdf248280a6f4dc Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sun, 3 Mar 2024 18:59:32 +0100 Subject: [PATCH 216/363] #3485 Exception for Mapping to target = "." without source --- .../internal/model/source/MappingOptions.java | 3 ++ .../mapstruct/ap/internal/util/Message.java | 1 + .../bugs/_3485/ErroneousIssue3485Mapper.java | 35 +++++++++++++++++++ .../ap/test/bugs/_3485/Issue3485Test.java | 33 +++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index f60eba1931..e0994d746f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -260,6 +260,9 @@ else if ( gem.nullValuePropertyMappingStrategy().hasValue() else if ( ".".equals( gem.target().get() ) && gem.ignore().hasValue() && gem.ignore().getValue() ) { message = Message.PROPERTYMAPPING_TARGET_THIS_AND_IGNORE; } + else if ( ".".equals( gem.target().get() ) && !gem.source().hasValue() ) { + message = Message.PROPERTYMAPPING_TARGET_THIS_NO_SOURCE; + } if ( message == null ) { return true; 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 ab8fa01010..68c079cb96 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 @@ -71,6 +71,7 @@ public enum Message { PROPERTYMAPPING_DEFAULT_EXPERSSION_AND_NVPMS( "DefaultExpression and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a defaultExpression or an nullValuePropertyMappingStrategy." ), PROPERTYMAPPING_IGNORE_AND_NVPMS( "Ignore and nullValuePropertyMappingStrategy are both defined in @Mapping, either define ignore or an nullValuePropertyMappingStrategy." ), PROPERTYMAPPING_TARGET_THIS_AND_IGNORE( "Using @Mapping( target = \".\", ignore = true ) is not allowed. You need to use @BeanMapping( ignoreByDefault = true ) if you would like to ignore all non explicitly mapped target properties." ), + PROPERTYMAPPING_TARGET_THIS_NO_SOURCE( "Using @Mapping( target = \".\") requires a source property. Expression or constant cannot be used as a source."), PROPERTYMAPPING_EXPRESSION_AND_QUALIFIER_BOTH_DEFINED("Expression and a qualifier both defined in @Mapping, either define an expression or a qualifier."), PROPERTYMAPPING_INVALID_EXPRESSION( "Value for expression must be given in the form \"java()\"." ), PROPERTYMAPPING_INVALID_DEFAULT_EXPRESSION( "Value for default expression must be given in the form \"java()\"." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java new file mode 100644 index 0000000000..5714e04684 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java @@ -0,0 +1,35 @@ +/* + * 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._3485; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author hduelme + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface ErroneousIssue3485Mapper { + + ErroneousIssue3485Mapper INSTANCE = Mappers.getMapper( ErroneousIssue3485Mapper.class ); + class Target { + private final String value; + + public Target( String value ) { + this.value = value; + } + + public String getValue() { + return value; + } + + } + + @Mapping(target = ".") + Target targetFromExpression(String s); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java new file mode 100644 index 0000000000..cf7e0563af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java @@ -0,0 +1,33 @@ +/* + * 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._3485; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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; + +/** + * @author hduelme + */ +@IssueKey("3463") +public class Issue3485Test { + + @ProcessorTest + @WithClasses(ErroneousIssue3485Mapper.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousIssue3485Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 33, + message = "Using @Mapping( target = \".\") requires a source property. Expression or " + + "constant cannot be used as a source.") + }) + void thisMappingWithoutSource() { + } +} From e815e3cb1eabfef2930accf7d5f2c442ff2a456e Mon Sep 17 00:00:00 2001 From: Oliver Erhart <8238759+thunderhook@users.noreply.github.com> Date: Sun, 10 Mar 2024 09:15:37 +0100 Subject: [PATCH 217/363] #3524 Provide tests with Lombok style super builders --- .../ap/test/superbuilder/AbstractVehicle.java | 46 +++++ .../test/superbuilder/AbstractVehicleDto.java | 20 +++ .../mapstruct/ap/test/superbuilder/Car.java | 63 +++++++ .../ap/test/superbuilder/CarDto.java | 21 +++ .../ap/test/superbuilder/CarMapper.java | 43 +++++ .../superbuilder/ChainedAccessorsCar.java | 58 +++++++ .../superbuilder/ChainedAccessorsVehicle.java | 72 ++++++++ .../superbuilder/InheritedAbstractCar.java | 54 ++++++ .../superbuilder/InheritedAbstractCarDto.java | 21 +++ .../ap/test/superbuilder/MuscleCar.java | 52 ++++++ .../ap/test/superbuilder/MuscleCarDto.java | 21 +++ .../ap/test/superbuilder/Passenger.java | 50 ++++++ .../ap/test/superbuilder/PassengerDto.java | 20 +++ .../test/superbuilder/SuperBuilderTest.java | 160 ++++++++++++++++++ .../ap/test/superbuilder/Vehicle.java | 50 ++++++ .../ap/test/superbuilder/VehicleDto.java | 25 +++ 16 files changed, 776 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicleDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/Car.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsCar.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsVehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCar.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCarDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCar.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCarDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/Passenger.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/PassengerDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/SuperBuilderTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/Vehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/superbuilder/VehicleDto.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicle.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicle.java new file mode 100644 index 0000000000..d9cf159888 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicle.java @@ -0,0 +1,46 @@ +/* + * 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.superbuilder; + +public abstract class AbstractVehicle { + + private final int amountOfTires; + private final Passenger passenger; + + protected AbstractVehicle(AbstractVehicleBuilder b) { + this.amountOfTires = b.amountOfTires; + this.passenger = b.passenger; + } + + public int getAmountOfTires() { + return this.amountOfTires; + } + + public Passenger getPassenger() { + return this.passenger; + } + + public abstract static class AbstractVehicleBuilder> { + + private int amountOfTires; + private Passenger passenger; + + public B amountOfTires(int amountOfTires) { + this.amountOfTires = amountOfTires; + return self(); + } + + public B passenger(Passenger passenger) { + this.passenger = passenger; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicleDto.java new file mode 100644 index 0000000000..0b97398ae0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/AbstractVehicleDto.java @@ -0,0 +1,20 @@ +/* + * 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.superbuilder; + +public abstract class AbstractVehicleDto { + + private final int amountOfTires; + + public AbstractVehicleDto(int amountOfTires) { + this.amountOfTires = amountOfTires; + } + + public int getAmountOfTires() { + return amountOfTires; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Car.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Car.java new file mode 100644 index 0000000000..af15ee6631 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Car.java @@ -0,0 +1,63 @@ +/* + * 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.superbuilder; + +public class Car extends Vehicle { + + private final String manufacturer; + private final Passenger passenger; + + protected Car(CarBuilder b) { + super( b ); + this.manufacturer = b.manufacturer; + this.passenger = b.passenger; + } + + public static CarBuilder builder() { + return new CarBuilderImpl(); + } + + public String getManufacturer() { + return this.manufacturer; + } + + public Passenger getPassenger() { + return this.passenger; + } + + public abstract static class CarBuilder> extends VehicleBuilder { + + private String manufacturer; + private Passenger passenger; + + public B manufacturer(String manufacturer) { + this.manufacturer = manufacturer; + return self(); + } + + public B passenger(Passenger passenger) { + this.passenger = passenger; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class CarBuilderImpl extends CarBuilder { + private CarBuilderImpl() { + } + + protected CarBuilderImpl self() { + return this; + } + + public Car build() { + return new Car( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarDto.java new file mode 100644 index 0000000000..9877e27d71 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarDto.java @@ -0,0 +1,21 @@ +/* + * 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.superbuilder; + +public class CarDto extends VehicleDto { + + private final String manufacturer; + + public CarDto(int amountOfTires, String manufacturer, PassengerDto passenger) { + super( amountOfTires, passenger ); + this.manufacturer = manufacturer; + } + + public String getManufacturer() { + return manufacturer; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarMapper.java new file mode 100644 index 0000000000..a3def64ead --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/CarMapper.java @@ -0,0 +1,43 @@ +/* + * 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.superbuilder; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CarMapper { + + CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); + + @Mapping(target = "amountOfTires", source = "tireCount") + Car carDtoToCar(CarDto source); + + @InheritInverseConfiguration(name = "carDtoToCar") + CarDto carToCarDto(Car source); + + @Mapping(target = "amountOfTires", source = "tireCount") + ChainedAccessorsCar carDtoToChainedAccessorsCar(CarDto source); + + @InheritInverseConfiguration(name = "carDtoToChainedAccessorsCar") + CarDto chainedAccessorsCarToCarDto(ChainedAccessorsCar source); + + @Mapping(target = "amountOfTires", source = "tireCount") + InheritedAbstractCar carDtoToInheritedAbstractCar(CarDto source); + + @InheritInverseConfiguration(name = "carDtoToInheritedAbstractCar") + CarDto inheritedAbstractCarToCarDto(InheritedAbstractCar source); + + @Mapping(target = "amountOfTires", source = "tireCount") + @Mapping(target = "horsePower", constant = "140.5f") + MuscleCar carDtoToMuscleCar(CarDto source); + + @InheritInverseConfiguration(name = "carDtoToMuscleCar") + CarDto muscleCarToCarDto(MuscleCar source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsCar.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsCar.java new file mode 100644 index 0000000000..48faa101c3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsCar.java @@ -0,0 +1,58 @@ +/* + * 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.superbuilder; + +public class ChainedAccessorsCar extends ChainedAccessorsVehicle { + + private String manufacturer; + + protected ChainedAccessorsCar(ChainedAccessorsCarBuilder b) { + super( b ); + this.manufacturer = b.manufacturer; + } + + public static ChainedAccessorsCarBuilder builder() { + return new ChainedAccessorsCarBuilderImpl(); + } + + public String getManufacturer() { + return this.manufacturer; + } + + public ChainedAccessorsCar setManufacturer(String manufacturer) { + this.manufacturer = manufacturer; + return this; + } + + public abstract static class ChainedAccessorsCarBuilder> extends ChainedAccessorsVehicleBuilder { + + private String manufacturer; + + public B manufacturer(String manufacturer) { + this.manufacturer = manufacturer; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class ChainedAccessorsCarBuilderImpl + extends ChainedAccessorsCarBuilder { + private ChainedAccessorsCarBuilderImpl() { + } + + protected ChainedAccessorsCarBuilderImpl self() { + return this; + } + + public ChainedAccessorsCar build() { + return new ChainedAccessorsCar( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsVehicle.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsVehicle.java new file mode 100644 index 0000000000..3344fb26f9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/ChainedAccessorsVehicle.java @@ -0,0 +1,72 @@ +/* + * 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.superbuilder; + +public class ChainedAccessorsVehicle { + private int amountOfTires; + private Passenger passenger; + + protected ChainedAccessorsVehicle(ChainedAccessorsVehicleBuilder b) { + this.amountOfTires = b.amountOfTires; + this.passenger = b.passenger; + } + + public static ChainedAccessorsVehicleBuilder builder() { + return new ChainedAccessorsVehicleBuilderImpl(); + } + + public int getAmountOfTires() { + return this.amountOfTires; + } + + public Passenger getPassenger() { + return this.passenger; + } + + public ChainedAccessorsVehicle setAmountOfTires(int amountOfTires) { + this.amountOfTires = amountOfTires; + return this; + } + + public ChainedAccessorsVehicle setPassenger(Passenger passenger) { + this.passenger = passenger; + return this; + } + + public abstract static class ChainedAccessorsVehicleBuilder> { + private int amountOfTires; + private Passenger passenger; + + public B amountOfTires(int amountOfTires) { + this.amountOfTires = amountOfTires; + return self(); + } + + public B passenger(Passenger passenger) { + this.passenger = passenger; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class ChainedAccessorsVehicleBuilderImpl + extends ChainedAccessorsVehicleBuilder { + private ChainedAccessorsVehicleBuilderImpl() { + } + + protected ChainedAccessorsVehicleBuilderImpl self() { + return this; + } + + public ChainedAccessorsVehicle build() { + return new ChainedAccessorsVehicle( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCar.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCar.java new file mode 100644 index 0000000000..1d07106af8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCar.java @@ -0,0 +1,54 @@ +/* + * 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.superbuilder; + +public class InheritedAbstractCar extends AbstractVehicle { + + private final String manufacturer; + + protected InheritedAbstractCar(InheritedAbstractCarBuilder b) { + super( b ); + this.manufacturer = b.manufacturer; + } + + public static InheritedAbstractCarBuilder builder() { + return new InheritedAbstractCarBuilderImpl(); + } + + public String getManufacturer() { + return this.manufacturer; + } + + public abstract static class InheritedAbstractCarBuilder> + + extends AbstractVehicleBuilder { + private String manufacturer; + + public B manufacturer(String manufacturer) { + this.manufacturer = manufacturer; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class InheritedAbstractCarBuilderImpl + extends InheritedAbstractCarBuilder { + private InheritedAbstractCarBuilderImpl() { + } + + protected InheritedAbstractCarBuilderImpl self() { + return this; + } + + public InheritedAbstractCar build() { + return new InheritedAbstractCar( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCarDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCarDto.java new file mode 100644 index 0000000000..a9cc6bbdbb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/InheritedAbstractCarDto.java @@ -0,0 +1,21 @@ +/* + * 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.superbuilder; + +public class InheritedAbstractCarDto extends AbstractVehicleDto { + + private final String manufacturer; + + public InheritedAbstractCarDto(int amountOfTires, String manufacturer) { + super( amountOfTires ); + this.manufacturer = manufacturer; + } + + public String getManufacturer() { + return manufacturer; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCar.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCar.java new file mode 100644 index 0000000000..ec330536ff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCar.java @@ -0,0 +1,52 @@ +/* + * 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.superbuilder; + +public class MuscleCar extends Car { + + private final float horsePower; + + protected MuscleCar(MuscleCarBuilder b) { + super( b ); + this.horsePower = b.horsePower; + } + + public static MuscleCarBuilder builder() { + return new MuscleCarBuilderImpl(); + } + + public float getHorsePower() { + return this.horsePower; + } + + public abstract static class MuscleCarBuilder> + extends CarBuilder { + + private float horsePower; + + public B horsePower(float horsePower) { + this.horsePower = horsePower; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class MuscleCarBuilderImpl extends MuscleCarBuilder { + private MuscleCarBuilderImpl() { + } + + protected MuscleCarBuilderImpl self() { + return this; + } + + public MuscleCar build() { + return new MuscleCar( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCarDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCarDto.java new file mode 100644 index 0000000000..7ffe150ecf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/MuscleCarDto.java @@ -0,0 +1,21 @@ +/* + * 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.superbuilder; + +public class MuscleCarDto extends CarDto { + + private final float horsePower; + + public MuscleCarDto(int amountOfTires, String manufacturer, PassengerDto passenger, float horsePower) { + super( amountOfTires, manufacturer, passenger ); + this.horsePower = horsePower; + } + + public float getHorsePower() { + return horsePower; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Passenger.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Passenger.java new file mode 100644 index 0000000000..2b6740af78 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Passenger.java @@ -0,0 +1,50 @@ +/* + * 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.superbuilder; + +public class Passenger { + + private final String name; + + protected Passenger(PassengerBuilder b) { + this.name = b.name; + } + + public static PassengerBuilder builder() { + return new PassengerBuilderImpl(); + } + + public String getName() { + return this.name; + } + + public abstract static class PassengerBuilder> { + + private String name; + + public B name(String name) { + this.name = name; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class PassengerBuilderImpl extends PassengerBuilder { + private PassengerBuilderImpl() { + } + + protected PassengerBuilderImpl self() { + return this; + } + + public Passenger build() { + return new Passenger( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/PassengerDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/PassengerDto.java new file mode 100644 index 0000000000..f63f6e0683 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/PassengerDto.java @@ -0,0 +1,20 @@ +/* + * 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.superbuilder; + +public class PassengerDto { + + private final String name; + + public PassengerDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/SuperBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/SuperBuilderTest.java new file mode 100644 index 0000000000..93dfb746b0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/SuperBuilderTest.java @@ -0,0 +1,160 @@ +/* + * 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.superbuilder; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests the mapping of models annotated with @SuperBuilder as source and target. + * + * @author Oliver Erhart + */ +@WithClasses({ + AbstractVehicleDto.class, + CarDto.class, + InheritedAbstractCarDto.class, + MuscleCarDto.class, + PassengerDto.class, + VehicleDto.class, + AbstractVehicle.class, + Car.class, + ChainedAccessorsCar.class, + ChainedAccessorsVehicle.class, + InheritedAbstractCar.class, + MuscleCar.class, + Passenger.class, + Vehicle.class, + CarMapper.class +}) +@IssueKey("3524") +public class SuperBuilderTest { + + @ProcessorTest + public void simpleMapping() { + + PassengerDto passenger = new PassengerDto( "Tom" ); + CarDto carDto = new CarDto( 4, "BMW", passenger ); + + Car car = CarMapper.INSTANCE.carDtoToCar( carDto ); + + assertThat( car.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( car.getAmountOfTires() ).isEqualTo( 4 ); + assertThat( car.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void simpleMappingInverse() { + + Passenger passenger = Passenger.builder().name( "Tom" ).build(); + Car car = Car.builder() + .manufacturer( "BMW" ) + .amountOfTires( 4 ) + .passenger( passenger ) + .build(); + + CarDto carDto = CarMapper.INSTANCE.carToCarDto( car ); + + assertThat( carDto.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( carDto.getTireCount() ).isEqualTo( 4 ); + assertThat( carDto.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void chainedMapping() { + + PassengerDto passenger = new PassengerDto( "Tom" ); + CarDto carDto = new CarDto( 4, "BMW", passenger ); + + ChainedAccessorsCar car = CarMapper.INSTANCE.carDtoToChainedAccessorsCar( carDto ); + + assertThat( car.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( car.getAmountOfTires() ).isEqualTo( 4 ); + assertThat( car.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void chainedMappingInverse() { + + Passenger passenger = Passenger.builder().name( "Tom" ).build(); + ChainedAccessorsCar chainedAccessorsCar = ChainedAccessorsCar.builder() + .manufacturer( "BMW" ) + .amountOfTires( 4 ) + .passenger( passenger ) + .build(); + + CarDto carDto = CarMapper.INSTANCE.chainedAccessorsCarToCarDto( chainedAccessorsCar ); + + assertThat( carDto.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( carDto.getTireCount() ).isEqualTo( 4 ); + assertThat( carDto.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void inheritedAbstractMapping() { + + PassengerDto passenger = new PassengerDto( "Tom" ); + CarDto carDto = new CarDto( 4, "BMW", passenger ); + + InheritedAbstractCar car = CarMapper.INSTANCE.carDtoToInheritedAbstractCar( carDto ); + + assertThat( car.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( car.getAmountOfTires() ).isEqualTo( 4 ); + assertThat( car.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void inheritedAbstractMappingInverse() { + + Passenger passenger = Passenger.builder().name( "Tom" ).build(); + InheritedAbstractCar inheritedAbstractCar = InheritedAbstractCar.builder() + .manufacturer( "BMW" ) + .amountOfTires( 4 ) + .passenger( passenger ) + .build(); + + CarDto carDto = CarMapper.INSTANCE.inheritedAbstractCarToCarDto( inheritedAbstractCar ); + + assertThat( carDto.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( carDto.getTireCount() ).isEqualTo( 4 ); + assertThat( carDto.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void secondLevelMapping() { + + PassengerDto passenger = new PassengerDto( "Tom" ); + CarDto carDto = new CarDto( 4, "BMW", passenger ); + + MuscleCar car = CarMapper.INSTANCE.carDtoToMuscleCar( carDto ); + + assertThat( car.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( car.getAmountOfTires() ).isEqualTo( 4 ); + assertThat( car.getHorsePower() ).isEqualTo( 140.5f ); + assertThat( car.getPassenger().getName() ).isEqualTo( "Tom" ); + } + + @ProcessorTest + public void secondLevelMappingInverse() { + + Passenger passenger = Passenger.builder().name( "Tom" ).build(); + MuscleCar muscleCar = MuscleCar.builder() + .manufacturer( "BMW" ) + .amountOfTires( 4 ) + .passenger( passenger ) + .build(); + + CarDto carDto = CarMapper.INSTANCE.muscleCarToCarDto( muscleCar ); + + assertThat( carDto.getManufacturer() ).isEqualTo( "BMW" ); + assertThat( carDto.getTireCount() ).isEqualTo( 4 ); + assertThat( carDto.getPassenger().getName() ).isEqualTo( "Tom" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Vehicle.java new file mode 100644 index 0000000000..2f4a19d842 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/Vehicle.java @@ -0,0 +1,50 @@ +/* + * 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.superbuilder; + +public class Vehicle { + + private final int amountOfTires; + + protected Vehicle(VehicleBuilder b) { + this.amountOfTires = b.amountOfTires; + } + + public static VehicleBuilder builder() { + return new VehicleBuilderImpl(); + } + + public int getAmountOfTires() { + return this.amountOfTires; + } + + public abstract static class VehicleBuilder> { + + private int amountOfTires; + + public B amountOfTires(int amountOfTires) { + this.amountOfTires = amountOfTires; + return self(); + } + + protected abstract B self(); + + public abstract C build(); + } + + private static final class VehicleBuilderImpl extends VehicleBuilder { + private VehicleBuilderImpl() { + } + + protected VehicleBuilderImpl self() { + return this; + } + + public Vehicle build() { + return new Vehicle( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/superbuilder/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/VehicleDto.java new file mode 100644 index 0000000000..04dfefb205 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/superbuilder/VehicleDto.java @@ -0,0 +1,25 @@ +/* + * 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.superbuilder; + +public class VehicleDto { + + private final int tireCount; + private final PassengerDto passenger; + + public VehicleDto(int tireCount, PassengerDto passenger) { + this.tireCount = tireCount; + this.passenger = passenger; + } + + public int getTireCount() { + return tireCount; + } + + public PassengerDto getPassenger() { + return passenger; + } +} From 8e66445fe9f13b37ea66191548067a80bcc6d048 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Tue, 2 Apr 2024 12:00:41 +0200 Subject: [PATCH 218/363] #3564 Correct issue key for `Issue3485Test` --- .../java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java index cf7e0563af..7427b63a4e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java @@ -15,7 +15,7 @@ /** * @author hduelme */ -@IssueKey("3463") +@IssueKey("3485") public class Issue3485Test { @ProcessorTest From 5fbd36c44392b7f910e24ceec35fa47a98abba89 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Sun, 28 Apr 2024 20:20:11 +0200 Subject: [PATCH 219/363] #3577 Improve `Mapping#ignoreByDefault` documentation --- core/src/main/java/org/mapstruct/BeanMapping.java | 6 ++++++ .../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/mapstruct/BeanMapping.java b/core/src/main/java/org/mapstruct/BeanMapping.java index a03546b07d..e94ff98f2d 100644 --- a/core/src/main/java/org/mapstruct/BeanMapping.java +++ b/core/src/main/java/org/mapstruct/BeanMapping.java @@ -19,6 +19,8 @@ /** * Configures the mapping between two bean types. *

      + * Unless otherwise specified these properties are inherited to the generated bean mapping methods. + *

      * Either {@link #resultType()}, {@link #qualifiedBy()} or {@link #nullValueMappingStrategy()} must be specified. *

      *

      Example: Determining the result type

      @@ -58,6 +60,8 @@ /** * Specifies the result type of the factory method to be used in case several factory methods qualify. + *

      + * NOTE: This property is not inherited to generated mapping methods * * @return the resultType to select */ @@ -145,6 +149,8 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() * source properties report. *

      * NOTE: This does not support ignoring nested source properties + *

      + * NOTE: This property is not inherited to generated mapping methods * * @return The source properties that should be ignored when performing a report * diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index eb3d5a80ec..2174822359 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -39,7 +39,7 @@ The property name as defined in the http://www.oracle.com/technetwork/java/javas ==== [TIP] ==== -By means of the `@BeanMapping(ignoreByDefault = true)` the default behavior will be *explicit mapping*, meaning that all mappings have to be specified by means of the `@Mapping` and no warnings will be issued on missing target properties. +By means of the `@BeanMapping(ignoreByDefault = true)` the default behavior will be *explicit mapping*, meaning that all mappings (including nested ones) have to be specified by means of the `@Mapping` and no warnings will be issued on missing target properties. This allows to ignore all fields, except the ones that are explicitly defined through `@Mapping`. ==== [TIP] From 0a935c67a7c9ca17a3c6980b96d964c46be527a1 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 28 Apr 2024 19:04:57 +0200 Subject: [PATCH 220/363] #3565 Presence check methods should not be considered as valid mapping candidates --- .../creation/MappingResolverImpl.java | 3 + .../ap/test/bugs/_3565/Issue3565Mapper.java | 57 +++++++++++++++++++ .../ap/test/bugs/_3565/Issue3565Test.java | 33 +++++++++++ 3 files changed, 93 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Test.java 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 b290ff56bc..e6a26ba045 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 @@ -470,6 +470,9 @@ private ConversionAssignment resolveViaConversion(Type sourceType, Type targetTy } private boolean isCandidateForMapping(Method methodCandidate) { + if ( methodCandidate.isPresenceCheck() ) { + return false; + } return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Mapper.java new file mode 100644 index 0000000000..b86e667e56 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Mapper.java @@ -0,0 +1,57 @@ +/* + * 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._3565; + +import java.util.Optional; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3565Mapper { + + Issue3565Mapper INSTANCE = Mappers.getMapper( Issue3565Mapper.class ); + + default T mapFromOptional(Optional value) { + return value.orElse( (T) null ); + } + + @Condition + default boolean isOptionalPresent(Optional value) { + return value.isPresent(); + } + + Target map(Source source); + + class Source { + + private final Boolean condition; + + public Source(Boolean condition) { + this.condition = condition; + } + + public Optional getCondition() { + return Optional.ofNullable( this.condition ); + } + } + + class Target { + private String condition; + + public Optional getCondition() { + return Optional.ofNullable( this.condition ); + } + + public void setCondition(String condition) { + this.condition = condition; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Test.java new file mode 100644 index 0000000000..c4cd028d45 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Test.java @@ -0,0 +1,33 @@ +/* + * 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._3565; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3565Mapper.class) +@IssueKey("3565") +class Issue3565Test { + + @ProcessorTest + void shouldGenerateValidCode() { + Issue3565Mapper.Target target = Issue3565Mapper.INSTANCE.map( new Issue3565Mapper.Source( null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getCondition() ).isEmpty(); + + target = Issue3565Mapper.INSTANCE.map( new Issue3565Mapper.Source( false ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getCondition() ).hasValue( "false" ); + } +} From 0a2a0aa526fe1c970baba61c4f9dcc23e975c517 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 29 Apr 2024 08:05:52 +0200 Subject: [PATCH 221/363] #2610 Add support for conditions on source parameters + fix incorrect use of source parameter in presence check method (#3543) The new `@SourceParameterCondition` is also going to cover the problems in #3270 and #3459. The changes in the `MethodFamilySelector` are also fixing #3561 --- .../main/java/org/mapstruct/Condition.java | 40 ++-- .../java/org/mapstruct/ConditionStrategy.java | 23 ++ .../mapstruct/SourceParameterCondition.java | 74 +++++++ ...apter-10-advanced-mapping-options.asciidoc | 53 ++++- .../ap/internal/gem/ConditionStrategyGem.java | 15 ++ .../ap/internal/model/BeanMappingMethod.java | 34 ++- .../model/PresenceCheckMethodResolver.java | 92 ++++++-- .../ap/internal/model/common/Parameter.java | 16 +- .../model/common/ParameterBinding.java | 109 +++++++--- .../model/source/ConditionMethodOptions.java | 45 ++++ .../model/source/ConditionOptions.java | 170 +++++++++++++++ .../ap/internal/model/source/Method.java | 12 +- .../internal/model/source/SourceMethod.java | 43 ++-- .../selector/CreateOrUpdateSelector.java | 1 + .../source/selector/MethodFamilySelector.java | 18 +- .../source/selector/SelectionContext.java | 39 ++++ .../source/selector/SelectionCriteria.java | 12 ++ .../processor/MethodRetrievalProcessor.java | 46 +++- .../creation/MappingResolverImpl.java | 2 +- .../mapstruct/ap/internal/util/Message.java | 5 + .../ap/internal/util/MetaAnnotations.java | 85 ++++++++ .../basic/ConditionalMappingTest.java | 198 ++++++++++++++++++ .../ConditionalMethodForSourceBeanMapper.java | 64 ++++++ ...odForSourceParameterAndPropertyMapper.java | 85 ++++++++ ...ourceParameterConditionalMethodMapper.java | 28 +++ ...nditionalWithoutAppliesToMethodMapper.java | 24 +++ ...terConditionalWithMappingTargetMapper.java | 26 +++ ...nditionalWithSourcePropertyNameMapper.java | 26 +++ ...nditionalWithTargetPropertyNameMapper.java | 26 +++ ...ameterConditionalWithTargetTypeMapper.java | 26 +++ .../mapstruct/ap/test/gem/EnumGemsTest.java | 8 + 31 files changed, 1326 insertions(+), 119 deletions(-) create mode 100644 core/src/main/java/org/mapstruct/ConditionStrategy.java create mode 100644 core/src/main/java/org/mapstruct/SourceParameterCondition.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java diff --git a/core/src/main/java/org/mapstruct/Condition.java b/core/src/main/java/org/mapstruct/Condition.java index 37f1553be1..1273deab0a 100644 --- a/core/src/main/java/org/mapstruct/Condition.java +++ b/core/src/main/java/org/mapstruct/Condition.java @@ -11,26 +11,35 @@ import java.lang.annotation.Target; /** - * This annotation marks a method as a presence check method to check check for presence in beans. + * This annotation marks a method as a presence check method to check for presence in beans + * or it can be used to define additional check methods for something like source parameters. *

      - * By default bean properties are checked against {@code null} or using a presence check method in the source bean. + * By default, bean properties are checked against {@code null} or using a presence check method in the source bean. * If a presence check method is available then it will be used instead. *

      * Presence check methods have to return {@code boolean}. * The following parameters are accepted for the presence check methods: *

        *
      • The parameter with the value of the source property. - * e.g. the value given by calling {@code getName()} for the name property of the source bean
      • + * e.g. the value given by calling {@code getName()} for the name property of the source bean + * - only possible when using the {@link ConditionStrategy#PROPERTIES} + * *
      • The mapping source parameter
      • *
      • {@code @}{@link Context} parameter
      • - *
      • {@code @}{@link TargetPropertyName} parameter
      • - *
      • {@code @}{@link SourcePropertyName} parameter
      • + *
      • + * {@code @}{@link TargetPropertyName} parameter - + * only possible when using the {@link ConditionStrategy#PROPERTIES} + *
      • + *
      • + * {@code @}{@link SourcePropertyName} parameter - + * only possible when using the {@link ConditionStrategy#PROPERTIES} + *
      • *
      * * Note: The usage of this annotation is mandatory * for a method to be considered as a presence check method. * - *
      
      + * 
      
        * public class PresenceCheckUtils {
        *
        *   @Condition
      @@ -45,11 +54,10 @@
        *     MovieDto map(Movie movie);
        * }
        * 
      - * + *

      * The following implementation of {@code MovieMapper} will be generated: * - *

      - * 
      + * 
      
        * public class MovieMapperImpl implements MovieMapper {
        *
        *     @Override
      @@ -67,14 +75,22 @@
        *         return movieDto;
        *     }
        * }
      - * 
      - * 
      + *
      + *

      + * This annotation can also be used as a meta-annotation to define the condition strategy. * * @author Filip Hrisafov + * @see SourceParameterCondition * @since 1.5 */ -@Target({ ElementType.METHOD }) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.CLASS) public @interface Condition { + /** + * @return the places where the condition should apply to + * @since 1.6 + */ + ConditionStrategy[] appliesTo() default ConditionStrategy.PROPERTIES; + } diff --git a/core/src/main/java/org/mapstruct/ConditionStrategy.java b/core/src/main/java/org/mapstruct/ConditionStrategy.java new file mode 100644 index 0000000000..6b042017c2 --- /dev/null +++ b/core/src/main/java/org/mapstruct/ConditionStrategy.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +/** + * Strategy for defining what to what a condition (check) method is applied to + * + * @author Filip Hrisafov + * @since 1.6 + */ +public enum ConditionStrategy { + /** + * The condition method should be applied whether a property should be mapped. + */ + PROPERTIES, + /** + * The condition method should be applied to check if a source parameters should be mapped. + */ + SOURCE_PARAMETERS, +} diff --git a/core/src/main/java/org/mapstruct/SourceParameterCondition.java b/core/src/main/java/org/mapstruct/SourceParameterCondition.java new file mode 100644 index 0000000000..8bff97abc4 --- /dev/null +++ b/core/src/main/java/org/mapstruct/SourceParameterCondition.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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation marks a method as a check method to check if a source parameter needs to be mapped. + *

      + * By default, source parameters are checked against {@code null}, unless they are primitives. + *

      + * Check methods have to return {@code boolean}. + * The following parameters are accepted for the presence check methods: + *

        + *
      • The mapping source parameter
      • + *
      • {@code @}{@link Context} parameter
      • + *
      + * + * Note: The usage of this annotation is mandatory + * for a method to be considered as a source check method. + * + *
      
      + * public class PresenceCheckUtils {
      + *
      + *   @SourceParameterCondition
      + *   public static boolean isDefined(Car car) {
      + *      return car != null && car.getId() != null;
      + *   }
      + * }
      + *
      + * @Mapper(uses = PresenceCheckUtils.class)
      + * public interface CarMapper {
      + *
      + *     CarDto map(Car car);
      + * }
      + * 
      + * + * The following implementation of {@code CarMapper} will be generated: + * + *
      
      + * public class CarMapperImpl implements CarMapper {
      + *
      + *     @Override
      + *     public CarDto map(Car car) {
      + *         if ( !PresenceCheckUtils.isDefined( car ) ) {
      + *             return null;
      + *         }
      + *
      + *         CarDto carDto = new CarDto();
      + *
      + *         carDto.setId( car.getId() );
      + *         // ...
      + *
      + *         return carDto;
      + *     }
      + * }
      + * 
      + * + * @author Filip Hrisafov + * @since 1.6 + * @see Condition @Condition + */ +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.CLASS) +@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) +public @interface SourceParameterCondition { + +} diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index db13c0548b..1e2bd133d4 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -303,8 +303,10 @@ null check, regardless the value of the `NullValueCheckStrategy` to avoid additi Conditional Mapping is a type of <>. The difference is that it allows users to write custom condition methods that will be invoked to check if a property needs to be mapped or not. +Conditional mapping can also be used to check if a source parameter should be mapped or not. -A custom condition method is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`. +A custom condition method for properties is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`. +A custom condition method for source parameters is annotated with `org.mapstruct.SourceParameterCondition`, `org.mapstruct.Condition(appliesTo = org.mapstruct.ConditionStrategy#SOURCE_PARAMETERS)` or meta-annotated with `Condition(appliesTo = ConditionStrategy#SOURCE_PARAMETERS)` e.g. if you only want to map a String property when it is not `null`, and it is not empty then you can do something like: @@ -484,6 +486,55 @@ Methods annotated with `@Condition` in addition to the value of the source prope <> is also valid for `@Condition` methods. In order to use a more specific condition method you will need to use one of `Mapping#conditionQualifiedByName` or `Mapping#conditionQualifiedBy`. +If we want to only map cars that have an id provided then we can do something like: + + +.Mapper using custom condition source parameter check method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface CarMapper { + + CarDto carToCarDto(Car car); + + @SourceParameterCondition + default boolean hasCar(Car car) { + return car != null && car.getId() != null; + } +} +---- +==== + +The generated mapper will look like: + +.Custom condition source parameter check generated implementation +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class CarMapperImpl implements CarMapper { + + @Override + public CarDto carToCarDto(Car car) { + if ( !hasCar( car ) ) { + return null; + } + + CarDto carDto = new CarDto(); + + carDto.setOwner( car.getOwner() ); + + // Mapping of other properties + + return carDto; + } +} +---- +==== + [[exceptions]] === Exceptions diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java new file mode 100644 index 0000000000..adea4b4c1f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java @@ -0,0 +1,15 @@ +/* + * 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.internal.gem; + +/** + * @author Filip Hrisafov + */ +public enum ConditionStrategyGem { + + PROPERTIES, + SOURCE_PARAMETERS +} 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 dd3a86e2cf..cf5179f9cb 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 @@ -411,6 +411,26 @@ else if ( !method.isUpdateMethod() ) { removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType ); } + Map presenceChecksByParameter = new LinkedHashMap<>(); + for ( Parameter sourceParameter : method.getSourceParameters() ) { + PresenceCheck parameterPresenceCheck = PresenceCheckMethodResolver.getPresenceCheckForSourceParameter( + method, + selectionParameters, + sourceParameter, + ctx + ); + if ( parameterPresenceCheck != null ) { + presenceChecksByParameter.put( sourceParameter.getName(), parameterPresenceCheck ); + } + else if ( !sourceParameter.getType().isPrimitive() ) { + presenceChecksByParameter.put( + sourceParameter.getName(), + new NullPresenceCheck( sourceParameter.getName() ) + ); + } + } + + return new BeanMappingMethod( method, getMethodAnnotations(), @@ -426,7 +446,8 @@ else if ( !method.isUpdateMethod() ) { afterMappingReferencesWithFinalizedReturnType, finalizeMethod, mappingReferences, - subclasses + subclasses, + presenceChecksByParameter ); } @@ -1891,7 +1912,8 @@ private BeanMappingMethod(Method method, List afterMappingReferencesWithFinalizedReturnType, MethodReference finalizerMethod, MappingReferences mappingReferences, - List subclassMappings) { + List subclassMappings, + Map presenceChecksByParameter) { super( method, annotations, @@ -1923,18 +1945,12 @@ private BeanMappingMethod(Method method, // parameter mapping. this.mappingsByParameter = new HashMap<>(); this.constantMappings = new ArrayList<>( propertyMappings.size() ); - this.presenceChecksByParameter = new LinkedHashMap<>(); + this.presenceChecksByParameter = presenceChecksByParameter; this.constructorMappingsByParameter = new LinkedHashMap<>(); this.constructorConstantMappings = new ArrayList<>(); Set sourceParameterNames = new HashSet<>(); for ( Parameter sourceParameter : getSourceParameters() ) { sourceParameterNames.add( sourceParameter.getName() ); - if ( !sourceParameter.getType().isPrimitive() ) { - presenceChecksByParameter.put( - sourceParameter.getName(), - new NullPresenceCheck( sourceParameter.getName() ) - ); - } } for ( PropertyMapping mapping : propertyMappings ) { if ( mapping.isConstructorMapping() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index a5c873743d..5906db8219 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.source.Method; @@ -18,6 +19,7 @@ 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.SelectionContext; +import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.util.Message; /** @@ -34,36 +36,53 @@ public static PresenceCheck getPresenceCheck( SelectionParameters selectionParameters, MappingBuilderContext ctx ) { - SelectedMethod matchingMethod = findMatchingPresenceCheckMethod( + List> matchingMethods = findMatchingMethods( method, - selectionParameters, + SelectionContext.forPresenceCheckMethods( method, selectionParameters, ctx.getTypeFactory() ), ctx ); - if ( matchingMethod == null ) { + if ( matchingMethods.isEmpty() ) { + return null; + } + + if ( matchingMethods.size() > 1 ) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD, + selectionParameters.getSourceRHS().getSourceType().describe(), + matchingMethods.stream() + .map( SelectedMethod::getMethod ) + .map( Method::describe ) + .collect( Collectors.joining( ", " ) ) + ); + return null; } + SelectedMethod matchingMethod = matchingMethods.get( 0 ); + MethodReference methodReference = getPresenceCheckMethodReference( method, matchingMethod, ctx ); return new MethodReferencePresenceCheck( methodReference ); } - private static SelectedMethod findMatchingPresenceCheckMethod( + public static PresenceCheck getPresenceCheckForSourceParameter( Method method, SelectionParameters selectionParameters, + Parameter sourceParameter, MappingBuilderContext ctx ) { - MethodSelectors selectors = new MethodSelectors( - ctx.getTypeUtils(), - ctx.getElementUtils(), - ctx.getMessager() - ); - - List> matchingMethods = selectors.getMatchingMethods( - getAllAvailableMethods( method, ctx.getSourceModel() ), - SelectionContext.forPresenceCheckMethods( method, selectionParameters, ctx.getTypeFactory() ) + List> matchingMethods = findMatchingMethods( + method, + SelectionContext.forSourceParameterPresenceCheckMethods( + method, + selectionParameters, + sourceParameter, + ctx.getTypeFactory() + ), + ctx ); if ( matchingMethods.isEmpty() ) { @@ -73,8 +92,8 @@ private static SelectedMethod findMatchingPresenceCheckMethod( if ( matchingMethods.size() > 1 ) { ctx.getMessager().printMessage( method.getExecutable(), - Message.GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD, - selectionParameters.getSourceRHS().getSourceType().describe(), + Message.GENERAL_AMBIGUOUS_SOURCE_PARAMETER_CHECK_METHOD, + sourceParameter.getType().describe(), matchingMethods.stream() .map( SelectedMethod::getMethod ) .map( Method::describe ) @@ -84,7 +103,29 @@ private static SelectedMethod findMatchingPresenceCheckMethod( return null; } - return matchingMethods.get( 0 ); + SelectedMethod matchingMethod = matchingMethods.get( 0 ); + + MethodReference methodReference = getPresenceCheckMethodReference( method, matchingMethod, ctx ); + + return new MethodReferencePresenceCheck( methodReference ); + + } + + private static List> findMatchingMethods( + Method method, + SelectionContext selectionContext, + MappingBuilderContext ctx + ) { + MethodSelectors selectors = new MethodSelectors( + ctx.getTypeUtils(), + ctx.getElementUtils(), + ctx.getMessager() + ); + + return selectors.getMatchingMethods( + getAllAvailableMethods( method, ctx.getSourceModel(), selectionContext.getSelectionCriteria() ), + selectionContext + ); } private static MethodReference getPresenceCheckMethodReference( @@ -116,7 +157,8 @@ private static MethodReference getPresenceCheckMethodReference( } } - private static List getAllAvailableMethods(Method method, List sourceModelMethods) { + private static List getAllAvailableMethods(Method method, List sourceModelMethods, + SelectionCriteria selectionCriteria) { ParameterProvidedMethods contextProvidedMethods = method.getContextProvidedMethods(); if ( contextProvidedMethods.isEmpty() ) { return sourceModelMethods; @@ -129,9 +171,19 @@ private static List getAllAvailableMethods(Method method, List( methodsProvidedByParams.size() + sourceModelMethods.size() ); for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) { - // add only methods from context that do have the @Condition annotation - if ( methodProvidedByParams.isPresenceCheck() ) { - availableMethods.add( methodProvidedByParams ); + if ( selectionCriteria.isPresenceCheckRequired() ) { + // add only methods from context that do have the @Condition for properties annotation + if ( methodProvidedByParams.getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.PROPERTIES ) ) { + availableMethods.add( methodProvidedByParams ); + } + } + else if ( selectionCriteria.isSourceParameterCheckRequired() ) { + // add only methods from context that do have the @Condition for source parameters annotation + if ( methodProvidedByParams.getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.SOURCE_PARAMETERS ) ) { + availableMethods.add( methodProvidedByParams ); + } } } availableMethods.addAll( sourceModelMethods ); 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 44ac0eb7f0..aaab7f46ca 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 @@ -133,6 +133,14 @@ public boolean isVarArgs() { return varArgs; } + public boolean isSourceParameter() { + return !isMappingTarget() && + !isTargetType() && + !isMappingContext() && + !isSourcePropertyName() && + !isTargetPropertyName(); + } + @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; @@ -224,12 +232,4 @@ public static Parameter getTargetPropertyNameParameter(List parameter return parameters.stream().filter( Parameter::isTargetPropertyName ).findAny().orElse( null ); } - private static boolean isSourceParameter( Parameter parameter ) { - return !parameter.isMappingTarget() && - !parameter.isTargetType() && - !parameter.isMappingContext() && - !parameter.isSourcePropertyName() && - !parameter.isTargetPropertyName(); - } - } 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 0791ee626a..c1a594c73a 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 @@ -6,7 +6,9 @@ package org.mapstruct.ap.internal.model.common; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.Set; @@ -19,23 +21,14 @@ public class ParameterBinding { private final Type type; private final String variableName; - private final boolean targetType; - private final boolean mappingTarget; - private final boolean mappingContext; - private final boolean sourcePropertyName; - private final boolean targetPropertyName; private final SourceRHS sourceRHS; + private final Collection bindingTypes; - private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType, - boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName, + private ParameterBinding(Type parameterType, String variableName, Collection bindingTypes, SourceRHS sourceRHS) { this.type = parameterType; this.variableName = variableName; - this.targetType = targetType; - this.mappingTarget = mappingTarget; - this.mappingContext = mappingContext; - this.sourcePropertyName = sourcePropertyName; - this.targetPropertyName = targetPropertyName; + this.bindingTypes = bindingTypes; this.sourceRHS = sourceRHS; } @@ -46,39 +39,47 @@ public String getVariableName() { return variableName; } + public boolean isSourceParameter() { + return bindingTypes.contains( BindingType.PARAMETER ); + } + /** * @return {@code true}, if the parameter being bound is a {@code @TargetType} parameter. */ public boolean isTargetType() { - return targetType; + return bindingTypes.contains( BindingType.TARGET_TYPE ); } /** * @return {@code true}, if the parameter being bound is a {@code @MappingTarget} parameter. */ public boolean isMappingTarget() { - return mappingTarget; + return bindingTypes.contains( BindingType.MAPPING_TARGET ); } /** * @return {@code true}, if the parameter being bound is a {@code @MappingContext} parameter. */ public boolean isMappingContext() { - return mappingContext; + return bindingTypes.contains( BindingType.CONTEXT ); + } + + public boolean isForSourceRhs() { + return bindingTypes.contains( BindingType.SOURCE_RHS ); } /** * @return {@code true}, if the parameter being bound is a {@code @SourcePropertyName} parameter. */ public boolean isSourcePropertyName() { - return sourcePropertyName; + return bindingTypes.contains( BindingType.SOURCE_PROPERTY_NAME ); } /** * @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter. */ public boolean isTargetPropertyName() { - return targetPropertyName; + return bindingTypes.contains( BindingType.TARGET_PROPERTY_NAME ); } /** @@ -96,7 +97,7 @@ public SourceRHS getSourceRHS() { } public Set getImportTypes() { - if ( targetType ) { + if ( isTargetType() ) { return type.getImportTypes(); } @@ -112,14 +113,31 @@ public Set getImportTypes() { * @return a parameter binding reflecting the given parameter as being used as argument for a method call */ public static ParameterBinding fromParameter(Parameter parameter) { + EnumSet bindingTypes = EnumSet.of( BindingType.PARAMETER ); + if ( parameter.isMappingTarget() ) { + bindingTypes.add( BindingType.MAPPING_TARGET ); + } + + if ( parameter.isTargetType() ) { + bindingTypes.add( BindingType.TARGET_TYPE ); + } + + if ( parameter.isMappingContext() ) { + bindingTypes.add( BindingType.CONTEXT ); + } + + if ( parameter.isSourcePropertyName() ) { + bindingTypes.add( BindingType.SOURCE_PROPERTY_NAME ); + } + + if ( parameter.isTargetPropertyName() ) { + bindingTypes.add( BindingType.TARGET_PROPERTY_NAME ); + } + return new ParameterBinding( parameter.getType(), parameter.getName(), - parameter.isMappingTarget(), - parameter.isTargetType(), - parameter.isMappingContext(), - parameter.isSourcePropertyName(), - parameter.isTargetPropertyName(), + bindingTypes, null ); } @@ -136,11 +154,7 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame return new ParameterBinding( parameterType, parameterName, - false, - false, - false, - false, - false, + Collections.emptySet(), null ); } @@ -150,21 +164,31 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame * @return a parameter binding representing a target type parameter */ public static ParameterBinding forTargetTypeBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, true, false, false, false, null ); + return new ParameterBinding( classTypeOf, null, Collections.singleton( BindingType.TARGET_TYPE ), null ); } /** * @return a parameter binding representing a target property name parameter */ public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, false, false, false, true, null ); + return new ParameterBinding( + classTypeOf, + null, + Collections.singleton( BindingType.TARGET_PROPERTY_NAME ), + null + ); } /** * @return a parameter binding representing a source property name parameter */ public static ParameterBinding forSourcePropertyNameBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, false, false, true, false, null ); + return new ParameterBinding( + classTypeOf, + null, + Collections.singleton( BindingType.SOURCE_PROPERTY_NAME ), + null + ); } /** @@ -172,7 +196,7 @@ public static ParameterBinding forSourcePropertyNameBinding(Type classTypeOf) { * @return a parameter binding representing a mapping target parameter */ public static ParameterBinding forMappingTargetBinding(Type resultType) { - return new ParameterBinding( resultType, null, true, false, false, false, false, null ); + return new ParameterBinding( resultType, null, Collections.singleton( BindingType.MAPPING_TARGET ), null ); } /** @@ -180,10 +204,27 @@ public static ParameterBinding forMappingTargetBinding(Type resultType) { * @return a parameter binding representing a mapping source type */ public static ParameterBinding forSourceTypeBinding(Type sourceType) { - return new ParameterBinding( sourceType, null, false, false, false, false, false, null ); + return new ParameterBinding( sourceType, null, Collections.singleton( BindingType.SOURCE_TYPE ), null ); } public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) { - return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, false, sourceRHS ); + return new ParameterBinding( + sourceRHS.getSourceType(), + null, + Collections.singleton( BindingType.SOURCE_RHS ), + sourceRHS + ); + } + + enum BindingType { + PARAMETER, + FROM_TYPE_AND_NAME, + TARGET_TYPE, + TARGET_PROPERTY_NAME, + SOURCE_PROPERTY_NAME, + MAPPING_TARGET, + CONTEXT, + SOURCE_TYPE, + SOURCE_RHS } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java new file mode 100644 index 0000000000..dfb0865ecc --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java @@ -0,0 +1,45 @@ +/* + * 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.internal.model.source; + +import java.util.Collection; +import java.util.Collections; + +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; + +/** + * Encapsulates all options specific for a condition check method. + * + * @author Filip Hrisafov + */ +public class ConditionMethodOptions { + + private static final ConditionMethodOptions EMPTY = new ConditionMethodOptions( Collections.emptyList() ); + + private final Collection conditionOptions; + + public ConditionMethodOptions(Collection conditionOptions) { + this.conditionOptions = conditionOptions; + } + + public boolean isStrategyApplicable(ConditionStrategyGem strategy) { + for ( ConditionOptions conditionOption : conditionOptions ) { + if ( conditionOption.getConditionStrategies().contains( strategy ) ) { + return true; + } + } + + return false; + } + + public boolean isAnyStrategyApplicable() { + return !conditionOptions.isEmpty(); + } + + public static ConditionMethodOptions empty() { + return EMPTY; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java new file mode 100644 index 0000000000..936d049af8 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java @@ -0,0 +1,170 @@ +/* + * 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.internal.model.source; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.ConditionGem; +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; + +/** + * @author Filip Hrisafov + */ +public class ConditionOptions { + + private final Set conditionStrategies; + + private ConditionOptions(Set conditionStrategies) { + this.conditionStrategies = conditionStrategies; + } + + public Collection getConditionStrategies() { + return conditionStrategies; + } + + public static ConditionOptions getInstanceOn(ConditionGem condition, ExecutableElement method, + List parameters, + FormattingMessager messager) { + if ( condition == null ) { + return null; + } + + TypeMirror returnType = method.getReturnType(); + TypeKind returnTypeKind = returnType.getKind(); + // We only allow methods that return boolean or Boolean to be condition methods + if ( returnTypeKind != TypeKind.BOOLEAN ) { + if ( returnTypeKind != TypeKind.DECLARED ) { + return null; + } + DeclaredType declaredType = (DeclaredType) returnType; + TypeElement returnTypeElement = (TypeElement) declaredType.asElement(); + if ( !returnTypeElement.getQualifiedName().contentEquals( Boolean.class.getCanonicalName() ) ) { + return null; + } + } + + Set strategies = condition.appliesTo().get() + .stream() + .map( ConditionStrategyGem::valueOf ) + .collect( Collectors.toCollection( () -> EnumSet.noneOf( ConditionStrategyGem.class ) ) ); + + if ( strategies.isEmpty() ) { + messager.printMessage( + method, + condition.mirror(), + condition.appliesTo().getAnnotationValue(), + Message.CONDITION_MISSING_APPLIES_TO_STRATEGY + ); + + return null; + } + + boolean allStrategiesValid = true; + + for ( ConditionStrategyGem strategy : strategies ) { + boolean isStrategyValid = isValid( strategy, condition, method, parameters, messager ); + allStrategiesValid &= isStrategyValid; + } + + return allStrategiesValid ? new ConditionOptions( strategies ) : null; + } + + protected static boolean isValid(ConditionStrategyGem strategy, ConditionGem condition, + ExecutableElement method, List parameters, + FormattingMessager messager) { + if ( strategy == ConditionStrategyGem.SOURCE_PARAMETERS ) { + return hasValidStrategyForSourceProperties( condition, method, parameters, messager ); + } + else if ( strategy == ConditionStrategyGem.PROPERTIES ) { + return hasValidStrategyForProperties( condition, method, parameters, messager ); + } + else { + throw new IllegalStateException( "Invalid condition strategy: " + strategy ); + } + } + + protected static boolean hasValidStrategyForSourceProperties(ConditionGem condition, ExecutableElement method, + List parameters, + FormattingMessager messager) { + for ( Parameter parameter : parameters ) { + if ( parameter.isSourceParameter() ) { + // source parameter is a valid parameter for a source condition check + continue; + } + + if ( parameter.isMappingContext() ) { + // mapping context parameter is a valid parameter for a source condition check + continue; + } + + messager.printMessage( + method, + condition.mirror(), + Message.CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER, + parameter.describe() + ); + return false; + } + return true; + } + + protected static boolean hasValidStrategyForProperties(ConditionGem condition, ExecutableElement method, + List parameters, + FormattingMessager messager) { + for ( Parameter parameter : parameters ) { + if ( parameter.isSourceParameter() ) { + // source parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isMappingContext() ) { + // mapping context parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isTargetType() ) { + // target type parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isMappingTarget() ) { + // mapping target parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isSourcePropertyName() ) { + // source property name parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isTargetPropertyName() ) { + // target property name parameter is a valid parameter for a property condition check + continue; + } + + messager.printMessage( + method, + condition.mirror(), + Message.CONDITION_PROPERTIES_INVALID_PARAMETER, + parameter + ); + return false; + } + return true; + } +} 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 0c60f41364..ad2882080a 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 @@ -90,14 +90,6 @@ public interface Method { */ boolean isObjectFactory(); - /** - * Returns whether the method is designated as a presence check method - * @return {@code true} if it is a presence check method - */ - default boolean isPresenceCheck() { - return false; - } - /** * Returns the parameter designated as target type (if present) {@link org.mapstruct.TargetType } * @@ -187,6 +179,10 @@ default boolean isPresenceCheck() { */ MappingMethodOptions getOptions(); + default ConditionMethodOptions getConditionOptions() { + return ConditionMethodOptions.empty(); + } + /** * * @return true when @MappingTarget annotated parameter is the same type as the return type. The method has 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 42c318ca49..7103fb2858 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 @@ -15,7 +15,6 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import org.mapstruct.ap.internal.gem.ConditionGem; import org.mapstruct.ap.internal.gem.ObjectFactoryGem; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.Parameter; @@ -50,11 +49,11 @@ public class SourceMethod implements Method { private final Parameter sourcePropertyNameParameter; private final Parameter targetPropertyNameParameter; private final boolean isObjectFactory; - private final boolean isPresenceCheck; private final Type returnType; private final Accessibility accessibility; private final List exceptionTypes; private final MappingMethodOptions mappingMethodOptions; + private final ConditionMethodOptions conditionMethodOptions; private final List prototypeMethods; private final Type mapperToImplement; @@ -95,6 +94,7 @@ public static class Builder { private List valueMappings; private EnumMappingOptions enumMappingOptions; private ParameterProvidedMethods contextProvidedMethods; + private Set conditionOptions; private List typeParameters; private Set subclassMappings; @@ -196,6 +196,11 @@ public Builder setContextProvidedMethods(ParameterProvidedMethods contextProvide return this; } + public Builder setConditionOptions(Set conditionOptions) { + this.conditionOptions = conditionOptions; + return this; + } + public Builder setVerboseLogging(boolean verboseLogging) { this.verboseLogging = verboseLogging; return this; @@ -223,17 +228,22 @@ public SourceMethod build() { subclassValidator ); + ConditionMethodOptions conditionMethodOptions = + conditionOptions != null ? new ConditionMethodOptions( conditionOptions ) : + ConditionMethodOptions.empty(); + this.typeParameters = this.executable.getTypeParameters() .stream() .map( Element::asType ) .map( typeFactory::getType ) .collect( Collectors.toList() ); - return new SourceMethod( this, mappingMethodOptions ); + return new SourceMethod( this, mappingMethodOptions, conditionMethodOptions ); } } - private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) { + private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions, + ConditionMethodOptions conditionMethodOptions) { this.declaringMapper = builder.declaringMapper; this.executable = builder.executable; this.parameters = builder.parameters; @@ -242,6 +252,7 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) this.accessibility = Accessibility.fromModifiers( builder.executable.getModifiers() ); this.mappingMethodOptions = mappingMethodOptions; + this.conditionMethodOptions = conditionMethodOptions; this.sourceParameters = Parameter.getSourceParameters( parameters ); this.contextParameters = Parameter.getContextParameters( parameters ); @@ -254,7 +265,6 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters ); this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null; this.isObjectFactory = determineIfIsObjectFactory(); - this.isPresenceCheck = determineIfIsPresenceCheck(); this.typeUtils = builder.typeUtils; this.typeFactory = builder.typeFactory; @@ -274,19 +284,6 @@ private boolean determineIfIsObjectFactory() { && ( hasObjectFactoryAnnotation || hasNoSourceParameters ); } - private boolean determineIfIsPresenceCheck() { - if ( returnType.isPrimitive() ) { - if ( !returnType.getName().equals( "boolean" ) ) { - return false; - } - } - else if ( !returnType.getFullyQualifiedName().equals( Boolean.class.getCanonicalName() ) ) { - return false; - } - - return ConditionGem.instanceOn( executable ) != null; - } - @Override public Type getDeclaringMapper() { return declaringMapper; @@ -547,6 +544,11 @@ public MappingMethodOptions getOptions() { return mappingMethodOptions; } + @Override + public ConditionMethodOptions getConditionOptions() { + return conditionMethodOptions; + } + @Override public boolean isStatic() { return executable.getModifiers().contains( Modifier.STATIC ); @@ -567,11 +569,6 @@ public boolean isLifecycleCallbackMethod() { return Executables.isLifecycleCallbackMethod( getExecutable() ); } - @Override - public boolean isPresenceCheck() { - return isPresenceCheck; - } - public boolean isAfterMappingMethod() { return Executables.isAfterMappingMethod( getExecutable() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java index 03a671de57..b6e5ca0a53 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java @@ -32,6 +32,7 @@ public List> getMatchingMethods(List> result = new ArrayList<>( methods.size() ); for ( SelectedMethod method : methods ) { - if ( method.getMethod().isObjectFactory() == criteria.isObjectFactoryRequired() + if ( criteria.isPresenceCheckRequired() ) { + if ( method.getMethod() + .getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.PROPERTIES ) ) { + result.add( method ); + } + } + else if ( criteria.isSourceParameterCheckRequired() ) { + if ( method.getMethod() + .getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.SOURCE_PARAMETERS ) ) { + result.add( method ); + } + } + else if ( method.getMethod().isObjectFactory() == criteria.isObjectFactoryRequired() && method.getMethod().isLifecycleCallbackMethod() == criteria.isLifecycleCallbackRequired() - && method.getMethod().isPresenceCheck() == criteria.isPresenceCheckRequired() ) { result.add( method ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java index 800278a4c4..84bd04d7db 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java @@ -163,6 +163,45 @@ public static SelectionContext forPresenceCheckMethods(Method mappingMethod, ); } + public static SelectionContext forSourceParameterPresenceCheckMethods(Method mappingMethod, + SelectionParameters selectionParameters, + Parameter sourceParameter, + TypeFactory typeFactory) { + SelectionCriteria criteria = SelectionCriteria.forSourceParameterCheckMethods( selectionParameters ); + Type booleanType = typeFactory.getType( Boolean.class ); + return new SelectionContext( + null, + criteria, + mappingMethod, + booleanType, + booleanType, + () -> getParameterBindingsForSourceParameterPresenceCheck( + mappingMethod, + booleanType, + sourceParameter, + typeFactory + ) + ); + } + + private static List getParameterBindingsForSourceParameterPresenceCheck(Method method, + Type targetType, + Parameter sourceParameter, + TypeFactory typeFactory) { + + List availableParams = new ArrayList<>( method.getParameters().size() + 3 ); + + availableParams.add( ParameterBinding.fromParameter( sourceParameter ) ); + availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) ); + for ( Parameter parameter : method.getParameters() ) { + if ( !parameter.isSourceParameter( ) ) { + availableParams.add( ParameterBinding.fromParameter( parameter ) ); + } + } + + return availableParams; + } + private static List getAvailableParameterBindingsFromMethod(Method method, Type targetType, SourceRHS sourceRHS, TypeFactory typeFactory) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java index 2d288dd56e..fa5e1c29c0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java @@ -97,6 +97,13 @@ public boolean isPresenceCheckRequired() { return type == Type.PRESENCE_CHECK; } + /** + * @return {@code true} if source parameter check methods should be selected, {@code false} otherwise + */ + public boolean isSourceParameterCheckRequired() { + return type == Type.SOURCE_PARAMETER_CHECK; + } + public void setIgnoreQualifiers(boolean ignoreQualifiers) { this.ignoreQualifiers = ignoreQualifiers; } @@ -177,6 +184,10 @@ public static SelectionCriteria forPresenceCheckMethods(SelectionParameters sele return new SelectionCriteria( selectionParameters, null, null, Type.PRESENCE_CHECK ); } + public static SelectionCriteria forSourceParameterCheckMethods(SelectionParameters selectionParameters) { + return new SelectionCriteria( selectionParameters, null, null, Type.SOURCE_PARAMETER_CHECK ); + } + public static SelectionCriteria forSubclassMappingMethods(SelectionParameters selectionParameters, MappingControl mappingControl) { return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED ); @@ -187,6 +198,7 @@ public enum Type { OBJECT_FACTORY, LIFECYCLE_CALLBACK, PRESENCE_CHECK, + SOURCE_PARAMETER_CHECK, SELF_NOT_ALLOWED, } } 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 a9099ea179..4bd2ed48a5 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 @@ -36,6 +36,7 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.BeanMappingOptions; +import org.mapstruct.ap.internal.model.source.ConditionOptions; import org.mapstruct.ap.internal.model.source.EnumMappingOptions; import org.mapstruct.ap.internal.model.source.IterableMappingOptions; import org.mapstruct.ap.internal.model.source.MapMappingOptions; @@ -53,6 +54,7 @@ import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.MetaAnnotations; import org.mapstruct.ap.internal.util.RepeatableAnnotations; import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.spi.EnumTransformationStrategy; @@ -73,6 +75,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor contextProvidedMethods = new ArrayList<>( contextParamMethods.size() ); for ( SourceMethod sourceMethod : contextParamMethods ) { if ( sourceMethod.isLifecycleCallbackMethod() || sourceMethod.isObjectFactory() - || sourceMethod.isPresenceCheck() ) { + || sourceMethod.getConditionOptions().isAnyStrategyApplicable() ) { contextProvidedMethods.add( sourceMethod ); } } @@ -393,6 +396,7 @@ private SourceMethod getReferencedMethod(TypeElement usedMapper, ExecutableType .setExceptionTypes( exceptionTypes ) .setTypeUtils( typeUtils ) .setTypeFactory( typeFactory ) + .setConditionOptions( getConditionOptions( method, parameters ) ) .setVerboseLogging( options.isVerbose() ) .build(); } @@ -633,6 +637,18 @@ private Set getSubclassMappings(List sourcePa .getProcessedAnnotations( method ); } + /** + * Retrieves the conditions configured via {@code @Condition} from the given method. + * + * @param method The method of interest + * @param parameters + * @return The condition options for the given method + */ + + private Set getConditionOptions(ExecutableElement method, List parameters) { + return new MetaConditions( parameters ).getProcessedAnnotations( method ); + } + private class RepeatableMappings extends RepeatableAnnotations { private BeanMappingOptions beanMappingOptions; @@ -774,4 +790,32 @@ protected void addInstances(ValueMappingsGem gems, Element source, Set { + + protected final List parameters; + + protected MetaConditions(List parameters) { + super( elementUtils, CONDITION_FQN ); + this.parameters = parameters; + } + + @Override + protected ConditionGem instanceOn(Element element) { + return ConditionGem.instanceOn( element ); + } + + @Override + protected void addInstance(ConditionGem gem, Element source, Set values) { + ConditionOptions options = ConditionOptions.getInstanceOn( + gem, + (ExecutableElement) source, + parameters, + messager + ); + if ( options != null ) { + values.add( options ); + } + } + } } 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 e6a26ba045..d84ba974db 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 @@ -470,7 +470,7 @@ private ConversionAssignment resolveViaConversion(Type sourceType, Type targetTy } private boolean isCandidateForMapping(Method methodCandidate) { - if ( methodCandidate.isPresenceCheck() ) { + if ( methodCandidate.getConditionOptions().isAnyStrategyApplicable() ) { return false; } return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate ); 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 68c079cb96..5887ee07f0 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 @@ -46,6 +46,10 @@ public enum Message { BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ), + CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ), + CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ), + CONDITION_PROPERTIES_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#PROPERTIES. Only source, @Context, @MappingTarget, @TargetType, @TargetPropertyName and @SourcePropertyName parameters are allowed for conditions applicable to properties." ), + PROPERTYMAPPING_MAPPING_NOTE( "mapping property: %s to: %s.", Diagnostic.Kind.NOTE ), PROPERTYMAPPING_CREATE_NOTE( "creating property mapping: %s.", Diagnostic.Kind.NOTE ), PROPERTYMAPPING_SELECT_NOTE( "selecting property mapping: %s.", Diagnostic.Kind.NOTE ), @@ -143,6 +147,7 @@ public enum Message { GENERAL_AMBIGUOUS_MAPPING_METHOD( "Ambiguous mapping methods found for mapping %s to %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), GENERAL_AMBIGUOUS_FACTORY_METHOD( "Ambiguous factory methods found for creating %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD( "Ambiguous presence check methods found for checking %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), + GENERAL_AMBIGUOUS_SOURCE_PARAMETER_CHECK_METHOD( "Ambiguous source parameter check methods found for checking %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), GENERAL_AMBIGUOUS_CONSTRUCTORS( "Ambiguous constructors found for creating %s: %s. Either declare parameterless constructor or annotate the default constructor with an annotation named @Default." ), GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS( "Incorrect @ConstructorProperties for %s. The size of the @ConstructorProperties does not match the number of constructor parameters" ), GENERAL_UNSUPPORTED_DATE_FORMAT_CHECK( "No dateFormat check is supported for types %s, %s" ), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java new file mode 100644 index 0000000000..f2328a91cc --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.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.internal.util; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.tools.gem.Gem; + +/** + * @author Filip Hrisafov + */ +public abstract class MetaAnnotations { + + private static final String JAVA_LANG_ANNOTATION_PGK = "java.lang.annotation"; + + private final ElementUtils elementUtils; + private final String annotationFqn; + + protected MetaAnnotations(ElementUtils elementUtils, String annotationFqn) { + this.elementUtils = elementUtils; + this.annotationFqn = annotationFqn; + } + + /** + * Retrieves the processed annotations. + * + * @param source The source element of interest + * @return The processed annotations for the given element + */ + public Set getProcessedAnnotations(Element source) { + return getValues( source, source, new LinkedHashSet<>(), new HashSet<>() ); + } + + protected abstract G instanceOn(Element element); + + protected abstract void addInstance(G gem, Element source, Set values); + + /** + * Retrieves the processed annotations. + * + * @param source The source element of interest + * @param element Element of interest: method, or (meta) annotation + * @param values the set of values found so far + * @param handledElements The collection of already handled elements to handle recursion correctly. + * @return The processed annotations for the given element + */ + private Set getValues(Element source, Element element, Set values, Set handledElements) { + for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { + Element annotationElement = annotationMirror.getAnnotationType().asElement(); + if ( isAnnotation( annotationElement, annotationFqn ) ) { + G gem = instanceOn( element ); + addInstance( gem, source, values ); + } + else if ( isNotJavaAnnotation( element ) && !handledElements.contains( annotationElement ) ) { + handledElements.add( annotationElement ); + getValues( source, annotationElement, values, handledElements ); + } + } + return values; + } + + private boolean isNotJavaAnnotation(Element element) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return !elementUtils.getPackageOf( element ).getQualifiedName().contentEquals( JAVA_LANG_ANNOTATION_PGK ); + } + return true; + } + + private boolean isAnnotation(Element element, String annotationFqn) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return ( (TypeElement) element ).getQualifiedName().contentEquals( annotationFqn ); + } + + return false; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java index 57a68a9526..ae309988c8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java @@ -208,6 +208,84 @@ public void conditionalMethodForCollection() { .containsExactly( "Test", "Test Vol. 2" ); } + @ProcessorTest + @WithClasses({ + ConditionalMethodForSourceBeanMapper.class + }) + public void conditionalMethodForSourceBean() { + ConditionalMethodForSourceBeanMapper mapper = ConditionalMethodForSourceBeanMapper.INSTANCE; + + ConditionalMethodForSourceBeanMapper.Employee employee = mapper.map( + new ConditionalMethodForSourceBeanMapper.EmployeeDto( + "1", + "Tester" + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "1" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + + employee = mapper.map( null ); + + assertThat( employee ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceBeanMapper.EmployeeDto( null, "Tester" ) ); + + assertThat( employee ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceBeanMapper.EmployeeDto( "test-123", "Tester" ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "test-123" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodForSourceParameterAndPropertyMapper.class + }) + public void conditionalMethodForSourceParameterAndProperty() { + ConditionalMethodForSourceParameterAndPropertyMapper mapper = + ConditionalMethodForSourceParameterAndPropertyMapper.INSTANCE; + + ConditionalMethodForSourceParameterAndPropertyMapper.Employee employee = mapper.map( + new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( + "1", + "Tester" + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "1" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + assertThat( employee.getManager() ).isNull(); + + employee = mapper.map( null ); + + assertThat( employee ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( + "1", + "Tester", + new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( null, "Manager" ) + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getManager() ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( + "1", + "Tester", + new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( "2", "Manager" ) + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "1" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + assertThat( employee.getManager() ).isNotNull(); + assertThat( employee.getManager().getId() ).isEqualTo( "2" ); + assertThat( employee.getManager().getName() ).isEqualTo( "Manager" ); + } + @ProcessorTest @WithClasses({ OptionalLikeConditionalMapper.class @@ -244,4 +322,124 @@ public void conditionalMethodWithMappingTarget() { assertThat( targetEmployee.getName() ).isEqualTo( "CurrentName" ); } + + @ProcessorTest + @WithClasses({ + ErroneousConditionalWithoutAppliesToMethodMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousConditionalWithoutAppliesToMethodMapper.class, + line = 19, + message = "'appliesTo' has to have at least one value in @Condition" + ) + } + ) + public void emptyConditional() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithMappingTargetMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithMappingTargetMapper.class, + line = 21, + message = "Parameter \"@MappingTarget BasicEmployee employee\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParameterConditionalWithMappingTarget() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithTargetTypeMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithTargetTypeMapper.class, + line = 21, + message = "Parameter \"@TargetType Class targetClass\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParameterConditionalWithTargetType() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.class, + line = 21, + message = "Parameter \"@TargetPropertyName String targetProperty\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParameterConditionalWithTargetPropertyName() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.class, + line = 21, + message = "Parameter \"@SourcePropertyName String sourceProperty\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParametersConditionalWithSourcePropertyName() { + } + + @ProcessorTest + @WithClasses({ + ErroneousAmbiguousSourceParameterConditionalMethodMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousAmbiguousSourceParameterConditionalMethodMapper.class, + line = 17, + message = "Ambiguous source parameter check methods found for checking BasicEmployeeDto: " + + "boolean hasName(BasicEmployeeDto value), " + + "boolean hasStrategy(BasicEmployeeDto value). " + + "See https://mapstruct.org/faq/#ambiguous for more info." + ) + } + ) + public void ambiguousSourceParameterConditionalMethod() { + + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java new file mode 100644 index 0000000000..dd2fe4ac90 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java @@ -0,0 +1,64 @@ +/* + * 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.conditional.basic; + +import org.mapstruct.Mapper; +import org.mapstruct.SourceParameterCondition; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodForSourceBeanMapper { + + ConditionalMethodForSourceBeanMapper INSTANCE = Mappers.getMapper( ConditionalMethodForSourceBeanMapper.class ); + + Employee map(EmployeeDto employee); + + @SourceParameterCondition + default boolean canMapEmployeeDto(EmployeeDto employee) { + return employee != null && employee.getId() != null; + } + + class EmployeeDto { + + private final String id; + private final String name; + + public EmployeeDto(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + } + + class Employee { + + private final String id; + private final String name; + + public Employee(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java new file mode 100644 index 0000000000..11a97b7239 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.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.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodForSourceParameterAndPropertyMapper { + + ConditionalMethodForSourceParameterAndPropertyMapper INSTANCE = Mappers.getMapper( + ConditionalMethodForSourceParameterAndPropertyMapper.class ); + + Employee map(EmployeeDto employee); + + @Condition(appliesTo = { + ConditionStrategy.SOURCE_PARAMETERS, + ConditionStrategy.PROPERTIES + }) + default boolean canMapEmployeeDto(EmployeeDto employee) { + return employee != null && employee.getId() != null; + } + + class EmployeeDto { + + private final String id; + private final String name; + private final EmployeeDto manager; + + public EmployeeDto(String id, String name) { + this( id, name, null ); + } + + public EmployeeDto(String id, String name, EmployeeDto manager) { + this.id = id; + this.name = name; + this.manager = manager; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public EmployeeDto getManager() { + return manager; + } + } + + class Employee { + + private final String id; + private final String name; + private final Employee manager; + + public Employee(String id, String name, Employee manager) { + this.id = id; + this.name = name; + this.manager = manager; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public Employee getManager() { + return manager; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java new file mode 100644 index 0000000000..39433e2641 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java @@ -0,0 +1,28 @@ +/* + * 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.conditional.basic; + +import org.mapstruct.Mapper; +import org.mapstruct.SourceParameterCondition; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousAmbiguousSourceParameterConditionalMethodMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @SourceParameterCondition + default boolean hasName(BasicEmployeeDto value) { + return value != null && value.getName() != null; + } + + @SourceParameterCondition + default boolean hasStrategy(BasicEmployeeDto value) { + return value != null && value.getStrategy() != null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java new file mode 100644 index 0000000000..76f62845d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java @@ -0,0 +1,24 @@ +/* + * 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.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousConditionalWithoutAppliesToMethodMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = {}) + default boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java new file mode 100644 index 0000000000..b1206798d6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java @@ -0,0 +1,26 @@ +/* + * 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.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithMappingTargetMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @MappingTarget BasicEmployee employee) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java new file mode 100644 index 0000000000..1bfd150afc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java @@ -0,0 +1,26 @@ +/* + * 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.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.SourcePropertyName; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithSourcePropertyNameMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @SourcePropertyName String sourceProperty) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java new file mode 100644 index 0000000000..e9dac1ece5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java @@ -0,0 +1,26 @@ +/* + * 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.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.TargetPropertyName; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithTargetPropertyNameMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @TargetPropertyName String targetProperty) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java new file mode 100644 index 0000000000..9ff55597ae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java @@ -0,0 +1,26 @@ +/* + * 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.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.TargetType; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithTargetTypeMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @TargetType Class targetClass) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java b/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java index 0c92fc6bbe..f460180651 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java @@ -11,12 +11,14 @@ import org.junit.jupiter.api.Test; import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.ConditionStrategy; import org.mapstruct.InjectionStrategy; import org.mapstruct.MappingInheritanceStrategy; import org.mapstruct.NullValueCheckStrategy; import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.ReportingPolicy; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; @@ -67,6 +69,12 @@ public void injectionStrategyGemIsCorrect() { namesOf( InjectionStrategyGem.values() ) ); } + @Test + public void conditionStrategyGemIsCorrect() { + assertThat( namesOf( ConditionStrategy.values() ) ).isEqualTo( + namesOf( ConditionStrategyGem.values() ) ); + } + private static List namesOf(Enum[] values) { return Stream.of( values ) .map( Enum::name ) From b33942a0104af4b72554858ae2a21934ca759bc5 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 29 Apr 2024 08:13:46 +0200 Subject: [PATCH 222/363] #3561 Add test case --- .../ap/test/bugs/_3561/Issue3561Mapper.java | 60 +++++++++++++++++++ .../ap/test/bugs/_3561/Issue3561Test.java | 35 +++++++++++ 2 files changed, 95 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Test.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Mapper.java new file mode 100644 index 0000000000..fd1137137b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Mapper.java @@ -0,0 +1,60 @@ +/* + * 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._3561; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3561Mapper { + + Issue3561Mapper INSTANCE = Mappers.getMapper( Issue3561Mapper.class ); + + @Mapping(target = "value", conditionQualifiedByName = "shouldMapValue") + Target map(Source source, @Context boolean shouldMapValue); + + @Condition + @Named("shouldMapValue") + default boolean shouldMapValue(@Context boolean shouldMapValue) { + return shouldMapValue; + } + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + + private String value; + private boolean valueInitialized; + + public String getValue() { + if ( valueInitialized ) { + return value; + } + + throw new IllegalStateException( "value is not initialized" ); + } + + public void setValue(String value) { + this.valueInitialized = true; + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Test.java new file mode 100644 index 0000000000..7cd0c26694 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Test.java @@ -0,0 +1,35 @@ +/* + * 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._3561; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3561Mapper.class) +@IssueKey("3561") +class Issue3561Test { + + @ProcessorTest + void shouldCorrectlyUseConditionWithContext() { + + Issue3561Mapper.Source source = new Issue3561Mapper.Source(); + + assertThatThrownBy( () -> Issue3561Mapper.INSTANCE.map( source, true ) ) + .isInstanceOf( IllegalStateException.class ) + .hasMessage( "value is not initialized" ); + + Issue3561Mapper.Target target = Issue3561Mapper.INSTANCE.map( source, false ); + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + } +} From 9a5e6b1892036d6485fe29e2c45bc68314d1300b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 11 May 2024 08:27:20 +0200 Subject: [PATCH 223/363] #3602 Automate release with JReleaser Add JReleaser for automating the release and add a step for automating the publishing of the website --- .github/scripts/update-website.sh | 76 ++++++++++++++++ .github/workflows/release.yml | 123 ++++++++++++++++++++++++++ NEXT_RELEASE_CHANGELOG.md | 29 ++++++ parent/pom.xml | 142 ++++++++++++++++++++++-------- pom.xml | 13 ++- 5 files changed, 345 insertions(+), 38 deletions(-) create mode 100644 .github/scripts/update-website.sh create mode 100644 .github/workflows/release.yml create mode 100644 NEXT_RELEASE_CHANGELOG.md diff --git a/.github/scripts/update-website.sh b/.github/scripts/update-website.sh new file mode 100644 index 0000000000..8fa989a1dd --- /dev/null +++ b/.github/scripts/update-website.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# +# Copyright MapStruct Authors. +# +# Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 +# + +# env vars: +# VERSION +# GH_BOT_EMAIL + +# This script has been inspired by the JReleaser update-website.sh (https://github.com/jreleaser/jreleaser/blob/main/.github/scripts/update-website.sh) +set -e + +function computePlainVersion() { + echo $1 | sed 's/\([[:digit:]]*\)\.\([[:digit:]]*\)\.\([[:digit:]]*\).*/\1.\2.\3/' +} + +function computeMajorMinorVersion() { + echo $1 | sed 's/\([[:digit:]]*\)\.\([[:digit:]]*\).*/\1.\2/' +} + +function isStable() { + local PLAIN_VERSION=$(computePlainVersion $1) + if [ "${PLAIN_VERSION}" == "$1" ]; then + echo "yes" + else + echo "no" + fi +} + +STABLE=$(isStable $VERSION) +MAJOR_MINOR_VERSION=$(computeMajorMinorVersion $VERSION) + +DEV_VERSION=`grep devVersion config.toml | sed 's/.*"\(.*\)"/\1/'` +MAJOR_MINOR_DEV_VERSION=$(computeMajorMinorVersion $DEV_VERSION) +STABLE_VERSION=`grep stableVersion config.toml | sed 's/.*"\(.*\)"/\1/'` +MAJOR_MINOR_STABLE_VERSION=$(computeMajorMinorVersion $STABLE_VERSION) + +echo "📝 Updating versions" +sed -i '' -e "s/^devVersion = \"\(.*\)\"/devVersion = \"${VERSION}\"/g" config.toml + +if [ "${STABLE}" == "yes" ]; then + sed -i '' -e "s/^stableVersion = \"\(.*\)\"/stableVersion = \"${VERSION}\"/g" config.toml + if [ "${MAJOR_MINOR_STABLE_VERSION}" != ${MAJOR_MINOR_VERSION} ]; then + echo "📝 Updating new stable version" + # This means that we have a new stable version and we need to change the order of the releases. + sed -i '' -e "s/^order = \(.*\)/order = 500/g" data/releases/${MAJOR_MINOR_VERSION}.toml + NEXT_STABLE_ORDER=$((`ls -1 data/releases | wc -l` - 2)) + sed -i '' -e "s/^order = \(.*\)/order = ${NEXT_STABLE_ORDER}/g" data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml + git add data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml + fi +elif [ "${MAJOR_MINOR_DEV_VERSION}" != "${MAJOR_MINOR_VERSION}" ]; then + echo "📝 Updating new dev version" + # This means that we are updating for a new dev version, but the last dev version is not the one that we are doing. + # Therefore, we need to update add the new data configuration + cp data/releases/${MAJOR_MINOR_DEV_VERSION}.toml data/releases/${MAJOR_MINOR_VERSION}.toml + sed -i '' -e "s/^order = \(.*\)/order = 1000/g" data/releases/${MAJOR_MINOR_VERSION}.toml +fi + +sed -i '' -e "s/^name = \"\(.*\)\"/name = \"${VERSION}\"/g" data/releases/${MAJOR_MINOR_VERSION}.toml +sed -i '' -e "s/^releaseDate = \(.*\)/releaseDate = $(date +%F)/g" data/releases/${MAJOR_MINOR_VERSION}.toml +git add data/releases/${MAJOR_MINOR_VERSION}.toml +git add config.toml + +echo "📝 Updating distribution resources" +tar -xf tmp/mapstruct-${VERSION}-dist.tar.gz --directory tmp +rm -rf static/documentation/${MAJOR_MINOR_VERSION} +cp -R tmp/mapstruct-${VERSION}/docs static/documentation/${MAJOR_MINOR_VERSION} +mv static/documentation/${MAJOR_MINOR_VERSION}/reference/html/mapstruct-reference-guide.html static/documentation/${MAJOR_MINOR_VERSION}/reference/html/index.html +git add static/documentation/${MAJOR_MINOR_VERSION} + +git config --global user.email "${GH_BOT_EMAIL}" +git config --global user.name "GitHub Action" +git commit -a -m "Releasing version ${VERSION}" +git push diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..5e4cddbeed --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,123 @@ +name: Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version' + required: true + next: + description: 'Next version' + required: false + +env: + JAVA_VERSION: '11' + JAVA_DISTRO: 'zulu' + +jobs: + release: + # This job has been inspired by the moditect release (https://github.com/moditect/moditect/blob/main/.github/workflows/release.yml) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: ${{ vars.JAVA_VERSION }} + distribution: ${{ vars.JAVA_DISTRO }} + cache: maven + + - name: Set release version + id: version + run: | + RELEASE_VERSION=${{ github.event.inputs.version }} + NEXT_VERSION=${{ github.event.inputs.next }} + PLAIN_VERSION=`echo ${RELEASE_VERSION} | awk 'match($0, /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)/) { print substr($0, RSTART, RLENGTH); }'` + COMPUTED_NEXT_VERSION="${PLAIN_VERSION}-SNAPSHOT" + if [ -z $NEXT_VERSION ] + then + NEXT_VERSION=$COMPUTED_NEXT_VERSION + fi + ./mvnw -ntp -B versions:set versions:commit -DnewVersion=$RELEASE_VERSION -pl :mapstruct-parent -DgenerateBackupPoms=false + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --global user.name "GitHub Action" + git commit -a -m "Releasing version $RELEASE_VERSION" + git push + echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV + echo "NEXT_VERSION=$NEXT_VERSION" >> $GITHUB_ENV + echo "PLAIN_VERSION=$PLAIN_VERSION" >> $GITHUB_ENV + + - name: Stage + run: | + export GPG_TTY=$(tty) + ./mvnw -ntp -B --file pom.xml \ + -Dmaven.site.skip=true -Drelease=true -Ppublication,stage + + - name: Release + env: + JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JRELEASER_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }} + JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + JRELEASER_NEXUS2_MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + JRELEASER_NEXUS2_MAVEN_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + run: | + ./mvnw -ntp -B --file pom.xml -pl :mapstruct-parent -Pjreleaser jreleaser:release + + - name: JReleaser output + if: always() + uses: actions/upload-artifact@v4 + with: + name: jreleaser-release + path: | + parent/target/jreleaser/trace.log + parent/target/jreleaser/output.properties + + - name: Set next version + run: | + ./mvnw -ntp -B versions:set versions:commit -DnewVersion=${{ env.NEXT_VERSION }} -pl :mapstruct-parent -DgenerateBackupPoms=false + sed -i -e "s@project.build.outputTimestamp>.*\${git.commit.author.time} UTF-8 + + mapstruct/mapstruct + /tmp/repository + 1.8 + ${java.version} + ${java.version} + + ${git.commit.author.time} + 1.0.0.Alpha3 3.4.1 3.2.2 @@ -30,7 +39,7 @@ 8.36.1 5.10.1 2.2.0 - + 1.12.0 1 3.24.2 @@ -392,10 +401,6 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 - - 1.8 - 1.8 - org.apache.maven.plugins @@ -410,11 +415,6 @@ true - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - org.apache.maven.plugins maven-enforcer-plugin @@ -461,21 +461,6 @@ 8 - - org.apache.maven.plugins - maven-release-plugin - 2.5.3 - - -DskipTests ${add.release.arguments} - clean install - false - true - @{project.version} - true - false - release - - org.apache.maven.plugins maven-resources-plugin @@ -522,6 +507,11 @@ + + org.codehaus.mojo + versions-maven-plugin + 2.16.2 + org.eclipse.m2e lifecycle-mapping @@ -663,6 +653,7 @@ maven-settings.xml readme.md CONTRIBUTING.md + NEXT_RELEASE_CHANGELOG.md .gitattributes .gitignore .factorypath @@ -791,12 +782,21 @@ + + org.codehaus.mojo + versions-maven-plugin + - release + publication + + + release + + @@ -823,18 +823,86 @@ + + + + + stage + + local::file:${maven.multiModuleProjectDirectory}/target/staging-deploy + + + deploy + + + + jreleaser + + - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - + org.jreleaser + jreleaser-maven-plugin + ${jreleaser.plugin.version} + + true + + + Mapstruct + + https://mapstruct.org/ + https://mapstruct.org/documentation/stable/reference/html/ + + + + ALWAYS + true + + + false + + + + + + ALWAYS + https://oss.sonatype.org/service/local + https://oss.sonatype.org/content/repositories/snapshots/ + true + true + ${maven.multiModuleProjectDirectory}/target/staging-deploy + + + org.mapstruct + mapstruct-jdk8 + false + false + + + + + + + + + {{projectVersion}} + {{projectVersion}} + + ${maven.multiModuleProjectDirectory}/NEXT_RELEASE_CHANGELOG.md + + + + + + + ${maven.multiModuleProjectDirectory}/distribution/target/mapstruct-{{projectVersion}}-dist.tar.gz + + + ${maven.multiModuleProjectDirectory}/distribution/target/mapstruct-{{projectVersion}}-dist.zip + + + + + diff --git a/pom.xml b/pom.xml index 25a5ead6bc..90402f0cae 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,6 @@ core core-jdk8 processor - integrationtest true @@ -71,5 +70,17 @@ distribution + + test + + + release + !true + + + + integrationtest + + From 8e53b4181fb76d992a5902d70afa5136768d4a41 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 11 May 2024 08:47:02 +0200 Subject: [PATCH 224/363] #3602 Fix setup-java action for release workflow --- .github/workflows/release.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5e4cddbeed..6dfd75a6f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,10 +10,6 @@ on: description: 'Next version' required: false -env: - JAVA_VERSION: '11' - JAVA_DISTRO: 'zulu' - jobs: release: # This job has been inspired by the moditect release (https://github.com/moditect/moditect/blob/main/.github/workflows/release.yml) @@ -26,8 +22,8 @@ jobs: - name: Setup Java uses: actions/setup-java@v4 with: - java-version: ${{ vars.JAVA_VERSION }} - distribution: ${{ vars.JAVA_DISTRO }} + java-version: 11 + distribution: 'zulu' cache: maven - name: Set release version @@ -99,12 +95,6 @@ jobs: fetch-depth: 0 token: ${{ secrets.GIT_WEBSITE_ACCESS_TOKEN }} - - name: Setup Java - uses: actions/setup-java@v4 - with: - java-version: ${{ vars.JAVA_VERSION }} - distribution: ${{ vars.JAVA_DISTRO }} - - name: Download assets shell: bash run: | From 21a8b88a0f3cab99673d0bf94f11e5f731f00b90 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 11 May 2024 07:01:58 +0000 Subject: [PATCH 225/363] Releasing version 1.6.0.Beta2 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0bdec734cb..95be5f0b90 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 160f963adf..8830420981 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index ac993c73d6..7c8059144a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index afa8e31002..5c4a656d75 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 0a62a3ef3c..be0d7efe59 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index f888e226ff..3e0fa3ec09 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index e4109325f7..7acf7d5952 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-05-11T07:01:58Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 90402f0cae..ab4476277d 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 12fc615f5f..7ce9548edf 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.Beta2 ../parent/pom.xml From 8a679b325d677ef93f7bda65c5a3da13b6aff524 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 11 May 2024 07:10:47 +0000 Subject: [PATCH 226/363] Next version 1.6.0-SNAPSHOT --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 95be5f0b90..0bdec734cb 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 8830420981..160f963adf 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 7c8059144a..ac993c73d6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 5c4a656d75..afa8e31002 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index be0d7efe59..0a62a3ef3c 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 3e0fa3ec09..f888e226ff 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 7acf7d5952..e4109325f7 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-05-11T07:01:58Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index ab4476277d..90402f0cae 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 7ce9548edf..12fc615f5f 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.Beta2 + 1.6.0-SNAPSHOT ../parent/pom.xml From baa02bf37725c2be8ca6ec8534170172c84583d9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 11 May 2024 09:32:27 +0200 Subject: [PATCH 227/363] #3602 Fix path for update-website.sh scrip in release workflow --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6dfd75a6f2..493fd2ad0c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,5 +109,5 @@ jobs: VERSION: ${{ github.event.inputs.version }} GH_BOT_EMAIL: ${{ vars.GH_BOT_EMAIL }} run: | - chmod +x update-website.sh - ./update-website.sh + chmod +x tmp/update-website.sh + tmp/update-website.sh From babb9dedd9cf3c9c75aa7e4c96a7bd24c40ed927 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 30 Jun 2024 14:47:27 +0200 Subject: [PATCH 228/363] #3602 Doing a release should reset NEXT_RELEASE_CHANGELOG.md --- .github/workflows/release.yml | 4 ++++ NEXT_RELEASE_CHANGELOG.md | 19 ------------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 493fd2ad0c..36cabf44f8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,6 +72,10 @@ jobs: parent/target/jreleaser/trace.log parent/target/jreleaser/output.properties + - name: Reset NEXT_RELEASE_CHANGELOG.md + run: | + echo -e "### Features\n\n### Enhancements\n\n### Bugs\n\n### Documentation\n\n### Build\n" > NEXT_RELEASE_CHANGELOG.md + - name: Set next version run: | ./mvnw -ntp -B versions:set versions:commit -DnewVersion=${{ env.NEXT_VERSION }} -pl :mapstruct-parent -DgenerateBackupPoms=false diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index ed3f0e9c56..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,29 +1,10 @@ ### Features -* Support conditional mapping for source parameters (#2610, #3459, #3270) -* Add `@SourcePropertyName` to handle a property name of the source object (#3323) - Currently only applicable for `@Condition` methods - ### Enhancements -* Improve error message for mapping to `target = "."` using expression (#3485) -* Improve error messages for auto generated mappings (#2788) -* Remove unnecessary casts to long (#3400) - ### Bugs -* `@Condition` cannot be used only with `@Context` parameters (#3561) -* `@Condition` treated as ambiguous mapping for methods returning Boolean/boolean (#3565) -* Subclass mapping warns about unmapped property that is mapped in referenced mapper (#3360) -* Interface inherited build method is not found (#3463) -* Bean with getter returning Stream is treating the Stream as an alternative setter (#3462) -* Using `Mapping#expression` and `Mapping#conditionalQualifiedBy(Name)` should lead to compile error (#3413) -* Defined mappings for subclass mappings with runtime exception subclass exhaustive strategy not working if result type is abstract class (#3331) - ### Documentation -* Clarify that `Mapping#ignoreByDefault` is inherited in nested mappings in documentation (#3577) - ### Build -* Improve tests to show that Lombok `@SuperBuilder` is supported (#3524) -* Add Java 21 CI matrix build (#3473) From 69371708ee9371bdaf4267529f45b9122df80c62 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 6 Jul 2024 10:31:32 +0200 Subject: [PATCH 229/363] #3574 Respect only explicit mappings but fail on unmapped source fields * #3574 Respect only explicit mappings but fail on unmapped source fields This reverts #2560, because we've decided that `@BeanMapping(ignoreByDefault = true)` should only be applied to target properties and not to source properties. Source properties are anyway ignored, the `BeanMapping#unmappedSourcePolicy` should be used to control what should happen with unmapped source policy --- NEXT_RELEASE_CHANGELOG.md | 4 ++++ .../ap/internal/model/BeanMappingMethod.java | 4 ---- ...neousSourceTargetMapperWithIgnoreByDefault.java} | 5 +++-- .../IgnoreByDefaultSourcesTest.java | 13 +++++++++++-- 4 files changed, 18 insertions(+), 8 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/{SourceTargetMapper.java => ErroneousSourceTargetMapperWithIgnoreByDefault.java} (74%) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index e0f4cd31f0..a6c424b08e 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -2,6 +2,10 @@ ### Enhancements +* Breaking change:g (#3574) - +This reverts #2560, because we've decided that `@BeanMapping(ignoreByDefault = true)` should only be applied to target properties and not to source properties. +Source properties are ignored anyway, the `BeanMapping#unmappedSourcePolicy` should be used to control what should happen with unmapped source policy + ### Bugs ### Documentation 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 cf5179f9cb..b437afadc0 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 @@ -1764,10 +1764,6 @@ private ReportingPolicyGem getUnmappedSourcePolicy() { if ( mappingReferences.isForForgedMethods() ) { return ReportingPolicyGem.IGNORE; } - // If we have ignoreByDefault = true, unprocessed source properties are not an issue. - if ( method.getOptions().getBeanMapping().isignoreByDefault() ) { - return ReportingPolicyGem.IGNORE; - } return method.getOptions().getBeanMapping().unmappedSourcePolicy(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapperWithIgnoreByDefault.java similarity index 74% rename from processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/SourceTargetMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapperWithIgnoreByDefault.java index e029bd7391..459e2f426a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapperWithIgnoreByDefault.java @@ -14,8 +14,9 @@ @Mapper( unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.ERROR) -public interface SourceTargetMapper { - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); +public interface ErroneousSourceTargetMapperWithIgnoreByDefault { + ErroneousSourceTargetMapperWithIgnoreByDefault INSTANCE = Mappers.getMapper( + ErroneousSourceTargetMapperWithIgnoreByDefault.class ); @Mapping(source = "one", target = "one") @BeanMapping(ignoreByDefault = true) diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java index 98f2cde4c0..fce6d49648 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java @@ -18,8 +18,17 @@ public class IgnoreByDefaultSourcesTest { @ProcessorTest - @WithClasses({ SourceTargetMapper.class, Source.class, Target.class }) - public void shouldSucceed() { + @WithClasses({ ErroneousSourceTargetMapperWithIgnoreByDefault.class, Source.class, Target.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousSourceTargetMapperWithIgnoreByDefault.class, + kind = Kind.ERROR, + line = 23, + message = "Unmapped source property: \"other\".") + } + ) + public void shouldRaiseErrorDueToNonIgnoredSourcePropertyWithBeanMappingIgnoreByDefault() { } @ProcessorTest From 037da5a1e1f450f670fa934640ee40ed67f0f781 Mon Sep 17 00:00:00 2001 From: Connor McGowan <53909969+cmcgowanprovidertrust@users.noreply.github.com> Date: Sun, 7 Jul 2024 14:19:38 -0500 Subject: [PATCH 230/363] #3635 Fix documentation of unmappedSourcePolicy default (#3637) --- documentation/src/main/asciidoc/chapter-2-set-up.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index 666954c4b1..58759ff8e0 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -261,7 +261,7 @@ Supported values are: If a policy is given for a specific mapper via `@Mapper#unmappedSourcePolicy()`, the value from the annotation takes precedence. If a policy is given for a specific bean mapping via `@BeanMapping#ignoreUnmappedSourceProperties()`, it takes precedence over both `@Mapper#unmappedSourcePolicy()` and the option. -|`WARN` +|`IGNORE` |`mapstruct. disableBuilders` From 8fa2f40944b3a143a987a17448473eec2f78f076 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 15 Jul 2024 23:18:32 +0200 Subject: [PATCH 231/363] Enforce whitespaces around the for colon with CheckStyle (#3642) --- build-config/src/main/resources/build-config/checkstyle.xml | 4 +++- .../org/mapstruct/ap/internal/model/BeanMappingMethod.java | 2 +- .../ap/test/conversion/_enum/EnumToIntegerConversionTest.java | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml index a1ff4af23a..71a7d35899 100644 --- a/build-config/src/main/resources/build-config/checkstyle.xml +++ b/build-config/src/main/resources/build-config/checkstyle.xml @@ -146,7 +146,9 @@ - + + + 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 b437afadc0..6535b07012 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 @@ -152,7 +152,7 @@ public Builder forgedMethod(ForgedMethod forgedMethod) { method( forgedMethod ); mappingReferences = forgedMethod.getMappingReferences(); Parameter sourceParameter = first( Parameter.getSourceParameters( forgedMethod.getParameters() ) ); - for ( MappingReference mappingReference: mappingReferences.getMappingReferences() ) { + for ( MappingReference mappingReference : mappingReferences.getMappingReferences() ) { SourceReference sourceReference = mappingReference.getSourceReference(); if ( sourceReference != null ) { mappingReference.setSourceReference( new SourceReference.BuilderFromSourceReference() diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java index 1302c22bc9..67d99dfbc6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java @@ -30,7 +30,7 @@ public class EnumToIntegerConversionTest { public void shouldApplyEnumToIntegerConversion() { EnumToIntegerSource source = new EnumToIntegerSource(); - for ( EnumToIntegerEnum value: EnumToIntegerEnum.values() ) { + for ( EnumToIntegerEnum value : EnumToIntegerEnum.values() ) { source.setEnumValue( value ); EnumToIntegerTarget target = EnumToIntegerMapper.INSTANCE.sourceToTarget( source ); From eef3bdfca4037735507ef3776568b49201ca4906 Mon Sep 17 00:00:00 2001 From: thunderhook <8238759+thunderhook@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:54:06 +0200 Subject: [PATCH 232/363] #3639 fix documentation link --- .../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index 2174822359..ed64580eb4 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -448,7 +448,7 @@ E.g. If an object factory exists for our `PersonBuilder` then this factory would [NOTE] ====== -Detected builders influence `@BeforeMapping` and `@AfterMapping` behavior. See chapter `Mapping customization with before-mapping and after-mapping methods` for more information. +Detected builders influence `@BeforeMapping` and `@AfterMapping` behavior. See <> for more information. ====== .Person with Builder example From 52877d36c2e227e639fbca43d268174f4fa68bc6 Mon Sep 17 00:00:00 2001 From: thunderhook <8238759+thunderhook@users.noreply.github.com> Date: Sun, 7 Jul 2024 13:49:52 +0200 Subject: [PATCH 233/363] #3634 fix typo in experimental note --- .../java/org/mapstruct/ap/spi/EnumMappingStrategy.java | 2 +- .../org/mapstruct/ap/spi/EnumTransformationStrategy.java | 2 +- .../org/mapstruct/ap/spi/MappingExclusionProvider.java | 9 ++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/spi/EnumMappingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/EnumMappingStrategy.java index 97088cd8c6..8ad6737c63 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/EnumMappingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/EnumMappingStrategy.java @@ -17,7 +17,7 @@ * * @since 1.4 */ -@Experimental("This SPI can have it's signature changed in subsequent releases") +@Experimental("This SPI can have its signature changed in subsequent releases") public interface EnumMappingStrategy { /** diff --git a/processor/src/main/java/org/mapstruct/ap/spi/EnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/EnumTransformationStrategy.java index 2c498fc3a8..796bdb4954 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/EnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/EnumTransformationStrategy.java @@ -13,7 +13,7 @@ * @author Filip Hrisafov * @since 1.4 */ -@Experimental("This SPI can have it's signature changed in subsequent releases") +@Experimental("This SPI can have its signature changed in subsequent releases") public interface EnumTransformationStrategy { /** diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java index afd7e69511..9dae81cda2 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java @@ -12,10 +12,10 @@ /** * A service provider interface that is used to control if MapStruct is allowed to generate automatic sub-mapping for * a given {@link TypeElement}. - * + *

      * When generating the implementation of a mapping method, MapStruct will apply the following routine for each * attribute pair in the source and target object: - * + *

      *

        *
      • If source and target attribute have the same type, the value will be simply copied from source to target. * If the attribute is a collection (e.g. a `List`) a copy of the collection will be set into the target @@ -30,14 +30,14 @@ *
      • If MapStruct could not create a name based mapping method an error will be raised at build time, * indicating the non-mappable attribute and its path.
      • *
      - * + *

      * With this SPI the last step before raising an error can be controlled. i.e. A user can control whether MapStruct * is allowed to generate such automatic sub-mapping method (for the source or target type) or not. * * @author Filip Hrisafov * @since 1.2 */ -@Experimental("This SPI can have it's signature changed in subsequent releases") +@Experimental("This SPI can have its signature changed in subsequent releases") public interface MappingExclusionProvider { /** @@ -46,7 +46,6 @@ public interface MappingExclusionProvider { * The given {@code typeElement} will be excluded from the automatic sub-mapping generation * * @param typeElement that needs to be checked - * * @return {@code true} if MapStruct should exclude the provided {@link TypeElement} from an automatic sub-mapping */ boolean isExcluded(TypeElement typeElement); From 66f4288842460f27b88ba4580cd5384551127d61 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 20 Jul 2024 13:53:39 +0200 Subject: [PATCH 234/363] #3601 Always use SourceParameterCondition when checking source parameter This is a breaking change, with this change whenever a source parameter is used as a source for a target property the condition has to apply to source parameters and not properties --- NEXT_RELEASE_CHANGELOG.md | 68 +++++++++ .../source/selector/SelectionCriteria.java | 129 ++++++++++++------ .../ap/test/bugs/_3601/Issue3601Mapper.java | 50 +++++++ .../ap/test/bugs/_3601/Issue3601Test.java | 47 +++++++ ...itionalMethodWithSourceToTargetMapper.java | 6 +- 5 files changed, 252 insertions(+), 48 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Test.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index a6c424b08e..305bdda947 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -8,7 +8,75 @@ Source properties are ignored anyway, the `BeanMapping#unmappedSourcePolicy` sho ### Bugs +* Breaking change: Presence check method used only once when multiple source parameters are provided (#3601) + ### Documentation ### Build +## Breaking changes + +### Presence checks for source parameters + +In 1.6, support for presence checks on source parameters has been added. +This means that even if you want to map a source parameter directly to some target property the new `@SourceParameterCondition` or `@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)` should be used. + +e.g. + +If we had the following in 1.5: +```java +@Mapper +public interface OrderMapper { + + @Mapping(source = "dto", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder") + Order map(OrderDTO dto); + + @Condition + @Named("mapCustomerFromOrder") + default boolean mapCustomerFromOrder(OrderDTO dto) { + return dto != null && dto.getCustomerName() != null; + } + +} +``` + +Them MapStruct would generate + +```java +public class OrderMapperImpl implements OrderMapper { + + @Override + public Order map(OrderDTO dto) { + if ( dto == null ) { + return null; + } + + Order order = new Order(); + + if ( mapCustomerFromOrder( dto ) ) { + order.setCustomer( orderDtoToCustomer( orderDTO ) ); + } + + return order; + } +} +``` + +In order for the same to be generated in 1.6, the mapper needs to look like this: + +```java +@Mapper +public interface OrderMapper { + + @Mapping(source = "dto", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder") + Order map(OrderDTO dto); + + @SourceParameterCondition + @Named("mapCustomerFromOrder") + default boolean mapCustomerFromOrder(OrderDTO dto) { + return dto != null && dto.getCustomerName() != null; + } + +} +``` + diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java index fa5e1c29c0..ecc9ac9e01 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.internal.model.source.selector; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -22,50 +21,31 @@ */ public class SelectionCriteria { - private final List qualifiers = new ArrayList<>(); - private final List qualifiedByNames = new ArrayList<>(); + private final QualifyingInfo qualifyingInfo; private final String targetPropertyName; - private final TypeMirror qualifyingResultType; private final SourceRHS sourceRHS; private boolean ignoreQualifiers = false; private Type type; - private final boolean allowDirect; - private final boolean allowConversion; - private final boolean allowMappingMethod; - private final boolean allow2Steps; + private final MappingControl mappingControl; public SelectionCriteria(SelectionParameters selectionParameters, MappingControl mappingControl, String targetPropertyName, Type type) { - if ( selectionParameters != null ) { - if ( type == Type.PRESENCE_CHECK ) { - qualifiers.addAll( selectionParameters.getConditionQualifiers() ); - qualifiedByNames.addAll( selectionParameters.getConditionQualifyingNames() ); - } - else { - qualifiers.addAll( selectionParameters.getQualifiers() ); - qualifiedByNames.addAll( selectionParameters.getQualifyingNames() ); - } - qualifyingResultType = selectionParameters.getResultType(); - sourceRHS = selectionParameters.getSourceRHS(); - } - else { - this.qualifyingResultType = null; - sourceRHS = null; - } - if ( mappingControl != null ) { - this.allowDirect = mappingControl.allowDirect(); - this.allowConversion = mappingControl.allowTypeConversion(); - this.allowMappingMethod = mappingControl.allowMappingMethod(); - this.allow2Steps = mappingControl.allowBy2Steps(); - } - else { - this.allowDirect = true; - this.allowConversion = true; - this.allowMappingMethod = true; - this.allow2Steps = true; - } + this( + QualifyingInfo.fromSelectionParameters( selectionParameters ), + selectionParameters != null ? selectionParameters.getSourceRHS() : null, + mappingControl, + targetPropertyName, + type + ); + } + + private SelectionCriteria(QualifyingInfo qualifyingInfo, SourceRHS sourceRHS, MappingControl mappingControl, + String targetPropertyName, Type type) { + this.qualifyingInfo = qualifyingInfo; this.targetPropertyName = targetPropertyName; + this.sourceRHS = sourceRHS; this.type = type; + this.mappingControl = mappingControl; } /** @@ -109,11 +89,11 @@ public void setIgnoreQualifiers(boolean ignoreQualifiers) { } public List getQualifiers() { - return ignoreQualifiers ? Collections.emptyList() : qualifiers; + return ignoreQualifiers ? Collections.emptyList() : qualifyingInfo.qualifiers(); } public List getQualifiedByNames() { - return ignoreQualifiers ? Collections.emptyList() : qualifiedByNames; + return ignoreQualifiers ? Collections.emptyList() : qualifyingInfo.qualifiedByNames(); } public String getTargetPropertyName() { @@ -121,7 +101,7 @@ public String getTargetPropertyName() { } public TypeMirror getQualifyingResultType() { - return qualifyingResultType; + return qualifyingInfo.qualifyingResultType(); } public boolean isPreferUpdateMapping() { @@ -137,23 +117,23 @@ public void setPreferUpdateMapping(boolean preferUpdateMapping) { } public boolean hasQualfiers() { - return !qualifiedByNames.isEmpty() || !qualifiers.isEmpty(); + return !qualifyingInfo.qualifiedByNames().isEmpty() || !qualifyingInfo.qualifiers().isEmpty(); } public boolean isAllowDirect() { - return allowDirect; + return mappingControl == null || mappingControl.allowDirect(); } public boolean isAllowConversion() { - return allowConversion; + return mappingControl == null || mappingControl.allowTypeConversion(); } public boolean isAllowMappingMethod() { - return allowMappingMethod; + return mappingControl == null || mappingControl.allowMappingMethod(); } public boolean isAllow2Steps() { - return allow2Steps; + return mappingControl == null || mappingControl.allowBy2Steps(); } public boolean isSelfAllowed() { @@ -181,7 +161,22 @@ public static SelectionCriteria forLifecycleMethods(SelectionParameters selectio } public static SelectionCriteria forPresenceCheckMethods(SelectionParameters selectionParameters) { - return new SelectionCriteria( selectionParameters, null, null, Type.PRESENCE_CHECK ); + SourceRHS sourceRHS = selectionParameters.getSourceRHS(); + Type type; + QualifyingInfo qualifyingInfo = new QualifyingInfo( + selectionParameters.getConditionQualifiers(), + selectionParameters.getConditionQualifyingNames(), + selectionParameters.getResultType() + ); + if ( sourceRHS != null && sourceRHS.isSourceReferenceParameter() ) { + // If the source reference is for a source parameter, + // then the presence check should be for the source parameter + type = Type.SOURCE_PARAMETER_CHECK; + } + else { + type = Type.PRESENCE_CHECK; + } + return new SelectionCriteria( qualifyingInfo, sourceRHS, null, null, type ); } public static SelectionCriteria forSourceParameterCheckMethods(SelectionParameters selectionParameters) { @@ -193,6 +188,50 @@ public static SelectionCriteria forSubclassMappingMethods(SelectionParameters se return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED ); } + private static class QualifyingInfo { + + private static final QualifyingInfo EMPTY = new QualifyingInfo( + Collections.emptyList(), + Collections.emptyList(), + null + ); + + private final List qualifiers; + private final List qualifiedByNames; + private final TypeMirror qualifyingResultType; + + private QualifyingInfo(List qualifiers, List qualifiedByNames, + TypeMirror qualifyingResultType) { + this.qualifiers = qualifiers; + this.qualifiedByNames = qualifiedByNames; + this.qualifyingResultType = qualifyingResultType; + } + + public List qualifiers() { + return qualifiers; + } + + public List qualifiedByNames() { + return qualifiedByNames; + } + + public TypeMirror qualifyingResultType() { + return qualifyingResultType; + } + + private static QualifyingInfo fromSelectionParameters(SelectionParameters selectionParameters) { + if ( selectionParameters == null ) { + return EMPTY; + } + return new QualifyingInfo( + selectionParameters.getQualifiers(), + selectionParameters.getQualifyingNames(), + selectionParameters.getResultType() + ); + } + } + + public enum Type { PREFER_UPDATE_MAPPING, OBJECT_FACTORY, diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Mapper.java new file mode 100644 index 0000000000..851e353de7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Mapper.java @@ -0,0 +1,50 @@ +/* + * 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._3601; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourceParameterCondition; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3601Mapper { + + Issue3601Mapper INSTANCE = Mappers.getMapper( Issue3601Mapper.class ); + + @Mapping(target = "currentId", source = "source.uuid") + @Mapping(target = "targetIds", source = "sourceIds") + Target map(Source source, List sourceIds); + + @SourceParameterCondition + default boolean isNotEmpty(List elements) { + return elements != null && !elements.isEmpty(); + } + + class Source { + private final String uuid; + + public Source(String uuid) { + this.uuid = uuid; + } + + public String getUuid() { + return uuid; + } + } + + class Target { + //CHECKSTYLE:OFF + public String currentId; + public List targetIds; + //CHECKSTYLE:ON + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Test.java new file mode 100644 index 0000000000..b9b68fb583 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Test.java @@ -0,0 +1,47 @@ +/* + * 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._3601; + +import java.util.Collections; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3601") +class Issue3601Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses( Issue3601Mapper.class ) + void shouldUseSourceParameterPresenceCheckCorrectly() { + Issue3601Mapper.Target target = Issue3601Mapper.INSTANCE.map( + new Issue3601Mapper.Source( "test1" ), + Collections.emptyList() + ); + + assertThat( target ).isNotNull(); + assertThat( target.currentId ).isEqualTo( "test1" ); + assertThat( target.targetIds ).isNull(); + + target = Issue3601Mapper.INSTANCE.map( + null, + Collections.emptyList() + ); + + assertThat( target ).isNull(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java index 35864fe62c..6577a6fd95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java @@ -5,10 +5,10 @@ */ package org.mapstruct.ap.test.conditional.qualifier; -import org.mapstruct.Condition; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Named; +import org.mapstruct.SourceParameterCondition; import org.mapstruct.factory.Mappers; /** @@ -29,13 +29,13 @@ public interface ConditionalMethodWithSourceToTargetMapper { Address convertToAddress(OrderDTO orderDTO); - @Condition + @SourceParameterCondition @Named("mapCustomerFromOrder") default boolean mapCustomerFromOrder(OrderDTO orderDTO) { return orderDTO != null && ( orderDTO.getCustomerName() != null || mapAddressFromOrder( orderDTO ) ); } - @Condition + @SourceParameterCondition @Named("mapAddressFromOrder") default boolean mapAddressFromOrder(OrderDTO orderDTO) { return orderDTO != null && ( orderDTO.getLine1() != null || orderDTO.getLine2() != null ); From df49ce5ff93fce8c062263a8d56594e776dc0a3a Mon Sep 17 00:00:00 2001 From: Obolrom <65775868+Obolrom@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:06:49 +0300 Subject: [PATCH 235/363] #3609 Pass bean mapping ignored unmapped source properties to subclass forged methods Co-authored-by: thunderhook <8238759+thunderhook@users.noreply.github.com> --- .../ap/internal/model/ForgedMethod.java | 21 ++++++++- .../model/source/BeanMappingOptions.java | 11 +++++ .../model/source/MappingMethodOptions.java | 13 ++++++ .../ap/test/bugs/_3609/Issue3609Mapper.java | 43 +++++++++++++++++++ .../ap/test/bugs/_3609/Issue3609Test.java | 23 ++++++++++ 5 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3609/Issue3609Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3609/Issue3609Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java index a33bc7520e..2aa12687d8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java @@ -146,13 +146,30 @@ public static ForgedMethod forSubclassMapping(String name, Type sourceType, Type basedOn, history, mappingReferences == null ? MappingReferences.empty() : mappingReferences, - forgedNameBased + forgedNameBased, + MappingMethodOptions.getSubclassForgedMethodInheritedOptions( basedOn.getOptions() ) ); } private ForgedMethod(String name, Type sourceType, Type returnType, List additionalParameters, Method basedOn, ForgedMethodHistory history, MappingReferences mappingReferences, boolean forgedNameBased) { + this( + name, + sourceType, + returnType, + additionalParameters, + basedOn, + history, + mappingReferences, + forgedNameBased, + MappingMethodOptions.getForgedMethodInheritedOptions( basedOn.getOptions() ) + ); + } + + private ForgedMethod(String name, Type sourceType, Type returnType, List additionalParameters, + Method basedOn, ForgedMethodHistory history, MappingReferences mappingReferences, + boolean forgedNameBased, MappingMethodOptions options) { // establish name String sourceParamSafeName; @@ -185,7 +202,7 @@ private ForgedMethod(String name, Type sourceType, Type returnType, List Date: Sat, 20 Jul 2024 16:19:59 +0200 Subject: [PATCH 236/363] #3591 Fix duplicate method generation with recursive auto mapping --- .../internal/model/AbstractBaseBuilder.java | 30 ++++--- .../model/ContainerMappingMethod.java | 3 +- .../ap/internal/model/PropertyMapping.java | 9 +- .../model/source/BeanMappingOptions.java | 4 +- .../model/source/IterableMappingOptions.java | 13 ++- .../model/source/MapMappingOptions.java | 4 +- .../internal/model/source/MappingOptions.java | 2 +- .../model/source/SelectionParameters.java | 15 ++++ .../mapstruct/ap/test/bugs/_3591/Bean.java | 36 ++++++++ .../mapstruct/ap/test/bugs/_3591/BeanDto.java | 30 +++++++ .../ap/test/bugs/_3591/BeanMapper.java | 21 +++++ .../ap/test/bugs/_3591/ContainerBean.java | 47 ++++++++++ .../ap/test/bugs/_3591/ContainerBeanDto.java | 40 +++++++++ .../test/bugs/_3591/ContainerBeanMapper.java | 22 +++++ .../ap/test/bugs/_3591/Issue3591Test.java | 79 +++++++++++++++++ .../bugs/_3591/ContainerBeanMapperImpl.java | 85 +++++++++++++++++++ 16 files changed, 416 insertions(+), 24 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Bean.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBean.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Issue3591Test.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java index ba13854667..311cc00368 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.model; +import java.util.function.Supplier; import javax.lang.model.element.AnnotationMirror; import org.mapstruct.ap.internal.model.common.Assignment; @@ -74,16 +75,9 @@ private boolean isDisableSubMappingMethodsGeneration() { */ Assignment createForgedAssignment(SourceRHS sourceRHS, BuilderType builderType, ForgedMethod forgedMethod) { - if ( ctx.getForgedMethodsUnderCreation().containsKey( forgedMethod ) ) { - return createAssignment( sourceRHS, ctx.getForgedMethodsUnderCreation().get( forgedMethod ) ); - } - else { - ctx.getForgedMethodsUnderCreation().put( forgedMethod, forgedMethod ); - } - - MappingMethod forgedMappingMethod; + Supplier forgedMappingMethodCreator; if ( MappingMethodUtils.isEnumMapping( forgedMethod ) ) { - forgedMappingMethod = new ValueMappingMethod.Builder() + forgedMappingMethodCreator = () -> new ValueMappingMethod.Builder() .method( forgedMethod ) .valueMappings( forgedMethod.getOptions().getValueMappings() ) .enumMapping( forgedMethod.getOptions().getEnumMappingOptions() ) @@ -91,15 +85,31 @@ Assignment createForgedAssignment(SourceRHS sourceRHS, BuilderType builderType, .build(); } else { - forgedMappingMethod = new BeanMappingMethod.Builder() + forgedMappingMethodCreator = () -> new BeanMappingMethod.Builder() .forgedMethod( forgedMethod ) .returnTypeBuilder( builderType ) .mappingContext( ctx ) .build(); } + return getOrCreateForgedAssignment( sourceRHS, forgedMethod, forgedMappingMethodCreator ); + } + + Assignment getOrCreateForgedAssignment(SourceRHS sourceRHS, ForgedMethod forgedMethod, + Supplier mappingMethodCreator) { + + if ( ctx.getForgedMethodsUnderCreation().containsKey( forgedMethod ) ) { + return createAssignment( sourceRHS, ctx.getForgedMethodsUnderCreation().get( forgedMethod ) ); + } + else { + ctx.getForgedMethodsUnderCreation().put( forgedMethod, forgedMethod ); + } + + MappingMethod forgedMappingMethod = mappingMethodCreator.get(); + Assignment forgedAssignment = createForgedAssignment( sourceRHS, forgedMethod, forgedMappingMethod ); ctx.getForgedMethodsUnderCreation().remove( forgedMethod ); + return forgedAssignment; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java index 7ecb7b0ed1..9e7e64fefa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java @@ -46,7 +46,8 @@ public abstract class ContainerMappingMethod extends NormalTypeMappingMethod { afterMappingReferences ); this.elementAssignment = parameterAssignment; this.loopVariableName = loopVariableName; - this.selectionParameters = selectionParameters; + this.selectionParameters = selectionParameters != null ? selectionParameters : SelectionParameters.empty(); + this.index1Name = Strings.getSafeVariableName( "i", existingVariables ); this.index2Name = Strings.getSafeVariableName( "j", existingVariables ); 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 2f30957962..db14ccbd8e 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 @@ -11,6 +11,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Supplier; import javax.lang.model.element.AnnotationMirror; import org.mapstruct.ap.internal.gem.BuilderGem; @@ -746,7 +747,7 @@ private Assignment forgeWithElementMapping(Type sourceType, Type targetType, Sou targetType = targetType.withoutBounds(); ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, "[]" ); - ContainerMappingMethod iterableMappingMethod = builder + Supplier mappingMethodCreator = () -> builder .mappingContext( ctx ) .method( methodRef ) .selectionParameters( selectionParameters ) @@ -754,7 +755,7 @@ private Assignment forgeWithElementMapping(Type sourceType, Type targetType, Sou .positionHint( positionHint ) .build(); - return createForgedAssignment( source, methodRef, iterableMappingMethod ); + return getOrCreateForgedAssignment( source, methodRef, mappingMethodCreator ); } private ForgedMethod prepareForgedMethod(Type sourceType, Type targetType, SourceRHS source, String suffix) { @@ -772,12 +773,12 @@ private Assignment forgeMapMapping(Type sourceType, Type targetType, SourceRHS s ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, "{}" ); MapMappingMethod.Builder builder = new MapMappingMethod.Builder(); - MapMappingMethod mapMappingMethod = builder + Supplier mapMappingMethodCreator = () -> builder .mappingContext( ctx ) .method( methodRef ) .build(); - return createForgedAssignment( source, methodRef, mapMappingMethod ); + return getOrCreateForgedAssignment( source, methodRef, mapMappingMethodCreator ); } private Assignment forgeMapping(SourceRHS sourceRHS) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index a31000e8b4..bc19860bb5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -58,7 +58,7 @@ public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping, public static BeanMappingOptions forForgedMethods(BeanMappingOptions beanMapping) { BeanMappingOptions options = new BeanMappingOptions( beanMapping.selectionParameters != null ? - SelectionParameters.withoutResultType( beanMapping.selectionParameters ) : null, + SelectionParameters.withoutResultType( beanMapping.selectionParameters ) : SelectionParameters.empty(), Collections.emptyList(), beanMapping.beanMapping, beanMapping @@ -78,7 +78,7 @@ public static BeanMappingOptions forSubclassForgedMethods(BeanMappingOptions bea } public static BeanMappingOptions empty(DelegatingOptions delegatingOptions) { - return new BeanMappingOptions( null, Collections.emptyList(), null, delegatingOptions ); + return new BeanMappingOptions( SelectionParameters.empty(), Collections.emptyList(), null, delegatingOptions ); } public static BeanMappingOptions getInstanceOn(BeanMappingGem beanMapping, MapperOptions mapperOptions, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java index 4affcd93e2..c4770efde6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java @@ -8,14 +8,14 @@ import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; -import org.mapstruct.ap.internal.util.ElementUtils; -import org.mapstruct.ap.internal.util.TypeUtils; -import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.gem.IterableMappingGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.tools.gem.GemValue; /** @@ -34,7 +34,12 @@ public static IterableMappingOptions fromGem(IterableMappingGem iterableMapping, FormattingMessager messager, TypeUtils typeUtils) { if ( iterableMapping == null || !isConsistent( iterableMapping, method, messager ) ) { - IterableMappingOptions options = new IterableMappingOptions( null, null, null, mapperOptions ); + IterableMappingOptions options = new IterableMappingOptions( + null, + SelectionParameters.empty(), + null, + mapperOptions + ); return options; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java index ca8f1ea544..fd1758d8d2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java @@ -38,9 +38,9 @@ public static MapMappingOptions fromGem(MapMappingGem mapMapping, MapperOptions if ( mapMapping == null || !isConsistent( mapMapping, method, messager ) ) { MapMappingOptions options = new MapMappingOptions( null, + SelectionParameters.empty(), null, - null, - null, + SelectionParameters.empty(), null, mapperOptions ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index e0994d746f..24a94137f1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -182,7 +182,7 @@ public static MappingOptions forIgnore(String targetName) { null, true, null, - null, + SelectionParameters.empty(), Collections.emptySet(), null, null, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java index 7237668e2c..4706d219cb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java @@ -21,6 +21,16 @@ */ public class SelectionParameters { + private static final SelectionParameters EMPTY = new SelectionParameters( + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + null, + null, + null + ); + private final List qualifiers; private final List qualifyingNames; private final List conditionQualifiers; @@ -225,4 +235,9 @@ public static SelectionParameters forSourceRHS(SourceRHS sourceRHS) { sourceRHS ); } + + public static SelectionParameters empty() { + return EMPTY; + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Bean.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Bean.java new file mode 100644 index 0000000000..7da423c906 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Bean.java @@ -0,0 +1,36 @@ +/* + * 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._3591; + +import java.util.List; + +public class Bean { + private List beans; + private String value; + + public Bean() { + } + + public Bean(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public List getBeans() { + return beans; + } + + public void setBeans(List beans) { + this.beans = beans; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanDto.java new file mode 100644 index 0000000000..00e70ff228 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanDto.java @@ -0,0 +1,30 @@ +/* + * 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._3591; + +import java.util.List; + +public class BeanDto { + + private List beans; + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public List getBeans() { + return beans; + } + + public void setBeans(List beans) { + this.beans = beans; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanMapper.java new file mode 100644 index 0000000000..eeb54b4710 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanMapper.java @@ -0,0 +1,21 @@ +/* + * 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._3591; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BeanMapper { + + BeanMapper INSTANCE = Mappers.getMapper( BeanMapper.class ); + + @Mapping(source = "beans", target = "beans") + BeanDto map(Bean bean, @MappingTarget BeanDto beanDto); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBean.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBean.java new file mode 100644 index 0000000000..b0e68ecfcf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBean.java @@ -0,0 +1,47 @@ +/* + * 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._3591; + +import java.util.Map; +import java.util.stream.Stream; + +public class ContainerBean { + + private String value; + private Map beanMap; + private Stream beanStream; + + public ContainerBean() { + } + + public ContainerBean(String value) { + this.value = value; + } + + public Map getBeanMap() { + return beanMap; + } + + public void setBeanMap(Map beanMap) { + this.beanMap = beanMap; + } + + public Stream getBeanStream() { + return beanStream; + } + + public void setBeanStream(Stream beanStream) { + this.beanStream = beanStream; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanDto.java new file mode 100644 index 0000000000..86bb0193ac --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanDto.java @@ -0,0 +1,40 @@ +/* + * 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._3591; + +import java.util.Map; +import java.util.stream.Stream; + +public class ContainerBeanDto { + + private String value; + private Map beanMap; + private Stream beanStream; + + public Map getBeanMap() { + return beanMap; + } + + public void setBeanMap(Map beanMap) { + this.beanMap = beanMap; + } + + public Stream getBeanStream() { + return beanStream; + } + + public void setBeanStream(Stream beanStream) { + this.beanStream = beanStream; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapper.java new file mode 100644 index 0000000000..0da338aadf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapper.java @@ -0,0 +1,22 @@ +/* + * 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._3591; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ContainerBeanMapper { + + ContainerBeanMapper INSTANCE = Mappers.getMapper( ContainerBeanMapper.class ); + + @Mapping(source = "beanMap", target = "beanMap") + @Mapping(source = "beanStream", target = "beanStream") + ContainerBeanDto mapWithMapMapping(ContainerBean containerBean, @MappingTarget ContainerBeanDto containerBeanDto); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Issue3591Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Issue3591Test.java new file mode 100644 index 0000000000..15bb191ffc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Issue3591Test.java @@ -0,0 +1,79 @@ +/* + * 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._3591; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3591") +class Issue3591Test { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + BeanDto.class, + Bean.class, + BeanMapper.class + }) + void mapNestedBeansWithMappingAnnotation() { + Bean bean = new Bean( "parent" ); + Bean child = new Bean( "child" ); + bean.setBeans( Collections.singletonList( child ) ); + + BeanDto beanDto = BeanMapper.INSTANCE.map( bean, new BeanDto() ); + + assertThat( beanDto ).isNotNull(); + assertThat( beanDto.getValue() ).isEqualTo( "parent" ); + assertThat( beanDto.getBeans() ) + .extracting( BeanDto::getValue ) + .containsExactly( "child" ); + } + + @ProcessorTest + @WithClasses({ + ContainerBean.class, + ContainerBeanDto.class, + ContainerBeanMapper.class, + }) + void shouldMapNestedMapAndStream() { + generatedSource.addComparisonToFixtureFor( ContainerBeanMapper.class ); + + ContainerBean containerBean = new ContainerBean( "parent" ); + Map beanMap = new HashMap<>(); + beanMap.put( "child", new ContainerBean( "mapChild" ) ); + containerBean.setBeanMap( beanMap ); + + Stream streamChild = Stream.of( new ContainerBean( "streamChild" ) ); + containerBean.setBeanStream( streamChild ); + + ContainerBeanDto dto = ContainerBeanMapper.INSTANCE.mapWithMapMapping( containerBean, new ContainerBeanDto() ); + + assertThat( dto ).isNotNull(); + + assertThat( dto.getBeanMap() ) + .extractingByKey( "child" ) + .extracting( ContainerBeanDto::getValue ) + .isEqualTo( "mapChild" ); + + assertThat( dto.getBeanStream() ) + .singleElement() + .extracting( ContainerBeanDto::getValue ) + .isEqualTo( "streamChild" ); + } + +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java new file mode 100644 index 0000000000..47baa689c8 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.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._3591; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-05-25T14:23:23+0200", + comments = "version: , compiler: javac, environment: Java 17.0.11 (N/A)" +) +public class ContainerBeanMapperImpl implements ContainerBeanMapper { + + @Override + public ContainerBeanDto mapWithMapMapping(ContainerBean containerBean, ContainerBeanDto containerBeanDto) { + if ( containerBean == null ) { + return containerBeanDto; + } + + if ( containerBeanDto.getBeanMap() != null ) { + Map map = stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ); + if ( map != null ) { + containerBeanDto.getBeanMap().clear(); + containerBeanDto.getBeanMap().putAll( map ); + } + else { + containerBeanDto.setBeanMap( null ); + } + } + else { + Map map = stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ); + if ( map != null ) { + containerBeanDto.setBeanMap( map ); + } + } + containerBeanDto.setBeanStream( containerBeanStreamToContainerBeanDtoStream( containerBean.getBeanStream() ) ); + containerBeanDto.setValue( containerBean.getValue() ); + + return containerBeanDto; + } + + protected Stream containerBeanStreamToContainerBeanDtoStream(Stream stream) { + if ( stream == null ) { + return null; + } + + return stream.map( containerBean -> containerBeanToContainerBeanDto( containerBean ) ); + } + + protected ContainerBeanDto containerBeanToContainerBeanDto(ContainerBean containerBean) { + if ( containerBean == null ) { + return null; + } + + ContainerBeanDto containerBeanDto = new ContainerBeanDto(); + + containerBeanDto.setBeanMap( stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ) ); + containerBeanDto.setBeanStream( containerBeanStreamToContainerBeanDtoStream( containerBean.getBeanStream() ) ); + containerBeanDto.setValue( containerBean.getValue() ); + + return containerBeanDto; + } + + protected Map stringContainerBeanMapToStringContainerBeanDtoMap(Map map) { + if ( map == null ) { + return null; + } + + Map map1 = new LinkedHashMap( Math.max( (int) ( map.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : map.entrySet() ) { + String key = entry.getKey(); + ContainerBeanDto value = containerBeanToContainerBeanDto( entry.getValue() ); + map1.put( key, value ); + } + + return map1; + } +} From e2edb1a0864b1a2b1a39341a7113a63f40650e92 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 20 Jul 2024 14:04:47 +0200 Subject: [PATCH 237/363] #3504 Add example classes for the passing target type documentation --- .../chapter-5-data-type-conversions.asciidoc | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index 30430fe97e..bc406cf0b0 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -401,6 +401,39 @@ When having a custom mapper hooked into the generated mapper with `@Mapper#uses( For instance, the `CarDto` could have a property `owner` of type `Reference` that contains the primary key of a `Person` entity. You could now create a generic custom mapper that resolves any `Reference` objects to their corresponding managed JPA entity instances. +e.g. + +.Example classes for the passing target type example +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class Car { + + private Person owner; + // ... +} + +public class Person extends BaseEntity { + + // ... +} + +public class Reference { + + private String pk; + // ... +} + +public class CarDto { + + private Reference owner; + // ... +} +---- +==== + + .Mapping method expecting mapping target type as parameter ==== [source, java, linenums] From 5ce9c537e963709c33f0fa830b6c1da8653bc885 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 20 Jul 2024 16:27:04 +0200 Subject: [PATCH 238/363] Add release notes --- NEXT_RELEASE_CHANGELOG.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 305bdda947..39f23af6fc 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,19 +1,26 @@ -### Features - ### Enhancements -* Breaking change:g (#3574) - +* Breaking change: (#3574) - This reverts #2560, because we've decided that `@BeanMapping(ignoreByDefault = true)` should only be applied to target properties and not to source properties. Source properties are ignored anyway, the `BeanMapping#unmappedSourcePolicy` should be used to control what should happen with unmapped source policy ### Bugs * Breaking change: Presence check method used only once when multiple source parameters are provided (#3601) +* Fix `@SubclassMapping` not working with `@BeanMapping#ignoreUnmappedSourceProperties` (#3609) +* Fix duplicate method generation with recursive auto mapping (#3591) ### Documentation +* Fix documentation of `unmappedSourcePolicy` default value (#3635) +* Fix documentation link of before and after mapping when using builders (#3639) +* Fix typo in experimental note (#3634) +* Add example classes for the passing target type documentation (#3504) + ### Build +* Enforce whitespaces around the for colon with CheckStyle (#3642) + ## Breaking changes ### Presence checks for source parameters From bbb9bb403c93e1c938907523ce9fcdeb67c2819c Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 20 Jul 2024 17:20:31 +0200 Subject: [PATCH 239/363] Fix typo in changelog --- NEXT_RELEASE_CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 39f23af6fc..f08c6a2fd9 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -47,7 +47,7 @@ public interface OrderMapper { } ``` -Them MapStruct would generate +Then MapStruct would generate ```java public class OrderMapperImpl implements OrderMapper { From 6ef64ea3aa8320c52a924375d3bc6edf60f0c86d Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:36:11 +0000 Subject: [PATCH 240/363] Releasing version 1.6.0.RC1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0bdec734cb..ff56bd7953 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 160f963adf..d2806fa413 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index ac993c73d6..478ef7308f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index afa8e31002..4bd4bc5dc4 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 0a62a3ef3c..d98933a414 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index f888e226ff..10850bc821 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index e4109325f7..2b3b8e65a6 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-07-20T15:36:10Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 90402f0cae..571623372b 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 12fc615f5f..e33e84ba65 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0.RC1 ../parent/pom.xml From 6365a606c1b7f2c8d2dfb1583c74d46fb122eb1d Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:45:11 +0000 Subject: [PATCH 241/363] Next version 1.6.0-SNAPSHOT --- NEXT_RELEASE_CHANGELOG.md | 83 +-------------------------------------- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 +- pom.xml | 2 +- processor/pom.xml | 2 +- 10 files changed, 12 insertions(+), 91 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index f08c6a2fd9..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,89 +1,10 @@ -### Enhancements +### Features -* Breaking change: (#3574) - -This reverts #2560, because we've decided that `@BeanMapping(ignoreByDefault = true)` should only be applied to target properties and not to source properties. -Source properties are ignored anyway, the `BeanMapping#unmappedSourcePolicy` should be used to control what should happen with unmapped source policy +### Enhancements ### Bugs -* Breaking change: Presence check method used only once when multiple source parameters are provided (#3601) -* Fix `@SubclassMapping` not working with `@BeanMapping#ignoreUnmappedSourceProperties` (#3609) -* Fix duplicate method generation with recursive auto mapping (#3591) - ### Documentation -* Fix documentation of `unmappedSourcePolicy` default value (#3635) -* Fix documentation link of before and after mapping when using builders (#3639) -* Fix typo in experimental note (#3634) -* Add example classes for the passing target type documentation (#3504) - ### Build -* Enforce whitespaces around the for colon with CheckStyle (#3642) - -## Breaking changes - -### Presence checks for source parameters - -In 1.6, support for presence checks on source parameters has been added. -This means that even if you want to map a source parameter directly to some target property the new `@SourceParameterCondition` or `@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)` should be used. - -e.g. - -If we had the following in 1.5: -```java -@Mapper -public interface OrderMapper { - - @Mapping(source = "dto", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder") - Order map(OrderDTO dto); - - @Condition - @Named("mapCustomerFromOrder") - default boolean mapCustomerFromOrder(OrderDTO dto) { - return dto != null && dto.getCustomerName() != null; - } - -} -``` - -Then MapStruct would generate - -```java -public class OrderMapperImpl implements OrderMapper { - - @Override - public Order map(OrderDTO dto) { - if ( dto == null ) { - return null; - } - - Order order = new Order(); - - if ( mapCustomerFromOrder( dto ) ) { - order.setCustomer( orderDtoToCustomer( orderDTO ) ); - } - - return order; - } -} -``` - -In order for the same to be generated in 1.6, the mapper needs to look like this: - -```java -@Mapper -public interface OrderMapper { - - @Mapping(source = "dto", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder") - Order map(OrderDTO dto); - - @SourceParameterCondition - @Named("mapCustomerFromOrder") - default boolean mapCustomerFromOrder(OrderDTO dto) { - return dto != null && dto.getCustomerName() != null; - } - -} -``` - diff --git a/build-config/pom.xml b/build-config/pom.xml index ff56bd7953..0bdec734cb 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index d2806fa413..160f963adf 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 478ef7308f..ac993c73d6 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 4bd4bc5dc4..afa8e31002 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d98933a414..0a62a3ef3c 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 10850bc821..f888e226ff 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 2b3b8e65a6..e4109325f7 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-07-20T15:36:10Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 571623372b..90402f0cae 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index e33e84ba65..12fc615f5f 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0.RC1 + 1.6.0-SNAPSHOT ../parent/pom.xml From 0f24633d04c0a568ab879e629a4720f37a51616b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 20 Jul 2024 17:56:48 +0200 Subject: [PATCH 242/363] Fix update website script to be able to run Linux --- .github/scripts/update-website.sh | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/scripts/update-website.sh b/.github/scripts/update-website.sh index 8fa989a1dd..7c92b8b43b 100644 --- a/.github/scripts/update-website.sh +++ b/.github/scripts/update-website.sh @@ -38,16 +38,22 @@ STABLE_VERSION=`grep stableVersion config.toml | sed 's/.*"\(.*\)"/\1/'` MAJOR_MINOR_STABLE_VERSION=$(computeMajorMinorVersion $STABLE_VERSION) echo "📝 Updating versions" -sed -i '' -e "s/^devVersion = \"\(.*\)\"/devVersion = \"${VERSION}\"/g" config.toml + +SEDOPTION="-i" +if [[ "$OSTYPE" == "darwin"* ]]; then + SEDOPTION="-i ''" +fi + +sed $SEDOPTION -e "s/^devVersion = \"\(.*\)\"/devVersion = \"${VERSION}\"/g" config.toml if [ "${STABLE}" == "yes" ]; then - sed -i '' -e "s/^stableVersion = \"\(.*\)\"/stableVersion = \"${VERSION}\"/g" config.toml + sed $SEDOPTION -e "s/^stableVersion = \"\(.*\)\"/stableVersion = \"${VERSION}\"/g" config.toml if [ "${MAJOR_MINOR_STABLE_VERSION}" != ${MAJOR_MINOR_VERSION} ]; then echo "📝 Updating new stable version" # This means that we have a new stable version and we need to change the order of the releases. - sed -i '' -e "s/^order = \(.*\)/order = 500/g" data/releases/${MAJOR_MINOR_VERSION}.toml + sed $SEDOPTION -e "s/^order = \(.*\)/order = 500/g" data/releases/${MAJOR_MINOR_VERSION}.toml NEXT_STABLE_ORDER=$((`ls -1 data/releases | wc -l` - 2)) - sed -i '' -e "s/^order = \(.*\)/order = ${NEXT_STABLE_ORDER}/g" data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml + sed $SEDOPTION -e "s/^order = \(.*\)/order = ${NEXT_STABLE_ORDER}/g" data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml git add data/releases/${MAJOR_MINOR_STABLE_VERSION}.toml fi elif [ "${MAJOR_MINOR_DEV_VERSION}" != "${MAJOR_MINOR_VERSION}" ]; then @@ -55,11 +61,11 @@ elif [ "${MAJOR_MINOR_DEV_VERSION}" != "${MAJOR_MINOR_VERSION}" ]; then # This means that we are updating for a new dev version, but the last dev version is not the one that we are doing. # Therefore, we need to update add the new data configuration cp data/releases/${MAJOR_MINOR_DEV_VERSION}.toml data/releases/${MAJOR_MINOR_VERSION}.toml - sed -i '' -e "s/^order = \(.*\)/order = 1000/g" data/releases/${MAJOR_MINOR_VERSION}.toml + sed $SEDOPTION -e "s/^order = \(.*\)/order = 1000/g" data/releases/${MAJOR_MINOR_VERSION}.toml fi -sed -i '' -e "s/^name = \"\(.*\)\"/name = \"${VERSION}\"/g" data/releases/${MAJOR_MINOR_VERSION}.toml -sed -i '' -e "s/^releaseDate = \(.*\)/releaseDate = $(date +%F)/g" data/releases/${MAJOR_MINOR_VERSION}.toml +sed $SEDOPTION -e "s/^name = \"\(.*\)\"/name = \"${VERSION}\"/g" data/releases/${MAJOR_MINOR_VERSION}.toml +sed $SEDOPTION -e "s/^releaseDate = \(.*\)/releaseDate = $(date +%F)/g" data/releases/${MAJOR_MINOR_VERSION}.toml git add data/releases/${MAJOR_MINOR_VERSION}.toml git add config.toml From 81ca739040022ab06991cf3d1d4d12fb8fa68e5b Mon Sep 17 00:00:00 2001 From: thunderhook <8238759+thunderhook@users.noreply.github.com> Date: Sun, 21 Jul 2024 22:59:41 +0200 Subject: [PATCH 243/363] #3638 Remove deprecation note of enum mapping via @Mapping --- core/src/main/java/org/mapstruct/Mapping.java | 5 +---- .../src/main/asciidoc/chapter-8-mapping-values.asciidoc | 6 ------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 42384152d0..8b0c4adb0c 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -20,7 +20,7 @@ import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; /** - * Configures the mapping of one bean attribute or enum constant. + * Configures the mapping of one bean attribute. *

      * The name of the mapped attribute or constant is to be specified via {@link #target()}. For mapped bean attributes it * is assumed by default that the attribute has the same name in the source bean. Alternatively, one of @@ -136,9 +136,6 @@ * } *

      * - * IMPORTANT NOTE: the enum mapping capability is deprecated and replaced by {@link ValueMapping} it - * will be removed in subsequent versions. - * * @author Gunnar Morling */ diff --git a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc index 965479541e..fcb353010d 100644 --- a/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc +++ b/documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc @@ -189,12 +189,6 @@ public class SpecialOrderMapperImpl implements SpecialOrderMapper { ---- ==== - -[WARNING] -==== -The mapping of enum to enum via the `@Mapping` annotation is *DEPRECATED*. It will be removed from future versions of MapStruct. Please adapt existing enum mapping methods to make use of `@ValueMapping` instead. -==== - === Mapping enum-to-String or String-to-enum MapStruct supports enum to a String mapping along the same lines as is described in <>. There are similarities and differences: From 38ec5c53350905fb8902935306b42fd1845c40c0 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 20:59:31 +0000 Subject: [PATCH 244/363] Releasing version 1.6.0 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0bdec734cb..3848c6f465 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 160f963adf..bc88fdbb71 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index ac993c73d6..f002c4dd28 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index afa8e31002..a44578dc47 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 0a62a3ef3c..f82f10d339 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index f888e226ff..4ee4319a23 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index e4109325f7..a47b1fad15 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-08-12T20:59:31Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 90402f0cae..361be72a33 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 12fc615f5f..589e37007e 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0-SNAPSHOT + 1.6.0 ../parent/pom.xml From 96d06984171f56ac6a910c144fd8a30cd42f1e3d Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 21:08:07 +0000 Subject: [PATCH 245/363] Next version 1.7.0-SNAPSHOT --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 3848c6f465..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index bc88fdbb71..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index f002c4dd28..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index a44578dc47..f826421092 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index f82f10d339..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 4ee4319a23..7c91837e79 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index a47b1fad15..94c84fb4d0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-08-12T20:59:31Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 361be72a33..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 589e37007e..ed2df7718b 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.0 + 1.7.0-SNAPSHOT ../parent/pom.xml From b452d7f2c8cc48c384d61b87ad530d2f9c882fa1 Mon Sep 17 00:00:00 2001 From: Stefan Simon <35351956+Hypnagokali@users.noreply.github.com> Date: Sun, 18 Aug 2024 17:46:35 +0200 Subject: [PATCH 246/363] #3652 Inverse Inheritance should be possible for ignore-mappings without source --- NEXT_RELEASE_CHANGELOG.md | 2 ++ .../internal/model/source/MappingOptions.java | 5 ++- .../org/mapstruct/ap/test/bugs/_3652/Bar.java | 30 ++++++++++++++++ .../org/mapstruct/ap/test/bugs/_3652/Foo.java | 21 +++++++++++ .../ap/test/bugs/_3652/FooBarConfig.java | 24 +++++++++++++ .../ap/test/bugs/_3652/FooBarMapper.java | 21 +++++++++++ .../ap/test/bugs/_3652/Issue3652Test.java | 35 +++++++++++++++++++ 7 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Bar.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Foo.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Issue3652Test.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index e0f4cd31f0..b4a6b4a75b 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -4,6 +4,8 @@ ### Bugs +* Inverse Inheritance Strategy not working for ignored mappings only with target (#3652) + ### Documentation ### Build diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index 24a94137f1..22f9ccdc2f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -479,13 +479,12 @@ public MappingControl getMappingControl(ElementUtils elementUtils) { } /** - * mapping can only be inversed if the source was not a constant nor an expression nor a nested property - * and the mapping is not a 'target-source-ignore' mapping + * Mapping can only be inversed if the source was not a constant nor an expression * * @return true when the above applies */ public boolean canInverse() { - return constant == null && javaExpression == null && !( isIgnored && sourceName == null ); + return constant == null && javaExpression == null; } public MappingOptions copyForInverseInheritance(SourceMethod templateMethod, diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Bar.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Bar.java new file mode 100644 index 0000000000..3ac8b595ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Bar.java @@ -0,0 +1,30 @@ +/* + * 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._3652; + +public class Bar { + + private int secret; + private int doesNotExistInFoo; + + public int getSecret() { + return secret; + } + + public void setSecret(int secret) { + this.secret = secret; + } + + public int getDoesNotExistInFoo() { + return doesNotExistInFoo; + } + + public void setDoesNotExistInFoo(int doesNotExistInFoo) { + this.doesNotExistInFoo = doesNotExistInFoo; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Foo.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Foo.java new file mode 100644 index 0000000000..02b5b6e8b0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Foo.java @@ -0,0 +1,21 @@ +/* + * 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._3652; + +public class Foo { + + private int secret; + + public int getSecret() { + return secret; + } + + public void setSecret(int secret) { + this.secret = secret; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarConfig.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarConfig.java new file mode 100644 index 0000000000..3cf19dfbfb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarConfig.java @@ -0,0 +1,24 @@ +/* + * 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._3652; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.MappingInheritanceStrategy; + +@MapperConfig(mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_ALL_FROM_CONFIG) +public interface FooBarConfig { + + @Mapping(target = "doesNotExistInFoo", ignore = true) + @Mapping(target = "secret", ignore = true) + Bar toBar(Foo foo); + + @InheritInverseConfiguration(name = "toBar") + Foo toFoo(Bar bar); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarMapper.java new file mode 100644 index 0000000000..d58be74f1e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarMapper.java @@ -0,0 +1,21 @@ +/* + * 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._3652; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(config = FooBarConfig.class) +public interface FooBarMapper { + + FooBarMapper INSTANCE = Mappers.getMapper( FooBarMapper.class ); + + Bar toBar(Foo foo); + + Foo toFoo(Bar bar); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Issue3652Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Issue3652Test.java new file mode 100644 index 0000000000..aa8a64ada8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Issue3652Test.java @@ -0,0 +1,35 @@ +/* + * 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._3652; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3652") +public class Issue3652Test { + + @WithClasses({ + Bar.class, + Foo.class, + FooBarConfig.class, + FooBarMapper.class, + }) + @ProcessorTest + void ignoreMappingsWithoutSourceShouldBeInvertible() { + Bar bar = new Bar(); + bar.setSecret( 123 ); + bar.setDoesNotExistInFoo( 6 ); + + Foo foo = FooBarMapper.INSTANCE.toFoo( bar ); + + assertThat( foo.getSecret() ).isEqualTo( 0 ); + } + +} From 60cd0a442026a3385385dbfb0fc8cd43289992fa Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 24 Aug 2024 11:27:52 +0200 Subject: [PATCH 247/363] #3670 Fix regression when using `InheritInverseConfiguration` with nested target properties and reversing `target = "."` --- NEXT_RELEASE_CHANGELOG.md | 1 + .../NestedTargetPropertyMappingHolder.java | 8 +- .../ap/test/bugs/_3670/Issue3670Mapper.java | 74 +++++++++++++++++++ .../ap/test/bugs/_3670/Issue3670Test.java | 22 ++++++ 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Test.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index b4a6b4a75b..a3ea4e5026 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -5,6 +5,7 @@ ### Bugs * Inverse Inheritance Strategy not working for ignored mappings only with target (#3652) +* Fix regression when using `InheritInverseConfiguration` with nested target properties and reversing `target = "."` (#3670) ### Documentation diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 20825c2dd9..92da1f4509 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -362,7 +362,13 @@ private GroupedTargetReferences groupByTargetReferences( ) { Map> singleTargetReferences = new LinkedHashMap<>(); for ( MappingReference mapping : mappingReferences.getMappingReferences() ) { TargetReference targetReference = mapping.getTargetReference(); - String property = first( targetReference.getPropertyEntries() ); + List propertyEntries = targetReference.getPropertyEntries(); + if ( propertyEntries.isEmpty() ) { + // This can happen if the target property is target = ".", + // this usually happens when doing a reverse mapping + continue; + } + String property = first( propertyEntries ); MappingReference newMapping = mapping.popTargetReference(); if ( newMapping != null ) { // group properties on current name. diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Mapper.java new file mode 100644 index 0000000000..a014a7d52d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Mapper.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._3670; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3670Mapper { + + @Mapping(target = "name", source = ".", qualifiedByName = "nestedName") + Target map(Source source); + + @InheritInverseConfiguration + @Mapping(target = "nested.nestedName", source = "name") + Source map(Target target); + + @Named("nestedName") + default String mapNestedName(Source source) { + if ( source == null ) { + return null; + } + + Nested nested = source.getNested(); + + return nested != null ? nested.getNestedName() : null; + } + + class Target { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class Nested { + private String nestedName; + + public String getNestedName() { + return nestedName; + } + + public void setNestedName(String nestedName) { + this.nestedName = nestedName; + } + } + + class Source { + + private Nested nested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Test.java new file mode 100644 index 0000000000..fc3929b685 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Test.java @@ -0,0 +1,22 @@ +/* + * 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._3670; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3670") +@WithClasses(Issue3670Mapper.class) +class Issue3670Test { + + @ProcessorTest + void shouldCompile() { + } +} From 6c8a2e184bd0254d8fac8bc1e04382da56750a60 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 18 Aug 2024 23:47:35 +0200 Subject: [PATCH 248/363] #3667, #3673 MappingReference should custom MappingOption equality instead of the default only target name based one --- .../model/beanmapping/MappingReference.java | 32 ++++++++- .../ap/test/bugs/_3667/Issue3667Mapper.java | 22 ++++++ .../ap/test/bugs/_3667/Issue3667Test.java | 43 ++++++++++++ .../mapstruct/ap/test/bugs/_3667/Source.java | 51 ++++++++++++++ .../mapstruct/ap/test/bugs/_3667/Target.java | 32 +++++++++ .../mapstruct/ap/test/bugs/_3673/Animal.java | 45 ++++++++++++ .../org/mapstruct/ap/test/bugs/_3673/Cat.java | 19 ++++++ .../mapstruct/ap/test/bugs/_3673/Details.java | 19 ++++++ .../org/mapstruct/ap/test/bugs/_3673/Dog.java | 19 ++++++ .../bugs/_3673/Issue3673ConstantMapper.java | 25 +++++++ .../bugs/_3673/Issue3673ExpressionMapper.java | 25 +++++++ .../ap/test/bugs/_3673/Issue3673Test.java | 68 +++++++++++++++++++ 12 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Animal.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Cat.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Details.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Dog.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ConstantMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ExpressionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java index e2501ea952..d013a77a7a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java @@ -71,7 +71,37 @@ public boolean equals(Object o) { return false; } MappingReference that = (MappingReference) o; - return mapping.equals( that.mapping ); + if ( ".".equals( that.mapping.getTargetName() ) ) { + // target this will never be equal to any other target this or any other. + return false; + } + + if (!Objects.equals( mapping.getTargetName(), that.mapping.getTargetName() ) ) { + return false; + } + + if ( !Objects.equals( mapping.getConstant(), that.mapping.getConstant() ) ) { + return false; + } + + if ( !Objects.equals( mapping.getJavaExpression(), that.mapping.getJavaExpression() ) ) { + return false; + } + + if ( sourceReference == null ) { + return that.sourceReference == null; + } + + if ( that.sourceReference == null ) { + return false; + } + + + if (!Objects.equals( sourceReference.getPropertyEntries(), that.sourceReference.getPropertyEntries() ) ) { + return false; + } + + return true; } @Override diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Mapper.java new file mode 100644 index 0000000000..d4f2dff0af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Mapper.java @@ -0,0 +1,22 @@ +/* + * 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._3667; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3667Mapper { + + Issue3667Mapper INSTANCE = Mappers.getMapper( Issue3667Mapper.class ); + + @Mapping(target = "nested.value", source = "nested.nested1.value") + Target mapFirst(Source source); + + @Mapping(target = "nested.value", source = "nested.nested2.value") + Target mapSecond(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Test.java new file mode 100644 index 0000000000..1e91de00d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Test.java @@ -0,0 +1,43 @@ +/* + * 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._3667; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3667") +@WithClasses({ + Issue3667Mapper.class, + Source.class, + Target.class +}) +class Issue3667Test { + + @ProcessorTest + void shouldCorrectlyMapNestedProperty() { + Source source = new Source( + new Source.Nested( + new Source.NestedNested( "value1" ), + new Source.NestedNested( "value2" ) + ) + ); + + Target target1 = Issue3667Mapper.INSTANCE.mapFirst( source ); + Target target2 = Issue3667Mapper.INSTANCE.mapSecond( source ); + + assertThat( target1 ).isNotNull(); + assertThat( target1.getNested() ).isNotNull(); + assertThat( target1.getNested().getValue() ).isEqualTo( "value1" ); + + assertThat( target2 ).isNotNull(); + assertThat( target2.getNested() ).isNotNull(); + assertThat( target2.getNested().getValue() ).isEqualTo( "value2" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Source.java new file mode 100644 index 0000000000..ede78edf0d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Source.java @@ -0,0 +1,51 @@ +/* + * 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._3667; + +public class Source { + + private final Nested nested; + + public Source(Nested nested) { + this.nested = nested; + } + + public Nested getNested() { + return nested; + } + + public static class Nested { + + private final NestedNested nested1; + private final NestedNested nested2; + + public Nested(NestedNested nested1, NestedNested nested2) { + this.nested1 = nested1; + this.nested2 = nested2; + } + + public NestedNested getNested1() { + return nested1; + } + + public NestedNested getNested2() { + return nested2; + } + } + + public static class NestedNested { + + private final String value; + + public NestedNested(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Target.java new file mode 100644 index 0000000000..e0c8d23296 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Target.java @@ -0,0 +1,32 @@ +/* + * 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._3667; + +public class Target { + + private Nested nested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public static class Nested { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Animal.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Animal.java new file mode 100644 index 0000000000..9fe97e0e79 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Animal.java @@ -0,0 +1,45 @@ +/* + * 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._3673; + +public class Animal { + + private AnimalDetails details; + + public AnimalDetails getDetails() { + return details; + } + + public void setDetails(AnimalDetails details) { + this.details = details; + } + + public enum Type { + CAT, + DOG + } + + public static class AnimalDetails { + private Type type; + private String name; + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Cat.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Cat.java new file mode 100644 index 0000000000..63e5391690 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Cat.java @@ -0,0 +1,19 @@ +/* + * 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._3673; + +public class Cat { + + private final Details details; + + public Cat(Details details) { + this.details = details; + } + + public Details getDetails() { + return details; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Details.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Details.java new file mode 100644 index 0000000000..8526793015 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Details.java @@ -0,0 +1,19 @@ +/* + * 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._3673; + +public class Details { + + private final String name; + + public Details(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Dog.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Dog.java new file mode 100644 index 0000000000..a021a5d579 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Dog.java @@ -0,0 +1,19 @@ +/* + * 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._3673; + +public class Dog { + + private final Details details; + + public Dog(Details details) { + this.details = details; + } + + public Details getDetails() { + return details; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ConstantMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ConstantMapper.java new file mode 100644 index 0000000000..d74a954f9f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ConstantMapper.java @@ -0,0 +1,25 @@ +/* + * 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._3673; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3673ConstantMapper { + + Issue3673ConstantMapper INSTANCE = Mappers.getMapper( Issue3673ConstantMapper.class ); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", constant = "DOG") + Animal map(Dog dog); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", constant = "CAT") + Animal map(Cat cat); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ExpressionMapper.java new file mode 100644 index 0000000000..3aca966807 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ExpressionMapper.java @@ -0,0 +1,25 @@ +/* + * 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._3673; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3673ExpressionMapper { + + Issue3673ExpressionMapper INSTANCE = Mappers.getMapper( Issue3673ExpressionMapper.class ); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", expression = "java(Animal.Type.DOG)") + Animal map(Dog dog); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", expression = "java(Animal.Type.CAT)") + Animal map(Cat cat); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673Test.java new file mode 100644 index 0000000000..2d796b4670 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673Test.java @@ -0,0 +1,68 @@ +/* + * 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._3673; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3673") +@WithClasses({ + Cat.class, + Dog.class, + Details.class, + Animal.class +}) +class Issue3673Test { + + @ProcessorTest + @WithClasses(Issue3673ConstantMapper.class) + void shouldCorrectlyMapNestedPropertyConstant() { + + Animal cat = Issue3673ConstantMapper.INSTANCE.map( + new Cat( new Details( "cat" ) ) + ); + + Animal dog = Issue3673ConstantMapper.INSTANCE.map( + new Dog( new Details( "dog" ) ) + ); + + assertThat( cat ).isNotNull(); + assertThat( cat.getDetails() ).isNotNull(); + assertThat( cat.getDetails().getName() ).isEqualTo( "cat" ); + assertThat( cat.getDetails().getType() ).isEqualTo( Animal.Type.CAT ); + + assertThat( dog ).isNotNull(); + assertThat( dog.getDetails() ).isNotNull(); + assertThat( dog.getDetails().getName() ).isEqualTo( "dog" ); + assertThat( dog.getDetails().getType() ).isEqualTo( Animal.Type.DOG ); + } + + @ProcessorTest + @WithClasses(Issue3673ExpressionMapper.class) + void shouldCorrectlyMapNestedPropertyExpression() { + + Animal cat = Issue3673ExpressionMapper.INSTANCE.map( + new Cat( new Details( "cat" ) ) + ); + + Animal dog = Issue3673ExpressionMapper.INSTANCE.map( + new Dog( new Details( "dog" ) ) + ); + + assertThat( cat ).isNotNull(); + assertThat( cat.getDetails() ).isNotNull(); + assertThat( cat.getDetails().getName() ).isEqualTo( "cat" ); + assertThat( cat.getDetails().getType() ).isEqualTo( Animal.Type.CAT ); + + assertThat( dog ).isNotNull(); + assertThat( dog.getDetails() ).isNotNull(); + assertThat( dog.getDetails().getName() ).isEqualTo( "dog" ); + assertThat( dog.getDetails().getType() ).isEqualTo( Animal.Type.DOG ); + } +} From c89b616f8c213f3210f3b5d5a6f7560d665b145b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 24 Aug 2024 12:22:37 +0200 Subject: [PATCH 249/363] #3668 Do not apply implicit mappings when using `SubclassExhaustiveStrategy#RUNTIME_EXCEPTION` and return type is abstract --- NEXT_RELEASE_CHANGELOG.md | 1 + .../ap/internal/model/BeanMappingMethod.java | 4 ++- .../mapstruct/ap/test/bugs/_3668/Child.java | 23 +++++++++++++ .../ap/test/bugs/_3668/ChildDto.java | 23 +++++++++++++ .../ap/test/bugs/_3668/ChildMapper.java | 31 +++++++++++++++++ .../ap/test/bugs/_3668/Issue3668Test.java | 30 +++++++++++++++++ .../mapstruct/ap/test/bugs/_3668/Parent.java | 33 +++++++++++++++++++ .../ap/test/bugs/_3668/ParentDto.java | 33 +++++++++++++++++++ .../ap/test/bugs/_3668/ParentMapper.java | 31 +++++++++++++++++ 9 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Child.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Issue3668Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Parent.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentMapper.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index a3ea4e5026..438f4e3a6f 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -5,6 +5,7 @@ ### Bugs * Inverse Inheritance Strategy not working for ignored mappings only with target (#3652) +* Inconsistent ambiguous mapping method error when using `SubclassMapping`: generic vs raw types (#3668) * Fix regression when using `InheritInverseConfiguration` with nested target properties and reversing `target = "."` (#3670) ### Documentation 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 6535b07012..1bec5038c4 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 @@ -295,7 +295,9 @@ else if ( !method.isUpdateMethod() ) { } } - boolean applyImplicitMappings = !mappingReferences.isRestrictToDefinedMappings(); + // If defined mappings should not be handled then we should not apply implicit mappings + boolean applyImplicitMappings = + shouldHandledDefinedMappings && !mappingReferences.isRestrictToDefinedMappings(); if ( applyImplicitMappings ) { applyImplicitMappings = beanMapping == null || !beanMapping.isignoreByDefault(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Child.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Child.java new file mode 100644 index 0000000000..3c10c4d470 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Child.java @@ -0,0 +1,23 @@ +/* + * 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._3668; + +public abstract class Child { + + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public static class ChildA extends Child { } + + public static class ChildB extends Child { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildDto.java new file mode 100644 index 0000000000..458cc57a9a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildDto.java @@ -0,0 +1,23 @@ +/* + * 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._3668; + +public abstract class ChildDto { + + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public static class ChildDtoA extends ChildDto { } + + public static class ChildDtoB extends ChildDto { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildMapper.java new file mode 100644 index 0000000000..c381a99fcd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildMapper.java @@ -0,0 +1,31 @@ +/* + * 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._3668; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; + +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface ChildMapper { + + @SubclassMapping(target = Child.ChildA.class, source = ChildDto.ChildDtoA.class) + @SubclassMapping(target = Child.ChildB.class, source = ChildDto.ChildDtoB.class) + Child toEntity(ChildDto childDto); + + @SubclassMapping(target = ChildDto.ChildDtoA.class, source = Child.ChildA.class) + @SubclassMapping(target = ChildDto.ChildDtoB.class, source = Child.ChildB.class) + ChildDto toDto(Child child); + + Child.ChildA toEntity(ChildDto.ChildDtoA childDto); + + ChildDto.ChildDtoA toDto(Child.ChildA child); + + Child.ChildB toEntity(ChildDto.ChildDtoB childDto); + + ChildDto.ChildDtoB toDto(Child.ChildB child); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Issue3668Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Issue3668Test.java new file mode 100644 index 0000000000..a35ac70dce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Issue3668Test.java @@ -0,0 +1,30 @@ +/* + * 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._3668; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3668") +@WithClasses({ + Child.class, + ChildDto.class, + ChildMapper.class, + Parent.class, + ParentDto.class, + ParentMapper.class, +}) +class Issue3668Test { + + @ProcessorTest + void shouldCompile() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Parent.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Parent.java new file mode 100644 index 0000000000..900a7fa68b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Parent.java @@ -0,0 +1,33 @@ +/* + * 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._3668; + +public abstract class Parent { + + private Long id; + + private T child; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public T getChild() { + return child; + } + + public void setChild(T child) { + this.child = child; + } + + public static class ParentA extends Parent { } + + public static class ParentB extends Parent { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentDto.java new file mode 100644 index 0000000000..f4736ceef0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentDto.java @@ -0,0 +1,33 @@ +/* + * 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._3668; + +public abstract class ParentDto { + + private Long id; + + private T child; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public T getChild() { + return child; + } + + public void setChild(T child) { + this.child = child; + } + + public static class ParentDtoA extends ParentDto { } + + public static class ParentDtoB extends ParentDto { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentMapper.java new file mode 100644 index 0000000000..484ddbc930 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentMapper.java @@ -0,0 +1,31 @@ +/* + * 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._3668; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; + +@Mapper(uses = { ChildMapper.class }, subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface ParentMapper { + + @SubclassMapping(target = Parent.ParentA.class, source = ParentDto.ParentDtoA.class) + @SubclassMapping(target = Parent.ParentB.class, source = ParentDto.ParentDtoB.class) + Parent toEntity(ParentDto parentDto); + + @SubclassMapping(target = ParentDto.ParentDtoA.class, source = Parent.ParentA.class) + @SubclassMapping(target = ParentDto.ParentDtoB.class, source = Parent.ParentB.class) + ParentDto toDto(Parent parent); + + Parent.ParentA toEntity(ParentDto.ParentDtoA parentDto); + + ParentDto.ParentDtoA toDto(Parent.ParentA parent); + + Parent.ParentB toEntity(ParentDto.ParentDtoB parentDto); + + ParentDto.ParentDtoB toDto(Parent.ParentB parent); + +} From 58dcb9d81387963caa2f74a8c4b836afaef471b9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 28 Aug 2024 11:56:21 +0200 Subject: [PATCH 250/363] Update latest version and remove some obsolete badges --- readme.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/readme.md b/readme.md index 21d820dc58..6e5debbb78 100644 --- a/readme.md +++ b/readme.md @@ -1,14 +1,11 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.5.5.Final-blue.svg)](https://search.maven.org/search?q=g:org.mapstruct%20AND%20v:1.*.Final) -[![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://search.maven.org/search?q=g:org.mapstruct) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.6.0-blue.svg)](https://central.sonatype.com/search?q=g:org.mapstruct%20v:1.6.0) +[![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://central.sonatype.com/search?q=g:org.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/main/LICENSE.txt) [![Build Status](https://github.com/mapstruct/mapstruct/workflows/CI/badge.svg?branch=main)](https://github.com/mapstruct/mapstruct/actions?query=branch%3Amain+workflow%3ACI) [![Coverage Status](https://img.shields.io/codecov/c/github/mapstruct/mapstruct.svg)](https://codecov.io/gh/mapstruct/mapstruct/tree/main) -[![Gitter](https://img.shields.io/gitter/room/mapstruct/mapstruct.svg)](https://gitter.im/mapstruct/mapstruct-users) -[![Code Quality: Java](https://img.shields.io/lgtm/grade/java/g/mapstruct/mapstruct.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mapstruct/mapstruct/context:java) -[![Total Alerts](https://img.shields.io/lgtm/alerts/g/mapstruct/mapstruct.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mapstruct/mapstruct/alerts) * [What is MapStruct?](#what-is-mapstruct) * [Requirements](#requirements) @@ -68,7 +65,7 @@ For Maven-based projects, add the following to your POM file in order to use Map ```xml ... - 1.5.5.Final + 1.6.0 ... @@ -84,10 +81,10 @@ For Maven-based projects, add the following to your POM file in order to use Map org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.13.0 - 1.8 - 1.8 + 17 + 1<7/target> org.mapstruct @@ -114,10 +111,10 @@ plugins { dependencies { ... - implementation 'org.mapstruct:mapstruct:1.5.5.Final' + implementation 'org.mapstruct:mapstruct:1.6.0' - annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' - testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' // if you are using mapstruct in test code + annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.0' + testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.0' // if you are using mapstruct in test code } ... ``` From c6010c917a6ee239c5c0d949b7460a6a7451df1f Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sat, 31 Aug 2024 16:31:51 +0200 Subject: [PATCH 251/363] Fix typo in readme Maven plugin config --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 6e5debbb78..c0f798af83 100644 --- a/readme.md +++ b/readme.md @@ -84,7 +84,7 @@ For Maven-based projects, add the following to your POM file in order to use Map 3.13.0 17 - 1<7/target> + 17 org.mapstruct From 1e89d7497b1327c91cf782b80cb484adfdf87192 Mon Sep 17 00:00:00 2001 From: Obolrom <65775868+Obolrom@users.noreply.github.com> Date: Mon, 2 Sep 2024 09:44:17 +0300 Subject: [PATCH 252/363] Fix method name typo (#3622) --- .../org/mapstruct/ap/internal/model/BeanMappingMethod.java | 4 ++-- .../ap/internal/model/source/BeanMappingOptions.java | 2 +- .../ap/internal/processor/MapperCreationProcessor.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) 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 1bec5038c4..4ccf2bb49a 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 @@ -299,7 +299,7 @@ else if ( !method.isUpdateMethod() ) { boolean applyImplicitMappings = shouldHandledDefinedMappings && !mappingReferences.isRestrictToDefinedMappings(); if ( applyImplicitMappings ) { - applyImplicitMappings = beanMapping == null || !beanMapping.isignoreByDefault(); + applyImplicitMappings = beanMapping == null || !beanMapping.isIgnoredByDefault(); } if ( applyImplicitMappings ) { @@ -1699,7 +1699,7 @@ private ReportingPolicyGem getUnmappedTargetPolicy() { return ReportingPolicyGem.IGNORE; } // If we have ignoreByDefault = true, unprocessed target properties are not an issue. - if ( method.getOptions().getBeanMapping().isignoreByDefault() ) { + if ( method.getOptions().getBeanMapping().isIgnoredByDefault() ) { return ReportingPolicyGem.IGNORE; } if ( method.getOptions().getBeanMapping() != null ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index bc19860bb5..ac27dfff00 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -223,7 +223,7 @@ public SelectionParameters getSelectionParameters() { return selectionParameters; } - public boolean isignoreByDefault() { + public boolean isIgnoredByDefault() { return Optional.ofNullable( beanMapping ).map( BeanMappingGem::ignoreByDefault ) .map( GemValue::get ) .orElse( false ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 24dd528f6b..5fc1b07823 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -565,7 +565,7 @@ else if ( applicableReversePrototypeMethods.size() > 1 ) { } // @BeanMapping( ignoreByDefault = true ) - if ( mappingOptions.getBeanMapping() != null && mappingOptions.getBeanMapping().isignoreByDefault() ) { + if ( mappingOptions.getBeanMapping() != null && mappingOptions.getBeanMapping().isIgnoredByDefault() ) { mappingOptions.applyIgnoreAll( method, typeFactory, mappingContext.getMessager() ); } From 23f4802374ae8e4703e8f127d85e332bf2e682fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B8=B0=EC=84=9C?= <81108344+rlarltj@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:05:01 +0900 Subject: [PATCH 253/363] Fix method name typo (#3691) --- .../java/org/mapstruct/ap/internal/util/NativeTypesTest.java | 4 ++-- .../mapstruct/ap/test/accessibility/AccessibilityTest.java | 2 +- .../ap/test/accessibility/DefaultSourceTargetMapperIfc.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java b/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java index 9c68a1de45..cd96592839 100644 --- a/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java @@ -122,7 +122,7 @@ public void testIntegerLiteralFromJLS() { .isNotNull(); // most negative int: dec / octal / int / binary - // NOTE parseInt should be changed to parseUnsignedInt in Java, than the - sign can disssapear (java8) + // NOTE parseInt should be changed to parseUnsignedInt in Java, than the - sign can dissapear (java8) // and the function will be true to what the compiler shows. assertThat( getLiteral( int.class.getCanonicalName(), "-2147483648" ) ).isNotNull(); assertThat( getLiteral( int.class.getCanonicalName(), "0x8000_0000" ) ).isNotNull(); @@ -177,7 +177,7 @@ public void testIntegerLiteralFromJLS() { * The following example shows other ways you can use the underscore in numeric literals: */ @Test - public void testFloatingPoingLiteralFromJLS() { + public void testFloatingPointLiteralFromJLS() { // The largest positive finite literal of type float is 3.4028235e38f. assertThat( getLiteral( float.class.getCanonicalName(), "3.4028235e38f" ) ).isNotNull(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java index d338cadedd..bab9ca9b71 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java @@ -41,7 +41,7 @@ public void testGeneratedModifiersFromInterfaceAreCorrect() throws Exception { assertTrue( isDefault( defaultFromIfc.getModifiers() ) ); - assertTrue( isPublic( modifiersFor( defaultFromIfc, "implicitlyPublicSoureToTarget" ) ) ); + assertTrue( isPublic( modifiersFor( defaultFromIfc, "implicitlyPublicSourceToTarget" ) ) ); } private static Class loadForMapper(Class mapper) throws ClassNotFoundException { diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java index 84e9aebbbc..027d60b9ec 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java @@ -12,5 +12,5 @@ */ @Mapper interface DefaultSourceTargetMapperIfc { - Target implicitlyPublicSoureToTarget(Source source); + Target implicitlyPublicSourceToTarget(Source source); } From 4d9894ba25ba4e17c76211409f951f4cce956b3e Mon Sep 17 00:00:00 2001 From: Obolrom <65775868+Obolrom@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:26:48 +0300 Subject: [PATCH 254/363] #3113 Use LinkedHashSet, LinkedHashSet new factory methods for java >= 19 --- .../model/common/ImplementationType.java | 25 +- .../ap/internal/model/common/TypeFactory.java | 26 +- .../DefaultModelElementProcessorContext.java | 3 +- .../processor/DefaultVersionInformation.java | 7 + .../internal/version/VersionInformation.java | 2 + .../ap/internal/model/IterableCreation.ftl | 12 +- .../testutil/runner/CompilationRequest.java | 4 + .../ap/testutil/runner/GeneratedSource.java | 21 +- .../test/bugs/_1453/Issue1453MapperImpl.java | 139 ++++++++++ .../bugs/_3591/ContainerBeanMapperImpl.java | 85 ++++++ .../DomainDtoWithNcvsAlwaysMapperImpl.java | 214 +++++++++++++++ .../DomainDtoWithNvmsDefaultMapperImpl.java | 245 +++++++++++++++++ .../_913/DomainDtoWithNvmsNullMapperImpl.java | 248 +++++++++++++++++ .../DomainDtoWithPresenceCheckMapperImpl.java | 214 +++++++++++++++ .../SourceTargetMapperImpl.java | 259 ++++++++++++++++++ .../updatemethods/CompanyMapper1Impl.java | 120 ++++++++ 16 files changed, 1605 insertions(+), 19 deletions(-) create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_1453/Issue1453MapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/updatemethods/CompanyMapper1Impl.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ImplementationType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ImplementationType.java index 45b73c492f..af8c7ecfb3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ImplementationType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ImplementationType.java @@ -16,23 +16,34 @@ public class ImplementationType { private final Type type; private final boolean initialCapacityConstructor; private final boolean loadFactorAdjustment; + private final String factoryMethodName; - private ImplementationType(Type type, boolean initialCapacityConstructor, boolean loadFactorAdjustment) { + private ImplementationType( + Type type, + boolean initialCapacityConstructor, + boolean loadFactorAdjustment, + String factoryMethodName + ) { this.type = type; this.initialCapacityConstructor = initialCapacityConstructor; this.loadFactorAdjustment = loadFactorAdjustment; + this.factoryMethodName = factoryMethodName; } public static ImplementationType withDefaultConstructor(Type type) { - return new ImplementationType( type, false, false ); + return new ImplementationType( type, false, false, null ); } public static ImplementationType withInitialCapacity(Type type) { - return new ImplementationType( type, true, false ); + return new ImplementationType( type, true, false, null ); } public static ImplementationType withLoadFactorAdjustment(Type type) { - return new ImplementationType( type, true, true ); + return new ImplementationType( type, true, true, null ); + } + + public static ImplementationType withFactoryMethod(Type type, String factoryMethodName) { + return new ImplementationType( type, true, false, factoryMethodName ); } /** @@ -44,7 +55,7 @@ public static ImplementationType withLoadFactorAdjustment(Type type) { * @return a new implementation type with the given {@code type} */ public ImplementationType createNew(Type type) { - return new ImplementationType( type, initialCapacityConstructor, loadFactorAdjustment ); + return new ImplementationType( type, initialCapacityConstructor, loadFactorAdjustment, factoryMethodName ); } /** @@ -71,4 +82,8 @@ public boolean hasInitialCapacityConstructor() { public boolean isLoadFactorAdjustment() { return loadFactorAdjustment; } + + public String getFactoryMethodName() { + return factoryMethodName; + } } 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 f28e0c687e..d65070426b 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 @@ -38,12 +38,11 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; -import org.mapstruct.ap.internal.util.ElementUtils; -import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.Extractor; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.JavaStreamConstants; @@ -51,13 +50,16 @@ import org.mapstruct.ap.internal.util.NativeTypes; import org.mapstruct.ap.internal.util.RoundContext; import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.version.VersionInformation; import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; import org.mapstruct.ap.spi.BuilderInfo; import org.mapstruct.ap.spi.MoreThanOneBuilderCreationMethodException; import org.mapstruct.ap.spi.TypeHierarchyErroneousException; import static org.mapstruct.ap.internal.model.common.ImplementationType.withDefaultConstructor; +import static org.mapstruct.ap.internal.model.common.ImplementationType.withFactoryMethod; import static org.mapstruct.ap.internal.model.common.ImplementationType.withInitialCapacity; import static org.mapstruct.ap.internal.model.common.ImplementationType.withLoadFactorAdjustment; @@ -82,6 +84,8 @@ public class TypeFactory { sb.append( ')' ); return sb.toString(); }; + private static final String LINKED_HASH_SET_FACTORY_METHOD_NAME = "newLinkedHashSet"; + private static final String LINKED_HASH_MAP_FACTORY_METHOD_NAME = "newLinkedHashMap"; private final ElementUtils elementUtils; private final TypeUtils typeUtils; @@ -100,7 +104,8 @@ public class TypeFactory { private final boolean loggingVerbose; public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMessager messager, - RoundContext roundContext, Map notToBeImportedTypes, boolean loggingVerbose) { + RoundContext roundContext, Map notToBeImportedTypes, boolean loggingVerbose, + VersionInformation versionInformation) { this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.messager = messager; @@ -118,11 +123,22 @@ public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMes implementationTypes.put( Collection.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); implementationTypes.put( List.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); - implementationTypes.put( Set.class.getName(), withLoadFactorAdjustment( getType( LinkedHashSet.class ) ) ); + boolean sourceVersionAtLeast19 = versionInformation.isSourceVersionAtLeast19(); + implementationTypes.put( + Set.class.getName(), + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashSet.class ) ) + ); implementationTypes.put( SortedSet.class.getName(), withDefaultConstructor( getType( TreeSet.class ) ) ); implementationTypes.put( NavigableSet.class.getName(), withDefaultConstructor( getType( TreeSet.class ) ) ); - implementationTypes.put( Map.class.getName(), withLoadFactorAdjustment( getType( LinkedHashMap.class ) ) ); + implementationTypes.put( + Map.class.getName(), + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashMap.class ) ) + ); implementationTypes.put( SortedMap.class.getName(), withDefaultConstructor( getType( TreeMap.class ) ) ); implementationTypes.put( NavigableMap.class.getName(), withDefaultConstructor( getType( TreeMap.class ) ) ); implementationTypes.put( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java index 51ea5dd786..8ac1cc067e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java @@ -62,7 +62,8 @@ public DefaultModelElementProcessorContext(ProcessingEnvironment processingEnvir messager, roundContext, notToBeImported, - options.isVerbose() + options.isVerbose(), + versionInformation ); this.options = options; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java index c4baf1bf5f..055bfe6095 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java @@ -41,6 +41,7 @@ public class DefaultVersionInformation implements VersionInformation { private final String runtimeVendor; private final String compiler; private final boolean sourceVersionAtLeast9; + private final boolean sourceVersionAtLeast19; private final boolean eclipseJDT; private final boolean javac; @@ -53,6 +54,7 @@ public class DefaultVersionInformation implements VersionInformation { this.javac = compiler.startsWith( COMPILER_NAME_JAVAC ); // If the difference between the source version and RELEASE_6 is more that 2 than we are at least on 9 this.sourceVersionAtLeast9 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 2; + this.sourceVersionAtLeast19 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 12; } @Override @@ -80,6 +82,11 @@ public boolean isSourceVersionAtLeast9() { return sourceVersionAtLeast9; } + @Override + public boolean isSourceVersionAtLeast19() { + return sourceVersionAtLeast19; + } + @Override public boolean isEclipseJDTCompiler() { return eclipseJDT; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java index 94e3520ad0..5e1972fcae 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java @@ -21,6 +21,8 @@ public interface VersionInformation { boolean isSourceVersionAtLeast9(); + boolean isSourceVersionAtLeast19(); + boolean isEclipseJDTCompiler(); boolean isJavacCompiler(); diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl index d49397a985..d083bd113d 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl @@ -11,13 +11,15 @@ <@includeModel object=factoryMethod targetType=resultType/> <#elseif enumSet> EnumSet.noneOf( <@includeModel object=enumSetElementType raw=true/>.class ) - <#else> - new - <#if resultType.implementationType??> - <@includeModel object=resultType.implementationType/><#if ext.useSizeIfPossible?? && ext.useSizeIfPossible && canUseSize>( <@sizeForCreation /> )<#else>() + <#elseif resultType.implementation??> + <#if resultType.implementation.factoryMethodName?? && ext.useSizeIfPossible?? && ext.useSizeIfPossible && canUseSize> + <@includeModel object=resultType.implementationType raw=true />.${resultType.implementation.factoryMethodName}( <@sizeForCreation /> ) <#else> - <@includeModel object=resultType/>() + new <@includeModel object=resultType.implementationType/><#if ext.useSizeIfPossible?? && ext.useSizeIfPossible && canUseSize>( <@sizeForCreation /> )<#else>() + <#else> + new <@includeModel object=resultType/>() + <#macro sizeForCreation> <@compress single_line=true> diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java index 60e9a30072..9f1b78caa0 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java @@ -76,4 +76,8 @@ public Map, Class> getServices() { public Collection getTestDependencies() { return testDependencies; } + + public Compiler getCompiler() { + return compiler; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java index c0993ea098..18a53b4971 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java @@ -44,12 +44,15 @@ public class GeneratedSource implements BeforeTestExecutionCallback, AfterTestEx */ private ThreadLocal sourceOutputDir = new ThreadLocal<>(); + private Compiler compiler; + private List> fixturesFor = new ArrayList<>(); @Override public void beforeTestExecution(ExtensionContext context) throws Exception { CompilationRequest compilationRequest = context.getStore( NAMESPACE ) .get( context.getUniqueId() + "-compilationRequest", CompilationRequest.class ); + this.compiler = compilationRequest.getCompiler(); setSourceOutputDir( context.getStore( NAMESPACE ) .get( compilationRequest, CompilationCache.class ) .getLastSourceOutputDir() ); @@ -118,13 +121,13 @@ public JavaFileAssert forJavaFile(String path) { private void handleFixtureComparison() throws UnsupportedEncodingException { for ( Class fixture : fixturesFor ) { - String expectedFixture = FIXTURES_ROOT + getMapperName( fixture ); - URL expectedFile = getClass().getClassLoader().getResource( expectedFixture ); + String fixtureName = getMapperName( fixture ); + URL expectedFile = getExpectedResource( fixtureName ); if ( expectedFile == null ) { fail( String.format( "No reference file could be found for Mapper %s. You should create a file %s", fixture.getName(), - expectedFixture + FIXTURES_ROOT + fixtureName ) ); } else { @@ -135,4 +138,16 @@ private void handleFixtureComparison() throws UnsupportedEncodingException { } } + + private URL getExpectedResource( String fixtureName ) { + ClassLoader classLoader = getClass().getClassLoader(); + for ( int version = Runtime.version().feature(); version >= 11 && compiler != Compiler.ECLIPSE; version-- ) { + URL resource = classLoader.getResource( FIXTURES_ROOT + "/" + version + "/" + fixtureName ); + if ( resource != null ) { + return resource; + } + } + + return classLoader.getResource( FIXTURES_ROOT + fixtureName ); + } } diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_1453/Issue1453MapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_1453/Issue1453MapperImpl.java new file mode 100644 index 0000000000..78c3bea0d7 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_1453/Issue1453MapperImpl.java @@ -0,0 +1,139 @@ +/* + * 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._1453; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-18T14:48:39+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class Issue1453MapperImpl implements Issue1453Mapper { + + @Override + public AuctionDto map(Auction auction) { + if ( auction == null ) { + return null; + } + + AuctionDto auctionDto = new AuctionDto(); + + auctionDto.setPayments( paymentListToPaymentDtoList( auction.getPayments() ) ); + auctionDto.setOtherPayments( paymentListToPaymentDtoList( auction.getOtherPayments() ) ); + auctionDto.setMapPayments( paymentPaymentMapToPaymentDtoPaymentDtoMap( auction.getMapPayments() ) ); + auctionDto.setMapSuperPayments( paymentPaymentMapToPaymentDtoPaymentDtoMap( auction.getMapSuperPayments() ) ); + + return auctionDto; + } + + @Override + public List mapExtend(List auctions) { + if ( auctions == null ) { + return null; + } + + List list = new ArrayList( auctions.size() ); + for ( Auction auction : auctions ) { + list.add( map( auction ) ); + } + + return list; + } + + @Override + public List mapSuper(List auctions) { + if ( auctions == null ) { + return null; + } + + List list = new ArrayList( auctions.size() ); + for ( Auction auction : auctions ) { + list.add( map( auction ) ); + } + + return list; + } + + @Override + public Map mapExtend(Map auctions) { + if ( auctions == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( auctions.size() ); + + for ( java.util.Map.Entry entry : auctions.entrySet() ) { + AuctionDto key = map( entry.getKey() ); + AuctionDto value = map( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public Map mapSuper(Map auctions) { + if ( auctions == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( auctions.size() ); + + for ( java.util.Map.Entry entry : auctions.entrySet() ) { + AuctionDto key = map( entry.getKey() ); + AuctionDto value = map( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + protected PaymentDto paymentToPaymentDto(Payment payment) { + if ( payment == null ) { + return null; + } + + PaymentDto paymentDto = new PaymentDto(); + + paymentDto.setPrice( payment.getPrice() ); + + return paymentDto; + } + + protected List paymentListToPaymentDtoList(List list) { + if ( list == null ) { + return null; + } + + List list1 = new ArrayList( list.size() ); + for ( Payment payment : list ) { + list1.add( paymentToPaymentDto( payment ) ); + } + + return list1; + } + + protected Map paymentPaymentMapToPaymentDtoPaymentDtoMap(Map map) { + if ( map == null ) { + return null; + } + + Map map1 = LinkedHashMap.newLinkedHashMap( map.size() ); + + for ( java.util.Map.Entry entry : map.entrySet() ) { + PaymentDto key = paymentToPaymentDto( entry.getKey() ); + PaymentDto value = paymentToPaymentDto( entry.getValue() ); + map1.put( key, value ); + } + + return map1; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.java new file mode 100644 index 0000000000..af3d293f7d --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapperImpl.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._3591; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-05-25T14:23:23+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class ContainerBeanMapperImpl implements ContainerBeanMapper { + + @Override + public ContainerBeanDto mapWithMapMapping(ContainerBean containerBean, ContainerBeanDto containerBeanDto) { + if ( containerBean == null ) { + return containerBeanDto; + } + + if ( containerBeanDto.getBeanMap() != null ) { + Map map = stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ); + if ( map != null ) { + containerBeanDto.getBeanMap().clear(); + containerBeanDto.getBeanMap().putAll( map ); + } + else { + containerBeanDto.setBeanMap( null ); + } + } + else { + Map map = stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ); + if ( map != null ) { + containerBeanDto.setBeanMap( map ); + } + } + containerBeanDto.setBeanStream( containerBeanStreamToContainerBeanDtoStream( containerBean.getBeanStream() ) ); + containerBeanDto.setValue( containerBean.getValue() ); + + return containerBeanDto; + } + + protected Stream containerBeanStreamToContainerBeanDtoStream(Stream stream) { + if ( stream == null ) { + return null; + } + + return stream.map( containerBean -> containerBeanToContainerBeanDto( containerBean ) ); + } + + protected ContainerBeanDto containerBeanToContainerBeanDto(ContainerBean containerBean) { + if ( containerBean == null ) { + return null; + } + + ContainerBeanDto containerBeanDto = new ContainerBeanDto(); + + containerBeanDto.setBeanMap( stringContainerBeanMapToStringContainerBeanDtoMap( containerBean.getBeanMap() ) ); + containerBeanDto.setBeanStream( containerBeanStreamToContainerBeanDtoStream( containerBean.getBeanStream() ) ); + containerBeanDto.setValue( containerBean.getValue() ); + + return containerBeanDto; + } + + protected Map stringContainerBeanMapToStringContainerBeanDtoMap(Map map) { + if ( map == null ) { + return null; + } + + Map map1 = LinkedHashMap.newLinkedHashMap( map.size() ); + + for ( java.util.Map.Entry entry : map.entrySet() ) { + String key = entry.getKey(); + ContainerBeanDto value = containerBeanToContainerBeanDto( entry.getValue() ); + map1.put( key, value ); + } + + return map1; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java new file mode 100644 index 0000000000..d5cfd24723 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapperImpl.java @@ -0,0 +1,214 @@ +/* + * 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._913; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-19T11:20:01+0300", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Amazon.com Inc.)" +) +public class DomainDtoWithNcvsAlwaysMapperImpl implements DomainDtoWithNcvsAlwaysMapper { + + private final Helper helper = new Helper(); + + @Override + public Domain create(DtoWithPresenceCheck source) { + if ( source == null ) { + return null; + } + + Domain domain = createNullDomain(); + + if ( source.hasStrings() ) { + List list = source.getStrings(); + domain.setStrings( new LinkedHashSet( list ) ); + } + if ( source.hasStrings() ) { + domain.setLongs( stringListToLongSet( source.getStrings() ) ); + } + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + domain.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + if ( source.hasStringsInitialized() ) { + domain.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + domain.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + domain.setStringsWithDefault( helper.toList( "3" ) ); + } + + return domain; + } + + @Override + public void update(DtoWithPresenceCheck source, Domain target) { + if ( source == null ) { + return; + } + + if ( target.getStrings() != null ) { + if ( source.hasStrings() ) { + target.getStrings().clear(); + target.getStrings().addAll( source.getStrings() ); + } + } + else { + if ( source.hasStrings() ) { + List list = source.getStrings(); + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + if ( source.hasStrings() ) { + target.getLongs().clear(); + target.getLongs().addAll( stringListToLongSet( source.getStrings() ) ); + } + } + else { + if ( source.hasStrings() ) { + target.setLongs( stringListToLongSet( source.getStrings() ) ); + } + } + if ( target.getStringsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( source.getStringsInitialized() ); + } + } + else { + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + else { + if ( source.hasStringsInitialized() ) { + target.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + if ( target.getStringsWithDefault() != null ) { + if ( source.hasStringsWithDefault() ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( source.getStringsWithDefault() ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + } + + @Override + public Domain updateWithReturn(DtoWithPresenceCheck source, Domain target) { + if ( source == null ) { + return target; + } + + if ( target.getStrings() != null ) { + if ( source.hasStrings() ) { + target.getStrings().clear(); + target.getStrings().addAll( source.getStrings() ); + } + } + else { + if ( source.hasStrings() ) { + List list = source.getStrings(); + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + if ( source.hasStrings() ) { + target.getLongs().clear(); + target.getLongs().addAll( stringListToLongSet( source.getStrings() ) ); + } + } + else { + if ( source.hasStrings() ) { + target.setLongs( stringListToLongSet( source.getStrings() ) ); + } + } + if ( target.getStringsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( source.getStringsInitialized() ); + } + } + else { + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + else { + if ( source.hasStringsInitialized() ) { + target.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + if ( target.getStringsWithDefault() != null ) { + if ( source.hasStringsWithDefault() ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( source.getStringsWithDefault() ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + + return target; + } + + protected Set stringListToLongSet(List list) { + if ( list == null ) { + return null; + } + + Set set = LinkedHashSet.newLinkedHashSet( list.size() ); + for ( String string : list ) { + set.add( Long.parseLong( string ) ); + } + + return set; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java new file mode 100644 index 0000000000..76de94378b --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java @@ -0,0 +1,245 @@ +/* + * 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._913; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-18T14:48:39+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefaultMapper { + + private final Helper helper = new Helper(); + + @Override + public Domain create(Dto source) { + + Domain domain = new Domain(); + + if ( source != null ) { + List list = source.getStrings(); + if ( list != null ) { + domain.setStrings( new LinkedHashSet( list ) ); + } + domain.setLongs( stringListToLongSet( source.getStrings() ) ); + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + domain.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + domain.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + domain.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + domain.setStringsWithDefault( helper.toList( "3" ) ); + } + } + + return domain; + } + + @Override + public void update(Dto source, Domain target) { + + if ( source != null ) { + if ( target.getStrings() != null ) { + List list = source.getStrings(); + if ( list != null ) { + target.getStrings().clear(); + target.getStrings().addAll( list ); + } + else { + target.setStrings( new LinkedHashSet() ); + } + } + else { + List list = source.getStrings(); + if ( list != null ) { + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.getLongs().clear(); + target.getLongs().addAll( set ); + } + else { + target.setLongs( new LinkedHashSet() ); + } + } + else { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.setLongs( set ); + } + } + if ( target.getStringsInitialized() != null ) { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( list1 ); + } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } + } + else { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( set1 ); + } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } + } + else { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.setLongsInitialized( set1 ); + } + } + if ( target.getStringsWithDefault() != null ) { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( list2 ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + } + } + + @Override + public Domain updateWithReturn(Dto source, Domain target) { + + if ( source != null ) { + if ( target.getStrings() != null ) { + List list = source.getStrings(); + if ( list != null ) { + target.getStrings().clear(); + target.getStrings().addAll( list ); + } + else { + target.setStrings( new LinkedHashSet() ); + } + } + else { + List list = source.getStrings(); + if ( list != null ) { + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.getLongs().clear(); + target.getLongs().addAll( set ); + } + else { + target.setLongs( new LinkedHashSet() ); + } + } + else { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.setLongs( set ); + } + } + if ( target.getStringsInitialized() != null ) { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( list1 ); + } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } + } + else { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( set1 ); + } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } + } + else { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.setLongsInitialized( set1 ); + } + } + if ( target.getStringsWithDefault() != null ) { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( list2 ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + } + + return target; + } + + protected Set stringListToLongSet(List list) { + if ( list == null ) { + return new LinkedHashSet(); + } + + Set set = LinkedHashSet.newLinkedHashSet( list.size() ); + for ( String string : list ) { + set.add( Long.parseLong( string ) ); + } + + return set; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java new file mode 100644 index 0000000000..c61c2c68e8 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapperImpl.java @@ -0,0 +1,248 @@ +/* + * 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._913; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-18T14:48:39+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class DomainDtoWithNvmsNullMapperImpl implements DomainDtoWithNvmsNullMapper { + + private final Helper helper = new Helper(); + + @Override + public Domain create(Dto source) { + if ( source == null ) { + return null; + } + + Domain domain = createNullDomain(); + + List list = source.getStrings(); + if ( list != null ) { + domain.setStrings( new LinkedHashSet( list ) ); + } + domain.setLongs( stringListToLongSet( source.getStrings() ) ); + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + domain.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + domain.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + domain.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + domain.setStringsWithDefault( helper.toList( "3" ) ); + } + + return domain; + } + + @Override + public void update(Dto source, Domain target) { + if ( source == null ) { + return; + } + + if ( target.getStrings() != null ) { + List list = source.getStrings(); + if ( list != null ) { + target.getStrings().clear(); + target.getStrings().addAll( list ); + } + else { + target.setStrings( null ); + } + } + else { + List list = source.getStrings(); + if ( list != null ) { + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.getLongs().clear(); + target.getLongs().addAll( set ); + } + else { + target.setLongs( null ); + } + } + else { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.setLongs( set ); + } + } + if ( target.getStringsInitialized() != null ) { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( list1 ); + } + else { + target.setStringsInitialized( null ); + } + } + else { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( set1 ); + } + else { + target.setLongsInitialized( null ); + } + } + else { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.setLongsInitialized( set1 ); + } + } + if ( target.getStringsWithDefault() != null ) { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( list2 ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + } + + @Override + public Domain updateWithReturn(Dto source, Domain target) { + if ( source == null ) { + return target; + } + + if ( target.getStrings() != null ) { + List list = source.getStrings(); + if ( list != null ) { + target.getStrings().clear(); + target.getStrings().addAll( list ); + } + else { + target.setStrings( null ); + } + } + else { + List list = source.getStrings(); + if ( list != null ) { + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.getLongs().clear(); + target.getLongs().addAll( set ); + } + else { + target.setLongs( null ); + } + } + else { + Set set = stringListToLongSet( source.getStrings() ); + if ( set != null ) { + target.setLongs( set ); + } + } + if ( target.getStringsInitialized() != null ) { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( list1 ); + } + else { + target.setStringsInitialized( null ); + } + } + else { + List list1 = source.getStringsInitialized(); + if ( list1 != null ) { + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( set1 ); + } + else { + target.setLongsInitialized( null ); + } + } + else { + Set set1 = stringListToLongSet( source.getStringsInitialized() ); + if ( set1 != null ) { + target.setLongsInitialized( set1 ); + } + } + if ( target.getStringsWithDefault() != null ) { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( list2 ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + List list2 = source.getStringsWithDefault(); + if ( list2 != null ) { + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + + return target; + } + + protected Set stringListToLongSet(List list) { + if ( list == null ) { + return null; + } + + Set set = LinkedHashSet.newLinkedHashSet( list.size() ); + for ( String string : list ) { + set.add( Long.parseLong( string ) ); + } + + return set; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java new file mode 100644 index 0000000000..6d01a91ec5 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapperImpl.java @@ -0,0 +1,214 @@ +/* + * 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._913; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-19T11:03:59+0300", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Amazon.com Inc.)" +) +public class DomainDtoWithPresenceCheckMapperImpl implements DomainDtoWithPresenceCheckMapper { + + private final Helper helper = new Helper(); + + @Override + public Domain create(DtoWithPresenceCheck source) { + if ( source == null ) { + return null; + } + + Domain domain = createNullDomain(); + + if ( source.hasStrings() ) { + List list = source.getStrings(); + domain.setStrings( new LinkedHashSet( list ) ); + } + if ( source.hasStrings() ) { + domain.setLongs( stringListToLongSet( source.getStrings() ) ); + } + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + domain.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + if ( source.hasStringsInitialized() ) { + domain.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + domain.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + domain.setStringsWithDefault( helper.toList( "3" ) ); + } + + return domain; + } + + @Override + public void update(DtoWithPresenceCheck source, Domain target) { + if ( source == null ) { + return; + } + + if ( target.getStrings() != null ) { + if ( source.hasStrings() ) { + target.getStrings().clear(); + target.getStrings().addAll( source.getStrings() ); + } + } + else { + if ( source.hasStrings() ) { + List list = source.getStrings(); + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + if ( source.hasStrings() ) { + target.getLongs().clear(); + target.getLongs().addAll( stringListToLongSet( source.getStrings() ) ); + } + } + else { + if ( source.hasStrings() ) { + target.setLongs( stringListToLongSet( source.getStrings() ) ); + } + } + if ( target.getStringsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( source.getStringsInitialized() ); + } + } + else { + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + else { + if ( source.hasStringsInitialized() ) { + target.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + if ( target.getStringsWithDefault() != null ) { + if ( source.hasStringsWithDefault() ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( source.getStringsWithDefault() ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + } + + @Override + public Domain updateWithReturn(DtoWithPresenceCheck source, Domain target) { + if ( source == null ) { + return target; + } + + if ( target.getStrings() != null ) { + if ( source.hasStrings() ) { + target.getStrings().clear(); + target.getStrings().addAll( source.getStrings() ); + } + } + else { + if ( source.hasStrings() ) { + List list = source.getStrings(); + target.setStrings( new LinkedHashSet( list ) ); + } + } + if ( target.getLongs() != null ) { + if ( source.hasStrings() ) { + target.getLongs().clear(); + target.getLongs().addAll( stringListToLongSet( source.getStrings() ) ); + } + } + else { + if ( source.hasStrings() ) { + target.setLongs( stringListToLongSet( source.getStrings() ) ); + } + } + if ( target.getStringsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getStringsInitialized().clear(); + target.getStringsInitialized().addAll( source.getStringsInitialized() ); + } + } + else { + if ( source.hasStringsInitialized() ) { + List list1 = source.getStringsInitialized(); + target.setStringsInitialized( new LinkedHashSet( list1 ) ); + } + } + if ( target.getLongsInitialized() != null ) { + if ( source.hasStringsInitialized() ) { + target.getLongsInitialized().clear(); + target.getLongsInitialized().addAll( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + else { + if ( source.hasStringsInitialized() ) { + target.setLongsInitialized( stringListToLongSet( source.getStringsInitialized() ) ); + } + } + if ( target.getStringsWithDefault() != null ) { + if ( source.hasStringsWithDefault() ) { + target.getStringsWithDefault().clear(); + target.getStringsWithDefault().addAll( source.getStringsWithDefault() ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + else { + if ( source.hasStringsWithDefault() ) { + List list2 = source.getStringsWithDefault(); + target.setStringsWithDefault( new ArrayList( list2 ) ); + } + else { + target.setStringsWithDefault( helper.toList( "3" ) ); + } + } + + return target; + } + + protected Set stringListToLongSet(List list) { + if ( list == null ) { + return null; + } + + Set set = LinkedHashSet.newLinkedHashSet( list.size() ); + for ( String string : list ) { + set.add( Long.parseLong( string ) ); + } + + return set; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java new file mode 100644 index 0000000000..3f0bee535a --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapperImpl.java @@ -0,0 +1,259 @@ +/* + * 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.collection.defaultimplementation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-18T14:48:39+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class SourceTargetMapperImpl implements SourceTargetMapper { + + @Override + public Target sourceToTarget(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + if ( target.getFooListNoSetter() != null ) { + List list = sourceFoosToTargetFoos( source.getFooList() ); + if ( list != null ) { + target.getFooListNoSetter().addAll( list ); + } + } + + return target; + } + + @Override + public TargetFoo sourceFooToTargetFoo(SourceFoo sourceFoo) { + if ( sourceFoo == null ) { + return null; + } + + TargetFoo targetFoo = new TargetFoo(); + + targetFoo.setName( sourceFoo.getName() ); + + return targetFoo; + } + + @Override + public List sourceFoosToTargetFoos(List foos) { + if ( foos == null ) { + return null; + } + + List list = new ArrayList( foos.size() ); + for ( SourceFoo sourceFoo : foos ) { + list.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return list; + } + + @Override + public Set sourceFoosToTargetFoos(Set foos) { + if ( foos == null ) { + return null; + } + + Set set = LinkedHashSet.newLinkedHashSet( foos.size() ); + for ( SourceFoo sourceFoo : foos ) { + set.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return set; + } + + @Override + public Collection sourceFoosToTargetFoos(Collection foos) { + if ( foos == null ) { + return null; + } + + Collection collection = new ArrayList( foos.size() ); + for ( SourceFoo sourceFoo : foos ) { + collection.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return collection; + } + + @Override + public Iterable sourceFoosToTargetFoos(Iterable foos) { + if ( foos == null ) { + return null; + } + + ArrayList iterable = new ArrayList(); + for ( SourceFoo sourceFoo : foos ) { + iterable.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return iterable; + } + + @Override + public void sourceFoosToTargetFoosUsingTargetParameter(List targetFoos, Iterable sourceFoos) { + if ( sourceFoos == null ) { + return; + } + + targetFoos.clear(); + for ( SourceFoo sourceFoo : sourceFoos ) { + targetFoos.add( sourceFooToTargetFoo( sourceFoo ) ); + } + } + + @Override + public Iterable sourceFoosToTargetFoosUsingTargetParameterAndReturn(Iterable sourceFoos, List targetFoos) { + if ( sourceFoos == null ) { + return targetFoos; + } + + targetFoos.clear(); + for ( SourceFoo sourceFoo : sourceFoos ) { + targetFoos.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return targetFoos; + } + + @Override + public SortedSet sourceFoosToTargetFooSortedSet(Collection foos) { + if ( foos == null ) { + return null; + } + + SortedSet sortedSet = new TreeSet(); + for ( SourceFoo sourceFoo : foos ) { + sortedSet.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return sortedSet; + } + + @Override + public NavigableSet sourceFoosToTargetFooNavigableSet(Collection foos) { + if ( foos == null ) { + return null; + } + + NavigableSet navigableSet = new TreeSet(); + for ( SourceFoo sourceFoo : foos ) { + navigableSet.add( sourceFooToTargetFoo( sourceFoo ) ); + } + + return navigableSet; + } + + @Override + public Map sourceFooMapToTargetFooMap(Map foos) { + if ( foos == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( foos.size() ); + + for ( java.util.Map.Entry entry : foos.entrySet() ) { + String key = String.valueOf( entry.getKey() ); + TargetFoo value = sourceFooToTargetFoo( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public SortedMap sourceFooMapToTargetFooSortedMap(Map foos) { + if ( foos == null ) { + return null; + } + + SortedMap sortedMap = new TreeMap(); + + for ( java.util.Map.Entry entry : foos.entrySet() ) { + String key = String.valueOf( entry.getKey() ); + TargetFoo value = sourceFooToTargetFoo( entry.getValue() ); + sortedMap.put( key, value ); + } + + return sortedMap; + } + + @Override + public NavigableMap sourceFooMapToTargetFooNavigableMap(Map foos) { + if ( foos == null ) { + return null; + } + + NavigableMap navigableMap = new TreeMap(); + + for ( java.util.Map.Entry entry : foos.entrySet() ) { + String key = String.valueOf( entry.getKey() ); + TargetFoo value = sourceFooToTargetFoo( entry.getValue() ); + navigableMap.put( key, value ); + } + + return navigableMap; + } + + @Override + public ConcurrentMap sourceFooMapToTargetFooConcurrentMap(Map foos) { + if ( foos == null ) { + return null; + } + + ConcurrentMap concurrentMap = new ConcurrentHashMap( Math.max( (int) ( foos.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : foos.entrySet() ) { + String key = String.valueOf( entry.getKey() ); + TargetFoo value = sourceFooToTargetFoo( entry.getValue() ); + concurrentMap.put( key, value ); + } + + return concurrentMap; + } + + @Override + public ConcurrentNavigableMap sourceFooMapToTargetFooConcurrentNavigableMap(Map foos) { + if ( foos == null ) { + return null; + } + + ConcurrentNavigableMap concurrentNavigableMap = new ConcurrentSkipListMap(); + + for ( java.util.Map.Entry entry : foos.entrySet() ) { + String key = String.valueOf( entry.getKey() ); + TargetFoo value = sourceFooToTargetFoo( entry.getValue() ); + concurrentNavigableMap.put( key, value ); + } + + return concurrentNavigableMap; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/updatemethods/CompanyMapper1Impl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/updatemethods/CompanyMapper1Impl.java new file mode 100644 index 0000000000..f34495cb28 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/updatemethods/CompanyMapper1Impl.java @@ -0,0 +1,120 @@ +/* + * 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.updatemethods; + +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-06-18T14:48:39+0200", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +public class CompanyMapper1Impl implements CompanyMapper1 { + + private final DepartmentEntityFactory departmentEntityFactory = new DepartmentEntityFactory(); + + @Override + public void toCompanyEntity(UnmappableCompanyDto dto, CompanyEntity entity) { + if ( dto == null ) { + return; + } + + entity.setName( dto.getName() ); + if ( dto.getDepartment() != null ) { + if ( entity.getDepartment() == null ) { + entity.setDepartment( departmentEntityFactory.createDepartmentEntity() ); + } + unmappableDepartmentDtoToDepartmentEntity( dto.getDepartment(), entity.getDepartment() ); + } + else { + entity.setDepartment( null ); + } + } + + @Override + public void toInBetween(UnmappableDepartmentDto dto, DepartmentInBetween entity) { + if ( dto == null ) { + return; + } + + entity.setName( dto.getName() ); + } + + @Override + public void toDepartmentEntity(DepartmentInBetween dto, DepartmentEntity entity) { + if ( dto == null ) { + return; + } + + entity.setName( dto.getName() ); + } + + protected SecretaryEntity secretaryDtoToSecretaryEntity(SecretaryDto secretaryDto) { + if ( secretaryDto == null ) { + return null; + } + + SecretaryEntity secretaryEntity = new SecretaryEntity(); + + secretaryEntity.setName( secretaryDto.getName() ); + + return secretaryEntity; + } + + protected EmployeeEntity employeeDtoToEmployeeEntity(EmployeeDto employeeDto) { + if ( employeeDto == null ) { + return null; + } + + EmployeeEntity employeeEntity = new EmployeeEntity(); + + employeeEntity.setName( employeeDto.getName() ); + + return employeeEntity; + } + + protected Map secretaryDtoEmployeeDtoMapToSecretaryEntityEmployeeEntityMap(Map map) { + if ( map == null ) { + return null; + } + + Map map1 = LinkedHashMap.newLinkedHashMap( map.size() ); + + for ( java.util.Map.Entry entry : map.entrySet() ) { + SecretaryEntity key = secretaryDtoToSecretaryEntity( entry.getKey() ); + EmployeeEntity value = employeeDtoToEmployeeEntity( entry.getValue() ); + map1.put( key, value ); + } + + return map1; + } + + protected void unmappableDepartmentDtoToDepartmentEntity(UnmappableDepartmentDto unmappableDepartmentDto, DepartmentEntity mappingTarget) { + if ( unmappableDepartmentDto == null ) { + return; + } + + mappingTarget.setName( unmappableDepartmentDto.getName() ); + if ( mappingTarget.getSecretaryToEmployee() != null ) { + Map map = secretaryDtoEmployeeDtoMapToSecretaryEntityEmployeeEntityMap( unmappableDepartmentDto.getSecretaryToEmployee() ); + if ( map != null ) { + mappingTarget.getSecretaryToEmployee().clear(); + mappingTarget.getSecretaryToEmployee().putAll( map ); + } + else { + mappingTarget.setSecretaryToEmployee( null ); + } + } + else { + Map map = secretaryDtoEmployeeDtoMapToSecretaryEntityEmployeeEntityMap( unmappableDepartmentDto.getSecretaryToEmployee() ); + if ( map != null ) { + mappingTarget.setSecretaryToEmployee( map ); + } + } + } +} From 5232df2707b2c396f6d8bf6453a9f292684ef0f3 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Sep 2024 15:06:36 +0200 Subject: [PATCH 255/363] Try to stabilise MapMappingTest and CarMapperTest --- .../org/mapstruct/ap/test/collection/map/MapMappingTest.java | 2 ++ .../test/java/org/mapstruct/ap/test/complex/CarMapperTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java index d0dd66d22b..1261525d4f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java @@ -11,6 +11,7 @@ import java.util.HashMap; import java.util.Map; +import org.junitpioneer.jupiter.DefaultTimeZone; import org.mapstruct.ap.test.collection.map.other.ImportedType; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; @@ -26,6 +27,7 @@ */ @WithClasses({ SourceTargetMapper.class, CustomNumberMapper.class, Source.class, Target.class, ImportedType.class }) @IssueKey("44") +@DefaultTimeZone("UTC") public class MapMappingTest { @ProcessorTest diff --git a/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java index 5eaf44765d..a1881a7613 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java @@ -11,6 +11,7 @@ import java.util.GregorianCalendar; import java.util.List; +import org.junitpioneer.jupiter.DefaultTimeZone; import org.mapstruct.ap.test.complex._target.CarDto; import org.mapstruct.ap.test.complex._target.PersonDto; import org.mapstruct.ap.test.complex.other.DateMapper; @@ -31,6 +32,7 @@ Category.class, DateMapper.class }) +@DefaultTimeZone("UTC") public class CarMapperTest { @ProcessorTest From 796dd9467486ec6ec91bdd919f663bde098896df Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Sep 2024 15:33:08 +0200 Subject: [PATCH 256/363] Update next release changelog with latest changes --- NEXT_RELEASE_CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 438f4e3a6f..19571780cd 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -2,11 +2,16 @@ ### Enhancements +* Use Java `LinkedHashSet` and `LinkedHashMap` new factory method with known capacity when on Java 19 or later (#3113) + ### Bugs * Inverse Inheritance Strategy not working for ignored mappings only with target (#3652) * Inconsistent ambiguous mapping method error when using `SubclassMapping`: generic vs raw types (#3668) * Fix regression when using `InheritInverseConfiguration` with nested target properties and reversing `target = "."` (#3670) +* Deep mapping with multiple mappings broken in 1.6.0 (#3667) +* Two different constants are ignored in 1.6.0 (#3673) +* Inconsistent ambiguous mapping method error: generic vs raw types in 1.6.0 (#3668) ### Documentation From 12c9c6c1f067fde585a5f1aa4e7327d2770c7ef8 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 6 Sep 2024 16:35:37 +0300 Subject: [PATCH 257/363] Use email variable for GitHub Bot git email --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 36cabf44f8..f18a12b813 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: NEXT_VERSION=$COMPUTED_NEXT_VERSION fi ./mvnw -ntp -B versions:set versions:commit -DnewVersion=$RELEASE_VERSION -pl :mapstruct-parent -DgenerateBackupPoms=false - git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --global user.email "${{ vars.GH_BOT_EMAIL }}" git config --global user.name "GitHub Action" git commit -a -m "Releasing version $RELEASE_VERSION" git push From 2686e852b6bd85295f723f705450d401e99f560d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 14 Sep 2024 01:17:45 +0200 Subject: [PATCH 258/363] #3661 Use correct type for the Record component read accessors --- .../itest/tests/MavenIntegrationTest.java | 7 +++ .../module-1/pom.xml | 22 +++++++++ .../records/module1/NestedInterface.java | 10 +++++ .../itest/records/module1/RootInterface.java | 10 +++++ .../records/module1/SourceNestedRecord.java | 11 +++++ .../records/module1/SourceRootRecord.java | 11 +++++ .../module-2/pom.xml | 30 +++++++++++++ .../module2/RecordInterfaceIssueMapper.java | 18 ++++++++ .../records/module2/TargetNestedRecord.java | 11 +++++ .../records/module2/TargetRootRecord.java | 11 +++++ .../itest/records/module2/RecordsTest.java | 26 +++++++++++ .../recordsCrossModuleInterfaceTest/pom.xml | 26 +++++++++++ .../itest/records/mapper/RecordsTest.java | 0 .../ap/internal/model/common/Type.java | 4 +- .../mapstruct/ap/internal/util/Filters.java | 33 +++++--------- .../util/accessor/ElementAccessor.java | 45 +++++++++++++++++++ .../internal/util/accessor/ReadAccessor.java | 12 ++++- ...cessor.java => RecordElementAccessor.java} | 7 +-- 18 files changed, 265 insertions(+), 29 deletions(-) create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/pom.xml create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/NestedInterface.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/RootInterface.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceNestedRecord.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceRootRecord.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/pom.xml create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/RecordInterfaceIssueMapper.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetNestedRecord.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetRootRecord.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java create mode 100644 integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/pom.xml rename integrationtest/src/test/resources/recordsCrossModuleTest/mapper/{ => src}/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java (100%) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java rename processor/src/main/java/org/mapstruct/ap/internal/util/accessor/{FieldElementAccessor.java => RecordElementAccessor.java} (77%) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 7e9175dd92..0bef2994f6 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -131,6 +131,13 @@ void recordsTest() { void recordsCrossModuleTest() { } + @ProcessorTest(baseDir = "recordsCrossModuleInterfaceTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC + }) + @EnabledForJreRange(min = JRE.JAVA_17) + void recordsCrossModuleInterfaceTest() { + } + @ProcessorTest(baseDir = "expressionTextBlocksTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }) diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/pom.xml new file mode 100644 index 0000000000..72df10f62c --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/pom.xml @@ -0,0 +1,22 @@ + + + + 4.0.0 + + + recordsCrossModuleInterfaceTest + org.mapstruct + 1.0.0 + + + records-cross-module-1 + + diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/NestedInterface.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/NestedInterface.java new file mode 100644 index 0000000000..ffa53f88b1 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/NestedInterface.java @@ -0,0 +1,10 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module1; + +public interface NestedInterface { + String field(); +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/RootInterface.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/RootInterface.java new file mode 100644 index 0000000000..fb23ffe157 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/RootInterface.java @@ -0,0 +1,10 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module1; + +public interface RootInterface { + NestedInterface nested(); +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceNestedRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceNestedRecord.java new file mode 100644 index 0000000000..6a0ddb86af --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceNestedRecord.java @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module1; + +public record SourceNestedRecord( + String field +) implements NestedInterface { +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceRootRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceRootRecord.java new file mode 100644 index 0000000000..151ad5208d --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-1/src/main/java/org/mapstruct/itest/records/module1/SourceRootRecord.java @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module1; + +public record SourceRootRecord( + SourceNestedRecord nested +) implements RootInterface { +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/pom.xml new file mode 100644 index 0000000000..5f42efd18e --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/pom.xml @@ -0,0 +1,30 @@ + + + + 4.0.0 + + + recordsCrossModuleInterfaceTest + org.mapstruct + 1.0.0 + + + records-cross-module-2 + + + + + org.mapstruct + records-cross-module-1 + 1.0.0 + + + diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/RecordInterfaceIssueMapper.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/RecordInterfaceIssueMapper.java new file mode 100644 index 0000000000..a763359a98 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/RecordInterfaceIssueMapper.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module2; + +import org.mapstruct.itest.records.module1.SourceRootRecord; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface RecordInterfaceIssueMapper { + + RecordInterfaceIssueMapper INSTANCE = Mappers.getMapper(RecordInterfaceIssueMapper.class); + + TargetRootRecord map(SourceRootRecord source); +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetNestedRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetNestedRecord.java new file mode 100644 index 0000000000..d02a4b58e0 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetNestedRecord.java @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module2; + +public record TargetNestedRecord( + String field +) { +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetRootRecord.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetRootRecord.java new file mode 100644 index 0000000000..09a69f1bf1 --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/main/java/org/mapstruct/itest/records/module2/TargetRootRecord.java @@ -0,0 +1,11 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module2; + +public record TargetRootRecord( + TargetNestedRecord nested +) { +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java new file mode 100644 index 0000000000..5f7a99273a --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/module-2/src/test/java/org/mapstruct/itest/records/module2/RecordsTest.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.module2; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.mapstruct.itest.records.module1.SourceRootRecord; +import org.mapstruct.itest.records.module1.SourceNestedRecord; + +public class RecordsTest { + + @Test + public void shouldMap() { + SourceRootRecord source = new SourceRootRecord( new SourceNestedRecord( "test" ) ); + TargetRootRecord target = RecordInterfaceIssueMapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.nested() ).isNotNull(); + assertThat( target.nested().field() ).isEqualTo( "test" ); + } + +} diff --git a/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/pom.xml b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/pom.xml new file mode 100644 index 0000000000..120c849dca --- /dev/null +++ b/integrationtest/src/test/resources/recordsCrossModuleInterfaceTest/pom.xml @@ -0,0 +1,26 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + recordsCrossModuleInterfaceTest + pom + + + module-1 + module-2 + + diff --git a/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java b/integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java similarity index 100% rename from integrationtest/src/test/resources/recordsCrossModuleTest/mapper/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java rename to integrationtest/src/test/resources/recordsCrossModuleTest/mapper/src/test/java/org/mapstruct/itest/records/mapper/RecordsTest.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 54305cffc1..6520834066 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -50,7 +50,7 @@ import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; -import org.mapstruct.ap.internal.util.accessor.FieldElementAccessor; +import org.mapstruct.ap.internal.util.accessor.ElementAccessor; import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; @@ -1047,7 +1047,7 @@ private List getAlternativeTargetAccessors() { List setterMethods = getSetters(); List readAccessors = new ArrayList<>( getPropertyReadAccessors().values() ); // All the fields are also alternative accessors - readAccessors.addAll( filters.fieldsIn( getAllFields(), FieldElementAccessor::new ) ); + readAccessors.addAll( filters.fieldsIn( getAllFields(), ElementAccessor::new ) ); // there could be a read accessor (field or method) for a list/map that is not present as setter. // an accessor could substitute the setter in that case and act as setter. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java index 123f72832b..5f7fe74bf2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java @@ -39,22 +39,16 @@ public class Filters { private static final Method RECORD_COMPONENTS_METHOD; - private static final Method RECORD_COMPONENT_ACCESSOR_METHOD; static { Method recordComponentsMethod; - Method recordComponentAccessorMethod; try { recordComponentsMethod = TypeElement.class.getMethod( "getRecordComponents" ); - recordComponentAccessorMethod = Class.forName( "javax.lang.model.element.RecordComponentElement" ) - .getMethod( "getAccessor" ); } - catch ( NoSuchMethodException | ClassNotFoundException e ) { + catch ( NoSuchMethodException e ) { recordComponentsMethod = null; - recordComponentAccessorMethod = null; } RECORD_COMPONENTS_METHOD = recordComponentsMethod; - RECORD_COMPONENT_ACCESSOR_METHOD = recordComponentAccessorMethod; } private final AccessorNamingUtils accessorNaming; @@ -89,25 +83,18 @@ public List recordComponentsIn(TypeElement typeElement) { } public Map recordAccessorsIn(Collection recordComponents) { - if ( RECORD_COMPONENT_ACCESSOR_METHOD == null ) { + if ( recordComponents.isEmpty() ) { return java.util.Collections.emptyMap(); } - try { - Map recordAccessors = new LinkedHashMap<>(); - for ( Element recordComponent : recordComponents ) { - ExecutableElement recordExecutableElement = - (ExecutableElement) RECORD_COMPONENT_ACCESSOR_METHOD.invoke( recordComponent ); - recordAccessors.put( - recordComponent.getSimpleName().toString(), - ReadAccessor.fromGetter( recordExecutableElement, getReturnType( recordExecutableElement ) ) - ); - } - - return recordAccessors; - } - catch ( IllegalAccessException | InvocationTargetException e ) { - return java.util.Collections.emptyMap(); + Map recordAccessors = new LinkedHashMap<>(); + for ( Element recordComponent : recordComponents ) { + recordAccessors.put( + recordComponent.getSimpleName().toString(), + ReadAccessor.fromRecordComponent( recordComponent ) + ); } + + return recordAccessors; } private TypeMirror getReturnType(ExecutableElement executableElement) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java new file mode 100644 index 0000000000..24e71cc85f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java @@ -0,0 +1,45 @@ +/* + * 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.internal.util.accessor; + +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * An {@link Accessor} that wraps a {@link VariableElement}. + * + * @author Filip Hrisafov + */ +public class ElementAccessor extends AbstractAccessor { + + private final AccessorType accessorType; + + public ElementAccessor(VariableElement variableElement) { + this( variableElement, AccessorType.FIELD ); + } + + public ElementAccessor(Element element, AccessorType accessorType) { + super( element ); + this.accessorType = accessorType; + } + + @Override + public TypeMirror getAccessedType() { + return element.asType(); + } + + @Override + public String toString() { + return element.toString(); + } + + @Override + public AccessorType getAccessorType() { + return accessorType; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java index a790a3361b..5177bfc75b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.util.accessor; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; @@ -17,7 +18,7 @@ public interface ReadAccessor extends Accessor { String getReadValueSource(); static ReadAccessor fromField(VariableElement variableElement) { - return new ReadDelegateAccessor( new FieldElementAccessor( variableElement ) ) { + return new ReadDelegateAccessor( new ElementAccessor( variableElement ) ) { @Override public String getReadValueSource() { return getSimpleName(); @@ -25,6 +26,15 @@ public String getReadValueSource() { }; } + static ReadAccessor fromRecordComponent(Element element) { + return new ReadDelegateAccessor( new ElementAccessor( element, AccessorType.GETTER ) ) { + @Override + public String getReadValueSource() { + return getSimpleName() + "()"; + } + }; + } + static ReadAccessor fromGetter(ExecutableElement element, TypeMirror accessedType) { return new ReadDelegateAccessor( new ExecutableElementAccessor( element, accessedType, AccessorType.GETTER ) ) { @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/FieldElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java similarity index 77% rename from processor/src/main/java/org/mapstruct/ap/internal/util/accessor/FieldElementAccessor.java rename to processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java index 5174900532..d163f462f9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/FieldElementAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.util.accessor; +import javax.lang.model.element.Element; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; @@ -13,9 +14,9 @@ * * @author Filip Hrisafov */ -public class FieldElementAccessor extends AbstractAccessor { +public class RecordElementAccessor extends AbstractAccessor { - public FieldElementAccessor(VariableElement element) { + public RecordElementAccessor(Element element) { super( element ); } @@ -31,7 +32,7 @@ public String toString() { @Override public AccessorType getAccessorType() { - return AccessorType.FIELD; + return AccessorType.GETTER; } } From 4c1df35ba676814d06336ddf036f4408d9e0526d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 14 Sep 2024 23:06:56 +0200 Subject: [PATCH 259/363] #3703 Use include model instead of manually writing the type name for return type for afterMappingReferencesWithFinalizedReturnType --- .../ap/internal/model/BeanMappingMethod.ftl | 2 +- .../ap/test/bugs/_3703/Issue3703Mapper.java | 37 +++++++++++++++++++ .../ap/test/bugs/_3703/Issue3703Test.java | 24 ++++++++++++ .../ap/test/bugs/_3703/dto/Contact.java | 37 +++++++++++++++++++ .../ap/test/bugs/_3703/entity/Contact.java | 37 +++++++++++++++++++ 5 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/dto/Contact.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/entity/Contact.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 8fb5bfdef0..61d9cc1837 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -136,7 +136,7 @@ <#if finalizerMethod??> <#if (afterMappingReferencesWithFinalizedReturnType?size > 0)> - ${returnType.name} ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; + <@includeModel object=returnType /> ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; <#list afterMappingReferencesWithFinalizedReturnType as callback> <#if callback_index = 0> diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Mapper.java new file mode 100644 index 0000000000..287e7655fb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Mapper.java @@ -0,0 +1,37 @@ +/* + * 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._3703; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ap.test.bugs._3703.dto.Contact; + +@Mapper +public interface Issue3703Mapper { + + Contact map(org.mapstruct.ap.test.bugs._3703.entity.Contact contact); + + org.mapstruct.ap.test.bugs._3703.entity.Contact map(Contact contact); + + @AfterMapping + default void afterMapping(@MappingTarget Contact target, org.mapstruct.ap.test.bugs._3703.entity.Contact contact) { + } + + @AfterMapping + default void afterMapping(@MappingTarget Contact.Builder targetBuilder, + org.mapstruct.ap.test.bugs._3703.entity.Contact contact) { + } + + @AfterMapping + default void afterMapping(@MappingTarget org.mapstruct.ap.test.bugs._3703.entity.Contact target, Contact contact) { + } + + @AfterMapping + default void afterMapping(@MappingTarget org.mapstruct.ap.test.bugs._3703.entity.Contact.Builder targetBuilder, + Contact contact) { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Test.java new file mode 100644 index 0000000000..ac5cae3959 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Test.java @@ -0,0 +1,24 @@ +/* + * 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._3703; + +import org.mapstruct.ap.test.bugs._3703.dto.Contact; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@IssueKey("3703") +@WithClasses({ + Contact.class, + org.mapstruct.ap.test.bugs._3703.entity.Contact.class, + Issue3703Mapper.class +}) +public class Issue3703Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/dto/Contact.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/dto/Contact.java new file mode 100644 index 0000000000..a949864791 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/dto/Contact.java @@ -0,0 +1,37 @@ +/* + * 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._3703.dto; + +public class Contact { + + private final String name; + + private Contact(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + private String name; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Contact build() { + return new Contact( name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/entity/Contact.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/entity/Contact.java new file mode 100644 index 0000000000..31fde373fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/entity/Contact.java @@ -0,0 +1,37 @@ +/* + * 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._3703.entity; + +public class Contact { + + private final String name; + + private Contact(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + private String name; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Contact build() { + return new Contact( name ); + } + } +} From 3011dd77d7ee2de7a7a5d93d3e8e965bad971a43 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 15 Sep 2024 10:28:20 +0200 Subject: [PATCH 260/363] #3678 before / after mapping for type using builder should only be kept if they are using the actual type in `@TargetType` or `@MappingTarget` --- .../ap/internal/model/BeanMappingMethod.java | 33 ++++- .../ap/test/bugs/_3678/Issue3678Mapper.java | 128 ++++++++++++++++++ .../ap/test/bugs/_3678/Issue3678Test.java | 50 +++++++ 3 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Test.java 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 4ccf2bb49a..e6ac5b5bcf 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 @@ -408,9 +408,8 @@ else if ( !method.isUpdateMethod() ) { existingVariableNames ) ); - // remove methods without parameters as they are already being invoked - removeMappingReferencesWithoutSourceParameters( beforeMappingReferencesWithFinalizedReturnType ); - removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType ); + keepMappingReferencesUsingTarget( beforeMappingReferencesWithFinalizedReturnType, actualReturnType ); + keepMappingReferencesUsingTarget( afterMappingReferencesWithFinalizedReturnType, actualReturnType ); } Map presenceChecksByParameter = new LinkedHashMap<>(); @@ -453,8 +452,32 @@ else if ( !sourceParameter.getType().isPrimitive() ) { ); } - private void removeMappingReferencesWithoutSourceParameters(List references) { - references.removeIf( r -> r.getSourceParameters().isEmpty() && r.getReturnType().isVoid() ); + private void keepMappingReferencesUsingTarget(List references, Type type) { + references.removeIf( reference -> { + List bindings = reference.getParameterBindings(); + if ( bindings.isEmpty() ) { + return true; + } + for ( ParameterBinding binding : bindings ) { + if ( binding.isMappingTarget() ) { + if ( type.isAssignableTo( binding.getType() ) ) { + // If the mapping target matches the type then we need to keep this + return false; + } + } + else if ( binding.isTargetType() ) { + Type targetType = binding.getType(); + List targetTypeTypeParameters = targetType.getTypeParameters(); + if ( targetTypeTypeParameters.size() == 1 ) { + if ( type.isAssignableTo( targetTypeTypeParameters.get( 0 ) ) ) { + return false; + } + } + } + } + + return true; + } ); } private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Mapper.java new file mode 100644 index 0000000000..374fbf931a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Mapper.java @@ -0,0 +1,128 @@ +/* + * 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._3678; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3678Mapper { + + Issue3678Mapper INSTANCE = Mappers.getMapper( Issue3678Mapper.class ); + + @Mapping(target = "name", source = "sourceA.name") + @Mapping(target = "description", source = "sourceB.description") + Target map(SourceA sourceA, SourceB sourceB, @Context MappingContext context); + + @Mapping(target = "description", constant = "some description") + Target map(SourceA sourceA, @Context MappingContext context); + + class MappingContext { + + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + public void beforeMappingSourceA(SourceA sourceA) { + invokedMethods.add( "beforeMappingSourceA" ); + } + + @AfterMapping + public void afterMappingSourceB(SourceA sourceA) { + invokedMethods.add( "afterMappingSourceA" ); + } + + @BeforeMapping + public void beforeMappingSourceB(SourceB sourceB) { + invokedMethods.add( "beforeMappingSourceB" ); + } + + @AfterMapping + public void afterMappingSourceB(SourceB sourceB) { + invokedMethods.add( "afterMappingSourceB" ); + } + + public List getInvokedMethods() { + return invokedMethods; + } + } + + class SourceA { + + private final String name; + + public SourceA(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class SourceB { + + private final String description; + + public SourceB(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + } + + final class Target { + + private final String name; + private final String description; + + private Target(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String name; + private String description; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Target build() { + return new Target( this.name, this.description ); + } + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Test.java new file mode 100644 index 0000000000..25e7e21622 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Test.java @@ -0,0 +1,50 @@ +/* + * 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._3678; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3678") +@WithClasses(Issue3678Mapper.class) +public class Issue3678Test { + + @ProcessorTest + void beforeAndAfterMappingOnlyCalledOnceForTwoSources() { + + Issue3678Mapper.MappingContext mappingContext = new Issue3678Mapper.MappingContext(); + Issue3678Mapper.SourceA sourceA = new Issue3678Mapper.SourceA( "name" ); + Issue3678Mapper.SourceB sourceB = new Issue3678Mapper.SourceB( "description" ); + Issue3678Mapper.INSTANCE.map( sourceA, sourceB, mappingContext ); + + assertThat( mappingContext.getInvokedMethods() ) + .containsExactly( + "beforeMappingSourceA", + "beforeMappingSourceB", + "afterMappingSourceA", + "afterMappingSourceB" + ); + } + + @ProcessorTest + void beforeAndAfterMappingOnlyCalledOnceForSingleSource() { + + Issue3678Mapper.MappingContext mappingContext = new Issue3678Mapper.MappingContext(); + Issue3678Mapper.SourceA sourceA = new Issue3678Mapper.SourceA( "name" ); + Issue3678Mapper.INSTANCE.map( sourceA, mappingContext ); + + assertThat( mappingContext.getInvokedMethods() ) + .containsExactly( + "beforeMappingSourceA", + "afterMappingSourceA" + ); + } + +} From c36f9ae5d10baa1fbae63df8a51e937db0569c07 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 15 Sep 2024 17:11:24 +0200 Subject: [PATCH 261/363] Prepare release notes for 1.6.1 --- NEXT_RELEASE_CHANGELOG.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 19571780cd..5337e70daf 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,5 +1,3 @@ -### Features - ### Enhancements * Use Java `LinkedHashSet` and `LinkedHashMap` new factory method with known capacity when on Java 19 or later (#3113) @@ -12,8 +10,6 @@ * Deep mapping with multiple mappings broken in 1.6.0 (#3667) * Two different constants are ignored in 1.6.0 (#3673) * Inconsistent ambiguous mapping method error: generic vs raw types in 1.6.0 (#3668) - -### Documentation - -### Build - +* Fix cross module records with interfaces not recognizing accessors (#3661) +* `@AfterMapping` methods are called twice when using target with builder (#3678) +* Compile error when using `@AfterMapping` method with Builder and TargetObject (#3703) From 10d69878a197c1bff1e8743a3d056e36eda856d3 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Sep 2024 15:52:17 +0000 Subject: [PATCH 262/363] Releasing version 1.6.1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..3e347b5c93 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..ca326ef6f0 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..da17fa59dc 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index f826421092..405f489865 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..5044ed7689 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 7c91837e79..96f86c6f0e 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 94c84fb4d0..8c36b4b6ea 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-09-15T15:52:17Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index f55f0955d3..a0e55059da 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index ed2df7718b..e605c32560 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.1 ../parent/pom.xml From c74e62a94c4c420ec5aa52fc177c56ea0483c50b Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Sep 2024 16:01:43 +0000 Subject: [PATCH 263/363] Next version 1.7.0-SNAPSHOT --- NEXT_RELEASE_CHANGELOG.md | 17 ++++++----------- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 10 files changed, 16 insertions(+), 21 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 5337e70daf..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,15 +1,10 @@ -### Enhancements +### Features -* Use Java `LinkedHashSet` and `LinkedHashMap` new factory method with known capacity when on Java 19 or later (#3113) +### Enhancements ### Bugs -* Inverse Inheritance Strategy not working for ignored mappings only with target (#3652) -* Inconsistent ambiguous mapping method error when using `SubclassMapping`: generic vs raw types (#3668) -* Fix regression when using `InheritInverseConfiguration` with nested target properties and reversing `target = "."` (#3670) -* Deep mapping with multiple mappings broken in 1.6.0 (#3667) -* Two different constants are ignored in 1.6.0 (#3673) -* Inconsistent ambiguous mapping method error: generic vs raw types in 1.6.0 (#3668) -* Fix cross module records with interfaces not recognizing accessors (#3661) -* `@AfterMapping` methods are called twice when using target with builder (#3678) -* Compile error when using `@AfterMapping` method with Builder and TargetObject (#3703) +### Documentation + +### Build + diff --git a/build-config/pom.xml b/build-config/pom.xml index 3e347b5c93..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index ca326ef6f0..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index da17fa59dc..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 405f489865..f826421092 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 5044ed7689..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 96f86c6f0e..7c91837e79 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 8c36b4b6ea..94c84fb4d0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-09-15T15:52:17Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index a0e55059da..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index e605c32560..ed2df7718b 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.1 + 1.7.0-SNAPSHOT ../parent/pom.xml From a3b4139070a3111872c3d17e0cb4dbcbaf8f4bf1 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 16 Sep 2024 09:19:39 +0200 Subject: [PATCH 264/363] #3717 Fix ClassCastException when getting thrown types for a record accessor --- .../itest/records/nested/Address.java | 13 ++++++++++ .../itest/records/nested/CareProvider.java | 13 ++++++++++ .../itest/records/nested/CareProviderDto.java | 13 ++++++++++ .../records/nested/CareProviderMapper.java | 25 ++++++++++++++++++ .../records/nested/NestedRecordsTest.java | 26 +++++++++++++++++++ .../ap/internal/model/common/TypeFactory.java | 6 ++++- 6 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/Address.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProvider.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderDto.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderMapper.java create mode 100644 integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/Address.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/Address.java new file mode 100644 index 0000000000..fb857e90ca --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/Address.java @@ -0,0 +1,13 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.nested; + +/** + * @author Filip Hrisafov + */ +public record Address(String street, String city) { + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProvider.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProvider.java new file mode 100644 index 0000000000..a0ce13c0ba --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProvider.java @@ -0,0 +1,13 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.nested; + +/** + * @author Filip Hrisafov + */ +public record CareProvider(String externalId, Address address) { + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderDto.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderDto.java new file mode 100644 index 0000000000..d7ce7229e9 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderDto.java @@ -0,0 +1,13 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.nested; + +/** + * @author Filip Hrisafov + */ +public record CareProviderDto(String id, String street, String city) { + +} diff --git a/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderMapper.java b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderMapper.java new file mode 100644 index 0000000000..89ed688976 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/main/java/org/mapstruct/itest/records/nested/CareProviderMapper.java @@ -0,0 +1,25 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.nested; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface CareProviderMapper { + + CareProviderMapper INSTANCE = Mappers.getMapper( CareProviderMapper.class ); + + @Mapping(target = "id", source = "externalId") + @Mapping(target = "street", source = "address.street") + @Mapping(target = "city", source = "address.city") + CareProviderDto map(CareProvider source); +} diff --git a/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java new file mode 100644 index 0000000000..c8ccaf1a65 --- /dev/null +++ b/integrationtest/src/test/resources/recordsTest/src/test/java/org/mapstruct/itest/records/nested/NestedRecordsTest.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.records.nested; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public class NestedRecordsTest { + + @Test + public void shouldMapRecord() { + CareProvider source = new CareProvider( "kermit", new Address( "Sesame Street", "New York" ) ); + CareProviderDto target = CareProviderMapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.id() ).isEqualTo( "kermit" ); + assertThat( target.street() ).isEqualTo( "Sesame Street" ); + assertThat( target.city() ).isEqualTo( "New York" ); + } +} 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 d65070426b..ceb190a332 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 @@ -491,7 +491,11 @@ public List getThrownTypes(Accessor accessor) { if (accessor.getAccessorType().isFieldAssignment()) { return new ArrayList<>(); } - return extractTypes( ( (ExecutableElement) accessor.getElement() ).getThrownTypes() ); + Element element = accessor.getElement(); + if ( element instanceof ExecutableElement ) { + return extractTypes( ( (ExecutableElement) element ).getThrownTypes() ); + } + return new ArrayList<>(); } private List extractTypes(List typeMirrors) { From 4fd22d6b267f845038786364f46f16c5b0903d0a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 16 Sep 2024 09:54:30 +0200 Subject: [PATCH 265/363] Prepare release notes for 1.6.2 --- NEXT_RELEASE_CHANGELOG.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index e0f4cd31f0..dd5c9b2349 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,10 +1,3 @@ -### Features - -### Enhancements - ### Bugs -### Documentation - -### Build - +* Regression from 1.6.1: ClassCastException when using records (#3717) From 212607b4470c9e0deb8b6ad1fed56d016d58aa08 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 07:55:31 +0000 Subject: [PATCH 266/363] Releasing version 1.6.2 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..fa50066316 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..648c193e39 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..37ea12be4d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index f826421092..4539528a91 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..dfac83cca8 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 7c91837e79..ab8dcb26b7 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 94c84fb4d0..94f614ecbb 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-09-16T07:55:31Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index f55f0955d3..919e2d6460 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index ed2df7718b..e0052bf5d4 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.2 ../parent/pom.xml From 4e0d73db1d17c40d251d39812550206607a16824 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 08:06:43 +0000 Subject: [PATCH 267/363] Next version 1.7.0-SNAPSHOT --- NEXT_RELEASE_CHANGELOG.md | 9 ++++++++- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 10 files changed, 18 insertions(+), 11 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index dd5c9b2349..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,3 +1,10 @@ +### Features + +### Enhancements + ### Bugs -* Regression from 1.6.1: ClassCastException when using records (#3717) +### Documentation + +### Build + diff --git a/build-config/pom.xml b/build-config/pom.xml index fa50066316..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 648c193e39..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index 37ea12be4d..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 4539528a91..f826421092 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index dfac83cca8..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index ab8dcb26b7..7c91837e79 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 94f614ecbb..94c84fb4d0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-09-16T07:55:31Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 919e2d6460..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index e0052bf5d4..ed2df7718b 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.2 + 1.7.0-SNAPSHOT ../parent/pom.xml From 26c5bcd923c7206ad161e93237f841a79ca85efd Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 27 Sep 2024 09:15:17 +0200 Subject: [PATCH 268/363] Update readme with 1.6.2 --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index c0f798af83..7903ddeb84 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.6.0-blue.svg)](https://central.sonatype.com/search?q=g:org.mapstruct%20v:1.6.0) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.6.2-blue.svg)](https://central.sonatype.com/search?q=g:org.mapstruct%20v:1.6.2) [![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://central.sonatype.com/search?q=g:org.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/main/LICENSE.txt) @@ -65,7 +65,7 @@ For Maven-based projects, add the following to your POM file in order to use Map ```xml ... - 1.6.0 + 1.6.2 ... @@ -111,10 +111,10 @@ plugins { dependencies { ... - implementation 'org.mapstruct:mapstruct:1.6.0' + implementation 'org.mapstruct:mapstruct:1.6.2' - annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.0' - testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.0' // if you are using mapstruct in test code + annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.2' + testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.2' // if you are using mapstruct in test code } ... ``` From 32f1fea7b50ab583b87e3634e25ac92752380682 Mon Sep 17 00:00:00 2001 From: Srimathi-S <75327301+Srimathi-S@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:22:52 +0530 Subject: [PATCH 269/363] #3370 Prevent stack overflow error for Immutables with custom builder --- .../ap/spi/DefaultBuilderProvider.java | 9 +- .../ap/spi/ImmutablesBuilderProvider.java | 27 +- .../bugs/_3370/Issue3370BuilderProvider.java | 25 ++ .../ap/test/bugs/_3370/Issue3370Test.java | 55 +++ .../ap/test/bugs/_3370/ItemMapper.java | 19 ++ .../test/bugs/_3370/domain/ImmutableItem.java | 316 ++++++++++++++++++ .../ap/test/bugs/_3370/domain/Item.java | 27 ++ .../ap/test/bugs/_3370/dto/ItemDTO.java | 26 ++ 8 files changed, 496 insertions(+), 8 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370BuilderProvider.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/ItemMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/ImmutableItem.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/Item.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/dto/ItemDTO.java diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index 6395364a35..92cd1ed80d 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -176,6 +176,10 @@ public DeclaredType visitDeclared(DeclaredType t, Void p) { * @throws MoreThanOneBuilderCreationMethodException if there are multiple builder creation methods */ protected BuilderInfo findBuilderInfo(TypeElement typeElement) { + return findBuilderInfo( typeElement, true ); + } + + protected BuilderInfo findBuilderInfo(TypeElement typeElement, boolean checkParent) { if ( shouldIgnore( typeElement ) ) { return null; } @@ -203,7 +207,10 @@ else if ( builderInfo.size() > 1 ) { throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), builderInfo ); } - return findBuilderInfo( typeElement.getSuperclass() ); + if ( checkParent ) { + return findBuilderInfo( typeElement.getSuperclass() ); + } + return null; } /** diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java index d4ccd029ef..3431808418 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java @@ -35,18 +35,31 @@ protected BuilderInfo findBuilderInfo(TypeElement typeElement) { if ( name.length() == 0 || JAVA_JAVAX_PACKAGE.matcher( name ).matches() ) { return null; } + + // First look if there is a builder defined in my own type + BuilderInfo info = findBuilderInfo( typeElement, false ); + if ( info != null ) { + return info; + } + + // Check for a builder in the generated immutable type + BuilderInfo immutableInfo = findBuilderInfoForImmutables( typeElement ); + if ( immutableInfo != null ) { + return immutableInfo; + } + + return super.findBuilderInfo( typeElement.getSuperclass() ); + } + + protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement) { TypeElement immutableAnnotation = elementUtils.getTypeElement( IMMUTABLE_FQN ); if ( immutableAnnotation != null ) { - BuilderInfo info = findBuilderInfoForImmutables( + return findBuilderInfoForImmutables( typeElement, immutableAnnotation ); - if ( info != null ) { - return info; - } } - - return super.findBuilderInfo( typeElement ); + return null; } protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement, @@ -55,7 +68,7 @@ protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement, if ( typeUtils.isSameType( annotationMirror.getAnnotationType(), immutableAnnotation.asType() ) ) { TypeElement immutableElement = asImmutableElement( typeElement ); if ( immutableElement != null ) { - return super.findBuilderInfo( immutableElement ); + return super.findBuilderInfo( immutableElement, false ); } else { // Immutables processor has not run yet. Trigger a postpone to the next round for MapStruct diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370BuilderProvider.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370BuilderProvider.java new file mode 100644 index 0000000000..55a3f783c3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370BuilderProvider.java @@ -0,0 +1,25 @@ +/* + * 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._3370; + +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.ap.spi.BuilderInfo; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesBuilderProvider; + +public class Issue3370BuilderProvider extends ImmutablesBuilderProvider implements BuilderProvider { + + @Override + protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement) { + Name name = typeElement.getQualifiedName(); + if ( name.toString().endsWith( ".Item" ) ) { + return super.findBuilderInfo( asImmutableElement( typeElement ) ); + } + return super.findBuilderInfo( typeElement ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370Test.java new file mode 100644 index 0000000000..0389de5b46 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370Test.java @@ -0,0 +1,55 @@ +/* + * 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._3370; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.ap.spi.AccessorNamingStrategy; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; +import org.mapstruct.ap.test.bugs._3370.domain.ImmutableItem; +import org.mapstruct.ap.test.bugs._3370.domain.Item; +import org.mapstruct.ap.test.bugs._3370.dto.ItemDTO; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.WithServiceImplementations; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + ItemMapper.class, + Item.class, + ImmutableItem.class, + ItemDTO.class, +}) +@IssueKey("3370") +@WithServiceImplementations({ + @WithServiceImplementation(provides = BuilderProvider.class, value = Issue3370BuilderProvider.class), + @WithServiceImplementation(provides = AccessorNamingStrategy.class, + value = ImmutablesAccessorNamingStrategy.class), +}) +public class Issue3370Test { + + @ProcessorTest + public void shouldUseBuilderOfImmutableSuperClass() { + + Map attributesMap = new HashMap<>(); + attributesMap.put( "a", "b" ); + attributesMap.put( "c", "d" ); + + ItemDTO item = new ItemDTO( "test", attributesMap ); + + Item target = ItemMapper.INSTANCE.map( item ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( "test" ); + assertThat( target.getAttributes() ).isEqualTo( attributesMap ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/ItemMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/ItemMapper.java new file mode 100644 index 0000000000..5583e191b3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/ItemMapper.java @@ -0,0 +1,19 @@ +/* + * 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._3370; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._3370.domain.Item; +import org.mapstruct.ap.test.bugs._3370.dto.ItemDTO; +import org.mapstruct.factory.Mappers; + +@Mapper +public abstract class ItemMapper { + + public static final ItemMapper INSTANCE = Mappers.getMapper( ItemMapper.class ); + + public abstract Item map(ItemDTO itemDTO); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/ImmutableItem.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/ImmutableItem.java new file mode 100644 index 0000000000..9258914c0b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/ImmutableItem.java @@ -0,0 +1,316 @@ +/* + * 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._3370.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable implementation of {@link Item}. + *

      + * Use the builder to create immutable instances: + * {@code new Item.Builder()}. + */ +@SuppressWarnings("all") +public final class ImmutableItem extends Item { + private final String id; + private final Map attributes; + + private ImmutableItem(String id, Map attributes) { + this.id = id; + this.attributes = attributes; + } + + /** + * @return The value of the {@code id} attribute + */ + @Override + public String getId() { + return id; + } + + /** + * @return The value of the {@code attributes} attribute + */ + @Override + public Map getAttributes() { + return attributes; + } + + /** + * Copy the current immutable object by setting a value for the {@link Item#getId() id} attribute. + * An equals check used to prevent copying of the same value by returning {@code this}. + * + * @param id A new value for id + * @return A modified copy of the {@code this} object + */ + public final ImmutableItem withId(String id) { + if ( this.id.equals( id ) ) { + return this; + } + String newValue = Objects.requireNonNull( id, "id" ); + return new ImmutableItem( newValue, this.attributes ); + } + + /** + * Copy the current immutable object by replacing the {@link Item#getAttributes() attributes} map with the specified map. + * Nulls are not permitted as keys or values. + * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}. + * + * @param attributes The entries to be added to the attributes map + * @return A modified copy of {@code this} object + */ + public final ImmutableItem withAttributes(Map attributes) { + if ( this.attributes == attributes ) { + return this; + } + Map newValue = createUnmodifiableMap( true, false, attributes ); + return new ImmutableItem( this.id, newValue ); + } + + /** + * This instance is equal to all instances of {@code ImmutableItem} that have equal attribute values. + * + * @return {@code true} if {@code this} is equal to {@code another} instance + */ + @Override + public boolean equals(Object another) { + if ( this == another ) { + return true; + } + return another instanceof ImmutableItem + && equalTo( (ImmutableItem) another ); + } + + private boolean equalTo(ImmutableItem another) { + return id.equals( another.id ) + && attributes.equals( another.attributes ); + } + + /** + * Computes a hash code from attributes: {@code id}, {@code attributes}. + * + * @return hashCode value + */ + @Override + public int hashCode() { + int h = 31; + h = h * 17 + id.hashCode(); + h = h * 17 + attributes.hashCode(); + return h; + } + + /** + * Prints the immutable value {@code Item} with attribute values. + * + * @return A string representation of the value + */ + @Override + public String toString() { + return "Item{" + + "id=" + id + + ", attributes=" + attributes + + "}"; + } + + /** + * Creates an immutable copy of a {@link Item} value. + * Uses accessors to get values to initialize the new immutable instance. + * If an instance is already immutable, it is returned as is. + * + * @param instance The instance to copy + * @return A copied immutable Item instance + */ + public static ImmutableItem copyOf(Item instance) { + if ( instance instanceof ImmutableItem ) { + return (ImmutableItem) instance; + } + return new Item.Builder() + .from( instance ) + .build(); + } + + /** + * Builds instances of type {@link ImmutableItem ImmutableItem}. + * Initialize attributes and then invoke the {@link #build()} method to create an + * immutable instance. + *

      {@code Builder} is not thread-safe and generally should not be stored in a field or collection, + * but instead used immediately to create instances. + */ + public static class Builder { + private static final long INIT_BIT_ID = 0x1L; + private long initBits = 0x1L; + + private String id; + private Map attributes = new LinkedHashMap(); + + /** + * Creates a builder for {@link ImmutableItem ImmutableItem} instances. + */ + public Builder() { + if ( !( this instanceof Item.Builder ) ) { + throw new UnsupportedOperationException( "Use: new Item.Builder()" ); + } + } + + /** + * Fill a builder with attribute values from the provided {@code Item} instance. + * Regular attribute values will be replaced with those from the given instance. + * Absent optional values will not replace present values. + * Collection elements and entries will be added, not replaced. + * + * @param instance The instance from which to copy values + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder from(Item instance) { + Objects.requireNonNull( instance, "instance" ); + id( instance.getId() ); + putAllAttributes( instance.getAttributes() ); + return (Item.Builder) this; + } + + /** + * Initializes the value for the {@link Item#getId() id} attribute. + * + * @param id The value for id + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder id(String id) { + this.id = Objects.requireNonNull( id, "id" ); + initBits &= ~INIT_BIT_ID; + return (Item.Builder) this; + } + + /** + * Put one entry to the {@link Item#getAttributes() attributes} map. + * + * @param key The key in the attributes map + * @param value The associated value in the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder putAttributes(String key, String value) { + this.attributes.put( + Objects.requireNonNull( key, "attributes key" ), + Objects.requireNonNull( value, "attributes value" ) + ); + return (Item.Builder) this; + } + + /** + * Put one entry to the {@link Item#getAttributes() attributes} map. Nulls are not permitted + * + * @param entry The key and value entry + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder putAttributes(Map.Entry entry) { + String k = entry.getKey(); + String v = entry.getValue(); + this.attributes.put( + Objects.requireNonNull( k, "attributes key" ), + Objects.requireNonNull( v, "attributes value" ) + ); + return (Item.Builder) this; + } + + /** + * Sets or replaces all mappings from the specified map as entries for the {@link Item#getAttributes() attributes} map. Nulls are not permitted + * + * @param attributes The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder attributes(Map attributes) { + this.attributes.clear(); + return putAllAttributes( attributes ); + } + + /** + * Put all mappings from the specified map as entries to {@link Item#getAttributes() attributes} map. Nulls are not permitted + * + * @param attributes The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder putAllAttributes(Map attributes) { + for ( Map.Entry entry : attributes.entrySet() ) { + String k = entry.getKey(); + String v = entry.getValue(); + this.attributes.put( + Objects.requireNonNull( k, "attributes key" ), + Objects.requireNonNull( v, "attributes value" ) + ); + } + return (Item.Builder) this; + } + + /** + * Builds a new {@link ImmutableItem ImmutableItem}. + * + * @return An immutable instance of Item + * @throws java.lang.IllegalStateException if any required attributes are missing + */ + public ImmutableItem build() { + if ( initBits != 0 ) { + throw new IllegalStateException( formatRequiredAttributesMessage() ); + } + return new ImmutableItem( id, createUnmodifiableMap( false, false, attributes ) ); + } + + private String formatRequiredAttributesMessage() { + List attributes = new ArrayList(); + if ( ( initBits & INIT_BIT_ID ) != 0 ) { + attributes.add( "id" ); + } + return "Cannot build Item, some of required attributes are not set " + attributes; + } + } + + private static Map createUnmodifiableMap(boolean checkNulls, boolean skipNulls, + Map map) { + switch ( map.size() ) { + case 0: + return Collections.emptyMap(); + case 1: { + Map.Entry e = map.entrySet().iterator().next(); + K k = e.getKey(); + V v = e.getValue(); + if ( checkNulls ) { + Objects.requireNonNull( k, "key" ); + Objects.requireNonNull( v, "value" ); + } + if ( skipNulls && ( k == null || v == null ) ) { + return Collections.emptyMap(); + } + return Collections.singletonMap( k, v ); + } + default: { + Map linkedMap = new LinkedHashMap( map.size() ); + if ( skipNulls || checkNulls ) { + for ( Map.Entry e : map.entrySet() ) { + K k = e.getKey(); + V v = e.getValue(); + if ( skipNulls ) { + if ( k == null || v == null ) { + continue; + } + } + else if ( checkNulls ) { + Objects.requireNonNull( k, "key" ); + Objects.requireNonNull( v, "value" ); + } + linkedMap.put( k, v ); + } + } + else { + linkedMap.putAll( map ); + } + return Collections.unmodifiableMap( linkedMap ); + } + } + } +} \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/Item.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/Item.java new file mode 100644 index 0000000000..a78c3bc007 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/Item.java @@ -0,0 +1,27 @@ +/* + * 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._3370.domain; + +import java.util.Collections; +import java.util.Map; + +public abstract class Item { + + public abstract String getId(); + + public abstract Map getAttributes(); + + public static Item.Builder builder() { + return new Item.Builder(); + } + + public static class Builder extends ImmutableItem.Builder { + + public ImmutableItem.Builder addSomeData(String key, String data) { + return super.attributes( Collections.singletonMap( key, data ) ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/dto/ItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/dto/ItemDTO.java new file mode 100644 index 0000000000..70dba87fe4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/dto/ItemDTO.java @@ -0,0 +1,26 @@ +/* + * 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._3370.dto; + +import java.util.Map; + +public class ItemDTO { + private final String id; + private final Map attributes; + + public ItemDTO(String id, Map attributes) { + this.id = id; + this.attributes = attributes; + } + + public String getId() { + return id; + } + + public Map getAttributes() { + return attributes; + } +} From 21fdaa0f824fc358efc05507c591f86767fa3245 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 3 Nov 2024 12:54:37 +0100 Subject: [PATCH 270/363] #3747 Do not generate redundant if condition with constructor mapping and RETURN_DEFAULT null value mapping strategy --- .../ap/internal/model/BeanMappingMethod.ftl | 2 +- .../ap/test/bugs/_3747/Issue3747Mapper.java | 42 +++++++++++++++++++ .../ap/test/bugs/_3747/Issue3747Test.java | 28 +++++++++++++ .../test/bugs/_3747/Issue3747MapperImpl.java | 29 +++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Test.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3747/Issue3747MapperImpl.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 61d9cc1837..3036e4a2c8 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -116,7 +116,7 @@ - <#else> + <#elseif !propertyMappingsByParameter(sourceParameters[0]).empty> <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { <#list propertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Mapper.java new file mode 100644 index 0000000000..04addc43f3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Mapper.java @@ -0,0 +1,42 @@ +/* + * 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._3747; + +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) +public interface Issue3747Mapper { + + Target map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Test.java new file mode 100644 index 0000000000..57650e4dc0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Test.java @@ -0,0 +1,28 @@ +/* + * 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._3747; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3747") +@WithClasses(Issue3747Mapper.class) +class Issue3747Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void shouldNotGenerateObsoleteCode() { + generatedSource.addComparisonToFixtureFor( Issue3747Mapper.class ); + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3747/Issue3747MapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3747/Issue3747MapperImpl.java new file mode 100644 index 0000000000..adc937dab1 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_3747/Issue3747MapperImpl.java @@ -0,0 +1,29 @@ +/* + * 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._3747; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-11-03T12:50:02+0100", + comments = "version: , compiler: javac, environment: Java 21.0.3 (N/A)" +) +public class Issue3747MapperImpl implements Issue3747Mapper { + + @Override + public Target map(Source source) { + + String value = null; + if ( source != null ) { + value = source.getValue(); + } + + Target target = new Target( value ); + + return target; + } +} From c2bd847599c1cdfad562eb02ca47b16e8dfa56d6 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 9 Nov 2024 10:15:33 +0100 Subject: [PATCH 271/363] #3732 Do not generate obsolete imports for LocalDateTime <-> LocalDate conversion --- ...avaLocalDateTimeToLocalDateConversion.java | 16 ------- .../ap/test/bugs/_3732/Issue3732Mapper.java | 47 +++++++++++++++++++ .../ap/test/bugs/_3732/Issue3732Test.java | 22 +++++++++ 3 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Test.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java index ae571ad6b0..9116bada4c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateTimeToLocalDateConversion.java @@ -7,11 +7,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Set; import org.mapstruct.ap.internal.model.common.ConversionContext; -import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.util.Collections; /** * SimpleConversion for mapping {@link LocalDateTime} to @@ -25,22 +22,9 @@ protected String getToExpression(ConversionContext conversionContext) { return ".toLocalDate()"; } - @Override - protected Set getToConversionImportTypes(ConversionContext conversionContext) { - return Collections.asSet( - conversionContext.getTypeFactory().getType( LocalDate.class ) - ); - } - @Override protected String getFromExpression(ConversionContext conversionContext) { return ".atStartOfDay()"; } - @Override - protected Set getFromConversionImportTypes(ConversionContext conversionContext) { - return Collections.asSet( - conversionContext.getTypeFactory().getType( LocalDateTime.class ) - ); - } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Mapper.java new file mode 100644 index 0000000000..79b13aa8ce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Mapper.java @@ -0,0 +1,47 @@ +/* + * 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._3732; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3732Mapper { + + Target map(Source source); + + Source map(Target source); + + class Source { + private LocalDateTime value; + + public LocalDateTime getValue() { + return value; + } + + public void setValue(LocalDateTime value) { + this.value = value; + } + } + + class Target { + + private LocalDate value; + + public LocalDate getValue() { + return value; + } + + public void setValue(LocalDate value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Test.java new file mode 100644 index 0000000000..9dbd9c0bb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Test.java @@ -0,0 +1,22 @@ +/* + * 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._3732; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3732") +@WithClasses({ Issue3732Mapper.class }) +class Issue3732Test { + + @ProcessorTest + void shouldGenerateCorrectMapper() { + } +} From efdf435770dd4361ccfa6e64d643cc88f156e79e Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 9 Nov 2024 10:44:54 +0100 Subject: [PATCH 272/363] #3751 Improve readme to include support for Java 16+ records --- readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 7903ddeb84..6932f4d748 100644 --- a/readme.md +++ b/readme.md @@ -19,7 +19,10 @@ ## What is MapStruct? -MapStruct is a Java [annotation processor](https://docs.oracle.com/javase/6/docs/technotes/guides/apt/index.html) for the generation of type-safe and performant mappers for Java bean classes. It saves you from writing mapping code by hand, which is a tedious and error-prone task. The generator comes with sensible defaults and many built-in type conversions, but it steps out of your way when it comes to configuring or implementing special behavior. +MapStruct is a Java [annotation processor](https://docs.oracle.com/en/java/javase/21/docs/specs/man/javac.html#annotation-processing) designed to generate type-safe and high-performance mappers for Java bean classes, including support for Java 16+ records. +By automating the creation of mappings, MapStruct eliminates the need for tedious and error-prone manual coding. +The generator provides sensible defaults and built-in type conversions, allowing it to handle standard mappings effortlessly, while also offering flexibility for custom configurations or specialized mapping behaviors. +With seamless integration into modern Java projects, MapStruct can map between conventional beans, records, and even complex hierarchies, making it an adaptable tool for diverse Java applications. Compared to mapping frameworks working at runtime, MapStruct offers the following advantages: From 772fae4c77ccd0d35c33afa7f04431090a964692 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 9 Nov 2024 12:20:14 +0100 Subject: [PATCH 273/363] Prepare release notes for 1.6.3 --- NEXT_RELEASE_CHANGELOG.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index e0f4cd31f0..dca36c820a 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,10 +1,9 @@ -### Features - -### Enhancements - ### Bugs -### Documentation +* Redundant if condition in Java record mapping with `RETURN_DEFAULT` strategy (#3747) +* Stackoverflow with Immutables custom builder (#3370) +* Unused import of `java.time.LocalDate` when mapping source `LocalDateTime` to target `LocalDate` (#3732) -### Build +### Documentation +* Add section to README.md comparing mapstruct with Java Records (#3751) From b4e25e49deae707b50ce061172e114292b414a23 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 9 Nov 2024 11:31:12 +0000 Subject: [PATCH 274/363] Releasing version 1.6.3 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..e60cda2afe 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..adec24c93b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..e276a889ce 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index f826421092..4d820495cd 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..45f4eb19c6 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 7c91837e79..d686c5a368 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 94c84fb4d0..ded84caf2e 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2024-11-09T11:31:12Z 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index f55f0955d3..9df5b02892 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index ed2df7718b..7410fd0a35 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.6.3 ../parent/pom.xml From 5bf2b152afcb5e3f4335f35ea9e0d747963e4298 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 9 Nov 2024 11:40:01 +0000 Subject: [PATCH 275/363] Next version 1.7.0-SNAPSHOT --- NEXT_RELEASE_CHANGELOG.md | 11 ++++++----- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 10 files changed, 16 insertions(+), 15 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index dca36c820a..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,9 +1,10 @@ -### Bugs +### Features + +### Enhancements -* Redundant if condition in Java record mapping with `RETURN_DEFAULT` strategy (#3747) -* Stackoverflow with Immutables custom builder (#3370) -* Unused import of `java.time.LocalDate` when mapping source `LocalDateTime` to target `LocalDate` (#3732) +### Bugs ### Documentation -* Add section to README.md comparing mapstruct with Java Records (#3751) +### Build + diff --git a/build-config/pom.xml b/build-config/pom.xml index e60cda2afe..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index adec24c93b..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e276a889ce..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index 4d820495cd..f826421092 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 45f4eb19c6..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index d686c5a368..7c91837e79 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index ded84caf2e..94c84fb4d0 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2024-11-09T11:31:12Z + ${git.commit.author.time} 1.0.0.Alpha3 3.4.1 diff --git a/pom.xml b/pom.xml index 9df5b02892..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 7410fd0a35..ed2df7718b 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.6.3 + 1.7.0-SNAPSHOT ../parent/pom.xml From 0df3f6af957c1468ae4baaf85b528131fbde9711 Mon Sep 17 00:00:00 2001 From: cussle <109949453+cussle@users.noreply.github.com> Date: Thu, 14 Nov 2024 05:22:57 +0900 Subject: [PATCH 276/363] docs & refactor: fix typos and improve readability in multiple classes (#3767) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AnnotatedConstructor: Fixed a variable name typo (noArgConstructorToInBecluded → noArgConstructorToBeIncluded). - AbstractBaseBuilder: Improved Javadoc by fixing typos and clarifying wording. - SourceRhsSelector: Corrected a typo in the class-level Javadoc. - InheritanceSelector: Enhanced readability by fixing typos and refining comments. --- .../ap/internal/model/AbstractBaseBuilder.java | 4 ++-- .../ap/internal/model/AnnotatedConstructor.java | 8 ++++---- .../model/source/selector/InheritanceSelector.java | 10 +++++----- .../model/source/selector/SourceRhsSelector.java | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java index 311cc00368..56a1831d52 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java @@ -41,7 +41,7 @@ public B method(Method sourceMethod) { } /** - * Checks if MapStruct is allowed to generate an automatic sub-mapping between {@code sourceType} and @{code + * Checks if MapStruct is allowed to generate an automatic sub-mapping between {@code sourceType} and {@code * targetType}. * This will evaluate to {@code true}, when: *

    11. @@ -66,7 +66,7 @@ private boolean isDisableSubMappingMethodsGeneration() { /** * Creates a forged assignment from the provided {@code sourceRHS} and {@code forgedMethod}. If a mapping method - * for the {@code forgedMethod} already exists, then this method used for the assignment. + * for the {@code forgedMethod} already exists, this method will be used for the assignment. * * @param sourceRHS that needs to be used for the assignment * @param forgedMethod the forged method for which we want to create an {@link Assignment} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java index 1c38b1062b..889d602cdb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java @@ -36,14 +36,14 @@ public static AnnotatedConstructor forComponentModels(String name, if ( constructor instanceof NoArgumentConstructor ) { noArgumentConstructor = (NoArgumentConstructor) constructor; } - NoArgumentConstructor noArgConstructorToInBecluded = null; + NoArgumentConstructor noArgConstructorToBeIncluded = null; Set fragmentsToBeIncluded = Collections.emptySet(); if ( includeNoArgConstructor ) { if ( noArgumentConstructor != null ) { - noArgConstructorToInBecluded = noArgumentConstructor; + noArgConstructorToBeIncluded = noArgumentConstructor; } else { - noArgConstructorToInBecluded = new NoArgumentConstructor( name, fragmentsToBeIncluded ); + noArgConstructorToBeIncluded = new NoArgumentConstructor( name, fragmentsToBeIncluded ); } } else if ( noArgumentConstructor != null ) { @@ -53,7 +53,7 @@ else if ( noArgumentConstructor != null ) { name, mapperReferences, annotations, - noArgConstructorToInBecluded, + noArgConstructorToBeIncluded, fragmentsToBeIncluded ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java index 297dcaa4a3..9a4fba6e30 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java @@ -33,7 +33,7 @@ public List> getMatchingMethods(List int addToCandidateListIfMinimal(List> candidatesWithBestMathingType, + private int addToCandidateListIfMinimal(List> candidatesWithBestMatchingType, int bestMatchingTypeDistance, SelectedMethod method, int currentTypeDistance) { if ( currentTypeDistance == bestMatchingTypeDistance ) { - candidatesWithBestMathingType.add( method ); + candidatesWithBestMatchingType.add( method ); } else if ( currentTypeDistance < bestMatchingTypeDistance ) { bestMatchingTypeDistance = currentTypeDistance; - candidatesWithBestMathingType.clear(); - candidatesWithBestMathingType.add( method ); + candidatesWithBestMatchingType.clear(); + candidatesWithBestMatchingType.add( method ); } return bestMatchingTypeDistance; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java index fb797f5808..91a30902e0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java @@ -12,7 +12,7 @@ import org.mapstruct.ap.internal.model.source.Method; /** - * Selector that tries to resolve an ambiquity between methods that contain source parameters and + * Selector that tries to resolve an ambiguity between methods that contain source parameters and * {@link org.mapstruct.ap.internal.model.common.SourceRHS SourceRHS} type parameters. * @author Filip Hrisafov */ From 8de18e5a65a353e6ff12f6a8f130a08173bf9672 Mon Sep 17 00:00:00 2001 From: hsjni0110 <92289935+hsjni0110@users.noreply.github.com> Date: Thu, 14 Nov 2024 05:35:20 +0900 Subject: [PATCH 277/363] fix typos in method and variable names (#3766) --- .../internal/model/source/SourceMethod.java | 2 +- .../processor/MapperCreationProcessor.java | 26 +++++++++---------- .../processor/MethodRetrievalProcessor.java | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) 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 7103fb2858..8427dd3eca 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 @@ -186,7 +186,7 @@ public Builder setPrototypeMethods(List prototypeMethods) { return this; } - public Builder setDefininingType(Type definingType) { + public Builder setDefiningType(Type definingType) { this.definingType = definingType; return this; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 5fc1b07823..3b38a05b59 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -195,7 +195,7 @@ private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List< addAllFieldsIn( mappingContext.getUsedSupportedMappings(), supportingFieldSet ); fields.addAll( supportingFieldSet ); - // handle constructorfragments + // handle constructor fragments Set constructorFragments = new LinkedHashSet<>(); addAllFragmentsIn( mappingContext.getUsedSupportedMappings(), constructorFragments ); @@ -632,7 +632,7 @@ else if ( nameFilteredcandidates.size() > 1 ) { reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, inverseConfiguration ); } else { - reportErrorWhenAmbigousReverseMapping( candidates, method, inverseConfiguration ); + reportErrorWhenAmbiguousReverseMapping( candidates, method, inverseConfiguration ); } } } @@ -702,21 +702,21 @@ else if ( sourceMethod.getName().equals( name ) ) { else if ( candidates.size() > 1 ) { // ambiguity: find a matching method that matches configuredBy - List nameFilteredcandidates = new ArrayList<>(); + List nameFilteredCandidates = new ArrayList<>(); for ( SourceMethod candidate : candidates ) { if ( candidate.getName().equals( name ) ) { - nameFilteredcandidates.add( candidate ); + nameFilteredCandidates.add( candidate ); } } - if ( nameFilteredcandidates.size() == 1 ) { - resultMethod = first( nameFilteredcandidates ); + if ( nameFilteredCandidates.size() == 1 ) { + resultMethod = first( nameFilteredCandidates ); } - else if ( nameFilteredcandidates.size() > 1 ) { - reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, inheritConfiguration ); + else if ( nameFilteredCandidates.size() > 1 ) { + reportErrorWhenSeveralNamesMatch( nameFilteredCandidates, method, inheritConfiguration ); } else { - reportErrorWhenAmbigousMapping( candidates, method, inheritConfiguration ); + reportErrorWhenAmbiguousMapping( candidates, method, inheritConfiguration ); } } } @@ -729,8 +729,8 @@ private AnnotationMirror getAnnotationMirror(InheritConfigurationGem inheritConf return inheritConfiguration == null ? null : inheritConfiguration.mirror(); } - private void reportErrorWhenAmbigousReverseMapping(List candidates, SourceMethod method, - InheritInverseConfigurationGem inverseGem) { + private void reportErrorWhenAmbiguousReverseMapping(List candidates, SourceMethod method, + InheritInverseConfigurationGem inverseGem) { List candidateNames = new ArrayList<>(); for ( SourceMethod candidate : candidates ) { @@ -780,8 +780,8 @@ private void reportErrorWhenNonMatchingName(SourceMethod onlyCandidate, SourceMe ); } - private void reportErrorWhenAmbigousMapping(List candidates, SourceMethod method, - InheritConfigurationGem gem) { + private void reportErrorWhenAmbiguousMapping(List candidates, SourceMethod method, + InheritConfigurationGem gem) { List candidateNames = new ArrayList<>(); for ( SourceMethod candidate : candidates ) { 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 4bd2ed48a5..2c8ad5dcb9 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 @@ -389,7 +389,7 @@ private SourceMethod getReferencedMethod(TypeElement usedMapper, ExecutableType return new SourceMethod.Builder() .setDeclaringMapper( usedMapper.equals( mapperToImplement ) ? null : usedMapperAsType ) - .setDefininingType( definingType ) + .setDefiningType( definingType ) .setExecutable( method ) .setParameters( parameters ) .setReturnType( returnType ) From bee983cd3c627333a613a58be555090ac754da95 Mon Sep 17 00:00:00 2001 From: Minji Kim <101392857+alsswl@users.noreply.github.com> Date: Sat, 16 Nov 2024 06:13:36 +0900 Subject: [PATCH 278/363] Fix typos in comments (#3769) --- distribution/pom.xml | 2 +- .../org/mapstruct/ap/internal/model/BeanMappingMethod.java | 6 +++--- .../mapstruct/ap/internal/model/MappingBuilderContext.java | 2 +- .../internal/model/NestedTargetPropertyMappingHolder.java | 4 ++-- .../org/mapstruct/ap/internal/model/ValueMappingMethod.java | 4 ++-- .../java/org/mapstruct/ap/spi/AccessorNamingStrategy.java | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/distribution/pom.xml b/distribution/pom.xml index f826421092..db654236e4 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -130,7 +130,7 @@ enumToStringMapping(Method method, Type sourceType ) return mappings; } - // Start to fill the mappings with the defined valuemappings + // Start to fill the mappings with the defined valueMappings for ( ValueMappingOptions valueMapping : valueMappings.regularValueMappings ) { mappings.add( new MappingEntry( valueMapping.getSource(), valueMapping.getTarget() ) ); unmappedSourceConstants.remove( valueMapping.getSource() ); @@ -305,7 +305,7 @@ private List stringToEnumMapping(Method method, Type targetType ) } Set mappedSources = new LinkedHashSet<>(); - // Start to fill the mappings with the defined valuemappings + // Start to fill the mappings with the defined value mappings for ( ValueMappingOptions valueMapping : valueMappings.regularValueMappings ) { mappedSources.add( valueMapping.getSource() ); mappings.add( new MappingEntry( valueMapping.getSource(), valueMapping.getTarget() ) ); diff --git a/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java index c9d0734753..b4dde64324 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java @@ -68,7 +68,7 @@ default void init(MapStructProcessingEnvironment processingEnvironment) { * * @return getter name for collection properties * - * @deprecated MapStuct will not call this method anymore. Use {@link #getMethodType(ExecutableElement)} to + * @deprecated MapStruct will not call this method anymore. Use {@link #getMethodType(ExecutableElement)} to * determine the {@link MethodType}. When collections somehow need to be treated special, it should be done in * {@link #getMethodType(ExecutableElement) } as well. In the future, this method will be removed. */ From 737af6b50a1641d936c8a5b152bb97bf1f843d18 Mon Sep 17 00:00:00 2001 From: Roman Obolonskyi <65775868+Obolrom@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:46:59 +0200 Subject: [PATCH 279/363] #3628 Add support for locale parameter for numberFormat and dateFormat --- .../java/org/mapstruct/IterableMapping.java | 19 + .../main/java/org/mapstruct/MapMapping.java | 27 + core/src/main/java/org/mapstruct/Mapping.java | 19 + .../BigDecimalToStringConversion.java | 20 +- .../BigIntegerToStringConversion.java | 20 +- .../internal/conversion/ConversionUtils.java | 12 + .../conversion/CreateDecimalFormat.java | 37 +- .../conversion/DateToStringConversion.java | 35 +- .../PrimitiveToStringConversion.java | 39 +- .../conversion/WrapperToStringConversion.java | 39 +- .../model/common/ConversionContext.java | 2 + .../common/DefaultConversionContext.java | 7 + .../model/common/FormattingParameters.java | 10 +- .../model/source/IterableMappingOptions.java | 3 +- .../model/source/MapMappingOptions.java | 14 +- .../internal/model/source/MappingOptions.java | 9 +- .../conversion/CreateDecimalFormat.ftl | 7 +- .../common/DefaultConversionContextTest.java | 6 +- .../conversion/date/DateConversionTest.java | 120 ++++ .../conversion/date/SourceTargetMapper.java | 24 +- .../numbers/NumberFormatConversionTest.java | 126 ++++- .../numbers/SourceTargetMapper.java | 41 +- .../numbers/SourceTargetMapperImpl.java | 525 ++++++++++++++++++ .../numbers/SourceTargetMapperImpl.java | 525 ++++++++++++++++++ 24 files changed, 1631 insertions(+), 55 deletions(-) create mode 100644 processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java diff --git a/core/src/main/java/org/mapstruct/IterableMapping.java b/core/src/main/java/org/mapstruct/IterableMapping.java index 76153127bd..d644dfe03b 100644 --- a/core/src/main/java/org/mapstruct/IterableMapping.java +++ b/core/src/main/java/org/mapstruct/IterableMapping.java @@ -66,19 +66,38 @@ /** * A format string as processable by {@link SimpleDateFormat} if the annotated method maps from an iterable of * {@code String} to an iterable {@link Date} or vice-versa. Will be ignored for all other element types. + *

      + * If the {@link #locale()} is also specified, the format will consider the specified locale when processing + * the date. Otherwise, the system's default locale will be used. * * @return A date format string as processable by {@link SimpleDateFormat}. + * @see #locale() */ String dateFormat() default ""; /** * A format string as processable by {@link DecimalFormat} if the annotated method maps from a * {@link Number} to a {@link String} or vice-versa. Will be ignored for all other element types. + *

      + * If the {@link #locale()} is also specified, the number format will be applied in the context of the given locale. + * Otherwise, the system's default locale will be used to process the number format. * * @return A decimal format string as processable by {@link DecimalFormat}. + * @see #locale() */ String numberFormat() default ""; + /** + * Specifies the locale to be used when processing {@link #dateFormat()} or {@link #numberFormat()}. + *

      + * The locale should be a plain tag representing the language, such as "en" for English, "de" for German, etc. + *

      + * If no locale is specified, the system's default locale will be used. + * + * @return A string representing the locale to be used when formatting dates or numbers. + */ + String locale() default ""; + /** * A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple * mappers (hand written of internal) qualify and result in an 'Ambiguous mapping methods found' error. diff --git a/core/src/main/java/org/mapstruct/MapMapping.java b/core/src/main/java/org/mapstruct/MapMapping.java index 271272bb45..093099cf5a 100644 --- a/core/src/main/java/org/mapstruct/MapMapping.java +++ b/core/src/main/java/org/mapstruct/MapMapping.java @@ -56,8 +56,12 @@ /** * A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with key type * {@code String} to an map with key type {@link Date} or vice-versa. Will be ignored for all other key types. + *

      + * If the {@link #locale()} is specified, the format will consider the specified locale when processing the date. + * Otherwise, the system's default locale will be used. * * @return A date format string as processable by {@link SimpleDateFormat}. + * @see #locale() */ String keyDateFormat() default ""; @@ -65,27 +69,50 @@ * A format string as processable by {@link SimpleDateFormat} if the annotated method maps from a map with value * type {@code String} to an map with value type {@link Date} or vice-versa. Will be ignored for all other value * types. + *

      + * If the {@link #locale()} is specified, the format will consider the specified locale when processing the date. + * Otherwise, the system's default locale will be used. * * @return A date format string as processable by {@link SimpleDateFormat}. + * @see #locale() */ String valueDateFormat() default ""; /** * A format string as processable by {@link DecimalFormat} if the annotated method maps from a * {@link Number} to a {@link String} or vice-versa. Will be ignored for all other key types. + *

      + * If the {@link #locale()} is specified, the number format will be applied in the context of the given locale. + * Otherwise, the system's default locale will be used. * * @return A decimal format string as processable by {@link DecimalFormat}. + * @see #locale() */ String keyNumberFormat() default ""; /** * A format string as processable by {@link DecimalFormat} if the annotated method maps from a * {@link Number} to a {@link String} or vice-versa. Will be ignored for all other value types. + *

      + * If the {@link #locale()} is specified, the number format will be applied in the context of the given locale. + * Otherwise, the system's default locale will be used. * * @return A decimal format string as processable by {@link DecimalFormat}. + * @see #locale() */ String valueNumberFormat() default ""; + /** + * Specifies the locale to be used when processing {@link SimpleDateFormat} or {@link DecimalFormat} for key or + * value mappings in maps. The locale should be a plain tag representing the language, such as "en" for English, + * "de" for German, etc. + *

      + * If no locale is specified, the system's default locale will be used. + * + * @return A string representing the locale to be used when formatting dates or numbers in maps. + */ + String locale() default ""; + /** * A key value qualifier can be specified to aid the selection process of a suitable mapper. This is useful in * case multiple mappers (hand written of internal) qualify and result in an 'Ambiguous mapping methods found' diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 8b0c4adb0c..6c95b0db2c 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -175,19 +175,38 @@ /** * A format string as processable by {@link SimpleDateFormat} if the attribute is mapped from {@code String} to * {@link Date} or vice-versa. Will be ignored for all other attribute types and when mapping enum constants. + *

      + * If the {@link #locale()} is also specified, the format will consider the specified locale when processing + * the date. Otherwise, the system's default locale will be used. * * @return A date format string as processable by {@link SimpleDateFormat}. + * @see #locale() */ String dateFormat() default ""; /** * A format string as processable by {@link DecimalFormat} if the annotated method maps from a * {@link Number} to a {@link String} or vice-versa. Will be ignored for all other element types. + *

      + * If the {@link #locale()} is also specified, the number format will be applied in the context of the given locale. + * Otherwise, the system's default locale will be used to process the number format. * * @return A decimal format string as processable by {@link DecimalFormat}. + * @see #locale() */ String numberFormat() default ""; + /** + * Specifies the locale to be used when processing {@link #dateFormat()} or {@link #numberFormat()}. + *

      + * The locale should be a plain tag representing the language, such as "en" for English, "de" for German, etc. + *

      + * If no locale is specified, the system's default locale will be used. + * + * @return A string representing the locale to be used when formatting dates or numbers. + */ + String locale() default ""; + /** * A constant {@link String} based on which the specified target property is to be set. *

      diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java index 92a93e3893..384013a7b3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java @@ -14,8 +14,9 @@ import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; -import static org.mapstruct.ap.internal.util.Collections.asSet; import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigDecimal; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between {@link BigDecimal} and {@link String}. @@ -64,18 +65,31 @@ protected Set getFromConversionImportTypes(ConversionContext conversionCon public List getRequiredHelperMethods(ConversionContext conversionContext) { List helpers = new ArrayList<>(); if ( conversionContext.getNumberFormat() != null ) { - helpers.add( new CreateDecimalFormat( conversionContext.getTypeFactory() ) ); + helpers.add( new CreateDecimalFormat( + conversionContext.getTypeFactory(), + conversionContext.getLocale() != null + ) ); } return helpers; } private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversionContext) { - sb.append( "createDecimalFormat( " ); + boolean withLocale = conversionContext.getLocale() != null; + sb.append( "createDecimalFormat" ); + if ( withLocale ) { + sb.append( "WithLocale" ); + } + sb.append( "( " ); if ( conversionContext.getNumberFormat() != null ) { sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); } + if ( withLocale ) { + sb.append( ", " ).append( locale( conversionContext ) ).append( ".forLanguageTag( \"" ); + sb.append( conversionContext.getLocale() ); + sb.append( "\" )" ); + } sb.append( " )" ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java index df0a48a673..540d89db58 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java @@ -15,9 +15,10 @@ import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Type; -import static org.mapstruct.ap.internal.util.Collections.asSet; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigDecimal; import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigInteger; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between {@link BigInteger} and {@link String}. @@ -72,18 +73,31 @@ protected Set getFromConversionImportTypes(ConversionContext conversionCon public List getRequiredHelperMethods(ConversionContext conversionContext) { List helpers = new ArrayList<>(); if ( conversionContext.getNumberFormat() != null ) { - helpers.add( new CreateDecimalFormat( conversionContext.getTypeFactory() ) ); + helpers.add( new CreateDecimalFormat( + conversionContext.getTypeFactory(), + conversionContext.getLocale() != null + ) ); } return helpers; } private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversionContext) { - sb.append( "createDecimalFormat( " ); + boolean withLocale = conversionContext.getLocale() != null; + sb.append( "createDecimalFormat" ); + if ( withLocale ) { + sb.append( "WithLocale" ); + } + sb.append( "( " ); if ( conversionContext.getNumberFormat() != null ) { sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); } + if ( withLocale ) { + sb.append( ", " ).append( locale( conversionContext ) ).append( ".forLanguageTag( \"" ); + sb.append( conversionContext.getLocale() ); + sb.append( "\" )" ); + } sb.append( " )" ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java index aa01a73276..96960c4a11 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java @@ -11,6 +11,7 @@ import java.sql.Time; import java.sql.Timestamp; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; @@ -279,4 +280,15 @@ public static String url(ConversionContext conversionContext) { return typeReferenceName( conversionContext, URL.class ); } + /** + * Name for {@link java.text.DecimalFormatSymbols}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String decimalFormatSymbols(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, DecimalFormatSymbols.class ); + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java index 77c59445ab..d1b49cff69 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java @@ -5,9 +5,11 @@ */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; import java.util.Set; import org.mapstruct.ap.internal.model.HelperMethod; @@ -16,6 +18,8 @@ import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.MappingMethodOptions; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * HelperMethod that creates a {@link java.text.DecimalFormat} * @@ -27,13 +31,30 @@ public class CreateDecimalFormat extends HelperMethod { private final Parameter parameter; + private final Parameter localeParameter; private final Type returnType; private final Set importTypes; - public CreateDecimalFormat(TypeFactory typeFactory) { + public CreateDecimalFormat(TypeFactory typeFactory, boolean withLocale) { this.parameter = new Parameter( "numberFormat", typeFactory.getType( String.class ) ); + this.localeParameter = withLocale ? new Parameter( "locale", typeFactory.getType( Locale.class ) ) : null; this.returnType = typeFactory.getType( DecimalFormat.class ); - this.importTypes = asSet( parameter.getType(), returnType ); + if ( withLocale ) { + this.importTypes = asSet( + parameter.getType(), + returnType, + typeFactory.getType( DecimalFormatSymbols.class ), + typeFactory.getType( Locale.class ) + ); + } + else { + this.importTypes = asSet( parameter.getType(), returnType ); + } + } + + @Override + public String getName() { + return localeParameter == null ? "createDecimalFormat" : "createDecimalFormatWithLocale"; } @Override @@ -60,4 +81,12 @@ public MappingMethodOptions getOptions() { public String describe() { return null; } + + @Override + public List getParameters() { + if ( localeParameter == null ) { + return super.getParameters(); + } + return Arrays.asList( getParameter(), localeParameter ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java index 361ce9d9ab..35c7f88d74 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToStringConversion.java @@ -10,15 +10,18 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Locale; +import java.util.Set; import org.mapstruct.ap.internal.model.HelperMethod; import org.mapstruct.ap.internal.model.TypeConversion; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; -import static java.util.Arrays.asList; -import static org.mapstruct.ap.internal.util.Collections.asSet; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; import static org.mapstruct.ap.internal.conversion.ConversionUtils.simpleDateFormat; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between {@link String} and {@link Date}. @@ -29,7 +32,7 @@ public class DateToStringConversion implements ConversionProvider { @Override public Assignment to(ConversionContext conversionContext) { - return new TypeConversion( asSet( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ), + return new TypeConversion( getImportTypes( conversionContext ), Collections.emptyList(), getConversionExpression( conversionContext, "format" ) ); @@ -37,8 +40,8 @@ public Assignment to(ConversionContext conversionContext) { @Override public Assignment from(ConversionContext conversionContext) { - return new TypeConversion( asSet( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ), - asList( conversionContext.getTypeFactory().getType( ParseException.class ) ), + return new TypeConversion( getImportTypes( conversionContext ), + Collections.singletonList( conversionContext.getTypeFactory().getType( ParseException.class ) ), getConversionExpression( conversionContext, "parse" ) ); } @@ -48,6 +51,17 @@ public List getRequiredHelperMethods(ConversionContext conversionC return Collections.emptyList(); } + private Set getImportTypes(ConversionContext conversionContext) { + if ( conversionContext.getLocale() == null ) { + return Collections.singleton( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ); + } + + return asSet( + conversionContext.getTypeFactory().getType( SimpleDateFormat.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + private String getConversionExpression(ConversionContext conversionContext, String method) { StringBuilder conversionString = new StringBuilder( "new " ); conversionString.append( simpleDateFormat( conversionContext ) ); @@ -56,7 +70,16 @@ private String getConversionExpression(ConversionContext conversionContext, Stri if ( conversionContext.getDateFormat() != null ) { conversionString.append( " \"" ); conversionString.append( conversionContext.getDateFormat() ); - conversionString.append( "\" " ); + conversionString.append( "\"" ); + + if ( conversionContext.getLocale() != null ) { + conversionString.append( ", " ).append( locale( conversionContext ) ).append( ".forLanguageTag( \"" ); + conversionString.append( conversionContext.getLocale() ); + conversionString.append( "\" ) " ); + } + else { + conversionString.append( " " ); + } } conversionString.append( ")." ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java index fcc7241290..909ce8c0f2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java @@ -6,7 +6,9 @@ package org.mapstruct.ap.internal.conversion; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.Collections; +import java.util.Locale; import java.util.Set; import org.mapstruct.ap.internal.model.common.ConversionContext; @@ -15,6 +17,9 @@ import org.mapstruct.ap.internal.util.Strings; import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormat; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormatSymbols; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between primitive types such as {@code byte} or {@code long} and @@ -53,9 +58,15 @@ public String getToExpression(ConversionContext conversionContext) { @Override public Set getToConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); } return Collections.emptySet(); @@ -80,9 +91,15 @@ public String getFromExpression(ConversionContext conversionContext) { @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); } return Collections.emptySet(); @@ -97,6 +114,16 @@ private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversi sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); + + if ( conversionContext.getLocale() != null ) { + sb.append( ", " ) + .append( decimalFormatSymbols( conversionContext ) ) + .append( ".getInstance( " ) + .append( locale( conversionContext ) ) + .append( ".forLanguageTag( \"" ) + .append( conversionContext.getLocale() ) + .append( " \" ) )" ); + } } sb.append( " )" ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java index 300c901216..dd7b6bac8d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java @@ -6,7 +6,9 @@ package org.mapstruct.ap.internal.conversion; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.Collections; +import java.util.Locale; import java.util.Set; import org.mapstruct.ap.internal.model.common.ConversionContext; @@ -15,6 +17,9 @@ import org.mapstruct.ap.internal.util.Strings; import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormat; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormatSymbols; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between wrapper types such as {@link Integer} and {@link String}. @@ -52,9 +57,15 @@ public String getToExpression(ConversionContext conversionContext) { @Override public Set getToConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); } return Collections.emptySet(); @@ -79,9 +90,15 @@ public String getFromExpression(ConversionContext conversionContext) { @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); } return Collections.emptySet(); @@ -96,6 +113,16 @@ private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversi sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); + + if ( conversionContext.getLocale() != null ) { + sb.append( ", " ) + .append( decimalFormatSymbols( conversionContext ) ) + .append( ".getInstance( " ) + .append( locale( conversionContext ) ) + .append( ".forLanguageTag( \"" ) + .append( conversionContext.getLocale() ) + .append( " \" ) )" ); + } } sb.append( " )" ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java index c2aa73f307..96d3d6fe78 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java @@ -32,6 +32,8 @@ public interface ConversionContext { String getNumberFormat(); + String getLocale(); + TypeFactory getTypeFactory(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java index f5a9fcc761..159f1663e2 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java @@ -21,6 +21,7 @@ public class DefaultConversionContext implements ConversionContext { private final FormattingParameters formattingParameters; private final String dateFormat; private final String numberFormat; + private final String locale; private final TypeFactory typeFactory; public DefaultConversionContext(TypeFactory typeFactory, FormattingMessager messager, Type sourceType, @@ -32,6 +33,7 @@ public DefaultConversionContext(TypeFactory typeFactory, FormattingMessager mess this.formattingParameters = formattingParameters; this.dateFormat = this.formattingParameters.getDate(); this.numberFormat = this.formattingParameters.getNumber(); + this.locale = this.formattingParameters.getLocale(); validateDateFormat(); } @@ -64,6 +66,11 @@ public String getNumberFormat() { return numberFormat; } + @Override + public String getLocale() { + return locale != null ? locale.toString() : null; + } + @Override public String getDateFormat() { return dateFormat; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java index e21f5d74fa..48cee4bbe5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java @@ -15,21 +15,23 @@ */ public class FormattingParameters { - public static final FormattingParameters EMPTY = new FormattingParameters( null, null, null, null, null ); + public static final FormattingParameters EMPTY = new FormattingParameters( null, null, null, null, null, null ); private final String date; private final String number; private final AnnotationMirror mirror; private final AnnotationValue dateAnnotationValue; private final Element element; + private final String locale; public FormattingParameters(String date, String number, AnnotationMirror mirror, - AnnotationValue dateAnnotationValue, Element element) { + AnnotationValue dateAnnotationValue, Element element, String locale) { this.date = date; this.number = number; this.mirror = mirror; this.dateAnnotationValue = dateAnnotationValue; this.element = element; + this.locale = locale; } public String getDate() { @@ -51,4 +53,8 @@ public AnnotationValue getDateAnnotationValue() { public Element getElement() { return element; } + + public String getLocale() { + return locale; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java index c4770efde6..50fca3e4d2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java @@ -55,7 +55,8 @@ public static IterableMappingOptions fromGem(IterableMappingGem iterableMapping, iterableMapping.numberFormat().get(), iterableMapping.mirror(), iterableMapping.dateFormat().getAnnotationValue(), - method + method, + iterableMapping.locale().getValue() ); IterableMappingOptions options = diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java index fd1758d8d2..9f3d12faf3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java @@ -8,14 +8,14 @@ import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; -import org.mapstruct.ap.internal.util.ElementUtils; -import org.mapstruct.ap.internal.util.TypeUtils; -import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.gem.MapMappingGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.tools.gem.GemValue; /** @@ -47,6 +47,8 @@ public static MapMappingOptions fromGem(MapMappingGem mapMapping, MapperOptions return options; } + String locale = mapMapping.locale().getValue(); + SelectionParameters keySelection = new SelectionParameters( mapMapping.keyQualifiedBy().get(), mapMapping.keyQualifiedByName().get(), @@ -66,7 +68,8 @@ public static MapMappingOptions fromGem(MapMappingGem mapMapping, MapperOptions mapMapping.keyNumberFormat().get(), mapMapping.mirror(), mapMapping.keyDateFormat().getAnnotationValue(), - method + method, + locale ); FormattingParameters valueFormatting = new FormattingParameters( @@ -74,7 +77,8 @@ public static MapMappingOptions fromGem(MapMappingGem mapMapping, MapperOptions mapMapping.valueNumberFormat().get(), mapMapping.mirror(), mapMapping.valueDateFormat().getAnnotationValue(), - method + method, + locale ); MapMappingOptions options = new MapMappingOptions( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index 22f9ccdc2f..746aca5a32 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -18,16 +18,16 @@ import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; -import org.mapstruct.ap.internal.util.ElementUtils; -import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.gem.MappingGem; import org.mapstruct.ap.internal.gem.MappingsGem; import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.tools.gem.GemValue; /** @@ -118,6 +118,8 @@ public static void addInstance(MappingGem mapping, ExecutableElement method, String conditionExpression = getConditionExpression( mapping, method, messager ); String dateFormat = mapping.dateFormat().getValue(); String numberFormat = mapping.numberFormat().getValue(); + String locale = mapping.locale().getValue(); + String defaultValue = mapping.defaultValue().getValue(); Set dependsOn = mapping.dependsOn().hasValue() ? @@ -129,7 +131,8 @@ public static void addInstance(MappingGem mapping, ExecutableElement method, numberFormat, mapping.mirror(), mapping.dateFormat().getAnnotationValue(), - method + method, + locale ); SelectionParameters selectionParams = new SelectionParameters( mapping.qualifiedBy().get(), diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl index ce0f605f11..6753a73d6f 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl @@ -5,9 +5,10 @@ Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 --> -private DecimalFormat ${name}( String numberFormat ) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private DecimalFormat ${name}( <#list parameters as param><@includeModel object=param/><#if param_has_next>, ) { - DecimalFormat df = new DecimalFormat( numberFormat ); + DecimalFormat df = new DecimalFormat( numberFormat<#if parameters.size() > 1>, DecimalFormatSymbols.getInstance( locale ) ); df.setParseBigDecimal( true ); return df; -} \ No newline at end of file +} 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 ce2f895e0f..401a6b2240 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 @@ -70,7 +70,7 @@ public void testInvalidDateFormatValidation() { statefulMessagerMock, type, type, - new FormattingParameters( "qwertz", null, null, null, null ) + new FormattingParameters( "qwertz", null, null, null, null, null ) ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isEqualTo( Diagnostic.Kind.ERROR ); } @@ -84,7 +84,7 @@ public void testNullDateFormatValidation() { statefulMessagerMock, type, type, - new FormattingParameters( null, null, null, null, null ) + new FormattingParameters( null, null, null, null, null, null ) ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isNull(); } @@ -97,7 +97,7 @@ public void testUnsupportedType() { statefulMessagerMock, type, type, - new FormattingParameters( "qwertz", null, null, null, null ) + new FormattingParameters( "qwertz", null, null, null, null, null ) ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isNull(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java index 9ccf8e7e1f..bc90228206 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java @@ -54,6 +54,21 @@ public void shouldApplyDateFormatForConversions() { assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13 00:00" ); } + @ProcessorTest + @EnabledOnJre( JRE.JAVA_8 ) + // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ + public void shouldApplyDateFormatForConversionsWithCustomLocale() { + Source source = new Source(); + source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getDate() ).isEqualTo( "juillet 06, 2013" ); + assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); + } + @ProcessorTest @EnabledForJreRange(min = JRE.JAVA_11) // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ @@ -69,6 +84,21 @@ public void shouldApplyDateFormatForConversionsJdk11() { assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); } + @ProcessorTest + @EnabledForJreRange(min = JRE.JAVA_11) + // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ + public void shouldApplyDateFormatForConversionsJdk11WithCustomLocale() { + Source source = new Source(); + source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getDate() ).isEqualTo( "juillet 06, 2013" ); + assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); + } + @ProcessorTest @EnabledOnJre(JRE.JAVA_8) // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ @@ -86,6 +116,23 @@ public void shouldApplyDateFormatForConversionInReverseMapping() { ); } + @ProcessorTest + @EnabledOnJre(JRE.JAVA_8) + // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ + public void shouldApplyDateFormatForConversionInReverseMappingWithCustomLocale() { + Target target = new Target(); + target.setDate( "juillet 06, 2013" ); + target.setAnotherDate( "14.02.13 8:30" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + assertThat( source.getAnotherDate() ).isEqualTo( + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() + ); + } + @ProcessorTest @EnabledForJreRange(min = JRE.JAVA_11) // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ @@ -103,6 +150,23 @@ public void shouldApplyDateFormatForConversionInReverseMappingJdk11() { ); } + @ProcessorTest + @EnabledForJreRange(min = JRE.JAVA_11) + // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ + public void shouldApplyDateFormatForConversionInReverseMappingJdk11WithCustomLocale() { + Target target = new Target(); + target.setDate( "juillet 06, 2013" ); + target.setAnotherDate( "14.02.13, 8:30" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + assertThat( source.getAnotherDate() ).isEqualTo( + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() + ); + } + @ProcessorTest public void shouldApplyStringConversionForIterableMethod() { List dates = Arrays.asList( @@ -117,6 +181,20 @@ public void shouldApplyStringConversionForIterableMethod() { assertThat( stringDates ).containsExactly( "06.07.2013", "14.02.2013", "11.04.2013" ); } + @ProcessorTest + public void shouldApplyStringConversionForIterableMethodWithCustomLocale() { + List dates = Arrays.asList( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + + List stringDates = SourceTargetMapper.INSTANCE.stringListToDateListWithCustomLocale( dates ); + + assertThat( stringDates ).isNotNull(); + assertThat( stringDates ).containsExactly( "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" ); + } + @ProcessorTest public void shouldApplyStringConversionForArrayMethod() { List dates = Arrays.asList( @@ -131,6 +209,20 @@ public void shouldApplyStringConversionForArrayMethod() { assertThat( stringDates ).isEqualTo( new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" } ); } + @ProcessorTest + public void shouldApplyStringConversionForArrayMethodWithCustomLocale() { + List dates = Arrays.asList( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + + String[] stringDates = SourceTargetMapper.INSTANCE.stringListToDateArrayWithCustomLocale( dates ); + + assertThat( stringDates ).isNotNull(); + assertThat( stringDates ).isEqualTo( new String[]{ "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" } ); + } + @ProcessorTest public void shouldApplyStringConversionForReverseIterableMethod() { List stringDates = Arrays.asList( "06.07.2013", "14.02.2013", "11.04.2013" ); @@ -145,6 +237,20 @@ public void shouldApplyStringConversionForReverseIterableMethod() { ); } + @ProcessorTest + public void shouldApplyStringConversionForReverseIterableMethodWithCustomLocale() { + List stringDates = Arrays.asList( "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" ); + + List dates = SourceTargetMapper.INSTANCE.dateListToStringListWithCustomLocale( stringDates ); + + assertThat( dates ).isNotNull(); + assertThat( dates ).containsExactly( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + } + @ProcessorTest public void shouldApplyStringConversionForReverseArrayMethod() { String[] stringDates = new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" }; @@ -159,6 +265,20 @@ public void shouldApplyStringConversionForReverseArrayMethod() { ); } + @ProcessorTest + public void shouldApplyStringConversionForReverseArrayMethodWithCustomLocale() { + String[] stringDates = new String[]{ "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" }; + + List dates = SourceTargetMapper.INSTANCE.stringArrayToDateListWithCustomLocale( stringDates ); + + assertThat( dates ).isNotNull(); + assertThat( dates ).containsExactly( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + } + @ProcessorTest public void shouldApplyStringConversionForReverseArrayArrayMethod() { Date[] dates = new Date[]{ diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java index 2ef6a7bd96..1971a8c066 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java @@ -22,21 +22,39 @@ public interface SourceTargetMapper { @Mapping(target = "date", dateFormat = "dd.MM.yyyy") Target sourceToTarget(Source source); - @InheritInverseConfiguration + @Mapping(target = "date", dateFormat = "MMMM dd, yyyy", locale = "fr") + Target sourceToTargetWithCustomLocale(Source source); + + @InheritInverseConfiguration(name = "sourceToTarget") Source targetToSource(Target target); + @InheritInverseConfiguration(name = "sourceToTargetWithCustomLocale") + Source targetToSourceWithCustomLocale(Target target); + @IterableMapping(dateFormat = "dd.MM.yyyy") List stringListToDateList(List dates); + @IterableMapping(dateFormat = "MMMM dd, yyyy", locale = "fr") + List stringListToDateListWithCustomLocale(List dates); + @IterableMapping(dateFormat = "dd.MM.yyyy") String[] stringListToDateArray(List dates); - @InheritInverseConfiguration + @IterableMapping(dateFormat = "MMMM dd, yyyy", locale = "fr") + String[] stringListToDateArrayWithCustomLocale(List dates); + + @InheritInverseConfiguration(name = "stringListToDateList") List dateListToStringList(List strings); - @InheritInverseConfiguration + @InheritInverseConfiguration(name = "stringListToDateListWithCustomLocale") + List dateListToStringListWithCustomLocale(List strings); + + @InheritInverseConfiguration(name = "stringListToDateArray") List stringArrayToDateList(String[] dates); + @InheritInverseConfiguration(name = "stringListToDateArrayWithCustomLocale") + List stringArrayToDateListWithCustomLocale(String[] dates); + @IterableMapping(dateFormat = "dd.MM.yyyy") String[] dateArrayToStringArray(Date[] dates); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java index 6dfc1b3ca1..39b4e98319 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java @@ -7,14 +7,15 @@ import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.junit.jupiter.api.extension.RegisterExtension; import org.junitpioneer.jupiter.DefaultLocale; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -27,6 +28,10 @@ @DefaultLocale("en") public class NumberFormatConversionTest { + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( SourceTargetMapper.class ); + @ProcessorTest public void shouldApplyStringConversions() { Source source = new Source(); @@ -68,6 +73,47 @@ public void shouldApplyStringConversions() { assertThat( target.getBigInteger1() ).isEqualTo( "1.23456789E12" ); } + @ProcessorTest + public void shouldApplyStringConversionsWithCustomLocale() { + Source source = new Source(); + source.setI( 1 ); + source.setIi( 2 ); + source.setD( 3.0 ); + source.setDd( 4.0 ); + source.setF( 3.0f ); + source.setFf( 4.0f ); + source.setL( 5L ); + source.setLl( 6L ); + source.setB( (byte) 7 ); + source.setBb( (byte) 8 ); + + source.setComplex1( 345346.456756 ); + source.setComplex2( 5007034.3 ); + + source.setBigDecimal1( new BigDecimal( "987E-20" ) ); + source.setBigInteger1( new BigInteger( "1234567890000" ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getI() ).isEqualTo( "1.00" ); + assertThat( target.getIi() ).isEqualTo( "2.00" ); + assertThat( target.getD() ).isEqualTo( "3.00" ); + assertThat( target.getDd() ).isEqualTo( "4.00" ); + assertThat( target.getF() ).isEqualTo( "3.00" ); + assertThat( target.getFf() ).isEqualTo( "4.00" ); + assertThat( target.getL() ).isEqualTo( "5.00" ); + assertThat( target.getLl() ).isEqualTo( "6.00" ); + assertThat( target.getB() ).isEqualTo( "7.00" ); + assertThat( target.getBb() ).isEqualTo( "8.00" ); + + assertThat( target.getComplex1() ).isEqualTo( "345.35E3" ); + assertThat( target.getComplex2() ).isEqualTo( "$5007034.30" ); + + assertThat( target.getBigDecimal1() ).isEqualTo( "9,87E-18" ); + assertThat( target.getBigInteger1() ).isEqualTo( "1,23456789E12" ); + } + @ProcessorTest public void shouldApplyReverseStringConversions() { Target target = new Target(); @@ -109,17 +155,73 @@ public void shouldApplyReverseStringConversions() { assertThat( source.getBigInteger1() ).isEqualTo( new BigInteger( "1234567890000" ) ); } + @ProcessorTest + public void shouldApplyReverseStringConversionsWithCustomLocale() { + Target target = new Target(); + target.setI( "1.00" ); + target.setIi( "2.00" ); + target.setD( "3.00" ); + target.setDd( "4.00" ); + target.setF( "3.00" ); + target.setFf( "4.00" ); + target.setL( "5.00" ); + target.setLl( "6.00" ); + target.setB( "7.00" ); + target.setBb( "8.00" ); + + target.setComplex1( "345.35E3" ); + target.setComplex2( "$5007034.30" ); + + target.setBigDecimal1( "9,87E-18" ); + target.setBigInteger1( "1,23456789E12" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getI() ).isEqualTo( 1 ); + assertThat( source.getIi() ).isEqualTo( Integer.valueOf( 2 ) ); + assertThat( source.getD() ).isEqualTo( 3.0 ); + assertThat( source.getDd() ).isEqualTo( Double.valueOf( 4.0 ) ); + assertThat( source.getF() ).isEqualTo( 3.0f ); + assertThat( source.getFf() ).isEqualTo( Float.valueOf( 4.0f ) ); + assertThat( source.getL() ).isEqualTo( 5L ); + assertThat( source.getLl() ).isEqualTo( Long.valueOf( 6L ) ); + assertThat( source.getB() ).isEqualTo( (byte) 7 ); + assertThat( source.getBb() ).isEqualTo( (byte) 8 ); + + assertThat( source.getComplex1() ).isEqualTo( 345350.0 ); + assertThat( source.getComplex2() ).isEqualTo( 5007034.3 ); + + assertThat( source.getBigDecimal1() ).isEqualTo( new BigDecimal( "987E-20" ) ); + assertThat( source.getBigInteger1() ).isEqualTo( new BigInteger( "1234567890000" ) ); + } + @ProcessorTest public void shouldApplyStringConversionsToIterables() { - List target = SourceTargetMapper.INSTANCE.sourceToTarget( Arrays.asList( 2f ) ); + List target = SourceTargetMapper.INSTANCE.sourceToTarget( List.of( 2f ) ); assertThat( target ).hasSize( 1 ); - assertThat( target ).isEqualTo( Arrays.asList( "2.00" ) ); + assertThat( target ).isEqualTo( List.of( "2.00" ) ); List source = SourceTargetMapper.INSTANCE.targetToSource( target ); assertThat( source ).hasSize( 1 ); - assertThat( source ).isEqualTo( Arrays.asList( 2.00f ) ); + assertThat( source ).isEqualTo( List.of( 2.00f ) ); + } + + @ProcessorTest + public void shouldApplyStringConversionsToIterablesWithCustomLocale() { + + List target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( + List.of( new BigDecimal("987E-20") ) + ); + + assertThat( target ).hasSize( 1 ); + assertThat( target ).isEqualTo( List.of( "9,87E-18" ) ); + + List source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + assertThat( source ).hasSize( 1 ); + assertThat( source ).isEqualTo( List.of( new BigDecimal("987E-20") ) ); } @ProcessorTest @@ -137,4 +239,20 @@ public void shouldApplyStringConversionsToMaps() { assertThat( source2 ).contains( entry( 1.00f, 2f ) ); } + + @ProcessorTest + public void shouldApplyStringConversionsToMapsWithCustomLocale() { + + Map source1 = new HashMap<>(); + source1.put( new BigDecimal( "987E-20" ), new BigDecimal( "97E-10" ) ); + + Map target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source1 ); + assertThat( target ).hasSize( 1 ); + assertThat( target ).contains( entry( "9,87E-18", "9,7E-9" ) ); + + Map source2 = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + assertThat( source2 ).hasSize( 1 ); + assertThat( source2 ).contains( entry( new BigDecimal( "987E-20" ), new BigDecimal( "97E-10" ) ) ); + + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.java index 537452fca2..b82a7a6199 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.conversion.numbers; +import java.math.BigDecimal; import java.util.List; import java.util.Map; import org.mapstruct.InheritInverseConfiguration; @@ -41,21 +42,55 @@ public interface SourceTargetMapper { } ) Target sourceToTarget(Source source); - @InheritInverseConfiguration + @Mappings( { + @Mapping( target = "i", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "ii", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "d", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "dd", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "f", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "ff", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "l", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "ll", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "b", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "bb", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "complex1", numberFormat = "##0.##E0", locale = "ru" ), + @Mapping( target = "complex2", numberFormat = "$#.00", locale = "ru" ), + @Mapping( target = "bigDecimal1", numberFormat = "#0.#E0", locale = "ru" ), + @Mapping( target = "bigInteger1", numberFormat = "0.#############E0", locale = "ru" ) + + } ) + Target sourceToTargetWithCustomLocale(Source source); + + @InheritInverseConfiguration( name = "sourceToTarget" ) Source targetToSource(Target target); + @InheritInverseConfiguration( name = "sourceToTargetWithCustomLocale" ) + Source targetToSourceWithCustomLocale(Target target); + @IterableMapping( numberFormat = NUMBER_FORMAT ) List sourceToTarget(List source); - @InheritInverseConfiguration + @InheritInverseConfiguration( name = "sourceToTarget" ) List targetToSource(List source); + @IterableMapping( numberFormat = "#0.#E0", locale = "fr" ) + List sourceToTargetWithCustomLocale(List source); + + @InheritInverseConfiguration( name = "sourceToTargetWithCustomLocale" ) + List targetToSourceWithCustomLocale(List source); + @MapMapping( keyNumberFormat = NUMBER_FORMAT, valueNumberFormat = "##" ) Map sourceToTarget(Map source); - @InheritInverseConfiguration + @MapMapping( keyNumberFormat = "#0.#E0", valueNumberFormat = "0.#############E0", locale = "fr" ) + Map sourceToTargetWithCustomLocale(Map source); + + @InheritInverseConfiguration( name = "sourceToTarget" ) Map targetToSource(Map source); + @InheritInverseConfiguration( name = "sourceToTargetWithCustomLocale" ) + Map targetToSourceWithCustomLocale(Map source); + } diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java new file mode 100644 index 0000000000..ccf8042e6b --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java @@ -0,0 +1,525 @@ +/* + * 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.conversion.numbers; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-09-14T11:37:30+0300", + comments = "version: , compiler: javac, environment: Java 21.0.2 (Amazon.com Inc.)" +) +public class SourceTargetMapperImpl implements SourceTargetMapper { + + @Override + public Target sourceToTarget(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + target.setI( new DecimalFormat( "##.00" ).format( source.getI() ) ); + if ( source.getIi() != null ) { + target.setIi( new DecimalFormat( "##.00" ).format( source.getIi() ) ); + } + target.setD( new DecimalFormat( "##.00" ).format( source.getD() ) ); + if ( source.getDd() != null ) { + target.setDd( new DecimalFormat( "##.00" ).format( source.getDd() ) ); + } + target.setF( new DecimalFormat( "##.00" ).format( source.getF() ) ); + if ( source.getFf() != null ) { + target.setFf( new DecimalFormat( "##.00" ).format( source.getFf() ) ); + } + target.setL( new DecimalFormat( "##.00" ).format( source.getL() ) ); + if ( source.getLl() != null ) { + target.setLl( new DecimalFormat( "##.00" ).format( source.getLl() ) ); + } + target.setB( new DecimalFormat( "##.00" ).format( source.getB() ) ); + if ( source.getBb() != null ) { + target.setBb( new DecimalFormat( "##.00" ).format( source.getBb() ) ); + } + target.setComplex1( new DecimalFormat( "##0.##E0" ).format( source.getComplex1() ) ); + target.setComplex2( new DecimalFormat( "$#.00" ).format( source.getComplex2() ) ); + if ( source.getBigDecimal1() != null ) { + target.setBigDecimal1( createDecimalFormat( "#0.#E0" ).format( source.getBigDecimal1() ) ); + } + if ( source.getBigInteger1() != null ) { + target.setBigInteger1( createDecimalFormat( "0.#############E0" ).format( source.getBigInteger1() ) ); + } + + return target; + } + + @Override + public Target sourceToTargetWithCustomLocale(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + target.setI( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getI() ) ); + if ( source.getIi() != null ) { + target.setIi( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getIi() ) ); + } + target.setD( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getD() ) ); + if ( source.getDd() != null ) { + target.setDd( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getDd() ) ); + } + target.setF( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getF() ) ); + if ( source.getFf() != null ) { + target.setFf( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getFf() ) ); + } + target.setL( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getL() ) ); + if ( source.getLl() != null ) { + target.setLl( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getLl() ) ); + } + target.setB( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getB() ) ); + if ( source.getBb() != null ) { + target.setBb( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getBb() ) ); + } + target.setComplex1( new DecimalFormat( "##0.##E0", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getComplex1() ) ); + target.setComplex2( new DecimalFormat( "$#.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getComplex2() ) ); + if ( source.getBigDecimal1() != null ) { + target.setBigDecimal1( createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "ru" ) ).format( source.getBigDecimal1() ) ); + } + if ( source.getBigInteger1() != null ) { + target.setBigInteger1( createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "ru" ) ).format( source.getBigInteger1() ) ); + } + + return target; + } + + @Override + public Source targetToSource(Target target) { + if ( target == null ) { + return null; + } + + Source source = new Source(); + + try { + if ( target.getI() != null ) { + source.setI( new DecimalFormat( "##.00" ).parse( target.getI() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getIi() != null ) { + source.setIi( new DecimalFormat( "##.00" ).parse( target.getIi() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getD() != null ) { + source.setD( new DecimalFormat( "##.00" ).parse( target.getD() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getDd() != null ) { + source.setDd( new DecimalFormat( "##.00" ).parse( target.getDd() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getF() != null ) { + source.setF( new DecimalFormat( "##.00" ).parse( target.getF() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getFf() != null ) { + source.setFf( new DecimalFormat( "##.00" ).parse( target.getFf() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getL() != null ) { + source.setL( new DecimalFormat( "##.00" ).parse( target.getL() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getLl() != null ) { + source.setLl( new DecimalFormat( "##.00" ).parse( target.getLl() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getB() != null ) { + source.setB( new DecimalFormat( "##.00" ).parse( target.getB() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBb() != null ) { + source.setBb( new DecimalFormat( "##.00" ).parse( target.getBb() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex1() != null ) { + source.setComplex1( new DecimalFormat( "##0.##E0" ).parse( target.getComplex1() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex2() != null ) { + source.setComplex2( new DecimalFormat( "$#.00" ).parse( target.getComplex2() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigDecimal1() != null ) { + source.setBigDecimal1( (BigDecimal) createDecimalFormat( "#0.#E0" ).parse( target.getBigDecimal1() ) ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigInteger1() != null ) { + source.setBigInteger1( ( (BigDecimal) createDecimalFormat( "0.#############E0" ).parse( target.getBigInteger1() ) ).toBigInteger() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + + return source; + } + + @Override + public Source targetToSourceWithCustomLocale(Target target) { + if ( target == null ) { + return null; + } + + Source source = new Source(); + + try { + if ( target.getI() != null ) { + source.setI( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getI() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getIi() != null ) { + source.setIi( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getIi() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getD() != null ) { + source.setD( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getD() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getDd() != null ) { + source.setDd( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getDd() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getF() != null ) { + source.setF( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getF() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getFf() != null ) { + source.setFf( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getFf() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getL() != null ) { + source.setL( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getL() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getLl() != null ) { + source.setLl( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getLl() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getB() != null ) { + source.setB( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getB() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBb() != null ) { + source.setBb( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getBb() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex1() != null ) { + source.setComplex1( new DecimalFormat( "##0.##E0", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getComplex1() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex2() != null ) { + source.setComplex2( new DecimalFormat( "$#.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getComplex2() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigDecimal1() != null ) { + source.setBigDecimal1( (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "ru" ) ).parse( target.getBigDecimal1() ) ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigInteger1() != null ) { + source.setBigInteger1( ( (BigDecimal) createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "ru" ) ).parse( target.getBigInteger1() ) ).toBigInteger() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + + return source; + } + + @Override + public List sourceToTarget(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( Float float1 : source ) { + list.add( new DecimalFormat( "##.00" ).format( float1 ) ); + } + + return list; + } + + @Override + public List targetToSource(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( String string : source ) { + try { + list.add( new DecimalFormat( "##.00" ).parse( string ).floatValue() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + } + + return list; + } + + @Override + public List sourceToTargetWithCustomLocale(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( BigDecimal bigDecimal : source ) { + list.add( createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).format( bigDecimal ) ); + } + + return list; + } + + @Override + public List targetToSourceWithCustomLocale(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( String string : source ) { + try { + list.add( (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).parse( string ) ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + } + + return list; + } + + @Override + public Map sourceToTarget(Map source) { + if ( source == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( source.size() ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + String key = new DecimalFormat( "##.00" ).format( entry.getKey() ); + String value = new DecimalFormat( "##" ).format( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public Map sourceToTargetWithCustomLocale(Map source) { + if ( source == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( source.size() ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + String key = createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).format( entry.getKey() ); + String value = createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "fr" ) ).format( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public Map targetToSource(Map source) { + if ( source == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( source.size() ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + Float key; + try { + key = new DecimalFormat( "##.00" ).parse( entry.getKey() ).floatValue(); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + Float value; + try { + value = new DecimalFormat( "##" ).parse( entry.getValue() ).floatValue(); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + map.put( key, value ); + } + + return map; + } + + @Override + public Map targetToSourceWithCustomLocale(Map source) { + if ( source == null ) { + return null; + } + + Map map = LinkedHashMap.newLinkedHashMap( source.size() ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + BigDecimal key; + try { + key = (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).parse( entry.getKey() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + BigDecimal value; + try { + value = (BigDecimal) createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "fr" ) ).parse( entry.getValue() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + map.put( key, value ); + } + + return map; + } + + private DecimalFormat createDecimalFormatWithLocale( String numberFormat, Locale locale ) { + + DecimalFormat df = new DecimalFormat( numberFormat, DecimalFormatSymbols.getInstance( locale ) ); + df.setParseBigDecimal( true ); + return df; + } + + private DecimalFormat createDecimalFormat( String numberFormat ) { + + DecimalFormat df = new DecimalFormat( numberFormat ); + df.setParseBigDecimal( true ); + return df; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java new file mode 100644 index 0000000000..0b536b8327 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java @@ -0,0 +1,525 @@ +/* + * 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.conversion.numbers; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2024-09-14T11:36:20+0300", + comments = "version: , compiler: javac, environment: Java 17.0.10 (Private Build)" +) +public class SourceTargetMapperImpl implements SourceTargetMapper { + + @Override + public Target sourceToTarget(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + target.setI( new DecimalFormat( "##.00" ).format( source.getI() ) ); + if ( source.getIi() != null ) { + target.setIi( new DecimalFormat( "##.00" ).format( source.getIi() ) ); + } + target.setD( new DecimalFormat( "##.00" ).format( source.getD() ) ); + if ( source.getDd() != null ) { + target.setDd( new DecimalFormat( "##.00" ).format( source.getDd() ) ); + } + target.setF( new DecimalFormat( "##.00" ).format( source.getF() ) ); + if ( source.getFf() != null ) { + target.setFf( new DecimalFormat( "##.00" ).format( source.getFf() ) ); + } + target.setL( new DecimalFormat( "##.00" ).format( source.getL() ) ); + if ( source.getLl() != null ) { + target.setLl( new DecimalFormat( "##.00" ).format( source.getLl() ) ); + } + target.setB( new DecimalFormat( "##.00" ).format( source.getB() ) ); + if ( source.getBb() != null ) { + target.setBb( new DecimalFormat( "##.00" ).format( source.getBb() ) ); + } + target.setComplex1( new DecimalFormat( "##0.##E0" ).format( source.getComplex1() ) ); + target.setComplex2( new DecimalFormat( "$#.00" ).format( source.getComplex2() ) ); + if ( source.getBigDecimal1() != null ) { + target.setBigDecimal1( createDecimalFormat( "#0.#E0" ).format( source.getBigDecimal1() ) ); + } + if ( source.getBigInteger1() != null ) { + target.setBigInteger1( createDecimalFormat( "0.#############E0" ).format( source.getBigInteger1() ) ); + } + + return target; + } + + @Override + public Target sourceToTargetWithCustomLocale(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + target.setI( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getI() ) ); + if ( source.getIi() != null ) { + target.setIi( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getIi() ) ); + } + target.setD( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getD() ) ); + if ( source.getDd() != null ) { + target.setDd( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getDd() ) ); + } + target.setF( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getF() ) ); + if ( source.getFf() != null ) { + target.setFf( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getFf() ) ); + } + target.setL( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getL() ) ); + if ( source.getLl() != null ) { + target.setLl( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getLl() ) ); + } + target.setB( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getB() ) ); + if ( source.getBb() != null ) { + target.setBb( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getBb() ) ); + } + target.setComplex1( new DecimalFormat( "##0.##E0", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getComplex1() ) ); + target.setComplex2( new DecimalFormat( "$#.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).format( source.getComplex2() ) ); + if ( source.getBigDecimal1() != null ) { + target.setBigDecimal1( createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "ru" ) ).format( source.getBigDecimal1() ) ); + } + if ( source.getBigInteger1() != null ) { + target.setBigInteger1( createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "ru" ) ).format( source.getBigInteger1() ) ); + } + + return target; + } + + @Override + public Source targetToSource(Target target) { + if ( target == null ) { + return null; + } + + Source source = new Source(); + + try { + if ( target.getI() != null ) { + source.setI( new DecimalFormat( "##.00" ).parse( target.getI() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getIi() != null ) { + source.setIi( new DecimalFormat( "##.00" ).parse( target.getIi() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getD() != null ) { + source.setD( new DecimalFormat( "##.00" ).parse( target.getD() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getDd() != null ) { + source.setDd( new DecimalFormat( "##.00" ).parse( target.getDd() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getF() != null ) { + source.setF( new DecimalFormat( "##.00" ).parse( target.getF() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getFf() != null ) { + source.setFf( new DecimalFormat( "##.00" ).parse( target.getFf() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getL() != null ) { + source.setL( new DecimalFormat( "##.00" ).parse( target.getL() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getLl() != null ) { + source.setLl( new DecimalFormat( "##.00" ).parse( target.getLl() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getB() != null ) { + source.setB( new DecimalFormat( "##.00" ).parse( target.getB() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBb() != null ) { + source.setBb( new DecimalFormat( "##.00" ).parse( target.getBb() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex1() != null ) { + source.setComplex1( new DecimalFormat( "##0.##E0" ).parse( target.getComplex1() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex2() != null ) { + source.setComplex2( new DecimalFormat( "$#.00" ).parse( target.getComplex2() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigDecimal1() != null ) { + source.setBigDecimal1( (BigDecimal) createDecimalFormat( "#0.#E0" ).parse( target.getBigDecimal1() ) ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigInteger1() != null ) { + source.setBigInteger1( ( (BigDecimal) createDecimalFormat( "0.#############E0" ).parse( target.getBigInteger1() ) ).toBigInteger() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + + return source; + } + + @Override + public Source targetToSourceWithCustomLocale(Target target) { + if ( target == null ) { + return null; + } + + Source source = new Source(); + + try { + if ( target.getI() != null ) { + source.setI( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getI() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getIi() != null ) { + source.setIi( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getIi() ).intValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getD() != null ) { + source.setD( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getD() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getDd() != null ) { + source.setDd( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getDd() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getF() != null ) { + source.setF( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getF() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getFf() != null ) { + source.setFf( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getFf() ).floatValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getL() != null ) { + source.setL( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getL() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getLl() != null ) { + source.setLl( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getLl() ).longValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getB() != null ) { + source.setB( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getB() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBb() != null ) { + source.setBb( new DecimalFormat( "##.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getBb() ).byteValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex1() != null ) { + source.setComplex1( new DecimalFormat( "##0.##E0", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getComplex1() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getComplex2() != null ) { + source.setComplex2( new DecimalFormat( "$#.00", DecimalFormatSymbols.getInstance( Locale.forLanguageTag( "ru " ) ) ).parse( target.getComplex2() ).doubleValue() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigDecimal1() != null ) { + source.setBigDecimal1( (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "ru" ) ).parse( target.getBigDecimal1() ) ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + try { + if ( target.getBigInteger1() != null ) { + source.setBigInteger1( ( (BigDecimal) createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "ru" ) ).parse( target.getBigInteger1() ) ).toBigInteger() ); + } + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + + return source; + } + + @Override + public List sourceToTarget(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( Float float1 : source ) { + list.add( new DecimalFormat( "##.00" ).format( float1 ) ); + } + + return list; + } + + @Override + public List targetToSource(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( String string : source ) { + try { + list.add( new DecimalFormat( "##.00" ).parse( string ).floatValue() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + } + + return list; + } + + @Override + public List sourceToTargetWithCustomLocale(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( BigDecimal bigDecimal : source ) { + list.add( createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).format( bigDecimal ) ); + } + + return list; + } + + @Override + public List targetToSourceWithCustomLocale(List source) { + if ( source == null ) { + return null; + } + + List list = new ArrayList( source.size() ); + for ( String string : source ) { + try { + list.add( (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).parse( string ) ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + } + + return list; + } + + @Override + public Map sourceToTarget(Map source) { + if ( source == null ) { + return null; + } + + Map map = new LinkedHashMap( Math.max( (int) ( source.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + String key = new DecimalFormat( "##.00" ).format( entry.getKey() ); + String value = new DecimalFormat( "##" ).format( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public Map sourceToTargetWithCustomLocale(Map source) { + if ( source == null ) { + return null; + } + + Map map = new LinkedHashMap( Math.max( (int) ( source.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + String key = createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).format( entry.getKey() ); + String value = createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "fr" ) ).format( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public Map targetToSource(Map source) { + if ( source == null ) { + return null; + } + + Map map = new LinkedHashMap( Math.max( (int) ( source.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + Float key; + try { + key = new DecimalFormat( "##.00" ).parse( entry.getKey() ).floatValue(); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + Float value; + try { + value = new DecimalFormat( "##" ).parse( entry.getValue() ).floatValue(); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + map.put( key, value ); + } + + return map; + } + + @Override + public Map targetToSourceWithCustomLocale(Map source) { + if ( source == null ) { + return null; + } + + Map map = new LinkedHashMap( Math.max( (int) ( source.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : source.entrySet() ) { + BigDecimal key; + try { + key = (BigDecimal) createDecimalFormatWithLocale( "#0.#E0", Locale.forLanguageTag( "fr" ) ).parse( entry.getKey() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + BigDecimal value; + try { + value = (BigDecimal) createDecimalFormatWithLocale( "0.#############E0", Locale.forLanguageTag( "fr" ) ).parse( entry.getValue() ); + } + catch ( ParseException e ) { + throw new RuntimeException( e ); + } + map.put( key, value ); + } + + return map; + } + + private DecimalFormat createDecimalFormatWithLocale( String numberFormat, Locale locale ) { + + DecimalFormat df = new DecimalFormat( numberFormat, DecimalFormatSymbols.getInstance( locale ) ); + df.setParseBigDecimal( true ); + return df; + } + + private DecimalFormat createDecimalFormat( String numberFormat ) { + + DecimalFormat df = new DecimalFormat( numberFormat ); + df.setParseBigDecimal( true ); + return df; + } +} From f3d2b2e65b5709b7575076a220b938ece7931d21 Mon Sep 17 00:00:00 2001 From: dudxor4587 Date: Fri, 22 Nov 2024 07:14:16 +0900 Subject: [PATCH 280/363] Delete unnecessary conditions and modify return statement (#3772) --- .../mapstruct/ap/internal/conversion/Conversions.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 6acb69492a..5090c903f3 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -268,10 +268,10 @@ private void registerNativeTypeConversion(Class sourceType, Class targetTy if ( sourceType.isPrimitive() && targetType.isPrimitive() ) { register( sourceType, targetType, new PrimitiveToPrimitiveConversion( sourceType ) ); } - else if ( sourceType.isPrimitive() && !targetType.isPrimitive() ) { + else if ( sourceType.isPrimitive() ) { register( sourceType, targetType, new PrimitiveToWrapperConversion( sourceType, targetType ) ); } - else if ( !sourceType.isPrimitive() && targetType.isPrimitive() ) { + else if ( targetType.isPrimitive() ) { register( sourceType, targetType, inverse( new PrimitiveToWrapperConversion( targetType, sourceType ) ) ); } else { @@ -390,11 +390,7 @@ public boolean equals(Object obj) { return false; } - if ( !Objects.equals( targetType, other.targetType ) ) { - return false; - } - - return true; + return Objects.equals( targetType, other.targetType ); } } } From 084cf3abc148255404b0e2b79c878e68c8d606d1 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Fri, 29 Nov 2024 15:33:41 +0700 Subject: [PATCH 281/363] Fix javadoc typos (#3780) --- .../java/org/mapstruct/NullValuePropertyMappingStrategy.java | 2 +- .../ap/internal/model/NestedTargetPropertyMappingHolder.java | 2 +- .../java/org/mapstruct/ap/internal/model/source/Method.java | 2 +- .../main/java/org/mapstruct/ap/internal/util/NativeTypes.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java index 9e06e723b6..556d4253b1 100644 --- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -10,7 +10,7 @@ * {@link NullValuePropertyMappingStrategy} can be defined on {@link MapperConfig}, {@link Mapper}, {@link BeanMapping} * and {@link Mapping}. * Precedence is arranged in the reverse order. So {@link Mapping} will override {@link BeanMapping}, will - * overide {@link Mapper} + * override {@link Mapper} * * The enum only applies to update methods: methods that update a pre-existing target (annotated with * {@code @}{@link MappingTarget}). diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index ea05b84cae..2b407e240c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -64,7 +64,7 @@ public List getProcessedSourceParameters() { } /** - * @return all the targets that were hanled + * @return all the targets that were handled */ public Set getHandledTargets() { return handledTargets; 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 ad2882080a..bea4d56101 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 @@ -83,7 +83,7 @@ public interface Method { Parameter getMappingTargetParameter(); /** - * Returns whether the meethod is designated as bean factory for + * Returns whether the method is designated as bean factory for * mapping target {@link org.mapstruct.ObjectFactory } * * @return true if it is a target bean factory. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java index 729cb180ef..0a4ca0cde0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java @@ -174,7 +174,7 @@ void removeAndValidateFloatingPointLiteralSuffix() { boolean endsWithDSuffix = PTRN_DOUBLE.matcher( val ).find(); // error handling if ( isFloat && endsWithDSuffix ) { - throw new NumberFormatException( "Assiging double to a float" ); + throw new NumberFormatException( "Assigning double to a float" ); } // remove suffix if ( endsWithLSuffix || endsWithFSuffix || endsWithDSuffix ) { From f98a742f9805dad05653d8fcbae05c085725e4da Mon Sep 17 00:00:00 2001 From: jinhyogyeom <148544516+jinhyogyeom@users.noreply.github.com> Date: Sun, 1 Dec 2024 19:22:51 +0900 Subject: [PATCH 282/363] Standardize Class Names to PascalCase in tests (#3773) --- .../referenced/AbstractSourceTargetMapperPrivate.java | 2 +- .../referenced/AbstractSourceTargetMapperProtected.java | 2 +- .../accessibility/referenced/ReferencedAccessibilityTest.java | 4 ++-- ...perPrivateBase.java => SourceTargetMapperPrivateBase.java} | 2 +- ...rotectedBase.java => SourceTargetMapperProtectedBase.java} | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/{SourceTargetmapperPrivateBase.java => SourceTargetMapperPrivateBase.java} (91%) rename processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/{SourceTargetmapperProtectedBase.java => SourceTargetMapperProtectedBase.java} (90%) diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java index 507e63a957..2a16c3980f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java @@ -14,7 +14,7 @@ * @author Sjaak Derksen */ @Mapper -public abstract class AbstractSourceTargetMapperPrivate extends SourceTargetmapperPrivateBase { +public abstract class AbstractSourceTargetMapperPrivate extends SourceTargetMapperPrivateBase { public static final AbstractSourceTargetMapperPrivate INSTANCE = Mappers.getMapper( AbstractSourceTargetMapperPrivate.class ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java index ef5df10285..ccdba61514 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java @@ -14,7 +14,7 @@ * @author Sjaak Derksen */ @Mapper -public abstract class AbstractSourceTargetMapperProtected extends SourceTargetmapperProtectedBase { +public abstract class AbstractSourceTargetMapperProtected extends SourceTargetMapperProtectedBase { public static final AbstractSourceTargetMapperProtected INSTANCE = Mappers.getMapper( AbstractSourceTargetMapperProtected.class ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java index 3cd62a8b4f..dc3d8ea10f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java @@ -72,12 +72,12 @@ public void shouldNotBeAbleToAccessDefaultMethodInReferencedInOtherPackage() { @ProcessorTest @IssueKey( "206" ) - @WithClasses( { AbstractSourceTargetMapperProtected.class, SourceTargetmapperProtectedBase.class } ) + @WithClasses( { AbstractSourceTargetMapperProtected.class, SourceTargetMapperProtectedBase.class } ) public void shouldBeAbleToAccessProtectedMethodInBase() { } @ProcessorTest @IssueKey("206") - @WithClasses({ AbstractSourceTargetMapperPrivate.class, SourceTargetmapperPrivateBase.class }) + @WithClasses({ AbstractSourceTargetMapperPrivate.class, SourceTargetMapperPrivateBase.class }) @ExpectedCompilationOutcome( value = CompilationResult.SUCCEEDED, diagnostics = { diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperPrivateBase.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivateBase.java similarity index 91% rename from processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperPrivateBase.java rename to processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivateBase.java index 94f9b2668f..06dbb0cec7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperPrivateBase.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivateBase.java @@ -8,7 +8,7 @@ /** * @author Sjaak Derksen */ -public class SourceTargetmapperPrivateBase { +public class SourceTargetMapperPrivateBase { @SuppressWarnings("unused") private ReferencedTarget sourceToTarget(ReferencedSource source) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperProtectedBase.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtectedBase.java similarity index 90% rename from processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperProtectedBase.java rename to processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtectedBase.java index 263d74bee6..fe5a50fa37 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperProtectedBase.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtectedBase.java @@ -9,7 +9,7 @@ * * @author Sjaak Derksen */ -public class SourceTargetmapperProtectedBase { +public class SourceTargetMapperProtectedBase { protected ReferencedTarget sourceToTarget( ReferencedSource source ) { ReferencedTarget target = new ReferencedTarget(); From 4812d2b030b199f6761fc8da031236d55bf3538b Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sat, 7 Dec 2024 14:57:13 +0100 Subject: [PATCH 283/363] Align README with v1.6.3 release (#3784) --- readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 6932f4d748..adb48b2fd4 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # MapStruct - Java bean mappings, the easy way! -[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.6.2-blue.svg)](https://central.sonatype.com/search?q=g:org.mapstruct%20v:1.6.2) +[![Latest Stable Version](https://img.shields.io/badge/Latest%20Stable%20Version-1.6.3-blue.svg)](https://central.sonatype.com/search?q=g:org.mapstruct%20v:1.6.3) [![Latest Version](https://img.shields.io/maven-central/v/org.mapstruct/mapstruct-processor.svg?maxAge=3600&label=Latest%20Release)](https://central.sonatype.com/search?q=g:org.mapstruct) [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/mapstruct/mapstruct/blob/main/LICENSE.txt) @@ -68,7 +68,7 @@ For Maven-based projects, add the following to your POM file in order to use Map ```xml ... - 1.6.2 + 1.6.3 ... @@ -114,10 +114,10 @@ plugins { dependencies { ... - implementation 'org.mapstruct:mapstruct:1.6.2' + implementation 'org.mapstruct:mapstruct:1.6.3' - annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.2' - testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.2' // if you are using mapstruct in test code + annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' + testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' // if you are using mapstruct in test code } ... ``` From 8f962919117c30c95165e5349314c0adea455d8f Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Sat, 18 Jan 2025 19:04:42 +0700 Subject: [PATCH 284/363] Fix documentation typo and code polish (#3787) --- .../src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc | 2 +- .../src/main/asciidoc/chapter-6-mapping-collections.asciidoc | 4 ++-- .../java/org/mapstruct/ap/spi/util/IntrospectorUtils.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index ed64580eb4..ea574603ea 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -302,7 +302,7 @@ If you don't want explicitly name all properties from nested source bean, you ca The generated code will map every property from `CustomerDto.record` to `Customer` directly, without need to manually name any of them. The same goes for `Customer.account`. -When there are conflicts, these can be resolved by explicitely defining the mapping. For instance in the example above. `name` occurs in `CustomerDto.record` and in `CustomerDto.account`. The mapping `@Mapping( target = "name", source = "record.name" )` resolves this conflict. +When there are conflicts, these can be resolved by explicitly defining the mapping. For instance in the example above. `name` occurs in `CustomerDto.record` and in `CustomerDto.account`. The mapping `@Mapping( target = "name", source = "record.name" )` resolves this conflict. This "target this" notation can be very useful when mapping hierarchical objects to flat objects and vice versa (`@InheritInverseConfiguration`). diff --git a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc index 17025da4de..79d4544f9b 100644 --- a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc +++ b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc @@ -192,7 +192,7 @@ The option `DEFAULT` should not be used explicitly. It is used to distinguish be [TIP] ==== -When working with an `adder` method and JPA entities, Mapstruct assumes that the target collections are initialized with a collection implementation (e.g. an `ArrayList`). You can use factories to create a new target entity with intialized collections instead of Mapstruct creating the target entity by its constructor. +When working with an `adder` method and JPA entities, Mapstruct assumes that the target collections are initialized with a collection implementation (e.g. an `ArrayList`). You can use factories to create a new target entity with initialized collections instead of Mapstruct creating the target entity by its constructor. ==== [[implementation-types-for-collection-mappings]] @@ -224,4 +224,4 @@ When an iterable or map mapping method declares an interface type as return type |`ConcurrentMap`|`ConcurrentHashMap` |`ConcurrentNavigableMap`|`ConcurrentSkipListMap` -|=== +|=== \ No newline at end of file diff --git a/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java index fccc22b38b..95dcd7a92d 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java @@ -32,7 +32,7 @@ private IntrospectorUtils() { * @return The decapitalized version of the string. */ public static String decapitalize(String name) { - if ( name == null || name.length() == 0 ) { + if ( name == null || name.isEmpty() ) { return name; } if ( name.length() > 1 && Character.isUpperCase( name.charAt( 1 ) ) && From e0a7d3d0e62695591eef532f63874729c56f64b7 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 17 Jan 2025 18:01:47 +0100 Subject: [PATCH 285/363] Use latest Maven Wrapper --- .mvn/wrapper/MavenWrapperDownloader.java | 117 ------ .mvn/wrapper/maven-wrapper.jar | Bin 50710 -> 0 bytes .mvn/wrapper/maven-wrapper.properties | 21 +- mvnw | 451 ++++++++++------------- mvnw.cmd | 281 +++++++------- 5 files changed, 343 insertions(+), 527 deletions(-) delete mode 100644 .mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 .mvn/wrapper/maven-wrapper.jar diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index b901097f2d..0000000000 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * 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. - */ -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50710 zcmbTd1CVCTmM+|7+wQV$+qP}n>auOywyU~q+qUhh+uxis_~*a##hm*_WW?9E7Pb7N%LRFiwbEGCJ0XP=%-6oeT$XZcYgtzC2~q zk(K08IQL8oTl}>>+hE5YRgXTB@fZ4TH9>7=79e`%%tw*SQUa9~$xKD5rS!;ZG@ocK zQdcH}JX?W|0_Afv?y`-NgLum62B&WSD$-w;O6G0Sm;SMX65z)l%m1e-g8Q$QTI;(Q z+x$xth4KFvH@Bs6(zn!iF#nenk^Y^ce;XIItAoCsow38eq?Y-Auh!1in#Rt-_D>H^ z=EjbclGGGa6VnaMGmMLj`x3NcwA43Jb(0gzl;RUIRAUDcR1~99l2SAPkVhoRMMtN} zXvC<tOmX83grD8GSo_Lo?%lNfhD#EBgPo z*nf@ppMC#B!T)Ae0RG$mlJWmGl7CkuU~B8-==5i;rS;8i6rJ=PoQxf446XDX9g|c> zU64ePyMlsI^V5Jq5A+BPe#e73+kpc_r1tv#B)~EZ;7^67F0*QiYfrk0uVW;Qb=NsG zN>gsuCwvb?s-KQIppEaeXtEMdc9dy6Dfduz-tMTms+i01{eD9JE&h?Kht*$eOl#&L zJdM_-vXs(V#$Ed;5wyNWJdPNh+Z$+;$|%qR(t`4W@kDhd*{(7-33BOS6L$UPDeE_53j${QfKN-0v-HG z(QfyvFNbwPK%^!eIo4ac1;b>c0vyf9}Xby@YY!lkz-UvNp zwj#Gg|4B~?n?G^{;(W;|{SNoJbHTMpQJ*Wq5b{l9c8(%?Kd^1?H1om1de0Da9M;Q=n zUfn{f87iVb^>Exl*nZ0hs(Yt>&V9$Pg`zX`AI%`+0SWQ4Zc(8lUDcTluS z5a_KerZWe}a-MF9#Cd^fi!y3%@RFmg&~YnYZ6<=L`UJ0v={zr)>$A;x#MCHZy1st7 ztT+N07NR+vOwSV2pvWuN1%lO!K#Pj0Fr>Q~R40{bwdL%u9i`DSM4RdtEH#cW)6}+I-eE< z&tZs+(Ogu(H_;$a$!7w`MH0r%h&@KM+<>gJL@O~2K2?VrSYUBbhCn#yy?P)uF3qWU z0o09mIik+kvzV6w>vEZy@&Mr)SgxPzUiDA&%07m17udz9usD82afQEps3$pe!7fUf z0eiidkJ)m3qhOjVHC_M(RYCBO%CZKZXFb8}s0-+}@CIn&EF(rRWUX2g^yZCvl0bI} zbP;1S)iXnRC&}5-Tl(hASKqdSnO?ASGJ*MIhOXIblmEudj(M|W!+I3eDc}7t`^mtg z)PKlaXe(OH+q-)qcQ8a@!llRrpGI8DsjhoKvw9T;TEH&?s=LH0w$EzI>%u;oD@x83 zJL7+ncjI9nn!TlS_KYu5vn%f*@qa5F;| zEFxY&B?g=IVlaF3XNm_03PA)=3|{n-UCgJoTr;|;1AU9|kPE_if8!Zvb}0q$5okF$ zHaJdmO&gg!9oN|M{!qGE=tb|3pVQ8PbL$}e;NgXz<6ZEggI}wO@aBP**2Wo=yN#ZC z4G$m^yaM9g=|&!^ft8jOLuzc3Psca*;7`;gnHm}tS0%f4{|VGEwu45KptfNmwxlE~ z^=r30gi@?cOm8kAz!EylA4G~7kbEiRlRIzwrb~{_2(x^$-?|#e6Bi_**(vyr_~9Of z!n>Gqf+Qwiu!xhi9f53=PM3`3tNF}pCOiPU|H4;pzjcsqbwg*{{kyrTxk<;mx~(;; z1NMrpaQ`57yn34>Jo3b|HROE(UNcQash!0p2-!Cz;{IRv#Vp5!3o$P8!%SgV~k&Hnqhp`5eLjTcy93cK!3Hm-$`@yGnaE=?;*2uSpiZTs_dDd51U%i z{|Zd9ou-;laGS_x=O}a+ zB||za<795A?_~Q=r=coQ+ZK@@ zId~hWQL<%)fI_WDIX#=(WNl!Dm$a&ROfLTd&B$vatq!M-2Jcs;N2vps$b6P1(N}=oI3<3luMTmC|0*{ zm1w8bt7vgX($!0@V0A}XIK)w!AzUn7vH=pZEp0RU0p?}ch2XC-7r#LK&vyc2=-#Q2 z^L%8)JbbcZ%g0Du;|8=q8B>X=mIQirpE=&Ox{TiuNDnOPd-FLI^KfEF729!!0x#Es z@>3ursjFSpu%C-8WL^Zw!7a0O-#cnf`HjI+AjVCFitK}GXO`ME&on|^=~Zc}^LBp9 zj=-vlN;Uc;IDjtK38l7}5xxQF&sRtfn4^TNtnzXv4M{r&ek*(eNbIu!u$>Ed%` z5x7+&)2P&4>0J`N&ZP8$vcR+@FS0126s6+Jx_{{`3ZrIMwaJo6jdrRwE$>IU_JTZ} z(||hyyQ)4Z1@wSlT94(-QKqkAatMmkT7pCycEB1U8KQbFX&?%|4$yyxCtm3=W`$4fiG0WU3yI@c zx{wfmkZAYE_5M%4{J-ygbpH|(|GD$2f$3o_Vti#&zfSGZMQ5_f3xt6~+{RX=$H8at z?GFG1Tmp}}lmm-R->ve*Iv+XJ@58p|1_jRvfEgz$XozU8#iJS})UM6VNI!3RUU!{5 zXB(+Eqd-E;cHQ>)`h0(HO_zLmzR3Tu-UGp;08YntWwMY-9i^w_u#wR?JxR2bky5j9 z3Sl-dQQU$xrO0xa&>vsiK`QN<$Yd%YXXM7*WOhnRdSFt5$aJux8QceC?lA0_if|s> ze{ad*opH_kb%M&~(~&UcX0nFGq^MqjxW?HJIP462v9XG>j(5Gat_)#SiNfahq2Mz2 zU`4uV8m$S~o9(W>mu*=h%Gs(Wz+%>h;R9Sg)jZ$q8vT1HxX3iQnh6&2rJ1u|j>^Qf`A76K%_ubL`Zu?h4`b=IyL>1!=*%!_K)=XC z6d}4R5L+sI50Q4P3upXQ3Z!~1ZXLlh!^UNcK6#QpYt-YC=^H=EPg3)z*wXo*024Q4b2sBCG4I# zlTFFY=kQ>xvR+LsuDUAk)q%5pEcqr(O_|^spjhtpb1#aC& zghXzGkGDC_XDa%t(X`E+kvKQ4zrQ*uuQoj>7@@ykWvF332)RO?%AA&Fsn&MNzmFa$ zWk&&^=NNjxLjrli_8ESU)}U|N{%j&TQmvY~lk!~Jh}*=^INA~&QB9em!in_X%Rl1&Kd~Z(u z9mra#<@vZQlOY+JYUwCrgoea4C8^(xv4ceCXcejq84TQ#sF~IU2V}LKc~Xlr_P=ry zl&Hh0exdCbVd^NPCqNNlxM3vA13EI8XvZ1H9#bT7y*U8Y{H8nwGpOR!e!!}*g;mJ#}T{ekSb}5zIPmye*If(}}_=PcuAW#yidAa^9-`<8Gr0 z)Fz=NiZ{)HAvw{Pl5uu)?)&i&Us$Cx4gE}cIJ}B4Xz~-q7)R_%owbP!z_V2=Aq%Rj z{V;7#kV1dNT9-6R+H}}(ED*_!F=~uz>&nR3gb^Ce%+0s#u|vWl<~JD3MvS0T9thdF zioIG3c#Sdsv;LdtRv3ml7%o$6LTVL>(H`^@TNg`2KPIk*8-IB}X!MT0`hN9Ddf7yN z?J=GxPL!uJ7lqwowsl?iRrh@#5C$%E&h~Z>XQcvFC*5%0RN-Opq|=IwX(dq(*sjs+ zqy99+v~m|6T#zR*e1AVxZ8djd5>eIeCi(b8sUk)OGjAsKSOg^-ugwl2WSL@d#?mdl zib0v*{u-?cq}dDGyZ%$XRY=UkQwt2oGu`zQneZh$=^! zj;!pCBWQNtvAcwcWIBM2y9!*W|8LmQy$H~5BEx)78J`4Z0(FJO2P^!YyQU{*Al+fs z){!4JvT1iLrJ8aU3k0t|P}{RN)_^v%$$r;+p0DY7N8CXzmS*HB*=?qaaF9D@#_$SN zSz{moAK<*RH->%r7xX~9gVW$l7?b|_SYI)gcjf0VAUJ%FcQP(TpBs; zg$25D!Ry_`8xpS_OJdeo$qh#7U+cepZ??TII7_%AXsT$B z=e)Bx#v%J0j``00Zk5hsvv6%T^*xGNx%KN-=pocSoqE5_R)OK%-Pbu^1MNzfds)mL zxz^F4lDKV9D&lEY;I+A)ui{TznB*CE$=9(wgE{m}`^<--OzV-5V4X2w9j(_!+jpTr zJvD*y6;39&T+==$F&tsRKM_lqa1HC}aGL0o`%c9mO=fts?36@8MGm7Vi{Y z^<7m$(EtdSr#22<(rm_(l_(`j!*Pu~Y>>xc>I9M#DJYDJNHO&4=HM%YLIp?;iR&$m z#_$ZWYLfGLt5FJZhr3jpYb`*%9S!zCG6ivNHYzNHcI%khtgHBliM^Ou}ZVD7ehU9 zS+W@AV=?Ro!=%AJ>Kcy9aU3%VX3|XM_K0A+ZaknKDyIS3S-Hw1C7&BSW5)sqj5Ye_ z4OSW7Yu-;bCyYKHFUk}<*<(@TH?YZPHr~~Iy%9@GR2Yd}J2!N9K&CN7Eq{Ka!jdu; zQNB*Y;i(7)OxZK%IHGt#Rt?z`I|A{q_BmoF!f^G}XVeTbe1Wnzh%1g>j}>DqFf;Rp zz7>xIs12@Ke0gr+4-!pmFP84vCIaTjqFNg{V`5}Rdt~xE^I;Bxp4)|cs8=f)1YwHz zqI`G~s2~qqDV+h02b`PQpUE#^^Aq8l%y2|ByQeXSADg5*qMprEAE3WFg0Q39`O+i1 z!J@iV!`Y~C$wJ!5Z+j5$i<1`+@)tBG$JL=!*uk=2k;T<@{|s1$YL079FvK%mPhyHV zP8^KGZnp`(hVMZ;s=n~3r2y;LTwcJwoBW-(ndU-$03{RD zh+Qn$ja_Z^OuMf3Ub|JTY74s&Am*(n{J3~@#OJNYuEVVJd9*H%)oFoRBkySGm`hx! zT3tG|+aAkXcx-2Apy)h^BkOyFTWQVeZ%e2@;*0DtlG9I3Et=PKaPt&K zw?WI7S;P)TWED7aSH$3hL@Qde?H#tzo^<(o_sv_2ci<7M?F$|oCFWc?7@KBj-;N$P zB;q!8@bW-WJY9do&y|6~mEruZAVe$!?{)N9rZZxD-|oltkhW9~nR8bLBGXw<632!l z*TYQn^NnUy%Ds}$f^=yQ+BM-a5X4^GHF=%PDrRfm_uqC zh{sKwIu|O0&jWb27;wzg4w5uA@TO_j(1X?8E>5Zfma|Ly7Bklq|s z9)H`zoAGY3n-+&JPrT!>u^qg9Evx4y@GI4$n-Uk_5wttU1_t?6><>}cZ-U+&+~JE) zPlDbO_j;MoxdLzMd~Ew|1o^a5q_1R*JZ=#XXMzg?6Zy!^hop}qoLQlJ{(%!KYt`MK z8umEN@Z4w!2=q_oe=;QttPCQy3Nm4F@x>@v4sz_jo{4m*0r%J(w1cSo;D_hQtJs7W z><$QrmG^+<$4{d2bgGo&3-FV}avg9zI|Rr(k{wTyl3!M1q+a zD9W{pCd%il*j&Ft z5H$nENf>>k$;SONGW`qo6`&qKs*T z2^RS)pXk9b@(_Fw1bkb)-oqK|v}r$L!W&aXA>IpcdNZ_vWE#XO8X`#Yp1+?RshVcd zknG%rPd*4ECEI0wD#@d+3NbHKxl}n^Sgkx==Iu%}HvNliOqVBqG?P2va zQ;kRJ$J6j;+wP9cS za#m;#GUT!qAV%+rdWolk+)6kkz4@Yh5LXP+LSvo9_T+MmiaP-eq6_k;)i6_@WSJ zlT@wK$zqHu<83U2V*yJ|XJU4farT#pAA&@qu)(PO^8PxEmPD4;Txpio+2)#!9 z>&=i7*#tc0`?!==vk>s7V+PL#S1;PwSY?NIXN2=Gu89x(cToFm))7L;< z+bhAbVD*bD=}iU`+PU+SBobTQ%S!=VL!>q$rfWsaaV}Smz>lO9JXT#`CcH_mRCSf4%YQAw`$^yY z3Y*^Nzk_g$xn7a_NO(2Eb*I=^;4f!Ra#Oo~LLjlcjke*k*o$~U#0ZXOQ5@HQ&T46l z7504MUgZkz2gNP1QFN8Y?nSEnEai^Rgyvl}xZfMUV6QrJcXp;jKGqB=D*tj{8(_pV zqyB*DK$2lgYGejmJUW)*s_Cv65sFf&pb(Yz8oWgDtQ0~k^0-wdF|tj}MOXaN@ydF8 zNr={U?=;&Z?wr^VC+`)S2xl}QFagy;$mG=TUs7Vi2wws5zEke4hTa2)>O0U?$WYsZ z<8bN2bB_N4AWd%+kncgknZ&}bM~eDtj#C5uRkp21hWW5gxWvc6b*4+dn<{c?w9Rmf zIVZKsPl{W2vQAlYO3yh}-{Os=YBnL8?uN5(RqfQ=-1cOiUnJu>KcLA*tQK3FU`_bM zM^T28w;nAj5EdAXFi&Kk1Nnl2)D!M{@+D-}bIEe+Lc4{s;YJc-{F#``iS2uk;2!Zp zF9#myUmO!wCeJIoi^A+T^e~20c+c2C}XltaR!|U-HfDA=^xF97ev}$l6#oY z&-&T{egB)&aV$3_aVA51XGiU07$s9vubh_kQG?F$FycvS6|IO!6q zq^>9|3U^*!X_C~SxX&pqUkUjz%!j=VlXDo$!2VLH!rKj@61mDpSr~7B2yy{>X~_nc zRI+7g2V&k zd**H++P9dg!-AOs3;GM`(g<+GRV$+&DdMVpUxY9I1@uK28$az=6oaa+PutlO9?6#? zf-OsgT>^@8KK>ggkUQRPPgC7zjKFR5spqQb3ojCHzj^(UH~v+!y*`Smv)VpVoPwa6 zWG18WJaPKMi*F6Zdk*kU^`i~NNTfn3BkJniC`yN98L-Awd)Z&mY? zprBW$!qL-OL7h@O#kvYnLsfff@kDIegt~?{-*5A7JrA;#TmTe?jICJqhub-G@e??D zqiV#g{)M!kW1-4SDel7TO{;@*h2=_76g3NUD@|c*WO#>MfYq6_YVUP+&8e4|%4T`w zXzhmVNziAHazWO2qXcaOu@R1MrPP{t)`N)}-1&~mq=ZH=w=;-E$IOk=y$dOls{6sRR`I5>|X zpq~XYW4sd;J^6OwOf**J>a7u$S>WTFPRkjY;BfVgQst)u4aMLR1|6%)CB^18XCz+r ztkYQ}G43j~Q&1em(_EkMv0|WEiKu;z2zhb(L%$F&xWwzOmk;VLBYAZ8lOCziNoPw1 zv2BOyXA`A8z^WH!nXhKXM`t0;6D*-uGds3TYGrm8SPnJJOQ^fJU#}@aIy@MYWz**H zvkp?7I5PE{$$|~{-ZaFxr6ZolP^nL##mHOErB^AqJqn^hFA=)HWj!m3WDaHW$C)i^ z9@6G$SzB=>jbe>4kqr#sF7#K}W*Cg-5y6kun3u&0L7BpXF9=#7IN8FOjWrWwUBZiU zT_se3ih-GBKx+Uw0N|CwP3D@-C=5(9T#BH@M`F2!Goiqx+Js5xC92|Sy0%WWWp={$(am!#l~f^W_oz78HX<0X#7 zp)p1u~M*o9W@O8P{0Qkg@Wa# z2{Heb&oX^CQSZWSFBXKOfE|tsAm#^U-WkDnU;IowZ`Ok4!mwHwH=s|AqZ^YD4!5!@ zPxJj+Bd-q6w_YG`z_+r;S86zwXb+EO&qogOq8h-Ect5(M2+>(O7n7)^dP*ws_3U6v zVsh)sk^@*c>)3EML|0<-YROho{lz@Nd4;R9gL{9|64xVL`n!m$-Jjrx?-Bacp!=^5 z1^T^eB{_)Y<9)y{-4Rz@9_>;_7h;5D+@QcbF4Wv7hu)s0&==&6u)33 zHRj+&Woq-vDvjwJCYES@$C4{$?f$Ibi4G()UeN11rgjF+^;YE^5nYprYoJNoudNj= zm1pXSeG64dcWHObUetodRn1Fw|1nI$D9z}dVEYT0lQnsf_E1x2vBLql7NrHH!n&Sq z6lc*mvU=WS6=v9Lrl}&zRiu_6u;6g%_DU{9b+R z#YHqX7`m9eydf?KlKu6Sb%j$%_jmydig`B*TN`cZL-g!R)iE?+Q5oOqBFKhx z%MW>BC^(F_JuG(ayE(MT{S3eI{cKiwOtPwLc0XO*{*|(JOx;uQOfq@lp_^cZo=FZj z4#}@e@dJ>Bn%2`2_WPeSN7si^{U#H=7N4o%Dq3NdGybrZgEU$oSm$hC)uNDC_M9xc zGzwh5Sg?mpBIE8lT2XsqTt3j3?We8}3bzLBTQd639vyg^$0#1epq8snlDJP2(BF)K zSx30RM+{f+b$g{9usIL8H!hCO117Xgv}ttPJm9wVRjPk;ePH@zxv%j9k5`TzdXLeT zFgFX`V7cYIcBls5WN0Pf6SMBN+;CrQ(|EsFd*xtwr#$R{Z9FP`OWtyNsq#mCgZ7+P z^Yn$haBJ)r96{ZJd8vlMl?IBxrgh=fdq_NF!1{jARCVz>jNdC)H^wfy?R94#MPdUjcYX>#wEx+LB#P-#4S-%YH>t-j+w zOFTI8gX$ard6fAh&g=u&56%3^-6E2tpk*wx3HSCQ+t7+*iOs zPk5ysqE}i*cQocFvA68xHfL|iX(C4h*67@3|5Qwle(8wT&!&{8*{f%0(5gH+m>$tq zp;AqrP7?XTEooYG1Dzfxc>W%*CyL16q|fQ0_jp%%Bk^k!i#Nbi(N9&T>#M{gez_Ws zYK=l}adalV(nH}I_!hNeb;tQFk3BHX7N}}R8%pek^E`X}%ou=cx8InPU1EE0|Hen- zyw8MoJqB5=)Z%JXlrdTXAE)eqLAdVE-=>wGHrkRet}>3Yu^lt$Kzu%$3#(ioY}@Gu zjk3BZuQH&~7H+C*uX^4}F*|P89JX;Hg2U!pt>rDi(n(Qe-c}tzb0#6_ItoR0->LSt zR~UT<-|@TO%O`M+_e_J4wx7^)5_%%u+J=yF_S#2Xd?C;Ss3N7KY^#-vx+|;bJX&8r zD?|MetfhdC;^2WG`7MCgs>TKKN=^=!x&Q~BzmQio_^l~LboTNT=I zC5pme^P@ER``p$2md9>4!K#vV-Fc1an7pl>_|&>aqP}+zqR?+~Z;f2^`a+-!Te%V? z;H2SbF>jP^GE(R1@%C==XQ@J=G9lKX+Z<@5}PO(EYkJh=GCv#)Nj{DkWJM2}F&oAZ6xu8&g7pn1ps2U5srwQ7CAK zN&*~@t{`31lUf`O;2w^)M3B@o)_mbRu{-`PrfNpF!R^q>yTR&ETS7^-b2*{-tZAZz zw@q5x9B5V8Qd7dZ!Ai$9hk%Q!wqbE1F1c96&zwBBaRW}(^axoPpN^4Aw}&a5dMe+*Gomky_l^54*rzXro$ z>LL)U5Ry>~FJi=*{JDc)_**c)-&faPz`6v`YU3HQa}pLtb5K)u%K+BOqXP0)rj5Au$zB zW1?vr?mDv7Fsxtsr+S6ucp2l#(4dnr9sD*v+@*>g#M4b|U?~s93>Pg{{a5|rm2xfI z`>E}?9S@|IoUX{Q1zjm5YJT|3S>&09D}|2~BiMo=z4YEjXlWh)V&qs;*C{`UMxp$9 zX)QB?G$fPD6z5_pNs>Jeh{^&U^)Wbr?2D6-q?)`*1k@!UvwQgl8eG$r+)NnFoT)L6 zg7lEh+E6J17krfYJCSjWzm67hEth24pomhz71|Qodn#oAILN)*Vwu2qpJirG)4Wnv}9GWOFrQg%Je+gNrPl8mw7ykE8{ z=|B4+uwC&bpp%eFcRU6{mxRV32VeH8XxX>v$du<$(DfinaaWxP<+Y97Z#n#U~V zVEu-GoPD=9$}P;xv+S~Ob#mmi$JQmE;Iz4(){y*9pFyW-jjgdk#oG$fl4o9E8bo|L zWjo4l%n51@Kz-n%zeSCD`uB?T%FVk+KBI}=ve zvlcS#wt`U6wrJo}6I6Rwb=1GzZfwE=I&Ne@p7*pH84XShXYJRgvK)UjQL%R9Zbm(m zxzTQsLTON$WO7vM)*vl%Pc0JH7WhP;$z@j=y#avW4X8iqy6mEYr@-}PW?H)xfP6fQ z&tI$F{NNct4rRMSHhaelo<5kTYq+(?pY)Ieh8*sa83EQfMrFupMM@nfEV@EmdHUv9 z35uzIrIuo4#WnF^_jcpC@uNNaYTQ~uZWOE6P@LFT^1@$o&q+9Qr8YR+ObBkpP9=F+$s5+B!mX2~T zAuQ6RenX?O{IlLMl1%)OK{S7oL}X%;!XUxU~xJN8xk z`xywS*naF(J#?vOpB(K=o~lE;m$zhgPWDB@=p#dQIW>xe_p1OLoWInJRKbEuoncf; zmS1!u-ycc1qWnDg5Nk2D)BY%jmOwCLC+Ny>`f&UxFowIsHnOXfR^S;&F(KXd{ODlm z$6#1ccqt-HIH9)|@fHnrKudu!6B$_R{fbCIkSIb#aUN|3RM>zuO>dpMbROZ`^hvS@ z$FU-;e4W}!ubzKrU@R*dW*($tFZ>}dd*4_mv)#O>X{U@zSzQt*83l9mI zI$8O<5AIDx`wo0}f2fsPC_l>ONx_`E7kdXu{YIZbp1$(^oBAH({T~&oQ&1{X951QW zmhHUxd)t%GQ9#ak5fTjk-cahWC;>^Rg7(`TVlvy0W@Y!Jc%QL3Ozu# zDPIqBCy&T2PWBj+d-JA-pxZlM=9ja2ce|3B(^VCF+a*MMp`(rH>Rt6W1$;r{n1(VK zLs>UtkT43LR2G$AOYHVailiqk7naz2yZGLo*xQs!T9VN5Q>eE(w zw$4&)&6xIV$IO^>1N-jrEUg>O8G4^@y+-hQv6@OmF@gy^nL_n1P1-Rtyy$Bl;|VcV zF=p*&41-qI5gG9UhKmmnjs932!6hceXa#-qfK;3d*a{)BrwNFeKU|ge?N!;zk+kB! zMD_uHJR#%b54c2tr~uGPLTRLg$`fupo}cRJeTwK;~}A>(Acy4k-Xk&Aa1&eWYS1ULWUj@fhBiWY$pdfy+F z@G{OG{*v*mYtH3OdUjwEr6%_ZPZ3P{@rfbNPQG!BZ7lRyC^xlMpWH`@YRar`tr}d> z#wz87t?#2FsH-jM6m{U=gp6WPrZ%*w0bFm(T#7m#v^;f%Z!kCeB5oiF`W33W5Srdt zdU?YeOdPG@98H7NpI{(uN{FJdu14r(URPH^F6tOpXuhU7T9a{3G3_#Ldfx_nT(Hec zo<1dyhsVsTw;ZkVcJ_0-h-T3G1W@q)_Q30LNv)W?FbMH+XJ* zy=$@39Op|kZv`Rt>X`zg&at(?PO^I=X8d9&myFEx#S`dYTg1W+iE?vt#b47QwoHI9 zNP+|3WjtXo{u}VG(lLUaW0&@yD|O?4TS4dfJI`HC-^q;M(b3r2;7|FONXphw-%7~* z&;2!X17|05+kZOpQ3~3!Nb>O94b&ZSs%p)TK)n3m=4eiblVtSx@KNFgBY_xV6ts;NF;GcGxMP8OKV^h6LmSb2E#Qnw ze!6Mnz7>lE9u{AgQ~8u2zM8CYD5US8dMDX-5iMlgpE9m*s+Lh~A#P1er*rF}GHV3h z=`STo?kIXw8I<`W0^*@mB1$}pj60R{aJ7>C2m=oghKyxMbFNq#EVLgP0cH3q7H z%0?L93-z6|+jiN|@v>ix?tRBU(v-4RV`}cQH*fp|)vd3)8i9hJ3hkuh^8dz{F5-~_ zUUr1T3cP%cCaTooM8dj|4*M=e6flH0&8ve32Q)0dyisl))XkZ7Wg~N}6y`+Qi2l+e zUd#F!nJp{#KIjbQdI`%oZ`?h=5G^kZ_uN`<(`3;a!~EMsWV|j-o>c?x#;zR2ktiB! z);5rrHl?GPtr6-o!tYd|uK;Vbsp4P{v_4??=^a>>U4_aUXPWQ$FPLE4PK$T^3Gkf$ zHo&9$U&G`d(Os6xt1r?sg14n)G8HNyWa^q8#nf0lbr4A-Fi;q6t-`pAx1T*$eKM*$ z|CX|gDrk#&1}>5H+`EjV$9Bm)Njw&7-ZR{1!CJTaXuP!$Pcg69`{w5BRHysB$(tWUes@@6aM69kb|Lx$%BRY^-o6bjH#0!7b;5~{6J+jKxU!Kmi# zndh@+?}WKSRY2gZ?Q`{(Uj|kb1%VWmRryOH0T)f3cKtG4oIF=F7RaRnH0Rc_&372={_3lRNsr95%ZO{IX{p@YJ^EI%+gvvKes5cY+PE@unghjdY5#9A!G z70u6}?zmd?v+{`vCu-53_v5@z)X{oPC@P)iA3jK$`r zSA2a7&!^zmUiZ82R2=1cumBQwOJUPz5Ay`RLfY(EiwKkrx%@YN^^XuET;tE zmr-6~I7j!R!KrHu5CWGSChO6deaLWa*9LLJbcAJsFd%Dy>a!>J`N)Z&oiU4OEP-!Ti^_!p}O?7`}i7Lsf$-gBkuY*`Zb z7=!nTT;5z$_5$=J=Ko+Cp|Q0J=%oFr>hBgnL3!tvFoLNhf#D0O=X^h+x08iB;@8pXdRHxX}6R4k@i6%vmsQwu^5z zk1ip`#^N)^#Lg#HOW3sPI33xqFB4#bOPVnY%d6prwxf;Y-w9{ky4{O6&94Ra8VN@K zb-lY;&`HtxW@sF!doT5T$2&lIvJpbKGMuDAFM#!QPXW87>}=Q4J3JeXlwHys?!1^#37q_k?N@+u&Ns20pEoBeZC*np;i;M{2C0Z4_br2gsh6eL z#8`#sn41+$iD?^GL%5?cbRcaa-Nx0vE(D=*WY%rXy3B%gNz0l?#noGJGP728RMY#q z=2&aJf@DcR?QbMmN)ItUe+VM_U!ryqA@1VVt$^*xYt~-qvW!J4Tp<-3>jT=7Zow5M z8mSKp0v4b%a8bxFr>3MwZHSWD73D@+$5?nZAqGM#>H@`)mIeC#->B)P8T$zh-Pxnc z8)~Zx?TWF4(YfKuF3WN_ckpCe5;x4V4AA3(i$pm|78{%!q?|~*eH0f=?j6i)n~Hso zmTo>vqEtB)`%hP55INf7HM@taH)v`Fw40Ayc*R!T?O{ziUpYmP)AH`euTK!zg9*6Z z!>M=$3pd0!&TzU=hc_@@^Yd3eUQpX4-33}b{?~5t5lgW=ldJ@dUAH%`l5US1y_`40 zs(X`Qk}vvMDYYq+@Rm+~IyCX;iD~pMgq^KY)T*aBz@DYEB={PxA>)mI6tM*sx-DmGQHEaHwRrAmNjO!ZLHO4b;;5mf@zzlPhkP($JeZGE7 z?^XN}Gf_feGoG~BjUgVa*)O`>lX=$BSR2)uD<9 z>o^|nb1^oVDhQbfW>>!;8-7<}nL6L^V*4pB=>wwW+RXAeRvKED(n1;R`A6v$6gy0I(;Vf?!4;&sgn7F%LpM}6PQ?0%2Z@b{It<(G1CZ|>913E0nR2r^Pa*Bp z@tFGi*CQ~@Yc-?{cwu1 zsilf=k^+Qs>&WZG(3WDixisHpR>`+ihiRwkL(3T|=xsoNP*@XX3BU8hr57l3k;pni zI``=3Nl4xh4oDj<%>Q1zYXHr%Xg_xrK3Nq?vKX3|^Hb(Bj+lONTz>4yhU-UdXt2>j z<>S4NB&!iE+ao{0Tx^N*^|EZU;0kJkx@zh}S^P{ieQjGl468CbC`SWnwLRYYiStXm zOxt~Rb3D{dz=nHMcY)#r^kF8|q8KZHVb9FCX2m^X*(|L9FZg!5a7((!J8%MjT$#Fs)M1Pb zq6hBGp%O1A+&%2>l0mpaIzbo&jc^!oN^3zxap3V2dNj3x<=TwZ&0eKX5PIso9j1;e zwUg+C&}FJ`k(M|%%}p=6RPUq4sT3-Y;k-<68ciZ~_j|bt>&9ZLHNVrp#+pk}XvM{8 z`?k}o-!if>hVlCP9j%&WI2V`5SW)BCeR5>MQhF)po=p~AYN%cNa_BbV6EEh_kk^@a zD>4&>uCGCUmyA-c)%DIcF4R6!>?6T~Mj_m{Hpq`*(wj>foHL;;%;?(((YOxGt)Bhx zuS+K{{CUsaC++%}S6~CJ=|vr(iIs-je)e9uJEU8ZJAz)w166q)R^2XI?@E2vUQ!R% zn@dxS!JcOimXkWJBz8Y?2JKQr>`~SmE2F2SL38$SyR1^yqj8_mkBp)o$@+3BQ~Mid z9U$XVqxX3P=XCKj0*W>}L0~Em`(vG<>srF8+*kPrw z20{z(=^w+ybdGe~Oo_i|hYJ@kZl*(9sHw#Chi&OIc?w`nBODp?ia$uF%Hs(X>xm?j zqZQ`Ybf@g#wli`!-al~3GWiE$K+LCe=Ndi!#CVjzUZ z!sD2O*;d28zkl))m)YN7HDi^z5IuNo3^w(zy8 zszJG#mp#Cj)Q@E@r-=NP2FVxxEAeOI2e=|KshybNB6HgE^(r>HD{*}S}mO>LuRGJT{*tfTzw_#+er-0${}%YPe@CMJ1Ng#j#)i)SnY@ss3gL;g zg2D~#Kpdfu#G;q1qz_TwSz1VJT(b3zby$Vk&;Y#1(A)|xj`_?i5YQ;TR%jice5E;0 zYHg;`zS5{S*9xI6o^j>rE8Ua*XhIw{_-*&@(R|C(am8__>+Ws&Q^ymy*X4~hR2b5r zm^p3sw}yv=tdyncy_Ui7{BQS732et~Z_@{-IhHDXAV`(Wlay<#hb>%H%WDi+K$862nA@BDtM#UCKMu+kM`!JHyWSi?&)A7_ z3{cyNG%a~nnH_!+;g&JxEMAmh-Z}rC!o7>OVzW&PoMyTA_g{hqXG)SLraA^OP**<7 zjWbr7z!o2n3hnx7A=2O=WL;`@9N{vQIM@&|G-ljrPvIuJHYtss0Er0fT5cMXNUf1B z7FAwBDixt0X7C3S)mPe5g`YtME23wAnbU)+AtV}z+e8G;0BP=bI;?(#|Ep!vVfDbK zvx+|CKF>yt0hWQ3drchU#XBU+HiuG*V^snFAPUp-5<#R&BUAzoB!aZ+e*KIxa26V}s6?nBK(U-7REa573wg-jqCg>H8~>O{ z*C0JL-?X-k_y%hpUFL?I>0WV{oV`Nb)nZbJG01R~AG>flIJf)3O*oB2i8~;!P?Wo_ z0|QEB*fifiL6E6%>tlAYHm2cjTFE@*<);#>689Z6S#BySQ@VTMhf9vYQyLeDg1*F} zjq>i1*x>5|CGKN{l9br3kB0EHY|k4{%^t7-uhjd#NVipUZa=EUuE5kS1_~qYX?>hJ z$}!jc9$O$>J&wnu0SgfYods^z?J4X;X7c77Me0kS-dO_VUQ39T(Kv(Y#s}Qqz-0AH z^?WRL(4RzpkD+T5FG_0NyPq-a-B7A5LHOCqwObRJi&oRi(<;OuIN7SV5PeHU$<@Zh zPozEV`dYmu0Z&Tqd>t>8JVde9#Pt+l95iHe$4Xwfy1AhI zDM4XJ;bBTTvRFtW>E+GzkN)9k!hA5z;xUOL2 zq4}zn-DP{qc^i|Y%rvi|^5k-*8;JZ~9a;>-+q_EOX+p1Wz;>i7c}M6Nv`^NY&{J-> z`(mzDJDM}QPu5i44**2Qbo(XzZ-ZDu%6vm8w@DUarqXj41VqP~ zs&4Y8F^Waik3y1fQo`bVUH;b=!^QrWb)3Gl=QVKr+6sxc=ygauUG|cm?|X=;Q)kQ8 zM(xrICifa2p``I7>g2R~?a{hmw@{!NS5`VhH8+;cV(F>B94M*S;5#O`YzZH1Z%yD? zZ61w(M`#aS-*~Fj;x|J!KM|^o;MI#Xkh0ULJcA?o4u~f%Z^16ViA27FxU5GM*rKq( z7cS~MrZ=f>_OWx8j#-Q3%!aEU2hVuTu(7`TQk-Bi6*!<}0WQi;_FpO;fhpL4`DcWp zGOw9vx0N~6#}lz(r+dxIGZM3ah-8qrqMmeRh%{z@dbUD2w15*_4P?I~UZr^anP}DB zU9CCrNiy9I3~d#&!$DX9e?A});BjBtQ7oGAyoI$8YQrkLBIH@2;lt4E^)|d6Jwj}z z&2_E}Y;H#6I4<10d_&P0{4|EUacwFHauvrjAnAm6yeR#}f}Rk27CN)vhgRqEyPMMS7zvunj2?`f;%?alsJ+-K+IzjJx>h8 zu~m_y$!J5RWAh|C<6+uiCNsOKu)E72M3xKK(a9Okw3e_*O&}7llNV!=P87VM2DkAk zci!YXS2&=P0}Hx|wwSc9JP%m8dMJA*q&VFB0yMI@5vWoAGraygwn){R+Cj6B1a2Px z5)u(K5{+;z2n*_XD!+Auv#LJEM)(~Hx{$Yb^ldQmcYF2zNH1V30*)CN_|1$v2|`LnFUT$%-tO0Eg|c5$BB~yDfzS zcOXJ$wpzVK0MfTjBJ0b$r#_OvAJ3WRt+YOLlJPYMx~qp>^$$$h#bc|`g0pF-Ao43? z>*A+8lx>}L{p(Tni2Vvk)dtzg$hUKjSjXRagj)$h#8=KV>5s)J4vGtRn5kP|AXIz! zPgbbVxW{2o4s-UM;c#We8P&mPN|DW7_uLF!a|^0S=wr6Esx9Z$2|c1?GaupU6$tb| zY_KU`(_29O_%k(;>^|6*pZURH3`@%EuKS;Ns z1lujmf;r{qAN&Q0&m{wJSZ8MeE7RM5+Sq;ul_ z`+ADrd_Um+G37js6tKsArNB}n{p*zTUxQr>3@wA;{EUbjNjlNd6$Mx zg0|MyU)v`sa~tEY5$en7^PkC=S<2@!nEdG6L=h(vT__0F=S8Y&eM=hal#7eM(o^Lu z2?^;05&|CNliYrq6gUv;|i!(W{0N)LWd*@{2q*u)}u*> z7MQgk6t9OqqXMln?zoMAJcc zMKaof_Up})q#DzdF?w^%tTI7STI^@8=Wk#enR*)&%8yje>+tKvUYbW8UAPg55xb70 zEn5&Ba~NmOJlgI#iS8W3-@N%>V!#z-ZRwfPO1)dQdQkaHsiqG|~we2ALqG7Ruup(DqSOft2RFg_X%3w?6VqvV1uzX_@F(diNVp z4{I|}35=11u$;?|JFBEE*gb;T`dy+8gWJ9~pNsecrO`t#V9jW-6mnfO@ff9od}b(3s4>p0i30gbGIv~1@a^F2kl7YO;DxmF3? zWi-RoXhzRJV0&XE@ACc?+@6?)LQ2XNm4KfalMtsc%4!Fn0rl zpHTrHwR>t>7W?t!Yc{*-^xN%9P0cs0kr=`?bQ5T*oOo&VRRu+1chM!qj%2I!@+1XF z4GWJ=7ix9;Wa@xoZ0RP`NCWw0*8247Y4jIZ>GEW7zuoCFXl6xIvz$ezsWgKdVMBH> z{o!A7f;R-@eK9Vj7R40xx)T<2$?F2E<>Jy3F;;=Yt}WE59J!1WN367 zA^6pu_zLoZIf*x031CcwotS{L8bJE(<_F%j_KJ2P_IusaZXwN$&^t716W{M6X2r_~ zaiMwdISX7Y&Qi&Uh0upS3TyEIXNDICQlT5fHXC`aji-c{U(J@qh-mWl-uMN|T&435 z5)a1dvB|oe%b2mefc=Vpm0C%IUYYh7HI*;3UdgNIz}R##(#{(_>82|zB0L*1i4B5j-xi9O4x10rs_J6*gdRBX=@VJ+==sWb&_Qc6tSOowM{BX@(zawtjl zdU!F4OYw2@Tk1L^%~JCwb|e#3CC>srRHQ*(N%!7$Mu_sKh@|*XtR>)BmWw!;8-mq7 zBBnbjwx8Kyv|hd*`5}84flTHR1Y@@uqjG`UG+jN_YK&RYTt7DVwfEDXDW4U+iO{>K zw1hr{_XE*S*K9TzzUlJH2rh^hUm2v7_XjwTuYap|>zeEDY$HOq3X4Tz^X}E9z)x4F zs+T?Ed+Hj<#jY-`Va~fT2C$=qFT-5q$@p9~0{G&eeL~tiIAHXA!f6C(rAlS^)&k<- zXU|ZVs}XQ>s5iONo~t!XXZgtaP$Iau;JT%h)>}v54yut~pykaNye4axEK#5@?TSsQ zE;Jvf9I$GVb|S`7$pG)4vgo9NXsKr?u=F!GnA%VS2z$@Z(!MR9?EPcAqi5ft)Iz6sNl`%kj+_H-X`R<>BFrBW=fSlD|{`D%@Rcbu2?%>t7i34k?Ujb)2@J-`j#4 zLK<69qcUuniIan-$A1+fR=?@+thwDIXtF1Tks@Br-xY zfB+zblrR(ke`U;6U~-;p1Kg8Lh6v~LjW@9l2P6s+?$2!ZRPX`(ZkRGe7~q(4&gEi<$ch`5kQ?*1=GSqkeV z{SA1EaW_A!t{@^UY2D^YO0(H@+kFVzZaAh0_`A`f(}G~EP~?B|%gtxu&g%^x{EYSz zk+T;_c@d;+n@$<>V%P=nk36?L!}?*=vK4>nJSm+1%a}9UlmTJTrfX4{Lb7smNQn@T zw9p2%(Zjl^bWGo1;DuMHN(djsEm)P8mEC2sL@KyPjwD@d%QnZ$ zMJ3cnn!_!iP{MzWk%PI&D?m?C(y2d|2VChluN^yHya(b`h>~GkI1y;}O_E57zOs!{ zt2C@M$^PR2U#(dZmA-sNreB@z-yb0Bf7j*yONhZG=onhx>t4)RB`r6&TP$n zgmN*)eCqvgriBO-abHQ8ECN0bw?z5Bxpx z=jF@?zFdVn?@gD5egM4o$m`}lV(CWrOKKq(sv*`mNcHcvw&Xryfw<{ch{O&qc#WCTXX6=#{MV@q#iHYba!OUY+MGeNTjP%Fj!WgM&`&RlI^=AWTOqy-o zHo9YFt!gQ*p7{Fl86>#-JLZo(b^O`LdFK~OsZBRR@6P?ad^Ujbqm_j^XycM4ZHFyg ziUbIFW#2tj`65~#2V!4z7DM8Z;fG0|APaQ{a2VNYpNotB7eZ5kp+tPDz&Lqs0j%Y4tA*URpcfi z_M(FD=fRGdqf430j}1z`O0I=;tLu81bwJXdYiN7_&a-?ly|-j*+=--XGvCq#32Gh(=|qj5F?kmihk{%M&$}udW5)DHK zF_>}5R8&&API}o0osZJRL3n~>76nUZ&L&iy^s>PMnNcYZ|9*1$v-bzbT3rpWsJ+y{ zPrg>5Zlery96Um?lc6L|)}&{992{_$J&=4%nRp9BAC6!IB=A&=tF>r8S*O-=!G(_( zwXbX_rGZgeiK*&n5E;f=k{ktyA1(;x_kiMEt0*gpp_4&(twlS2e5C?NoD{n>X2AT# zY@Zp?#!b1zNq96MQqeO*M1MMBin5v#RH52&Xd~DO6-BZLnA6xO1$sou(YJ1Dlc{WF zVa%2DyYm`V#81jP@70IJ;DX@y*iUt$MLm)ByAD$eUuji|5{ptFYq(q)mE(5bOpxjM z^Q`AHWq44SG3`_LxC9fwR)XRVIp=B%<(-lOC3jI#bb@dK(*vjom!=t|#<@dZql%>O z15y^{4tQoeW9Lu%G&V$90x6F)xN6y_oIn;!Q zs)8jT$;&;u%Y>=T3hg34A-+Y*na=|glcStr5D;&5*t5*DmD~x;zQAV5{}Ya`?RRGa zT*t9@$a~!co;pD^!J5bo?lDOWFx%)Y=-fJ+PDGc0>;=q=s?P4aHForSB+)v0WY2JH z?*`O;RHum6j%#LG)Vu#ciO#+jRC3!>T(9fr+XE7T2B7Z|0nR5jw@WG)kDDzTJ=o4~ zUpeyt7}_nd`t}j9BKqryOha{34erm)RmST)_9Aw)@ zHbiyg5n&E{_CQR@h<}34d7WM{s{%5wdty1l+KX8*?+-YkNK2Be*6&jc>@{Fd;Ps|| z26LqdI3#9le?;}risDq$K5G3yoqK}C^@-8z^wj%tdgw-6@F#Ju{Sg7+y)L?)U$ez> zoOaP$UFZ?y5BiFycir*pnaAaY+|%1%8&|(@VB)zweR%?IidwJyK5J!STzw&2RFx zZV@qeaCB01Hu#U9|1#=Msc8Pgz5P*4Lrp!Q+~(G!OiNR{qa7|r^H?FC6gVhkk3y7=uW#Sh;&>78bZ}aK*C#NH$9rX@M3f{nckYI+5QG?Aj1DM)@~z_ zw!UAD@gedTlePB*%4+55naJ8ak_;))#S;4ji!LOqY5VRI){GMwHR~}6t4g>5C_#U# ztYC!tjKjrKvRy=GAsJVK++~$|+s!w9z3H4G^mACv=EErXNSmH7qN}%PKcN|8%9=i)qS5+$L zu&ya~HW%RMVJi4T^pv?>mw*Gf<)-7gf#Qj|e#w2|v4#t!%Jk{&xlf;$_?jW*n!Pyx zkG$<18kiLOAUPuFfyu-EfWX%4jYnjBYc~~*9JEz6oa)_R|8wjZA|RNrAp%}14L7fW zi7A5Wym*K+V8pkqqO-X#3ft{0qs?KVt^)?kS>AicmeO&q+~J~ zp0YJ_P~_a8j= zsAs~G=8F=M{4GZL{|B__UorX@MRNQLn?*_gym4aW(~+i13knnk1P=khoC-ViMZk+x zLW(l}oAg1H`dU+Fv**;qw|ANDSRs>cGqL!Yw^`; zv;{E&8CNJcc)GHzTYM}f&NPw<6j{C3gaeelU#y!M)w-utYEHOCCJo|Vgp7K6C_$14 zqIrLUB0bsgz^D%V%fbo2f9#yb#CntTX?55Xy|Kps&Xek*4_r=KDZ z+`TQuv|$l}MWLzA5Ay6Cvsa^7xvwXpy?`w(6vx4XJ zWuf1bVSb#U8{xlY4+wlZ$9jjPk)X_;NFMqdgq>m&W=!KtP+6NL57`AMljW+es zzqjUjgz;V*kktJI?!NOg^s_)ph45>4UDA!Vo0hn>KZ+h-3=?Y3*R=#!fOX zP$Y~+14$f66ix?UWB_6r#fMcC^~X4R-<&OD1CSDNuX~y^YwJ>sW0j`T<2+3F9>cLo z#!j57$ll2K9(%$4>eA7(>FJX5e)pR5&EZK!IMQzOfik#FU*o*LGz~7u(8}XzIQRy- z!U7AlMTIe|DgQFmc%cHy_9^{o`eD%ja_L>ckU6$O4*U**o5uR7`FzqkU8k4gxtI=o z^P^oGFPm5jwZMI{;nH}$?p@uV8FT4r=|#GziKXK07bHJLtK}X%I0TON$uj(iJ`SY^ zc$b2CoxCQ>7LH@nxcdW&_C#fMYBtTxcg46dL{vf%EFCZ~eErMvZq&Z%Lhumnkn^4A zsx$ay(FnN7kYah}tZ@0?-0Niroa~13`?hVi6`ndno`G+E8;$<6^gsE-K3)TxyoJ4M zb6pj5=I8^FD5H@`^V#Qb2^0cx7wUz&cruA5g>6>qR5)O^t1(-qqP&1g=qvY#s&{bx zq8Hc%LsbK1*%n|Y=FfojpE;w~)G0-X4i*K3{o|J7`krhIOd*c*$y{WIKz2n2*EXEH zT{oml3Th5k*vkswuFXdGDlcLj15Nec5pFfZ*0?XHaF_lVuiB%Pv&p7z)%38}%$Gup zVTa~C8=cw%6BKn_|4E?bPNW4PT7}jZQLhDJhvf4z;~L)506IE0 zX!tWXX(QOQPRj-p80QG79t8T2^az4Zp2hOHziQlvT!|H)jv{Ixodabzv6lBj)6WRB z{)Kg@$~~(7$-az?lw$4@L%I&DI0Lo)PEJJziWP33a3azb?jyXt1v0N>2kxwA6b%l> zZqRpAo)Npi&loWbjFWtEV)783BbeIAhqyuc+~>i7aQ8shIXt)bjCWT6$~ro^>99G} z2XfmT0(|l!)XJb^E!#3z4oEGIsL(xd; zYX1`1I(cG|u#4R4T&C|m*9KB1`UzKvho5R@1eYtUL9B72{i(ir&ls8g!pD ztR|25xGaF!4z5M+U@@lQf(12?xGy`!|3E}7pI$k`jOIFjiDr{tqf0va&3pOn6Pu)% z@xtG2zjYuJXrV)DUrIF*y<1O1<$#54kZ#2;=X51J^F#0nZ0(;S$OZDt_U2bx{RZ=Q zMMdd$fH|!s{ zXq#l;{`xfV`gp&C>A`WrQU?d{!Ey5(1u*VLJt>i27aZ-^&2IIk=zP5p+{$q(K?2(b z8?9h)kvj9SF!Dr zoyF}?V|9;6abHxWk2cEvGs$-}Pg}D+ZzgkaN&$Snp%;5m%zh1E#?Wac-}x?BYlGN#U#Mek*}kek#I9XaHt?mz3*fDrRTQ#&#~xyeqJk1QJ~E$7qsw6 z?sV;|?*=-{M<1+hXoj?@-$y+(^BJ1H~wQ9G8C0#^aEAyhDduNX@haoa=PuPp zYsGv8UBfQaRHgBgLjmP^eh>fLMeh{8ic)?xz?#3kX-D#Z{;W#cd_`9OMFIaJg-=t`_3*!YDgtNQ2+QUEAJB9M{~AvT$H`E)IKmCR21H532+ata8_i_MR@ z2Xj<3w<`isF~Ah$W{|9;51ub*f4#9ziKrOR&jM{x7I_7()O@`F*5o$KtZ?fxU~g`t zUovNEVKYn$U~VX8eR)qb`7;D8pn*Pp$(otYTqL)5KH$lUS-jf}PGBjy$weoceAcPp z&5ZYB$r&P$MN{0H0AxCe4Qmd3T%M*5d4i%#!nmBCN-WU-4m4Tjxn-%j3HagwTxCZ9 z)j5vO-C7%s%D!&UfO>bi2oXiCw<-w{vVTK^rVbv#W=WjdADJy8$khnU!`ZWCIU`># zyjc^1W~pcu>@lDZ{zr6gv%)2X4n27~Ve+cQqcND%0?IFSP4sH#yIaXXYAq^z3|cg` z`I3$m%jra>e2W-=DiD@84T!cb%||k)nPmEE09NC%@PS_OLhkrX*U!cgD*;;&gIaA(DyVT4QD+q_xu z>r`tg{hiGY&DvD-)B*h+YEd+Zn)WylQl}<4>(_NlsKXCRV;a)Rcw!wtelM2_rWX`j zTh5A|i6=2BA(iMCnj_fob@*eA;V?oa4Z1kRBGaU07O70fb6-qmA$Hg$ps@^ka1=RO zTbE_2#)1bndC3VuK@e!Sftxq4=Uux}fDxXE#Q5_x=E1h>T5`DPHz zbH<_OjWx$wy7=%0!mo*qH*7N4tySm+R0~(rbus`7;+wGh;C0O%x~fEMkt!eV>U$`i z5>Q(o z=t$gPjgGh0&I7KY#k50V7DJRX<%^X z>6+ebc9efB3@eE2Tr){;?_w`vhgF>`-GDY(YkR{9RH(MiCnyRtd!LxXJ75z+?2 zGi@m^+2hKJ5sB1@Xi@s_@p_Kwbc<*LQ_`mr^Y%j}(sV_$`J(?_FWP)4NW*BIL~sR>t6 zM;qTJZ~GoY36&{h-Pf}L#y2UtR}>ZaI%A6VkU>vG4~}9^i$5WP2Tj?Cc}5oQxe2=q z8BeLa$hwCg_psjZyC2+?yX4*hJ58Wu^w9}}7X*+i5Rjqu5^@GzXiw#SUir1G1`jY% zOL=GE_ENYxhcyUrEt9XlMNP6kx6h&%6^u3@zB8KUCAa18T(R2J`%JjWZ z!{7cXaEW+Qu*iJPu+m>QqW}Lo$4Z+!I)0JNzZ&_M%=|B1yejFRM04bGAvu{=lNPd+ zJRI^DRQ(?FcVUD+bgEcAi@o(msqys9RTCG#)TjI!9~3-dc`>gW;HSJuQvH~d`MQs86R$|SKXHh zqS9Qy)u;T`>>a!$LuaE2keJV%;8g)tr&Nnc;EkvA-RanHXsy)D@XN0a>h}z2j81R; zsUNJf&g&rKpuD0WD@=dDrPHdBoK42WoBU|nMo17o(5^;M|dB4?|FsAGVrSyWcI`+FVw^vTVC`y}f(BwJl zrw3Sp151^9=}B})6@H*i4-dIN_o^br+BkcLa^H56|^2XsT0dESw2 zMX>(KqNl=x2K5=zIKg}2JpGAZu{I_IO}0$EQ5P{4zol**PCt3F4`GX}2@vr8#Y)~J zKb)gJeHcFnR@4SSh%b;c%J`l=W*40UPjF#q{<}ywv-=vHRFmDjv)NtmC zQx9qm)d%0zH&qG7AFa3VAU1S^(n8VFTC~Hb+HjYMjX8r#&_0MzlNR*mnLH5hi}`@{ zK$8qiDDvS_(L9_2vHgzEQ${DYSE;DqB!g*jhJghE&=LTnbgl&Xepo<*uRtV{2wDHN z)l;Kg$TA>Y|K8Lc&LjWGj<+bp4Hiye_@BfU(y#nF{fpR&|Ltbye?e^j0}8JC4#xi% zv29ZR%8%hk=3ZDvO-@1u8KmQ@6p%E|dlHuy#H1&MiC<*$YdLkHmR#F3ae;bKd;@*i z2_VfELG=B}JMLCO-6UQy^>RDE%K4b>c%9ki`f~Z2Qu8hO7C#t%Aeg8E%+}6P7Twtg z-)dj(w}_zFK&86KR@q9MHicUAucLVshUdmz_2@32(V`y3`&Kf8Q2I)+!n0mR=rrDU zXvv^$ho;yh*kNqJ#r1}b0|i|xRUF6;lhx$M*uG3SNLUTC@|htC z-=fsw^F%$qqz4%QdjBrS+ov}Qv!z00E+JWas>p?z@=t!WWU3K*?Z(0meTuTOC7OTx zU|kFLE0bLZ+WGcL$u4E}5dB0g`h|uwv3=H6f+{5z9oLv-=Q45+n~V4WwgO=CabjM% zBAN+RjM65(-}>Q2V#i1Na@a0`08g&y;W#@sBiX6Tpy8r}*+{RnyGUT`?XeHSqo#|J z^ww~c;ou|iyzpErDtlVU=`8N7JSu>4M z_pr9=tX0edVn9B}YFO2y(88j#S{w%E8vVOpAboK*27a7e4Ekjt0)hIX99*1oE;vex z7#%jhY=bPijA=Ce@9rRO(Vl_vnd00!^TAc<+wVvRM9{;hP*rqEL_(RzfK$er_^SN; z)1a8vo8~Dr5?;0X0J62Cusw$A*c^Sx1)dom`-)Pl7hsW4i(r*^Mw`z5K>!2ixB_mu z*Ddqjh}zceRFdmuX1akM1$3>G=#~|y?eYv(e-`Qy?bRHIq=fMaN~fB zUa6I8Rt=)jnplP>yuS+P&PxeWpJ#1$F`iqRl|jF$WL_aZFZl@kLo&d$VJtu&w?Q0O zzuXK>6gmygq(yXJy0C1SL}T8AplK|AGNUOhzlGeK_oo|haD@)5PxF}rV+5`-w{Aag zus45t=FU*{LguJ11Sr-28EZkq;!mJO7AQGih1L4rEyUmp>B!%X0YemsrV3QFvlgt* z5kwlPzaiJ+kZ^PMd-RRbl(Y?F*m`4*UIhIuf#8q>H_M=fM*L_Op-<_r zBZagV=4B|EW+KTja?srADTZXCd3Yv%^Chfpi)cg{ED${SI>InNpRj5!euKv?=Xn92 zsS&FH(*w`qLIy$doc>RE&A5R?u zzkl1sxX|{*fLpXvIW>9d<$ePROttn3oc6R!sN{&Y+>Jr@yeQN$sFR z;w6A<2-0%UA?c8Qf;sX7>>uKRBv3Ni)E9pI{uVzX|6Bb0U)`lhLE3hK58ivfRs1}d zNjlGK0hdq0qjV@q1qI%ZFMLgcpWSY~mB^LK)4GZ^h_@H+3?dAe_a~k*;9P_d7%NEFP6+ zgV(oGr*?W(ql?6SQ~`lUsjLb%MbfC4V$)1E0Y_b|OIYxz4?O|!kRb?BGrgiH5+(>s zoqM}v*;OBfg-D1l`M6T6{K`LG+0dJ1)!??G5g(2*vlNkm%Q(MPABT$r13q?|+kL4- zf)Mi5r$sn;u41aK(K#!m+goyd$c!KPl~-&-({j#D4^7hQkV3W|&>l_b!}!z?4($OA z5IrkfuT#F&S1(`?modY&I40%gtroig{YMvF{K{>5u^I51k8RriGd${z)=5k2tG zM|&Bp5kDTfb#vfuTTd?)a=>bX=lokw^y9+2LS?kwHQIWI~pYgy7 zb?A-RKVm_vM5!9?C%qYdfRAw& zAU7`up~%g=p@}pg#b7E)BFYx3g%(J36Nw(Dij!b>cMl@CSNbrW!DBDbTD4OXk!G4x zi}JBKc8HBYx$J~31PXH+4^x|UxK~(<@I;^3pWN$E=sYma@JP|8YL`L(zI6Y#c%Q{6 z*APf`DU$S4pr#_!60BH$FGViP14iJmbrzSrOkR;f3YZa{#E7Wpd@^4E-zH8EgPc-# zKWFPvh%WbqU_%ZEt`=Q?odKHc7@SUmY{GK`?40VuL~o)bS|is$Hn=<=KGHOsEC5tB zFb|q}gGlL97NUf$G$>^1b^3E18PZ~Pm9kX%*ftnolljiEt@2#F2R5ah$zbXd%V_Ev zyDd{1o_uuoBga$fB@Fw!V5F3jIr=a-ykqrK?WWZ#a(bglI_-8pq74RK*KfQ z0~Dzus7_l;pMJYf>Bk`)`S8gF!To-BdMnVw5M-pyu+aCiC5dwNH|6fgRsIKZcF&)g zr}1|?VOp}I3)IR@m1&HX1~#wsS!4iYqES zK}4J{Ei>;e3>LB#Oly>EZkW14^@YmpbgxCDi#0RgdM${&wxR+LiX}B+iRioOB0(pDKpVEI;ND?wNx>%e|m{RsqR_{(nmQ z3ZS}@t!p4a(BKx_-CYwrcyJ5u1TO9bcXti$8sy>xcLKqKCc#~UOZYD{llKTSFEjJ~ zyNWt>tLU}*>^`TvPxtP%F`ZJQw@W0^>x;!^@?k_)9#bF$j0)S3;mH-IR5y82l|%=F z2lR8zhP?XNP-ucZZ6A+o$xOyF!w;RaLHGh57GZ|TCXhJqY~GCh)aXEV$1O&$c}La1 zjuJxkY9SM4av^Hb;i7efiYaMwI%jGy`3NdY)+mcJhF(3XEiSlU3c|jMBi|;m-c?~T z+x0_@;SxcoY=(6xNgO$bBt~Pj8`-<1S|;Bsjrzw3@zSjt^JC3X3*$HI79i~!$RmTz zsblZsLYs7L$|=1CB$8qS!tXrWs!F@BVuh?kN(PvE5Av-*r^iYu+L^j^m9JG^#=m>@ z=1soa)H*w6KzoR$B8mBCXoU;f5^bVuwQ3~2LKg!yxomG1#XPmn(?YH@E~_ED+W6mxs%x{%Z<$pW`~ON1~2XjP5v(0{C{+6Dm$00tsd3w=f=ZENy zOgb-=f}|Hb*LQ$YdWg<(u7x3`PKF)B7ZfZ6;1FrNM63 z?O6tE%EiU@6%rVuwIQjvGtOofZBGZT1Sh(xLIYt9c4VI8`!=UJd2BfLjdRI#SbVAX ziT(f*RI^T!IL5Ac>ql7uduF#nuCRJ1)2bdvAyMxp-5^Ww5p#X{rb5)(X|fEhDHHW{ zw(Lfc$g;+Q`B0AiPGtmK%*aWfQQ$d!*U<|-@n2HZvCWSiw^I>#vh+LyC;aaVWGbmkENr z&kl*8o^_FW$T?rDYLO1Pyi%>@&kJKQoH2E0F`HjcN}Zlnx1ddoDA>G4Xu_jyp6vuT zPvC}pT&Owx+qB`zUeR|4G;OH(<<^_bzkjln0k40t`PQxc$7h(T8Ya~X+9gDc8Z9{Z z&y0RAU}#_kQGrM;__MK9vwIwK^aoqFhk~dK!ARf1zJqHMxF2?7-8|~yoO@_~Ed;_wvT%Vs{9RK$6uUQ|&@#6vyBsFK9eZW1Ft#D2)VpQRwpR(;x^ zdoTgMqfF9iBl%{`QDv7B0~8{8`8k`C4@cbZAXBu00v#kYl!#_Wug{)2PwD5cNp?K^ z9+|d-4z|gZ!L{57>!Ogfbzchm>J1)Y%?NThxIS8frAw@z>Zb9v%3_3~F@<=LG%r*U zaTov}{{^z~SeX!qgSYow`_5)ij*QtGp4lvF`aIGQ>@3ZTkDmsl#@^5*NGjOuu82}o zzLF~Q9SW+mP=>88%eSA1W4_W7-Q>rdq^?t=m6}^tDPaBRGFLg%ak93W!kOp#EO{6& zP%}Iff5HZQ9VW$~+9r=|Quj#z*=YwcnssS~9|ub2>v|u1JXP47vZ1&L1O%Z1DsOrDfSIMHU{VT>&>H=9}G3i@2rP+rx@eU@uE8rJNec zij~#FmuEBj03F1~ct@C@$>y)zB+tVyjV3*n`mtAhIM0$58vM9jOQC}JJOem|EpwqeMuYPxu3sv}oMS?S#o6GGK@8PN59)m&K4Dc&X% z(;XL_kKeYkafzS3Wn5DD>Yiw{LACy_#jY4op(>9q>>-*9@C0M+=b#bknAWZ37^(Ij zq>H%<@>o4a#6NydoF{_M4i4zB_KG)#PSye9bk0Ou8h%1Dtl7Q_y#7*n%g)?m>xF~( zjqvOwC;*qvN_3(*a+w2|ao0D?@okOvg8JskUw(l7n`0fncglavwKd?~l_ryKJ^Ky! zKCHkIC-o7%fFvPa$)YNh022lakMar^dgL=t#@XLyNHHw!b?%WlM)R@^!)I!smZL@k zBi=6wE5)2v&!UNV(&)oOYW(6Qa!nUjDKKBf-~Da=#^HE4(@mWk)LPvhyN3i4goB$3K8iV7uh zsv+a?#c4&NWeK(3AH;ETrMOIFgu{_@%XRwCZ;L=^8Ts)hix4Pf3yJRQ<8xb^CkdmC z?c_gB)XmRsk`9ch#tx4*hO=#qS7={~Vb4*tTf<5P%*-XMfUUYkI9T1cEF;ObfxxI-yNuA=I$dCtz3ey znVkctYD*`fUuZ(57+^B*R=Q}~{1z#2!ca?)+YsRQb+lt^LmEvZt_`=j^wqig+wz@n@ z`LIMQJT3bxMzuKg8EGBU+Q-6cs5(@5W?N>JpZL{$9VF)veF`L5%DSYTNQEypW%6$u zm_~}T{HeHj1bAlKl8ii92l9~$dm=UM21kLemA&b$;^!wB7#IKWGnF$TVq!!lBlG4 z{?Rjz?P(uvid+|i$VH?`-C&Gcb3{(~Vpg`w+O);Wk1|Mrjxrht0GfRUnZqz2MhrXa zqgVC9nemD5)H$to=~hp)c=l9?#~Z_7i~=U-`FZxb-|TR9@YCxx;Zjo-WpMNOn2)z) zFPGGVl%3N$f`gp$gPnWC+f4(rmts%fidpo^BJx72zAd7|*Xi{2VXmbOm)1`w^tm9% znM=0Fg4bDxH5PxPEm{P3#A(mxqlM7SIARP?|2&+c7qmU8kP&iApzL|F>Dz)Ixp_`O zP%xrP1M6@oYhgo$ZWwrAsYLa4 z|I;DAvJxno9HkQrhLPQk-8}=De{9U3U%)dJ$955?_AOms!9gia%)0E$Mp}$+0er@< zq7J&_SzvShM?e%V?_zUu{niL@gt5UFOjFJUJ}L?$f%eU%jUSoujr{^O=?=^{19`ON zlRIy8Uo_nqcPa6@yyz`CM?pMJ^^SN^Fqtt`GQ8Q#W4kE7`V9^LT}j#pMChl!j#g#J zr-=CCaV%xyFeQ9SK+mG(cTwW*)xa(eK;_Z(jy)woZp~> zA(4}-&VH+TEeLzPTqw&FOoK(ZjD~m{KW05fiGLe@E3Z2`rLukIDahE*`u!ubU)9`o zn^-lyht#E#-dt~S>}4y$-mSbR8{T@}22cn^refuQ08NjLOv?JiEWjyOnzk<^R5%gO zhUH_B{oz~u#IYwVnUg8?3P*#DqD8#X;%q%HY**=I>>-S|!X*-!x1{^l#OnR56O>iD zc;i;KS+t$koh)E3)w0OjWJl_aW2;xF=9D9Kr>)(5}4FqUbk# zI#$N8o0w;IChL49m9CJTzoC!|u{Ljd%ECgBOf$}&jA^$(V#P#~)`&g`H8E{uv52pp zwto`xUL-L&WTAVREEm$0g_gYPL(^vHq(*t1WCH_6alhkeW&GCZ3hL)|{O-jiFOBrF z!EW=Jej|dqQitT6!B-7&io2K)WIm~Q)v@yq%U|VpV+I?{y0@Yd%n8~-NuuM*pM~KA z85YB};IS~M(c<}4Hxx>qRK0cdl&e?t253N%vefkgds>Ubn8X}j6Vpgs>a#nFq$osY z1ZRwLqFv=+BTb=i%D2Wv>_yE0z}+niZ4?rE|*a3d7^kndWGwnFqt+iZ(7+aln<}jzbAQ(#Z2SS}3S$%Bd}^ zc9ghB%O)Z_mTZMRC&H#)I#fiLuIkGa^`4e~9oM5zKPx?zjkC&Xy0~r{;S?FS%c7w< zWbMpzc(xSw?9tGxG~_l}Acq}zjt5ClaB7-!vzqnlrX;}$#+PyQ9oU)_DfePh2E1<7 ztok6g6K^k^DuHR*iJ?jw?bs_whk|bx`dxu^nC6#e{1*m~z1eq7m}Cf$*^Eua(oi_I zAL+3opNhJteu&mWQ@kQWPucmiP)4|nFG`b2tpC;h{-PI@`+h?9v=9mn|0R-n8#t=+Z*FD(c5 zjj79Jxkgck*DV=wpFgRZuwr%}KTm+dx?RT@aUHJdaX-ODh~gByS?WGx&czAkvkg;x zrf92l8$Or_zOwJVwh>5rB`Q5_5}ef6DjS*$x30nZbuO3dijS*wvNEqTY5p1_A0gWr znH<(Qvb!os14|R)n2Ost>jS2;d1zyLHu`Svm|&dZD+PpP{Bh>U&`Md;gRl64q;>{8MJJM$?UNUd`aC>BiLe>*{ zJY15->yW+<3rLgYeTruFDtk1ovU<$(_y7#HgUq>)r0{^}Xbth}V#6?%5jeFYt;SG^ z3qF)=uWRU;Jj)Q}cpY8-H+l_n$2$6{ZR?&*IGr{>ek!69ZH0ZoJ*Ji+ezzlJ^%qL3 zO5a`6gwFw(moEzqxh=yJ9M1FTn!eo&qD#y5AZXErHs%22?A+JmS&GIolml!)rZTnUDM3YgzYfT#;OXn)`PWv3Ta z!-i|-Wojv*k&bC}_JJDjiAK(Ba|YZgUI{f}TdEOFT2+}nPmttytw7j%@bQZDV1vvj z^rp{gRkCDmYJHGrE1~e~AE!-&6B6`7UxVQuvRrfdFkGX8H~SNP_X4EodVd;lXd^>eV1jN+Tt4}Rsn)R0LxBz0c=NXU|pUe!MQQFkGBWbR3&(jLm z%RSLc#p}5_dO{GD=DEFr=Fc% z85CBF>*t!6ugI?soX(*JNxBp+-DdZ4X0LldiK}+WWGvXV(C(Ht|!3$psR=&c*HIM=BmX;pRIpz@Ale{9dhGe(U2|Giv;# zOc|;?p67J=Q(kamB*aus=|XP|m{jN^6@V*Bpm?ye56Njh#vyJqE=DweC;?Rv7faX~ zde03n^I~0B2vUmr;w^X37tVxUK?4}ifsSH5_kpKZIzpYu0;Kv}SBGfI2AKNp+VN#z`nI{UNDRbo-wqa4NEls zICRJpu)??cj^*WcZ^MAv+;bDbh~gpN$1Cor<{Y2oyIDws^JsfW^5AL$azE(T0p&pP z1Mv~6Q44R&RHoH95&OuGx2srIr<@zYJTOMKiVs;Bx3py89I87LOb@%mr`0)#;7_~Z zzcZj8?w=)>%5@HoCHE_&hnu(n_yQ-L(~VjpjjkbT7e)Dk5??fApg(d>vwLRJ-x{um z*Nt?DqTSxh_MIyogY!vf1mU1`Gld-&L)*43f6dilz`Q@HEz;+>MDDYv9u!s;WXeao zUq=TaL$P*IFgJzrGc>j1dDOd zed+=ZBo?w4mr$2)Ya}?vedDopomhW1`#P<%YOJ_j=WwClX0xJH-f@s?^tmzs_j7t!k zK@j^zS0Q|mM4tVP5Ram$VbS6|YDY&y?Q1r1joe9dj08#CM{RSMTU}(RCh`hp_Rkl- zGd|Cv~G@F{DLhCizAm9AN!^{rNs8hu!G@8RpnGx7e`-+K$ffN<0qjR zGq^$dj_Tv!n*?zOSyk5skI7JVKJ)3jysnjIu-@VSzQiP8r6MzudCU=~?v-U8yzo^7 zGf~SUTvEp+S*!X9uX!sq=o}lH;r{pzk~M*VA(uyQ`3C8!{C;)&6)95fv(cK!%Cuz$ z_Zal57H6kPN>25KNiI6z6F)jzEkh#%OqU#-__Xzy)KyH};81#N6OfX$$IXWzOn`Q& z4f$Z1t>)8&8PcYfEwY5UadU1yg+U*(1m2ZlHoC-!2?gB!!fLhmTl))D@dhvkx#+Yj z1O=LV{(T%{^IeCuFK>%QR!VZ4GnO5tK8a+thWE zg4VytZrwcS?7^ zuZfhYnB8dwd%VLO?DK7pV5Wi<(`~DYqOXn8#jUIL^)12*Dbhk4GmL_E2`WX&iT16o zk(t|hok(Y|v-wzn?4x34T)|+SfZP>fiq!><*%vnxGN~ypST-FtC+@TPv*vYv@iU!_ z@2gf|PrgQ?Ktf*9^CnJ(x*CtZVB8!OBfg0%!wL;Z8(tYYre0vcnPGlyCc$V(Ipl*P z_(J!a=o@vp^%Efme!K74(Ke7A>Y}|sxV+JL^aYa{~m%5#$$+R1? zGaQhZTTX!#s#=Xtpegqero$RNt&`4xn3g$)=y*;=N=Qai)}~`xtxI_N*#MMCIq#HFifT zz(-*m;pVH&+4bixL&Bbg)W5FN^bH87pAHp)zPkWNMfTFqS=l~AC$3FX3kQUSh_C?-ZftyClgM)o_D7cX$RGlEYblux0jv5 zTr|i-I3@ZPCGheCl~BGhImF)K4!9@?pC(gi3ozX=a!|r1)LFxy_8c&wY0<^{2cm|P zv6Y`QktY*;I)IUd5y3ne1CqpVanlY45z8hf4&$EUBnucDj16pDa4&GI&TArYhf*xh zdj>*%APH8(h~c>o@l#%T>R$e>rwVx_WUB|~V`p^JHsg*y12lzj&zF}w6W09HwB2yb z%Q~`es&(;7#*DUC_w-Dmt7|$*?TA_m;zB+-u{2;Bg{O}nV7G_@7~<)Bv8fH^G$XG8$(&{A zwXJK5LRK%M34(t$&NI~MHT{UQ9qN-V_yn|%PqC81EIiSzmMM=2zb`mIwiP_b)x+2M z7Gd`83h79j#SItpQ}luuf2uOU`my_rY5T{6P#BNlb%h%<#MZb=m@y5aW;#o1^2Z)SWo+b`y0gV^iRcZtz5!-05vF z7wNo=hc6h4hc&s@uL^jqRvD6thVYtbErDK9k!;+a0xoE0WL7zLixjn5;$fXvT=O3I zT6jI&^A7k6R{&5#lVjz#8%_RiAa2{di{`kx79K+j72$H(!ass|B%@l%KeeKchYLe_ z>!(JC2fxsv>XVen+Y42GeYPxMWqm`6F$(E<6^s|g(slNk!lL*6v^W2>f6hh^mE$s= z3D$)}{V5(Qm&A6bp%2Q}*GZ5Qrf}n7*Hr51?bJOyA-?B4vg6y_EX<*-e20h{=0Mxs zbuQGZ$fLyO5v$nQ&^kuH+mNq9O#MWSfThtH|0q1i!NrWj^S}_P;Q1OkYLW6U^?_7G zx2wg?CULj7))QU(n{$0JE%1t2dWrMi2g-Os{v|8^wK{@qlj%+1b^?NI z$}l2tjp0g>K3O+p%yK<9!XqmQ?E9>z&(|^Pi~aSRwI5x$jaA62GFz9%fmO3t3a>cq zK8Xbv=5Ps~4mKN5+Eqw12(!PEyedFXv~VLxMB~HwT1Vfo51pQ#D8e$e4pFZ{&RC2P z5gTIzl{3!&(tor^BwZfR8j4k{7Rq#`riKXP2O-Bh66#WWK2w=z;iD9GLl+3 zpHIaI4#lQ&S-xBK8PiQ%dwOh?%BO~DCo06pN7<^dnZCN@NzY{_Z1>rrB0U|nC&+!2 z2y!oBcTd2;@lzyk(B=TkyZ)zy0deK05*Q0zk+o$@nun`VI1Er7pjq>8V zNmlW{p7S^Btgb(TA}jL(uR>`0w8gHP^T~Sh5Tkip^spk4SBAhC{TZU}_Z)UJw-}zm zPq{KBm!k)?P{`-(9?LFt&YN4s%SIZ-9lJ!Ws~B%exHOeVFk3~}HewnnH(d)qkLQ_d z6h>O)pEE{vbOVw}E+jdYC^wM+AAhaI(YAibUc@B#_mDss0Ji&BK{WG`4 zOk>vSNq(Bq2IB@s>>Rxm6Wv?h;ZXkpb1l8u|+_qXWdC*jjcPCixq;!%BVPSp#hP zqo`%cNf&YoQXHC$D=D45RiT|5ngPlh?0T~?lUf*O)){K@*Kbh?3RW1j9-T?%lDk@y z4+~?wKI%Y!-=O|_IuKz|=)F;V7ps=5@g)RrE;;tvM$gUhG>jHcw2Hr@fS+k^Zr~>G z^JvPrZc}_&d_kEsqAEMTMJw!!CBw)u&ZVzmq+ZworuaE&TT>$pYsd9|g9O^0orAe8 z221?Va!l1|Y5X1Y?{G7rt1sX#qFA^?RLG^VjoxPf63;AS=_mVDfGJKg73L zsGdnTUD40y(>S##2l|W2Cy!H(@@5KBa(#gs`vlz}Y~$ot5VsqPQ{{YtjYFvIumZzt zA{CcxZLJR|4#{j7k~Tu*jkwz8QA|5G1$Cl895R`Zyp;irp1{KN){kB30O8P1W5;@bG znvX74roeMmQlUi=v9Y%(wl$ZC#9tKNFpvi3!C}f1m6Ct|l2g%psc{TJp)@yu)*e2> z((p0Fg*8gJ!|3WZke9;Z{8}&NRkv7iP=#_y-F}x^y?2m%-D_aj^)f04%mneyjo_;) z6qc_Zu$q37d~X``*eP~Q>I2gg%rrV8v=kDfpp$=%Vj}hF)^dsSWygoN(A$g*E=Do6FX?&(@F#7pbiJ`;c0c@Ul zDqW_90Wm#5f2L<(Lf3)3TeXtI7nhYwRm(F;*r_G6K@OPW4H(Y3O5SjUzBC}u3d|eQ8*8d@?;zUPE+i#QNMn=r(ap?2SH@vo*m z3HJ%XuG_S6;QbWy-l%qU;8x;>z>4pMW7>R}J%QLf%@1BY(4f_1iixd-6GlO7Vp*yU zp{VU^3?s?90i=!#>H`lxT!q8rk>W_$2~kbpz7eV{3wR|8E=8**5?qn8#n`*(bt1xRQrdGxyx2y%B$qmw#>ZV$c7%cO#%JM1lY$Y0q?Yuo> ze9KdJoiM)RH*SB%^;TAdX-zEjA7@%y=!0=Zg%iWK7jVI9b&Dk}0$Af&08KHo+ zOwDhFvA(E|ER%a^cdh@^wLUlmIv6?_3=BvX8jKk92L=Y}7Jf5OGMfh` zBdR1wFCi-i5@`9km{isRb0O%TX+f~)KNaEz{rXQa89`YIF;EN&gN)cigu6mNh>?Cm zAO&Im2flv6D{jwm+y<%WsPe4!89n~KN|7}Cb{Z;XweER73r}Qp2 zz}WP4j}U0&(uD&9yGy6`!+_v-S(yG*iytsTR#x_Rc>=6u^vnRDnf1gP{#2>`ffrAC% zTZ5WQ@hAK;P;>kX{D)mIXe4%a5p=LO1xXH@8T?mz7Q@d)$3pL{{B!2{-v70L*o1AO+|n5beiw~ zk@(>m?T3{2k2c;NWc^`4@P&Z?BjxXJ@;x1qhn)9Mn*IFdt_J-dIqx5#d`NfyfX~m( zIS~5)MfZ2Uy?_4W`47i}u0ZgPh<{D|w_d#;D}Q&U$Q-G}xM1A@1f{#%A$jh6Qp&0hQ<0bPOM z-{1Wm&p%%#eb_?x7i;bol EfAhh=DF6Tf diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index abd303b673..d58dfb70ba 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,2 +1,19 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/mvnw b/mvnw index 41c0f0c23d..19529ddf8c 100755 --- a/mvnw +++ b/mvnw @@ -19,292 +19,241 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir +# Apache Maven Wrapper startup batch script, version 3.3.2 # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac -fi +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 fi fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" +} - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" done + printf %x\\n $h +} - saveddir=`pwd` +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } - M2_HOME=`dirname "$PRG"`/.. +die() { + printf %s\\n "$1" >&2 + exit 1 +} - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" fi -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" fi -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; fi -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 86115719e5..249bdf3822 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,3 +1,4 @@ +<# : batch portion @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @@ -18,165 +19,131 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir +@REM Apache Maven Wrapper startup batch script, version 3.3.2 @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output @REM ---------------------------------------------------------------------------- -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) ) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" From 57d4f88a48f6dc95f02ecbd9d19b6c396be2444f Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 20 Jan 2025 07:56:19 +0100 Subject: [PATCH 286/363] Java EA GitHub Actions improvements (#3803) * Use Java 17 for building MapStruct (still Java 8 compatible) * Upgrade to Spring 6 for tests * Adjust excludes and min tests version for some integration tests --- .github/workflows/java-ea.yml | 6 +-- .github/workflows/main.yml | 43 ++++++++----------- .github/workflows/release.yml | 2 +- ...eatureCompilationExclusionCliEnhancer.java | 3 ++ .../itest/tests/MavenIntegrationTest.java | 8 ++++ .../ProcessorInvocationInterceptor.java | 10 ++++- .../test/resources/fullFeatureTest/pom.xml | 10 +++++ .../superTypeGenerationTest/generator/pom.xml | 1 - .../superTypeGenerationTest/usage/pom.xml | 1 - .../generator/pom.xml | 1 - .../targetTypeGenerationTest/usage/pom.xml | 1 - parent/pom.xml | 6 +-- processor/pom.xml | 2 +- .../_1395/{ => spring}/Issue1395Mapper.java | 2 +- .../_1395/{ => spring}/Issue1395Test.java | 2 +- .../_1395/{ => spring}/NotUsedService.java | 2 +- .../test/bugs/_1395/{ => spring}/Source.java | 2 +- .../test/bugs/_1395/{ => spring}/Target.java | 2 +- .../_2807/{ => spring}/Issue2807Test.java | 8 ++-- .../{ => spring}/SpringLifeCycleMapper.java | 8 ++-- .../_2807/{ => spring}/after/AfterMethod.java | 2 +- .../{ => spring}/before/BeforeMethod.java | 2 +- .../beforewithtarget/BeforeWithTarget.java | 2 +- .../test/bugs/_880/{ => spring}/Config.java | 2 +- .../DefaultsToProcessorOptionsMapper.java | 2 +- .../bugs/_880/{ => spring}/Issue880Test.java | 2 +- .../test/bugs/_880/{ => spring}/Poodle.java | 2 +- .../UsesConfigFromAnnotationMapper.java | 2 +- .../decorator/jsr330/Jsr330DecoratorTest.java | 3 ++ ...30DefaultCompileOptionFieldMapperTest.java | 3 ++ ...330CompileOptionConstructorMapperTest.java | 3 ++ .../Jsr330ConstructorMapperTest.java | 3 ++ .../jsr330/field/Jsr330FieldMapperTest.java | 3 ++ .../jsr330/setter/Jsr330SetterMapperTest.java | 3 ++ .../org/mapstruct/ap/testutil/WithSpring.java | 3 ++ readme.md | 2 +- 36 files changed, 96 insertions(+), 63 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Issue1395Mapper.java (91%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Issue1395Test.java (92%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/NotUsedService.java (81%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Source.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/{ => spring}/Target.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/Issue2807Test.java (69%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/SpringLifeCycleMapper.java (70%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/after/AfterMethod.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/before/BeforeMethod.java (87%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/{ => spring}/beforewithtarget/BeforeWithTarget.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/Config.java (87%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/DefaultsToProcessorOptionsMapper.java (86%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/Issue880Test.java (97%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/Poodle.java (88%) rename processor/src/test/java/org/mapstruct/ap/test/bugs/_880/{ => spring}/UsesConfigFromAnnotationMapper.java (90%) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index d9b018bfb8..fce2dc06b2 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -7,11 +7,7 @@ env: jobs: test_jdk_ea: - strategy: - fail-fast: false - matrix: - java: [19-ea] - name: 'Linux JDK ${{ matrix.java }}' + name: 'Linux JDK EA' runs-on: ubuntu-latest steps: - name: 'Checkout' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b27dfc075a..9cc098161d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,45 +24,38 @@ jobs: distribution: 'zulu' java-version: ${{ matrix.java }} - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true - linux: - name: 'Linux JDK 11' - runs-on: ubuntu-latest - steps: - - name: 'Checkout' - uses: actions/checkout@v3 - - name: 'Set up JDK 11' - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: 11 - - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} install + run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 17 }} install -DskipDistribution=${{ matrix.java != 17 }} - name: 'Generate coverage report' + if: matrix.java == 17 run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' + if: matrix.java == 17 uses: codecov/codecov-action@v2 - name: 'Publish Snapshots' - if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' + if: matrix.java == 17 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' run: ./mvnw -s etc/ci-settings.xml -DskipTests=true -DskipDistribution=true deploy - linux-jdk-8: - name: 'Linux JDK 8' + integration_test_jdk: + strategy: + fail-fast: false + matrix: + java: [ 8, 11 ] + name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: - name: 'Checkout' uses: actions/checkout@v3 - - name: 'Set up JDK 11 for building everything' + - name: 'Set up JDK 17 for building everything' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: 'Install Processor' run: ./mvnw ${MAVEN_ARGS} -DskipTests install -pl processor -am - - name: 'Set up JDK 8 for running integration tests' + - name: 'Set up JDK ${{ matrix.java }} for running integration tests' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 8 + java-version: ${{ matrix.java }} - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest windows: @@ -70,11 +63,11 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v3 - - name: 'Set up JDK 11' + - name: 'Set up JDK 17' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: 'Test' run: ./mvnw %MAVEN_ARGS% install mac: @@ -82,10 +75,10 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 - - name: 'Set up JDK 11' + - name: 'Set up JDK 17' uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f18a12b813..682fa36876 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: 'zulu' cache: maven diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index 15d9e42598..77a5e2af06 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -30,6 +30,7 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces switch ( currentJreVersion ) { case JAVA_8: + additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/annotatewith/deprecated/jdk11/*.java" ); @@ -42,6 +43,8 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces // TODO find out why this fails: additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" ); break; + case JAVA_11: + additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" ); default: } diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 0bef2994f6..ab43cc4cba 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -5,6 +5,7 @@ */ package org.mapstruct.itest.tests; +import org.junit.jupiter.api.condition.DisabledOnJre; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.parallel.Execution; @@ -80,12 +81,15 @@ void jakartaJaxbTest() { } @ProcessorTest(baseDir = "jsr330Test") + @EnabledForJreRange(min = JRE.JAVA_17) + @DisabledOnJre(JRE.OTHER) void jsr330Test() { } @ProcessorTest(baseDir = "lombokBuilderTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }) + @DisabledOnJre(JRE.OTHER) void lombokBuilderTest() { } @@ -94,6 +98,7 @@ void lombokBuilderTest() { ProcessorTest.ProcessorType.JAVAC_WITH_PATHS }) @EnabledForJreRange(min = JRE.JAVA_11) + @DisabledOnJre(JRE.OTHER) void lombokModuleTest() { } @@ -150,6 +155,7 @@ void expressionTextBlocksTest() { }, forkJvm = true) // We have to fork the jvm because there is an NPE in com.intellij.openapi.util.SystemInfo.getRtVersion // and the kotlin-maven-plugin uses that. See also https://youtrack.jetbrains.com/issue/IDEA-238907 + @DisabledOnJre(JRE.OTHER) void kotlinDataTest() { } @@ -163,6 +169,8 @@ void defaultPackageTest() { } @ProcessorTest(baseDir = "springTest") + @EnabledForJreRange(min = JRE.JAVA_17) + @DisabledOnJre(JRE.OTHER) void springTest() { } diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java index 89009aaffe..85ce64952f 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java @@ -134,14 +134,20 @@ private void addAdditionalCliArguments(Verifier verifier) } private void configureProcessor(Verifier verifier) { - String compilerId = processorTestContext.getProcessor().getCompilerId(); + ProcessorTest.ProcessorType processor = processorTestContext.getProcessor(); + String compilerId = processor.getCompilerId(); if ( compilerId != null ) { - String profile = processorTestContext.getProcessor().getProfile(); + String profile = processor.getProfile(); if ( profile == null ) { profile = "generate-via-compiler-plugin"; } verifier.addCliOption( "-P" + profile ); verifier.addCliOption( "-Dcompiler-id=" + compilerId ); + if ( processor == ProcessorTest.ProcessorType.JAVAC ) { + if ( CURRENT_VERSION.ordinal() >= JRE.JAVA_21.ordinal() ) { + verifier.addCliOption( "-Dmaven.compiler.proc=full" ); + } + } } else { verifier.addCliOption( "-Pgenerate-via-processor-plugin" ); diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index 1a31b28221..849f7150fc 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -27,6 +27,11 @@ x x x + x + x + x + x + x @@ -49,6 +54,11 @@ ${additionalExclude4} ${additionalExclude5} ${additionalExclude6} + ${additionalExclude7} + ${additionalExclude8} + ${additionalExclude9} + ${additionalExclude10} + ${additionalExclude11} diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml index 1b84638ef2..5ab2d0d18f 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml @@ -32,7 +32,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -proc:none diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml index ee0d556b9f..d1e1dd7dff 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml @@ -38,7 +38,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -XprintProcessorInfo diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml index bf0d704851..67df383a18 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml @@ -32,7 +32,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -proc:none diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml index da72f667a4..bd06b79a49 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml @@ -38,7 +38,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -XprintProcessorInfo diff --git a/parent/pom.xml b/parent/pom.xml index 94c84fb4d0..5509e783d2 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -34,7 +34,7 @@ 3.4.1 3.2.2 3.1.0 - 5.3.31 + 6.2.2 1.6.0 8.36.1 5.10.1 @@ -47,7 +47,7 @@ jdt_apt 1.8 3.21.7 @@ -400,7 +400,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.13.0 org.apache.maven.plugins diff --git a/processor/pom.xml b/processor/pom.xml index ed2df7718b..13deafb413 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -24,7 +24,7 @@ - 11 + 17 diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.java similarity index 91% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.java index ebc9ae27e0..4e48020c8d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; import org.mapstruct.InjectionStrategy; import org.mapstruct.Mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.java similarity index 92% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.java index 042d0288cd..3417fbd707 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/NotUsedService.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java similarity index 81% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/NotUsedService.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java index 765764ef2b..ba47cf179f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/NotUsedService.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; /** * @author Filip Hrisafov diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.java index 1b676c2b62..8f2a52d462 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; /** * @author Filip Hrisafov diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Target.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.java index 2c7b22fd4d..1ba393dbe0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._1395; +package org.mapstruct.ap.test.bugs._1395.spring; /** * @author Filip Hrisafov diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.java similarity index 69% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.java index 2cb96ce37e..102a9d26eb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/Issue2807Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.java @@ -3,11 +3,11 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807; +package org.mapstruct.ap.test.bugs._2807.spring; -import org.mapstruct.ap.test.bugs._2807.after.AfterMethod; -import org.mapstruct.ap.test.bugs._2807.before.BeforeMethod; -import org.mapstruct.ap.test.bugs._2807.beforewithtarget.BeforeWithTarget; +import org.mapstruct.ap.test.bugs._2807.spring.after.AfterMethod; +import org.mapstruct.ap.test.bugs._2807.spring.before.BeforeMethod; +import org.mapstruct.ap.test.bugs._2807.spring.beforewithtarget.BeforeWithTarget; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.java similarity index 70% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.java index daabe7f52a..408a2ca3e0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/SpringLifeCycleMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.java @@ -3,15 +3,15 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807; +package org.mapstruct.ap.test.bugs._2807.spring; import java.util.List; import org.mapstruct.Mapper; import org.mapstruct.ReportingPolicy; -import org.mapstruct.ap.test.bugs._2807.after.AfterMethod; -import org.mapstruct.ap.test.bugs._2807.before.BeforeMethod; -import org.mapstruct.ap.test.bugs._2807.beforewithtarget.BeforeWithTarget; +import org.mapstruct.ap.test.bugs._2807.spring.after.AfterMethod; +import org.mapstruct.ap.test.bugs._2807.spring.before.BeforeMethod; +import org.mapstruct.ap.test.bugs._2807.spring.beforewithtarget.BeforeWithTarget; import org.mapstruct.factory.Mappers; /** diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.java index f7c7457348..05770c6603 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/after/AfterMethod.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807.after; +package org.mapstruct.ap.test.bugs._2807.spring.after; import java.util.List; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.java similarity index 87% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.java index 5252bee150..9fb9e7e883 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/before/BeforeMethod.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807.before; +package org.mapstruct.ap.test.bugs._2807.spring.before; import org.mapstruct.BeforeMapping; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.java index 69e62bdea1..a3ee8b57ea 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/beforewithtarget/BeforeWithTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._2807.beforewithtarget; +package org.mapstruct.ap.test.bugs._2807.spring.beforewithtarget; import java.util.List; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.java similarity index 87% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.java index 32f58384b9..7aef1e9e1f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import org.mapstruct.MapperConfig; import org.mapstruct.ReportingPolicy; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/DefaultsToProcessorOptionsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/DefaultsToProcessorOptionsMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java index c54da3f7cb..5f67b41552 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/DefaultsToProcessorOptionsMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import org.mapstruct.Mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java similarity index 97% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java index 9d8540b409..d2f371abed 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import javax.tools.Diagnostic.Kind; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java index e822db1cfe..71166f6d4c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; /** * @author Andreas Gudian diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/UsesConfigFromAnnotationMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.java similarity index 90% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/UsesConfigFromAnnotationMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.java index d9a1efe33e..bb9a7def9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/UsesConfigFromAnnotationMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.bugs._880; +package org.mapstruct.ap.test.bugs._880.spring; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java index a3bad68b92..be1a67cf09 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java @@ -11,6 +11,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.decorator.Address; import org.mapstruct.ap.test.decorator.AddressDto; @@ -46,6 +48,7 @@ @ComponentScan(basePackageClasses = Jsr330DecoratorTest.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330DecoratorTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java index 6b92d9815a..192059c8a5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -44,6 +46,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330DefaultCompileOptionFieldMapper.class) @WithJavaxInject @Configuration +@DisabledOnJre(JRE.OTHER) public class Jsr330DefaultCompileOptionFieldMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java index fe9cf295f4..02b634da8d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -44,6 +46,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330CompileOptionConstructorMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330CompileOptionConstructorMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java index a5cfce5c2e..d51843f00c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -44,6 +46,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330ConstructorMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330ConstructorMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java index 84e67f63a8..d713f9cc88 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -46,6 +48,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330FieldMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330FieldMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java index 558206e51c..a6f18ed594 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java @@ -7,6 +7,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; @@ -42,6 +44,7 @@ @ComponentScan(basePackageClasses = CustomerJsr330SetterMapper.class) @Configuration @WithJavaxInject +@DisabledOnJre(JRE.OTHER) public class Jsr330SetterMapperTest { @RegisterExtension diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java index 40db5afd81..04e387d429 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java @@ -10,6 +10,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; /** * Meta annotation that adds the needed Spring Dependencies @@ -23,6 +25,7 @@ "spring-beans", "spring-context" }) +@DisabledOnJre(JRE.OTHER) public @interface WithSpring { } diff --git a/readme.md b/readme.md index adb48b2fd4..907c8411cd 100644 --- a/readme.md +++ b/readme.md @@ -130,7 +130,7 @@ To learn more about MapStruct, refer to the [project homepage](https://mapstruct ## Building from Source -MapStruct uses Maven for its build. Java 11 is required for building MapStruct from source. To build the complete project, run +MapStruct uses Maven for its build. Java 17 is required for building MapStruct from source. To build the complete project, run ./mvnw clean install From c08ba4ca7e2832ad398a61b6a5a181ebaecd4e0d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Wed, 22 Jan 2025 08:51:49 +0100 Subject: [PATCH 287/363] Update setup-java and checkout actions to v4 (#3804) --- .github/workflows/java-ea.yml | 2 +- .github/workflows/main.yml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index fce2dc06b2..fc3a4a0fb1 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Set up JDK' uses: oracle-actions/setup-java@v1 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9cc098161d..a760175c0e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,9 +17,9 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Set up JDK' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: ${{ matrix.java }} @@ -43,16 +43,16 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Set up JDK 17 for building everything' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 - name: 'Install Processor' run: ./mvnw ${MAVEN_ARGS} -DskipTests install -pl processor -am - name: 'Set up JDK ${{ matrix.java }} for running integration tests' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: ${{ matrix.java }} @@ -62,9 +62,9 @@ jobs: name: 'Windows' runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: 'Set up JDK 17' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 @@ -76,7 +76,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: 'Set up JDK 17' - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 From 39551242d7cfbab7e6bbadfed8c771b483cc1a13 Mon Sep 17 00:00:00 2001 From: Zegveld <41897697+Zegveld@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:41:35 +0100 Subject: [PATCH 288/363] #3786: Improve error message when mapping non-iterable to array --- .../processor/MethodRetrievalProcessor.java | 5 +++ .../mapstruct/ap/internal/util/Message.java | 1 + .../bugs/_3786/ErroneousByteArrayMapper.java | 13 +++++++ .../ap/test/bugs/_3786/Issue3786Test.java | 36 +++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/ErroneousByteArrayMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/Issue3786Test.java 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 2c8ad5dcb9..c8c13e6bf9 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 @@ -555,6 +555,11 @@ private boolean checkParameterAndReturnType(ExecutableElement method, List Date: Sat, 10 May 2025 14:12:20 +0200 Subject: [PATCH 289/363] #3815: chore(docs): Improved wording about @Condition usage --- .../main/asciidoc/chapter-10-advanced-mapping-options.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 1e2bd133d4..b5c116ffd3 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -308,7 +308,7 @@ Conditional mapping can also be used to check if a source parameter should be ma A custom condition method for properties is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`. A custom condition method for source parameters is annotated with `org.mapstruct.SourceParameterCondition`, `org.mapstruct.Condition(appliesTo = org.mapstruct.ConditionStrategy#SOURCE_PARAMETERS)` or meta-annotated with `Condition(appliesTo = ConditionStrategy#SOURCE_PARAMETERS)` -e.g. if you only want to map a String property when it is not `null`, and it is not empty then you can do something like: +e.g. if you only want to map a String property when it is not `null` and not empty then you can do something like: .Mapper using custom condition check method ==== From 668eeb5de1b1dc79cbf90e68cdf38fbba03c98a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:25:00 +0000 Subject: [PATCH 290/363] Bump com.google.protobuf:protobuf-java from 3.21.7 to 3.25.5 in /parent Bumps [com.google.protobuf:protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.21.7 to 3.25.5. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.21.7...v3.25.5) --- updated-dependencies: - dependency-name: com.google.protobuf:protobuf-java dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index 5509e783d2..5735d4760c 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -50,7 +50,7 @@ The processor module needs at least Java 17. --> 1.8 - 3.21.7 + 3.25.5 2.3.2 From 2c84d04463a3ec2edd6c05e726a579b4ba936847 Mon Sep 17 00:00:00 2001 From: Cause Chung Date: Sun, 11 May 2025 17:33:35 +0200 Subject: [PATCH 291/363] #3240 Add Support for Java21 SequencedSet and SequencedMap --- .github/workflows/main.yml | 24 +++--- .github/workflows/release.yml | 2 +- .../chapter-6-mapping-collections.asciidoc | 4 + ...eatureCompilationExclusionCliEnhancer.java | 7 ++ .../test/resources/fullFeatureTest/pom.xml | 2 + parent/pom.xml | 2 +- processor/pom.xml | 2 +- .../ap/internal/model/common/TypeFactory.java | 13 ++++ .../util/JavaCollectionConstants.java | 21 ++++++ ...dCollectionsDefaultImplementationTest.java | 74 +++++++++++++++++++ .../jdk21/SequencedCollectionsMapper.java | 26 +++++++ readme.md | 3 +- 12 files changed, 164 insertions(+), 16 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a760175c0e..166466348d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - java: [17, 21] + java: [21] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: @@ -24,31 +24,31 @@ jobs: distribution: 'zulu' java-version: ${{ matrix.java }} - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 17 }} install -DskipDistribution=${{ matrix.java != 17 }} + run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 21 }} install -DskipDistribution=${{ matrix.java != 21 }} - name: 'Generate coverage report' - if: matrix.java == 17 + if: matrix.java == 21 run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' - if: matrix.java == 17 + if: matrix.java == 21 uses: codecov/codecov-action@v2 - name: 'Publish Snapshots' - if: matrix.java == 17 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' + if: matrix.java == 21 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' run: ./mvnw -s etc/ci-settings.xml -DskipTests=true -DskipDistribution=true deploy integration_test_jdk: strategy: fail-fast: false matrix: - java: [ 8, 11 ] + java: [ 8, 11, 17 ] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: - name: 'Checkout' uses: actions/checkout@v4 - - name: 'Set up JDK 17 for building everything' + - name: 'Set up JDK 21 for building everything' uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 17 + java-version: 21 - name: 'Install Processor' run: ./mvnw ${MAVEN_ARGS} -DskipTests install -pl processor -am - name: 'Set up JDK ${{ matrix.java }} for running integration tests' @@ -63,11 +63,11 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v4 - - name: 'Set up JDK 17' + - name: 'Set up JDK 21' uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 17 + java-version: 21 - name: 'Test' run: ./mvnw %MAVEN_ARGS% install mac: @@ -75,10 +75,10 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 - - name: 'Set up JDK 17' + - name: 'Set up JDK 21' uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 17 + java-version: 21 - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 682fa36876..61f347af3c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'zulu' cache: maven diff --git a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc index 79d4544f9b..4510c82cc0 100644 --- a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc +++ b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc @@ -212,12 +212,16 @@ When an iterable or map mapping method declares an interface type as return type |`Set`|`LinkedHashSet` +|`SequencedSet`|`LinkedHashSet` + |`SortedSet`|`TreeSet` |`NavigableSet`|`TreeSet` |`Map`|`LinkedHashMap` +|`SequencedMap`|`LinkedHashMap` + |`SortedMap`|`TreeMap` |`NavigableMap`|`TreeMap` diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index 77a5e2af06..0cc1a87818 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -34,6 +34,7 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/annotatewith/deprecated/jdk11/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); if ( processorType == ProcessorTest.ProcessorType.ECLIPSE_JDT ) { additionalExcludes.add( "org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java" ); @@ -42,9 +43,15 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces case JAVA_9: // TODO find out why this fails: additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); break; case JAVA_11: additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); + break; + case JAVA_17: + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); + break; default: } diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index 849f7150fc..b5ddae9a79 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -59,6 +59,8 @@ ${additionalExclude9} ${additionalExclude10} ${additionalExclude11} + ${additionalExclude12} + ${additionalExclude13} diff --git a/parent/pom.xml b/parent/pom.xml index 5735d4760c..f6ad60a22a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -47,7 +47,7 @@ jdt_apt 1.8 3.25.5 diff --git a/processor/pom.xml b/processor/pom.xml index 13deafb413..2341c4ddb0 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -24,7 +24,7 @@ - 17 + 21 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 ceb190a332..c2646f63db 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 @@ -45,6 +45,7 @@ import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.Extractor; import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.JavaCollectionConstants; import org.mapstruct.ap.internal.util.JavaStreamConstants; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.NativeTypes; @@ -149,6 +150,18 @@ public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMes ConcurrentNavigableMap.class.getName(), withDefaultConstructor( getType( ConcurrentSkipListMap.class ) ) ); + implementationTypes.put( + JavaCollectionConstants.SEQUENCED_SET_FQN, + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashSet.class ) ) + ); + implementationTypes.put( + JavaCollectionConstants.SEQUENCED_MAP_FQN, + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashMap.class ) ) + ); this.loggingVerbose = loggingVerbose; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java new file mode 100644 index 0000000000..68d556c542 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java @@ -0,0 +1,21 @@ +/* + * 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.internal.util; + +/** + * Helper holding Java collections full qualified class names for conversion registration, + * to achieve Java compatibility. + * + * @author Cause Chung + */ +public final class JavaCollectionConstants { + public static final String SEQUENCED_MAP_FQN = "java.util.SequencedMap"; + public static final String SEQUENCED_SET_FQN = "java.util.SequencedSet"; + + private JavaCollectionConstants() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java new file mode 100644 index 0000000000..4551d5b0b3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.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.collection.defaultimplementation.jdk21; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SequencedMap; +import java.util.SequencedSet; + +import org.mapstruct.ap.test.collection.defaultimplementation.Source; +import org.mapstruct.ap.test.collection.defaultimplementation.SourceFoo; +import org.mapstruct.ap.test.collection.defaultimplementation.Target; +import org.mapstruct.ap.test.collection.defaultimplementation.TargetFoo; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +@WithClasses({ + Source.class, + Target.class, + SourceFoo.class, + TargetFoo.class, + SequencedCollectionsMapper.class +}) +@IssueKey("3420") +class SequencedCollectionsDefaultImplementationTest { + + @ProcessorTest + public void shouldUseDefaultImplementationForSequencedMap() { + SequencedMap target = + SequencedCollectionsMapper.INSTANCE.sourceFooMapToTargetFooSequencedMap( createSourceFooMap() ); + + assertResultMap( target ); + } + + @ProcessorTest + public void shouldUseDefaultImplementationForSequencedSet() { + SequencedSet target = + SequencedCollectionsMapper.INSTANCE.sourceFoosToTargetFooSequencedSet( createSourceFooList() ); + + assertResultList( target ); + } + + private void assertResultList(Iterable fooIterable) { + assertThat( fooIterable ).isNotNull(); + assertThat( fooIterable ).containsOnly( new TargetFoo( "Bob" ), new TargetFoo( "Alice" ) ); + } + + private void assertResultMap(Map result) { + assertThat( result ).isNotNull(); + assertThat( result ).hasSize( 2 ); + assertThat( result ).contains( entry( "1", new TargetFoo( "Bob" ) ), entry( "2", new TargetFoo( "Alice" ) ) ); + } + + private Map createSourceFooMap() { + Map map = new HashMap<>(); + map.put( 1L, new SourceFoo( "Bob" ) ); + map.put( 2L, new SourceFoo( "Alice" ) ); + + return map; + } + + private List createSourceFooList() { + return Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java new file mode 100644 index 0000000000..bbffb56b0c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java @@ -0,0 +1,26 @@ +/* + * 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.collection.defaultimplementation.jdk21; + +import java.util.Collection; +import java.util.Map; +import java.util.SequencedMap; +import java.util.SequencedSet; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.collection.defaultimplementation.SourceFoo; +import org.mapstruct.ap.test.collection.defaultimplementation.TargetFoo; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SequencedCollectionsMapper { + + SequencedCollectionsMapper INSTANCE = Mappers.getMapper( SequencedCollectionsMapper.class ); + + SequencedSet sourceFoosToTargetFooSequencedSet(Collection foos); + + SequencedMap sourceFooMapToTargetFooSequencedMap(Map foos); +} diff --git a/readme.md b/readme.md index 907c8411cd..16a70f3f0b 100644 --- a/readme.md +++ b/readme.md @@ -130,7 +130,8 @@ To learn more about MapStruct, refer to the [project homepage](https://mapstruct ## Building from Source -MapStruct uses Maven for its build. Java 17 is required for building MapStruct from source. To build the complete project, run +MapStruct uses Maven for its build. Java 21 is required for building MapStruct from source. +To build the complete project, run ./mvnw clean install From 602e29083fe980be0256fd01d37906ceab1ff6ca Mon Sep 17 00:00:00 2001 From: zral <73640367+zyberzebra@users.noreply.github.com> Date: Sun, 11 May 2025 19:59:59 +0200 Subject: [PATCH 292/363] #1140 Add warning when target has no properties --- .../ap/internal/model/BeanMappingMethod.java | 78 ++++++++++++------- .../mapstruct/ap/internal/util/Message.java | 1 + .../test/annotatewith/AnnotateWithTest.java | 5 +- .../ErroneousMapperWithClassOnMethod.java | 10 +-- .../ap/test/bugs/_1111/Issue1111Mapper.java | 25 +++++- .../ap/test/bugs/_1111/Issue1111Test.java | 2 +- .../ap/test/bugs/_1130/Issue1130Mapper.java | 19 +++++ ...roneousIssue1242MapperMultipleSources.java | 3 +- .../ap/test/bugs/_1242/Issue1242Mapper.java | 3 +- .../ap/test/bugs/_1242/Issue1242Test.java | 2 +- .../mapstruct/ap/test/bugs/_1242/TargetB.java | 9 +++ .../ap/test/bugs/_1345/Issue1345Mapper.java | 18 +++++ .../ap/test/bugs/_1460/Issue1460Mapper.java | 21 ++++- .../_1460/java8/Issue1460JavaTimeMapper.java | 11 ++- .../mapstruct/ap/test/bugs/_1590/Book.java | 10 +++ .../ap/test/bugs/_1590/Issue1590Test.java | 2 +- .../org/mapstruct/ap/test/bugs/_1650/C.java | 10 +++ .../mapstruct/ap/test/bugs/_1650/CPrime.java | 10 +++ .../ap/test/bugs/_1650/Issue1650Test.java | 2 +- .../ap/test/bugs/_1821/Issue1821Mapper.java | 20 +++++ .../ap/test/bugs/_1821/Issue1821Test.java | 2 +- .../ap/test/bugs/_611/Issue611Test.java | 4 +- .../ap/test/bugs/_611/SomeClass.java | 40 ++++++++++ .../ap/test/bugs/_611/SomeOtherClass.java | 20 +++++ .../simple/BuilderInfoTargetTest.java | 16 +++- .../simple/SimpleBuilderMapper.java | 3 - .../simple/SimpleImmutableUpdateMapper.java | 19 +++++ .../immutabletarget/ImmutableProductTest.java | 10 +++ .../ap/test/collection/wildcard/Idea.java | 9 +++ .../ap/test/collection/wildcard/Plan.java | 9 +++ .../visibility/ConstructorVisibilityTest.java | 12 +++ .../AbstractDestinationClassNameMapper.java | 2 +- .../AbstractDestinationPackageNameMapper.java | 2 +- .../DestinationClassNameMapper.java | 2 +- .../DestinationClassNameMapperDecorated.java | 2 +- .../DestinationClassNameMapperDecorator.java | 4 +- .../DestinationClassNameMapperWithConfig.java | 2 +- ...tionClassNameMapperWithConfigOverride.java | 2 +- .../destination/DestinationClassNameTest.java | 1 + .../DestinationClassNameWithJsr330Mapper.java | 2 +- .../DestinationPackageNameMapper.java | 2 +- ...DestinationPackageNameMapperDecorated.java | 2 +- ...DestinationPackageNameMapperDecorator.java | 4 +- ...estinationPackageNameMapperWithConfig.java | 2 +- ...onPackageNameMapperWithConfigOverride.java | 2 +- ...estinationPackageNameMapperWithSuffix.java | 2 +- .../DestinationPackageNameTest.java | 1 + .../mapstruct/ap/test/destination/Target.java | 19 +++++ .../ap/test/emptytarget/EmptyTarget.java | 9 +++ .../test/emptytarget/EmptyTargetMapper.java | 18 +++++ .../ap/test/emptytarget/EmptyTargetTest.java | 39 ++++++++++ .../mapstruct/ap/test/emptytarget/Source.java | 36 +++++++++ .../mapstruct/ap/test/emptytarget/Target.java | 34 ++++++++ .../test/emptytarget/TargetWithNoSetters.java | 15 ++++ .../ap/test/imports/SourceTargetMapper.java | 10 ++- .../ap/test/java8stream/wildcard/Idea.java | 9 +++ .../ap/test/java8stream/wildcard/Plan.java | 9 +++ .../ErroneousSourceTargetMapper.java | 16 +++- .../MissingIgnoredSourceTest.java | 2 +- .../objectfactory/ObjectFactoryMapper.java | 13 +++- .../fixture/AbstractParentSource.java | 9 +++ .../fixture/AbstractParentTarget.java | 9 +++ .../fixture/ImplementedParentSource.java | 9 +++ .../fixture/ImplementedParentTarget.java | 9 +++ .../ap/test/versioninfo/SimpleMapper.java | 3 +- .../SuppressTimestampViaMapper.java | 3 +- .../SuppressTimestampViaMapperConfig.java | 3 +- .../ap/test/versioninfo/VersionInfoTest.java | 3 +- .../fixture/SubclassAbstractMapperImpl.java | 13 ++++ .../SubclassImplementedMapperImpl.java | 8 ++ .../fixture/SubclassInterfaceMapperImpl.java | 5 ++ 71 files changed, 657 insertions(+), 85 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/destination/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java 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 4ff2e8079f..08c8ceda59 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 @@ -235,9 +235,10 @@ else if ( !method.isUpdateMethod() ) { this.unprocessedTargetProperties = new LinkedHashMap<>( accessors ); + boolean constructorAccessorHadError = false; if ( !method.isUpdateMethod() && !hasFactoryMethod ) { ConstructorAccessor constructorAccessor = getConstructorAccessor( resultTypeToMap ); - if ( constructorAccessor != null ) { + if ( constructorAccessor != null && !constructorAccessor.hasError ) { this.unprocessedConstructorProperties = constructorAccessor.constructorAccessors; @@ -250,8 +251,10 @@ else if ( !method.isUpdateMethod() ) { else { this.unprocessedConstructorProperties = new LinkedHashMap<>(); } + constructorAccessorHadError = constructorAccessor != null && constructorAccessor.hasError; this.targetProperties.addAll( this.unprocessedConstructorProperties.keySet() ); + this.unprocessedTargetProperties.putAll( this.unprocessedConstructorProperties ); } else { @@ -322,7 +325,7 @@ else if ( !method.isUpdateMethod() ) { // report errors on unmapped properties if ( shouldHandledDefinedMappings ) { - reportErrorForUnmappedTargetPropertiesIfRequired(); + reportErrorForUnmappedTargetPropertiesIfRequired( resultTypeToMap, constructorAccessorHadError ); reportErrorForUnmappedSourcePropertiesIfRequired(); } reportErrorForMissingIgnoredSourceProperties(); @@ -964,7 +967,7 @@ private ConstructorAccessor getConstructorAccessor(Type type) { ) .collect( Collectors.joining( ", " ) ) ); - return null; + return new ConstructorAccessor( true, Collections.emptyList(), Collections.emptyMap() ); } else { return getConstructorAccessor( type, accessibleConstructors.get( 0 ) ); @@ -1023,7 +1026,7 @@ else if ( constructorProperties.size() != constructorParameters.size() ) { GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS, type ); - return null; + return new ConstructorAccessor( true, Collections.emptyList(), Collections.emptyMap() ); } else { Map constructorAccessors = new LinkedHashMap<>(); @@ -1731,36 +1734,45 @@ private ReportingPolicyGem getUnmappedTargetPolicy() { return method.getOptions().getMapper().unmappedTargetPolicy(); } - private void reportErrorForUnmappedTargetPropertiesIfRequired() { + private void reportErrorForUnmappedTargetPropertiesIfRequired(Type resultType, + boolean constructorAccessorHadError) { // fetch settings from element to implement ReportingPolicyGem unmappedTargetPolicy = getUnmappedTargetPolicy(); - if ( method instanceof ForgedMethod && targetProperties.isEmpty() ) { - //TODO until we solve 1140 we report this error when the target properties are empty - ForgedMethod forgedMethod = (ForgedMethod) method; - if ( forgedMethod.getHistory() == null ) { - Type sourceType = this.method.getParameters().get( 0 ).getType(); - Type targetType = this.method.getReturnType(); - ctx.getMessager().printMessage( - this.method.getExecutable(), - Message.PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND, - sourceType.describe(), - targetType.describe(), - targetType.describe(), - sourceType.describe() - ); + if ( targetProperties.isEmpty() ) { + if ( method instanceof ForgedMethod ) { + ForgedMethod forgedMethod = (ForgedMethod) method; + if ( forgedMethod.getHistory() == null ) { + Type sourceType = this.method.getParameters().get( 0 ).getType(); + Type targetType = this.method.getReturnType(); + ctx.getMessager().printMessage( + this.method.getExecutable(), + Message.PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND, + sourceType.describe(), + targetType.describe(), + targetType.describe(), + sourceType.describe() + ); + } + else { + ForgedMethodHistory history = forgedMethod.getHistory(); + ctx.getMessager().printMessage( + this.method.getExecutable(), + Message.PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND, + history.createSourcePropertyErrorMessage(), + history.getTargetType().describe(), + history.createTargetPropertyName(), + history.getTargetType().describe(), + history.getSourceType().describe() + ); + } } - else { - ForgedMethodHistory history = forgedMethod.getHistory(); + else if ( !constructorAccessorHadError ) { ctx.getMessager().printMessage( - this.method.getExecutable(), - Message.PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND, - history.createSourcePropertyErrorMessage(), - history.getTargetType().describe(), - history.createTargetPropertyName(), - history.getTargetType().describe(), - history.getSourceType().describe() + method.getExecutable(), + Message.PROPERTYMAPPING_TARGET_HAS_NO_TARGET_PROPERTIES, + resultType.describe() ); } } @@ -1780,7 +1792,8 @@ else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.require reportErrorForUnmappedProperties( unprocessedTargetProperties, unmappedPropertiesMsg, - unmappedForgedPropertiesMsg ); + unmappedForgedPropertiesMsg + ); } } @@ -1907,12 +1920,19 @@ private void reportErrorForUnusedSourceParameters() { } private static class ConstructorAccessor { + private final boolean hasError; private final List parameterBindings; private final Map constructorAccessors; private ConstructorAccessor( List parameterBindings, Map constructorAccessors) { + this( false, parameterBindings, constructorAccessors ); + } + + private ConstructorAccessor(boolean hasError, List parameterBindings, + Map constructorAccessors) { + this.hasError = hasError; this.parameterBindings = parameterBindings; this.constructorAccessors = constructorAccessors; } 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 221aed2859..883b3e3792 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 @@ -92,6 +92,7 @@ public enum Message { PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET("No property named \"%s\" exists in source parameter(s). Please define the source explicitly."), PROPERTYMAPPING_NO_SUITABLE_COLLECTION_OR_MAP_CONSTRUCTOR( "%s does not have an accessible copy or no-args constructor." ), PROPERTYMAPPING_EXPRESSION_AND_CONDITION_QUALIFIED_BY_NAME_BOTH_DEFINED( "Expression and condition qualified by name are both defined in @Mapping, either define an expression or a condition qualified by name." ), + PROPERTYMAPPING_TARGET_HAS_NO_TARGET_PROPERTIES( "No target property found for target \"%s\".", Diagnostic.Kind.WARNING ), CONVERSION_LOSSY_WARNING( "%s has a possibly lossy conversion from %s to %s.", Diagnostic.Kind.WARNING ), CONVERSION_LOSSY_ERROR( "Can't map %s. It has a possibly lossy conversion from %s to %s." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java index 3687fe2bc0..eaa8832df7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -176,12 +177,12 @@ public void erroneousMapperWithAnnotationOnlyOnClass() { @Diagnostic( kind = javax.tools.Diagnostic.Kind.ERROR, type = ErroneousMapperWithClassOnMethod.class, - line = 17, + line = 18, message = "Annotation \"CustomClassOnlyAnnotation\" is not allowed on methods." ) } ) - @WithClasses({ ErroneousMapperWithClassOnMethod.class, CustomClassOnlyAnnotation.class }) + @WithClasses({ ErroneousMapperWithClassOnMethod.class, CustomClassOnlyAnnotation.class, WithProperties.class }) public void erroneousMapperWithClassOnMethod() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java index d3d53c910a..c34bce304f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java @@ -7,6 +7,7 @@ import org.mapstruct.AnnotateWith; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; /** * @author Ben Zegveld @@ -15,13 +16,6 @@ public interface ErroneousMapperWithClassOnMethod { @AnnotateWith( value = CustomClassOnlyAnnotation.class ) - Target toString(Source value); + WithProperties toString(String string); - class Source { - - } - - class Target { - - } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java index a5a624e684..7cdfa82316 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java @@ -24,8 +24,29 @@ public interface Issue1111Mapper { List> listList(List> in); - class Source { } + class Source { + private final String value; - class Target { } + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java index 5619b64175..f5efb21e3a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java @@ -27,7 +27,7 @@ public class Issue1111Test { @ProcessorTest public void shouldCompile() { - List> source = Arrays.asList( Arrays.asList( new Source() ) ); + List> source = Arrays.asList( Arrays.asList( new Source( "test" ) ) ); List> target = Issue1111Mapper.INSTANCE.listList( source ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java index 042d5a2f8b..350d95eebc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java @@ -30,6 +30,16 @@ public void setB(BEntity b) { } static class BEntity { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } } static class ADto { @@ -46,6 +56,7 @@ public void setB(BDto b) { class BDto { private final String passedViaConstructor; + private String id; BDto(String passedViaConstructor) { this.passedViaConstructor = passedViaConstructor; @@ -54,6 +65,14 @@ class BDto { String getPassedViaConstructor() { return passedViaConstructor; } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } } abstract void mergeA(AEntity source, @MappingTarget ADto target); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java index 7259d343b8..7a8ad05542 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java @@ -7,13 +7,14 @@ import org.mapstruct.Mapper; import org.mapstruct.ObjectFactory; +import org.mapstruct.ReportingPolicy; /** * Results in an ambiguous factory method error, as there are two methods with matching source types available. * * @author Andreas Gudian */ -@Mapper(uses = TargetFactories.class) +@Mapper(uses = TargetFactories.class, unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class ErroneousIssue1242MapperMultipleSources { abstract TargetA toTargetA(SourceA source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java index 0491b5ffe0..90ea60a2ee 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java @@ -7,13 +7,14 @@ import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; +import org.mapstruct.ReportingPolicy; /** * Test mapper for properly resolving the best fitting factory method * * @author Andreas Gudian */ -@Mapper(uses = TargetFactories.class) +@Mapper(uses = TargetFactories.class, unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class Issue1242Mapper { abstract TargetA toTargetA(SourceA source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java index f27051670c..036a63f312 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java @@ -54,7 +54,7 @@ public void factoryMethodWithSourceParamIsChosen() { diagnostics = { @Diagnostic(type = ErroneousIssue1242MapperMultipleSources.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 20, + line = 21, message = "Ambiguous factory methods found for creating TargetB: " + "TargetB anotherTargetBCreator(SourceB source), " + "TargetB TargetFactories.createTargetB(SourceB source, @TargetType Class clazz), " + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java index ab4f639697..6a28adc828 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java @@ -9,6 +9,7 @@ * @author Andreas Gudian */ class TargetB { + protected String value; private final String passedViaConstructor; TargetB(String passedViaConstructor) { @@ -18,4 +19,12 @@ class TargetB { String getPassedViaConstructor() { return passedViaConstructor; } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java index 7699a8f7e9..b2d89c96e6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java @@ -27,8 +27,17 @@ public interface Issue1345Mapper { class A { + private String value; private String readOnlyProperty; + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + public String getReadOnlyProperty() { return readOnlyProperty; } @@ -36,8 +45,17 @@ public String getReadOnlyProperty() { class B { + private String value; private String property; + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + public String getProperty() { return property; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java index 90ed83d28a..1d0d4fccbb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java @@ -18,13 +18,13 @@ public abstract class Issue1460Mapper { public abstract Target map(Source source); - public abstract String forceUsageOfIssue1460Enum(Issue1460Enum source); + public abstract Value forceUsageOfIssue1460Enum(Issue1460Enum source); - public abstract String forceUsageOfLocale(Locale source); + public abstract Value forceUsageOfLocale(Locale source); - public abstract String forceUsageOfLocalDate(LocalDate source); + public abstract Value forceUsageOfLocalDate(LocalDate source); - public abstract String forceUsageOfDateTime(DateTime source); + public abstract Value forceUsageOfDateTime(DateTime source); public static class Issue1460Enum { } @@ -37,4 +37,17 @@ public static class LocalDate { public static class DateTime { } + + public static class Value { + + private final T source; + + public Value(T source) { + this.source = source; + } + + public T getSource() { + return source; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java index 216cda21fd..b9bedfe451 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java @@ -18,8 +18,17 @@ public abstract class Issue1460JavaTimeMapper { public abstract Target map(Source source); - public abstract String forceUsageOfLocalDate(LocalDate source); + public abstract LocalTarget forceUsageOfLocalDate(LocalDate source); public static class LocalDate { } + + public static class LocalTarget { + + private final LocalDate source; + + public LocalTarget(LocalDate source) { + this.source = source; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java index 53355ee603..bfe79c1772 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java @@ -6,4 +6,14 @@ package org.mapstruct.ap.test.bugs._1590; public class Book { + + private final String name; + + public Book(String name) { + this.name = name; + } + + public String getName() { + return name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java index d77add25de..0a9113797c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java @@ -28,7 +28,7 @@ public class Issue1590Test { @ProcessorTest public void shouldTestMappingLocalDates() { BookShelf source = new BookShelf(); - source.setBooks( Arrays.asList( new Book() ) ); + source.setBooks( Arrays.asList( new Book("Test") ) ); BookShelf target = BookShelfMapper.INSTANCE.map( source ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java index 705f2e2d69..dbbd74ec95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java @@ -6,4 +6,14 @@ package org.mapstruct.ap.test.bugs._1650; public class C { + + private final int value; + + public C(int value) { + this.value = value; + } + + public int getValue() { + return value; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java index 24e4376f88..db504189f8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java @@ -6,4 +6,14 @@ package org.mapstruct.ap.test.bugs._1650; public class CPrime { + + private int value; + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java index 2d1437eb77..33237e726c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java @@ -27,7 +27,7 @@ public void shouldCompile() { A a = new A(); a.setB( new B() ); - a.getB().setC( new C() ); + a.getB().setC( new C( 10 ) ); APrime aPrime = new APrime(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java index 08eccae32e..406fa7b71b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java @@ -22,11 +22,31 @@ public interface Issue1821Mapper { ExtendedTarget mapExtended(Source source); class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } } class ExtendedTarget extends Target { } class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java index 4481ed43e2..865ef8f6c1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java @@ -15,7 +15,7 @@ public class Issue1821Test { @ProcessorTest public void shouldNotGiveNullPtr() { - Issue1821Mapper.INSTANCE.map( new Issue1821Mapper.Source() ); + Issue1821Mapper.INSTANCE.map( new Issue1821Mapper.Source( "test" ) ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java index 79a6e045ec..b77da90ad9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java @@ -43,8 +43,8 @@ public void mapperNestedInsideNestedClassIsFound() { */ @ProcessorTest public void rightMapperIsFound() { - SomeClass.InnerMapper.Source source1 = new SomeClass.InnerMapper.Source(); - SomeOtherClass.InnerMapper.Source source2 = new SomeOtherClass.InnerMapper.Source(); + SomeClass.InnerMapper.Source source1 = new SomeClass.InnerMapper.Source( "test" ); + SomeOtherClass.InnerMapper.Source source2 = new SomeOtherClass.InnerMapper.Source( "test2" ); SomeClass.InnerMapper.Target target1 = SomeClass.InnerMapper.INSTANCE.toTarget( source1 ); SomeOtherClass.InnerMapper.Target target2 = SomeOtherClass.InnerMapper.INSTANCE.toTarget( source2 ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java index 0d41128711..e0fb59d4d7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java @@ -19,9 +19,29 @@ public interface InnerMapper { Target toTarget(Source in); class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } @@ -33,9 +53,29 @@ public interface InnerMapper { Target toTarget(Source in); class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java index e50187a82a..2ff2d0d831 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java @@ -19,9 +19,29 @@ public interface InnerMapper { Target toTarget(Source in); class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } } } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java index 4f3d8b85f4..5d0ff919ec 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java @@ -9,6 +9,9 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; 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.GeneratedSource; import static org.assertj.core.api.Assertions.assertThat; @@ -80,6 +83,17 @@ public void testUpdateMutableWithBuilder() { } @ProcessorTest + @WithClasses( { + SimpleImmutableUpdateMapper.class + } ) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = SimpleImmutableUpdateMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 18, + message = "No target property found for target \"SimpleImmutableTarget\"."), + }) + public void updatingTargetWithNoSettersShouldNotFail() { SimpleMutableSource source = new SimpleMutableSource(); @@ -90,7 +104,7 @@ public void updatingTargetWithNoSettersShouldNotFail() { .build(); assertThat( target.getAge() ).isEqualTo( 20 ); - SimpleBuilderMapper.INSTANCE.toImmutable( source, target ); + SimpleImmutableUpdateMapper.INSTANCE.toImmutable( source, target ); assertThat( target.getAge() ).isEqualTo( 20 ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java index 9f7187c634..6185daee97 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java @@ -21,9 +21,6 @@ public interface SimpleBuilderMapper { @Mapping(target = "builder.name", source = "source.fullName") void updateImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget.Builder builder); - // This method is fine as if the mapping target has setters it would use them, otherwise it won't - void toImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget target); - @Mapping(target = "name", source = "fullName") SimpleImmutableTarget toImmutable(SimpleMutableSource source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java new file mode 100644 index 0000000000..c6eccc96c8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java @@ -0,0 +1,19 @@ +/* + * 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.builder.mappingTarget.simple; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SimpleImmutableUpdateMapper { + + SimpleImmutableUpdateMapper INSTANCE = Mappers.getMapper( SimpleImmutableUpdateMapper.class ); + + // This method is fine as if the mapping target has setters it would use them, otherwise it won't + void toImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java index de22436296..8097dfdfe9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java @@ -11,6 +11,9 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; 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 static org.assertj.core.api.Assertions.assertThat; @@ -41,6 +44,13 @@ public void shouldHandleImmutableTarget() { CupboardNoSetterMapper.class, CupboardEntityOnlyGetter.class }) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = CupboardNoSetterMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 22, + message = "No target property found for target \"CupboardEntityOnlyGetter\"."), + }) public void shouldIgnoreImmutableTarget() { CupboardDto in = new CupboardDto(); in.setContent( Arrays.asList( "flour", "peas" ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java index fc9b89b571..4e21b223db 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java @@ -11,4 +11,13 @@ */ public class Idea { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java index 29fcfc2f3a..00db02170c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java @@ -11,4 +11,13 @@ */ public class Plan { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java index b7a254296d..5eebaf5d05 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java @@ -10,6 +10,9 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; 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 static org.assertj.core.api.Assertions.assertThat; @@ -44,6 +47,15 @@ public void shouldUseSinglePublicConstructorAlways() { @WithClasses({ SimpleWithPublicParameterlessConstructorMapper.class }) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = SimpleWithPublicParameterlessConstructorMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 21, + message = "No target property found for target " + + "\"SimpleWithPublicParameterlessConstructorMapper.Person\"."), + }) + public void shouldUsePublicParameterConstructorIfPresent() { PersonDto source = new PersonDto(); source.setName( "Bob" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java index 9498b832f7..8dd010cae3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java @@ -16,5 +16,5 @@ public abstract class AbstractDestinationClassNameMapper { public static final AbstractDestinationClassNameMapper INSTANCE = Mappers.getMapper( AbstractDestinationClassNameMapper.class ); - public abstract String intToString(Integer source); + public abstract Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java index b59d5cd21b..e26a6bbe71 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java @@ -16,5 +16,5 @@ public abstract class AbstractDestinationPackageNameMapper { public static final AbstractDestinationPackageNameMapper INSTANCE = Mappers.getMapper( AbstractDestinationPackageNameMapper.class ); - public abstract String intToString(Integer source); + public abstract Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java index a331096a63..99bd8915a5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java @@ -15,5 +15,5 @@ public interface DestinationClassNameMapper { DestinationClassNameMapper INSTANCE = Mappers.getMapper( DestinationClassNameMapper.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java index ef44767379..454e5e15bc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java @@ -17,5 +17,5 @@ public interface DestinationClassNameMapperDecorated { DestinationClassNameMapperDecorated INSTANCE = Mappers.getMapper( DestinationClassNameMapperDecorated.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java index 216786da24..3ba1433e21 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java @@ -16,7 +16,7 @@ protected DestinationClassNameMapperDecorator(DestinationClassNameMapperDecorate } @Override - public String intToString(Integer source) { - return delegate.intToString( source ); + public Target map(Integer source) { + return delegate.map( source ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java index d76392d11d..d13675f0ab 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java @@ -15,5 +15,5 @@ public interface DestinationClassNameMapperWithConfig { DestinationClassNameMapperWithConfig INSTANCE = Mappers.getMapper( DestinationClassNameMapperWithConfig.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java index b218fff158..a3f845462b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java @@ -16,5 +16,5 @@ public interface DestinationClassNameMapperWithConfigOverride { DestinationClassNameMapperWithConfigOverride INSTANCE = Mappers.getMapper( DestinationClassNameMapperWithConfigOverride.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java index 30611a8d60..1f6e255b95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java @@ -16,6 +16,7 @@ /** * @author Christophe Labouisse on 27/05/2015. */ +@WithClasses( Target.class ) public class DestinationClassNameTest { @ProcessorTest @WithClasses({ DestinationClassNameMapper.class }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java index 92c72a877d..3358f68fd4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java @@ -13,5 +13,5 @@ */ @Mapper(implementationName = "Jsr330Impl", componentModel = MappingConstants.ComponentModel.JSR330) public interface DestinationClassNameWithJsr330Mapper { - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java index 8c4740e323..c049036a13 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapper { DestinationPackageNameMapper INSTANCE = Mappers.getMapper( DestinationPackageNameMapper.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java index 1dd371b48a..98f6236027 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java @@ -17,5 +17,5 @@ public interface DestinationPackageNameMapperDecorated { DestinationPackageNameMapperDecorated INSTANCE = Mappers.getMapper( DestinationPackageNameMapperDecorated.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java index 4f6dd3242b..667bd99349 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java @@ -16,7 +16,7 @@ protected DestinationPackageNameMapperDecorator(DestinationPackageNameMapperDeco } @Override - public String intToString(Integer source) { - return delegate.intToString( source ); + public Target map(Integer source) { + return delegate.map( source ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java index fbe69ce43e..6a5a870520 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapperWithConfig { DestinationPackageNameMapperWithConfig INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithConfig.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java index b53dcaae42..79699006b1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java @@ -16,5 +16,5 @@ public interface DestinationPackageNameMapperWithConfigOverride { DestinationPackageNameMapperWithConfigOverride INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithConfigOverride.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java index 2a221e5f5b..068f30331d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapperWithSuffix { DestinationPackageNameMapperWithSuffix INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithSuffix.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java index 63c7ece6d5..5e02c88d93 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java @@ -15,6 +15,7 @@ * @author Christophe Labouisse on 27/05/2015. */ @IssueKey( "556" ) +@WithClasses( Target.class ) public class DestinationPackageNameTest { @ProcessorTest @WithClasses({ DestinationPackageNameMapper.class }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/Target.java b/processor/src/test/java/org/mapstruct/ap/test/destination/Target.java new file mode 100644 index 0000000000..5b3bb4ba34 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/Target.java @@ -0,0 +1,19 @@ +/* + * 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.destination; + +public class Target { + + private final Integer source; + + public Target(Integer source) { + this.source = source; + } + + public Integer getSource() { + return source; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java new file mode 100644 index 0000000000..f1b3a0f073 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java @@ -0,0 +1,9 @@ +/* + * 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.emptytarget; + +public class EmptyTarget { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java new file mode 100644 index 0000000000..8c82f81b7b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java @@ -0,0 +1,18 @@ +/* + * 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.emptytarget; + +import org.mapstruct.Mapper; + +@Mapper +public interface EmptyTargetMapper { + + TargetWithNoSetters mapToTargetWithSetters(Source source); + + EmptyTarget mapToEmptyTarget(Source source); + + Target mapToTarget(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java new file mode 100644 index 0000000000..66fa65b796 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java @@ -0,0 +1,39 @@ +/* + * 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.emptytarget; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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; + +@IssueKey("1140") +@WithClasses({ + EmptyTarget.class, + EmptyTargetMapper.class, + Source.class, + Target.class, + TargetWithNoSetters.class, +}) +class EmptyTargetTest { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = EmptyTargetMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 13, + message = "No target property found for target \"TargetWithNoSetters\"."), + @Diagnostic(type = EmptyTargetMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 15, + message = "No target property found for target \"EmptyTarget\".") + }) + void shouldProvideWarnings() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java new file mode 100644 index 0000000000..643cdccf3a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java @@ -0,0 +1,36 @@ +/* + * 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.emptytarget; + +public class Source { + private String label; + private double weight; + private Object content; + + public Object getContent() { + return content; + } + + public void setContent(Object content) { + this.content = content; + } + + public double getWeight() { + return weight; + } + + public void setWeight(double weight) { + this.weight = weight; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java new file mode 100644 index 0000000000..488a1fa8a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java @@ -0,0 +1,34 @@ +/* + * 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.emptytarget; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final String label; + private final double weight; + private final Object content; + + public Target(String label, double weight, Object content) { + this.label = label; + this.weight = weight; + this.content = content; + } + + public String getLabel() { + return label; + } + + public double getWeight() { + return weight; + } + + public Object getContent() { + return content; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java new file mode 100644 index 0000000000..edeb80f0d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java @@ -0,0 +1,15 @@ +/* + * 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.emptytarget; + +public class TargetWithNoSetters { + private int flightNumber; + private String airplaneName; + + public String getAirplaneName() { + return airplaneName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java index 1380ca400a..845544ba9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java @@ -25,7 +25,7 @@ public interface SourceTargetMapper { ParseException sourceToTarget(Named source); //custom types - Map listToMap(List list); + Value listToMap(List list); java.util.List namedsToExceptions(java.util.List source); @@ -36,4 +36,12 @@ public interface SourceTargetMapper { java.util.Map stringsToDates(java.util.Map stringDates); Target sourceToTarget(Source target); + + class Value { + private T value; + + public Value(List list) { + + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java index bb8434fd63..063a5e5615 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java @@ -11,4 +11,13 @@ */ public class Idea { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java index 8c55ebc273..7aa366ec95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java @@ -11,4 +11,13 @@ */ public class Plan { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java index 659867ca40..1196d773cb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java @@ -8,14 +8,24 @@ import org.mapstruct.BeanMapping; import org.mapstruct.Mapper; import org.mapstruct.ReportingPolicy; -import org.mapstruct.factory.Mappers; @Mapper( unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.IGNORE) public interface ErroneousSourceTargetMapper { - ErroneousSourceTargetMapper INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapper.class ); @BeanMapping(ignoreUnmappedSourceProperties = "bar") - Object sourceToTarget(Object source); + Target map(Target source); + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java index 77f80c5537..5794126604 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java @@ -22,7 +22,7 @@ public class MissingIgnoredSourceTest { diagnostics = { @Diagnostic(type = ErroneousSourceTargetMapper.class, kind = Kind.ERROR, - line = 20, + line = 18, message = "Ignored unknown source property: \"bar\".") } ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java index 2ea183bfc9..5ea0a54cb2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/objectfactory/ObjectFactoryMapper.java @@ -7,10 +7,11 @@ import org.mapstruct.Mapper; import org.mapstruct.ObjectFactory; +import org.mapstruct.ReportingPolicy; import org.mapstruct.TargetType; import org.mapstruct.factory.Mappers; -@Mapper +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) public interface ObjectFactoryMapper { ObjectFactoryMapper INSTANCE = Mappers.getMapper( ObjectFactoryMapper.class ); @@ -44,6 +45,16 @@ public boolean isA() { } abstract class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } } class TargetA extends Target { diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java index 7f9f19a2ab..295b4e2871 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentSource.java @@ -7,4 +7,13 @@ public abstract class AbstractParentSource { + private String parentValue; + + public String getParentValue() { + return parentValue; + } + + public void setParentValue(String parentValue) { + this.parentValue = parentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java index 3215aee644..a95f58b260 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/AbstractParentTarget.java @@ -7,4 +7,13 @@ public abstract class AbstractParentTarget { + private String parentValue; + + public String getParentValue() { + return parentValue; + } + + public void setParentValue(String parentValue) { + this.parentValue = parentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java index 94dde481b0..a047ee2573 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentSource.java @@ -7,4 +7,13 @@ public class ImplementedParentSource extends AbstractParentSource { + private String implementedParentValue; + + public String getImplementedParentValue() { + return implementedParentValue; + } + + public void setImplementedParentValue(String implementedParentValue) { + this.implementedParentValue = implementedParentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java index 7f3d254e21..a187a87091 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/fixture/ImplementedParentTarget.java @@ -7,4 +7,13 @@ public class ImplementedParentTarget extends AbstractParentTarget { + private String implementedParentValue; + + public String getImplementedParentValue() { + return implementedParentValue; + } + + public void setImplementedParentValue(String implementedParentValue) { + this.implementedParentValue = implementedParentValue; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java index 3bcea83364..f40525538c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SimpleMapper.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.versioninfo; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; /** * @author Andreas Gudian @@ -13,5 +14,5 @@ */ @Mapper public interface SimpleMapper { - Object toObject(Object object); + WithProperties toObject(WithProperties object); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java index 02a7b467b1..c3f9d39d26 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapper.java @@ -6,11 +6,12 @@ package org.mapstruct.ap.test.versioninfo; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; /** * @author Filip Hrisafov */ @Mapper(suppressTimestampInGenerated = true) public interface SuppressTimestampViaMapper { - Object toObject(Object object); + WithProperties toObject(WithProperties object); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java index a471cad028..54dc6b2fb7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/SuppressTimestampViaMapperConfig.java @@ -7,13 +7,14 @@ import org.mapstruct.Mapper; import org.mapstruct.MapperConfig; +import org.mapstruct.ap.test.WithProperties; /** * @author Filip Hrisafov */ @Mapper(config = SuppressTimestampViaMapperConfig.Config.class) public interface SuppressTimestampViaMapperConfig { - Object toObject(Object object); + WithProperties toObject(WithProperties object); @MapperConfig(suppressTimestampInGenerated = true) interface Config { diff --git a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java index f3192b85ad..60781c399f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/versioninfo/VersionInfoTest.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.versioninfo; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.WithProperties; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -18,7 +19,7 @@ * */ @IssueKey( "424" ) -@WithClasses( SimpleMapper.class ) +@WithClasses( { SimpleMapper.class, WithProperties.class } ) public class VersionInfoTest { @RegisterExtension final GeneratedSource generatedSource = new GeneratedSource(); diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java index a66b646f1e..b5dd2893c8 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassAbstractMapperImpl.java @@ -58,6 +58,8 @@ protected SubTarget subSourceToSubTarget(SubSource subSource) { SubTarget subTarget = new SubTarget(); + subTarget.setParentValue( subSource.getParentValue() ); + subTarget.setImplementedParentValue( subSource.getImplementedParentValue() ); subTarget.setValue( subSource.getValue() ); return subTarget; @@ -74,6 +76,9 @@ protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSource SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + subTargetOther.setParentValue( subSourceOther.getParentValue() ); + subTargetOther.setImplementedParentValue( subSourceOther.getImplementedParentValue() ); + return subTargetOther; } @@ -88,6 +93,9 @@ protected SubSourceSeparate subTargetSeparateToSubSourceSeparate(SubTargetSepara SubSourceSeparate subSourceSeparate = new SubSourceSeparate( separateValue ); + subSourceSeparate.setParentValue( subTargetSeparate.getParentValue() ); + subSourceSeparate.setImplementedParentValue( subTargetSeparate.getImplementedParentValue() ); + return subSourceSeparate; } @@ -102,6 +110,9 @@ protected SubSourceOverride subTargetOtherToSubSourceOverride(SubTargetOther sub SubSourceOverride subSourceOverride = new SubSourceOverride( finalValue ); + subSourceOverride.setParentValue( subTargetOther.getParentValue() ); + subSourceOverride.setImplementedParentValue( subTargetOther.getImplementedParentValue() ); + return subSourceOverride; } @@ -112,6 +123,8 @@ protected SubSource subTargetToSubSource(SubTarget subTarget) { SubSource subSource = new SubSource(); + subSource.setParentValue( subTarget.getParentValue() ); + subSource.setImplementedParentValue( subTarget.getImplementedParentValue() ); subSource.setValue( subTarget.getValue() ); return subSource; diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java index 444b012078..695ef03577 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassImplementedMapperImpl.java @@ -29,6 +29,9 @@ else if (item instanceof SubSourceOther) { else { ImplementedParentTarget implementedParentTarget = new ImplementedParentTarget(); + implementedParentTarget.setParentValue( item.getParentValue() ); + implementedParentTarget.setImplementedParentValue( item.getImplementedParentValue() ); + return implementedParentTarget; } } @@ -40,6 +43,8 @@ protected SubTarget subSourceToSubTarget(SubSource subSource) { SubTarget subTarget = new SubTarget(); + subTarget.setParentValue( subSource.getParentValue() ); + subTarget.setImplementedParentValue( subSource.getImplementedParentValue() ); subTarget.setValue( subSource.getValue() ); return subTarget; @@ -56,6 +61,9 @@ protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSource SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + subTargetOther.setParentValue( subSourceOther.getParentValue() ); + subTargetOther.setImplementedParentValue( subSourceOther.getImplementedParentValue() ); + return subTargetOther; } } diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java index 8366d53ea9..782831d6e3 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/subclassmapping/fixture/SubclassInterfaceMapperImpl.java @@ -38,6 +38,8 @@ protected SubTarget subSourceToSubTarget(SubSource subSource) { SubTarget subTarget = new SubTarget(); + subTarget.setParentValue( subSource.getParentValue() ); + subTarget.setImplementedParentValue( subSource.getImplementedParentValue() ); subTarget.setValue( subSource.getValue() ); return subTarget; @@ -54,6 +56,9 @@ protected SubTargetOther subSourceOtherToSubTargetOther(SubSourceOther subSource SubTargetOther subTargetOther = new SubTargetOther( finalValue ); + subTargetOther.setParentValue( subSourceOther.getParentValue() ); + subTargetOther.setImplementedParentValue( subSourceOther.getImplementedParentValue() ); + return subTargetOther; } } From 2fb5776350abf59f09cf26c727eb16ebfffadbc3 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 11 May 2025 20:07:53 +0200 Subject: [PATCH 293/363] Add release notes for next version --- NEXT_RELEASE_CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index e0f4cd31f0..20cbc8c3f8 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,10 +1,26 @@ ### Features +* Support for Java 21 Sequenced Collections (#3240) + + ### Enhancements +* Add support for locale parameter for numberFormat and dateFormat (#3628) +* Behaviour change: Warning when the target has no target properties (#1140) + + + ### Bugs +* Improve error message when mapping non-iterable to array (#3786) + ### Documentation ### Build +### Behaviour Change + +#### Warning when the target has no target properties + +With this change, if the target bean does not have any target properties, a warning will be shown. +This is like this to avoid potential mistakes by users, where they might think that the target bean has properties, but it does not. From fce73aee6ab655f3896dee440fa890a1971a281f Mon Sep 17 00:00:00 2001 From: roelmang Date: Sun, 11 May 2025 21:58:47 +0200 Subject: [PATCH 294/363] #3729 Support for using inner class Builder without using static factory method --- NEXT_RELEASE_CHANGELOG.md | 3 + .../chapter-3-defining-a-mapper.asciidoc | 7 +- .../ap/spi/DefaultBuilderProvider.java | 98 +++++++++++++--- .../ErroneousSimpleBuilderMapper.java | 5 +- .../{ => innerclass}/SimpleBuilderMapper.java | 5 +- ...lderThroughInnerClassConstructorTest.java} | 15 +-- ...ImmutablePersonWithInnerClassBuilder.java} | 14 +-- .../ErroneousSimpleBuilderMapper.java | 23 ++++ .../SimpleBuilderMapper.java | 23 ++++ ...BuilderThroughStaticFactoryMethodTest.java | 61 ++++++++++ ...ePersonWithStaticFactoryMethodBuilder.java | 105 ++++++++++++++++++ 11 files changed, 321 insertions(+), 38 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{ => innerclass}/ErroneousSimpleBuilderMapper.java (72%) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{ => innerclass}/SimpleBuilderMapper.java (74%) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{SimpleImmutableBuilderTest.java => innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java} (78%) rename processor/src/test/java/org/mapstruct/ap/test/builder/simple/{SimpleImmutablePerson.java => innerclass/SimpleImmutablePersonWithInnerClassBuilder.java} (86%) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 20cbc8c3f8..2d0555cb24 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -6,6 +6,9 @@ ### Enhancements * Add support for locale parameter for numberFormat and dateFormat (#3628) +* Detect Builder without a factory method (#3729) - With this if there is an inner class that ends with `Builder` and has a constructor with parameters, +it will be treated as a potential builder. +Builders through static methods on the type have a precedence. * Behaviour change: Warning when the target has no target properties (#1140) diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index ea574603ea..81980a35ac 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -421,8 +421,11 @@ If a Builder exists for a certain type, then that builder will be used for the m The default implementation of the `BuilderProvider` assumes the following: -* The type has a parameterless public static builder creation method that returns a builder. -So for example `Person` has a public static method that returns `PersonBuilder`. +* The type has either +** A parameterless public static builder creation method that returns a builder. +e.g. `Person` has a public static method that returns `PersonBuilder`. +** A public static inner class with the name having the suffix "Builder", and a public no-args constructor +e.g. `Person` has an inner class `PersonBuilder` with a public no-args constructor. * The builder type has a parameterless public method (build method) that returns the type being built. In our example `PersonBuilder` has a method returning `Person`. * In case there are multiple build methods, MapStruct will look for a method called `build`, if such method exists diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index 92cd1ed80d..afcee7d247 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -10,6 +10,8 @@ import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -184,32 +186,96 @@ protected BuilderInfo findBuilderInfo(TypeElement typeElement, boolean checkPare return null; } - List methods = ElementFilter.methodsIn( typeElement.getEnclosedElements() ); - List builderInfo = new ArrayList<>(); - for ( ExecutableElement method : methods ) { - if ( isPossibleBuilderCreationMethod( method, typeElement ) ) { - TypeElement builderElement = getTypeElement( method.getReturnType() ); - Collection buildMethods = findBuildMethods( builderElement, typeElement ); - if ( !buildMethods.isEmpty() ) { - builderInfo.add( new BuilderInfo.Builder() - .builderCreationMethod( method ) - .buildMethod( buildMethods ) - .build() - ); + // Builder infos which are determined by a static method on the type itself + List methodBuilderInfos = new ArrayList<>(); + // Builder infos which are determined by an inner builder class in the type itself + List innerClassBuilderInfos = new ArrayList<>(); + + for ( Element enclosedElement : typeElement.getEnclosedElements() ) { + if ( ElementKind.METHOD == enclosedElement.getKind() ) { + ExecutableElement method = (ExecutableElement) enclosedElement; + BuilderInfo builderInfo = determineMethodBuilderInfo( method, typeElement ); + if ( builderInfo != null ) { + methodBuilderInfos.add( builderInfo ); } } + else if ( ElementKind.CLASS == enclosedElement.getKind() ) { + if ( !methodBuilderInfos.isEmpty() ) { + // Small optimization to not check the inner classes + // if we already have at least one builder through a method + continue; + } + TypeElement classElement = (TypeElement) enclosedElement; + BuilderInfo builderInfo = determineInnerClassBuilderInfo( classElement, typeElement ); + if ( builderInfo != null ) { + innerClassBuilderInfos.add( builderInfo ); + } + } + } - if ( builderInfo.size() == 1 ) { - return builderInfo.get( 0 ); + if ( methodBuilderInfos.size() == 1 ) { + return methodBuilderInfos.get( 0 ); + } + else if ( methodBuilderInfos.size() > 1 ) { + throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), methodBuilderInfos ); } - else if ( builderInfo.size() > 1 ) { - throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), builderInfo ); + else if ( innerClassBuilderInfos.size() == 1 ) { + return innerClassBuilderInfos.get( 0 ); + } + else if ( innerClassBuilderInfos.size() > 1 ) { + throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), innerClassBuilderInfos ); } if ( checkParent ) { return findBuilderInfo( typeElement.getSuperclass() ); } + + return null; + } + + private BuilderInfo determineMethodBuilderInfo(ExecutableElement method, + TypeElement typeElement) { + if ( isPossibleBuilderCreationMethod( method, typeElement ) ) { + TypeElement builderElement = getTypeElement( method.getReturnType() ); + Collection buildMethods = findBuildMethods( builderElement, typeElement ); + if ( !buildMethods.isEmpty() ) { + return new BuilderInfo.Builder() + .builderCreationMethod( method ) + .buildMethod( buildMethods ) + .build(); + } + } + + return null; + } + + private BuilderInfo determineInnerClassBuilderInfo(TypeElement innerClassElement, + TypeElement typeElement) { + if ( innerClassElement.getModifiers().contains( Modifier.PUBLIC ) + && innerClassElement.getModifiers().contains( Modifier.STATIC ) + && innerClassElement.getSimpleName().toString().endsWith( "Builder" ) ) { + for ( Element element : innerClassElement.getEnclosedElements() ) { + if ( ElementKind.CONSTRUCTOR == element.getKind() ) { + ExecutableElement constructor = (ExecutableElement) element; + if ( constructor.getParameters().isEmpty() ) { + // We have a no-arg constructor + // Now check if we have build methods + Collection buildMethods = findBuildMethods( innerClassElement, typeElement ); + if ( !buildMethods.isEmpty() ) { + return new BuilderInfo.Builder() + .builderCreationMethod( constructor ) + .buildMethod( buildMethods ) + .build(); + } + // If we don't have any build methods + // then we can stop since we are only interested in the no-arg constructor + return null; + } + } + } + } + return null; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.java similarity index 72% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.java index 305ae77e80..18e82ed19c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.java @@ -3,12 +3,13 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; @Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) public interface ErroneousSimpleBuilderMapper { @@ -18,5 +19,5 @@ public interface ErroneousSimpleBuilderMapper { @Mapping(target = "job", ignore = true ), @Mapping(target = "city", ignore = true ) }) - SimpleImmutablePerson toImmutable(SimpleMutablePerson source); + SimpleImmutablePersonWithInnerClassBuilder toImmutable(SimpleMutablePerson source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.java similarity index 74% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.java index 0b0e961b30..e2c673401a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.java @@ -3,12 +3,13 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import org.mapstruct.CollectionMappingStrategy; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; @Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) public interface SimpleBuilderMapper { @@ -18,5 +19,5 @@ public interface SimpleBuilderMapper { @Mapping(target = "job", constant = "programmer"), @Mapping(target = "city", expression = "java(\"Bengalore\")") }) - SimpleImmutablePerson toImmutable(SimpleMutablePerson source); + SimpleImmutablePersonWithInnerClassBuilder toImmutable(SimpleMutablePerson source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java similarity index 78% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java index 7f77621644..0a2c5287bc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java @@ -3,11 +3,12 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import java.util.Arrays; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; @@ -20,16 +21,16 @@ @WithClasses({ SimpleMutablePerson.class, - SimpleImmutablePerson.class + SimpleImmutablePersonWithInnerClassBuilder.class }) -public class SimpleImmutableBuilderTest { +public class SimpleImmutableBuilderThroughInnerClassConstructorTest { @RegisterExtension final GeneratedSource generatedSource = new GeneratedSource(); @ProcessorTest @WithClasses({ SimpleBuilderMapper.class }) - public void testSimpleImmutableBuilderHappyPath() { + public void testSimpleImmutableBuilderThroughInnerClassConstructorHappyPath() { SimpleBuilderMapper mapper = Mappers.getMapper( SimpleBuilderMapper.class ); SimpleMutablePerson source = new SimpleMutablePerson(); source.setAge( 3 ); @@ -37,7 +38,7 @@ public void testSimpleImmutableBuilderHappyPath() { source.setChildren( Arrays.asList( "Alice", "Tom" ) ); source.setAddress( "Plaza 1" ); - SimpleImmutablePerson targetObject = mapper.toImmutable( source ); + SimpleImmutablePersonWithInnerClassBuilder targetObject = mapper.toImmutable( source ); assertThat( targetObject.getAge() ).isEqualTo( 3 ); assertThat( targetObject.getName() ).isEqualTo( "Bob" ); @@ -53,8 +54,8 @@ public void testSimpleImmutableBuilderHappyPath() { diagnostics = @Diagnostic( kind = javax.tools.Diagnostic.Kind.ERROR, type = ErroneousSimpleBuilderMapper.class, - line = 21, + line = 22, message = "Unmapped target property: \"name\".")) - public void testSimpleImmutableBuilderMissingPropertyFailsToCompile() { + public void testSimpleImmutableBuilderThroughInnerClassConstructorMissingPropertyFailsToCompile() { } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java rename to processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.java index 2fe2c8ded2..a9bda42d33 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.java @@ -3,12 +3,12 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.builder.simple; +package org.mapstruct.ap.test.builder.simple.innerclass; import java.util.ArrayList; import java.util.List; -public class SimpleImmutablePerson { +public class SimpleImmutablePersonWithInnerClassBuilder { private final String name; private final int age; private final String job; @@ -16,7 +16,7 @@ public class SimpleImmutablePerson { private final String address; private final List children; - SimpleImmutablePerson(Builder builder) { + SimpleImmutablePersonWithInnerClassBuilder(Builder builder) { this.name = builder.name; this.age = builder.age; this.job = builder.job; @@ -25,10 +25,6 @@ public class SimpleImmutablePerson { this.children = new ArrayList<>(builder.children); } - public static Builder builder() { - return new Builder(); - } - public int getAge() { return age; } @@ -66,8 +62,8 @@ public Builder age(int age) { return this; } - public SimpleImmutablePerson build() { - return new SimpleImmutablePerson( this ); + public SimpleImmutablePersonWithInnerClassBuilder build() { + return new SimpleImmutablePersonWithInnerClassBuilder( this ); } public Builder name(String name) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java new file mode 100644 index 0000000000..6fc51ace3d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java @@ -0,0 +1,23 @@ +/* + * 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.builder.simple.staticfactorymethod; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface ErroneousSimpleBuilderMapper { + + @Mappings({ + @Mapping(target = "address", ignore = true ), + @Mapping(target = "job", ignore = true ), + @Mapping(target = "city", ignore = true ) + }) + SimpleImmutablePersonWithStaticFactoryMethodBuilder toImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java new file mode 100644 index 0000000000..4bd200bc7c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java @@ -0,0 +1,23 @@ +/* + * 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.builder.simple.staticfactorymethod; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface SimpleBuilderMapper { + + @Mappings({ + @Mapping(target = "name", source = "fullName"), + @Mapping(target = "job", constant = "programmer"), + @Mapping(target = "city", expression = "java(\"Bengalore\")") + }) + SimpleImmutablePersonWithStaticFactoryMethodBuilder toImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java new file mode 100644 index 0000000000..90fac6061e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java @@ -0,0 +1,61 @@ +/* + * 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.builder.simple.staticfactorymethod; + +import java.util.Arrays; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; +import org.mapstruct.ap.testutil.ProcessorTest; +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.GeneratedSource; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + SimpleMutablePerson.class, + SimpleImmutablePersonWithStaticFactoryMethodBuilder.class +}) +public class SimpleImmutableBuilderThroughStaticFactoryMethodTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ SimpleBuilderMapper.class }) + public void testSimpleImmutableBuilderThroughStaticFactoryMethodHappyPath() { + SimpleBuilderMapper mapper = Mappers.getMapper( SimpleBuilderMapper.class ); + SimpleMutablePerson source = new SimpleMutablePerson(); + source.setAge( 3 ); + source.setFullName( "Bob" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + source.setAddress( "Plaza 1" ); + + SimpleImmutablePersonWithStaticFactoryMethodBuilder targetObject = mapper.toImmutable( source ); + + assertThat( targetObject.getAge() ).isEqualTo( 3 ); + assertThat( targetObject.getName() ).isEqualTo( "Bob" ); + assertThat( targetObject.getJob() ).isEqualTo( "programmer" ); + assertThat( targetObject.getCity() ).isEqualTo( "Bengalore" ); + assertThat( targetObject.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( targetObject.getChildren() ).contains( "Alice", "Tom" ); + } + + @ProcessorTest + @WithClasses({ ErroneousSimpleBuilderMapper.class }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSimpleBuilderMapper.class, + line = 22, + message = "Unmapped target property: \"name\".")) + public void testSimpleImmutableBuilderThroughStaticFactoryMethodMissingPropertyFailsToCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java new file mode 100644 index 0000000000..c3dc590210 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java @@ -0,0 +1,105 @@ +/* + * 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.builder.simple.staticfactorymethod; + +import java.util.ArrayList; +import java.util.List; + +public class SimpleImmutablePersonWithStaticFactoryMethodBuilder { + private final String name; + private final int age; + private final String job; + private final String city; + private final String address; + private final List children; + + SimpleImmutablePersonWithStaticFactoryMethodBuilder(Builder builder) { + this.name = builder.name; + this.age = builder.age; + this.job = builder.job; + this.city = builder.city; + this.address = builder.address; + this.children = new ArrayList<>( builder.children ); + } + + public static Builder builder() { + return new Builder(); + } + + public int getAge() { + return age; + } + + public String getName() { + return name; + } + + public String getJob() { + return job; + } + + public String getCity() { + return city; + } + + public String getAddress() { + return address; + } + + public List getChildren() { + return children; + } + + public static class Builder { + private String name; + private int age; + private String job; + private String city; + private String address; + private List children = new ArrayList<>(); + + private Builder() { + } + + public Builder age(int age) { + this.age = age; + return this; + } + + public SimpleImmutablePersonWithStaticFactoryMethodBuilder build() { + return new SimpleImmutablePersonWithStaticFactoryMethodBuilder( this ); + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder job(String job) { + this.job = job; + return this; + } + + public Builder city(String city) { + this.city = city; + return this; + } + + public Builder address(String address) { + this.address = address; + return this; + } + + public List getChildren() { + throw new UnsupportedOperationException( "This is just a marker method" ); + } + + public Builder addChild(String child) { + this.children.add( child ); + return this; + } + } +} From 6e6fd01a2eb08177d5cc360a97e1721db511239d Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 18 May 2025 00:40:51 +0800 Subject: [PATCH 295/363] #3821: Add support for custom exception for subclass exhaustive strategy for `@SubclassMapping` --------- Signed-off-by: TangYang --- .../main/java/org/mapstruct/BeanMapping.java | 12 +++ core/src/main/java/org/mapstruct/Mapper.java | 12 +++ .../main/java/org/mapstruct/MapperConfig.java | 12 +++ .../ap/internal/model/BeanMappingMethod.java | 17 ++++- .../model/source/BeanMappingOptions.java | 9 +++ .../internal/model/source/DefaultOptions.java | 4 + .../model/source/DelegatingOptions.java | 4 + .../model/source/MapperConfigOptions.java | 6 ++ .../internal/model/source/MapperOptions.java | 7 ++ .../ap/internal/model/BeanMappingMethod.ftl | 2 +- .../CustomExceptionSubclassMapper.java | 25 ++++++ .../CustomSubclassMappingException.java | 12 +++ .../CustomSubclassMappingExceptionTest.java | 76 +++++++++++++++++++ .../MapperConfigSubclassMapper.java | 29 +++++++ .../MapperSubclassMapper.java | 24 ++++++ 15 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.java diff --git a/core/src/main/java/org/mapstruct/BeanMapping.java b/core/src/main/java/org/mapstruct/BeanMapping.java index e94ff98f2d..309458f861 100644 --- a/core/src/main/java/org/mapstruct/BeanMapping.java +++ b/core/src/main/java/org/mapstruct/BeanMapping.java @@ -132,6 +132,18 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() */ SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR; + /** + * Specifies the exception type to be thrown when a missing subclass implementation is detected + * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}. + *

      + * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to + * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}. + * + * @return the exception class to throw when missing implementations are found. + * Defaults to {@link IllegalArgumentException}. + */ + Class subclassExhaustiveException() default IllegalArgumentException.class; + /** * Default ignore all mappings. All mappings have to be defined manually. No automatic mapping will take place. No * warning will be issued on missing source or target properties. diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index 8a6f48dad9..398dc1870a 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -281,6 +281,18 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default */ SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR; + /** + * Specifies the exception type to be thrown when a missing subclass implementation is detected + * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}. + *

      + * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to + * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}. + * + * @return the exception class to throw when missing implementations are found. + * Defaults to {@link IllegalArgumentException}. + */ + Class subclassExhaustiveException() default IllegalArgumentException.class; + /** * Determines whether to use field or constructor injection. This is only used on annotated based component models * such as CDI, Spring and JSR 330. diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index 915f3dd120..8631562a56 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -249,6 +249,18 @@ MappingInheritanceStrategy mappingInheritanceStrategy() */ SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR; + /** + * Specifies the exception type to be thrown when a missing subclass implementation is detected + * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}. + *

      + * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to + * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}. + * + * @return the exception class to throw when missing implementations are found. + * Defaults to {@link IllegalArgumentException}. + */ + Class subclassExhaustiveException() default IllegalArgumentException.class; + /** * Determines whether to use field or constructor injection. This is only used on annotated based component models * such as CDI, Spring and JSR 330. 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 08c8ceda59..34c1ea3ccb 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 @@ -100,6 +100,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { private final String finalizedResultName; private final List beforeMappingReferencesWithFinalizedReturnType; private final List afterMappingReferencesWithFinalizedReturnType; + private final Type subclassExhaustiveException; private final MappingReferences mappingReferences; @@ -378,6 +379,11 @@ else if ( !method.isUpdateMethod() ) { } + TypeMirror subclassExhaustiveException = method.getOptions() + .getBeanMapping() + .getSubclassExhaustiveException(); + Type subclassExhaustiveExceptionType = ctx.getTypeFactory().getType( subclassExhaustiveException ); + List subclasses = new ArrayList<>(); for ( SubclassMappingOptions subclassMappingOptions : method.getOptions().getSubclassMappings() ) { subclasses.add( createSubclassMapping( subclassMappingOptions ) ); @@ -451,7 +457,8 @@ else if ( !sourceParameter.getType().isPrimitive() ) { finalizeMethod, mappingReferences, subclasses, - presenceChecksByParameter + presenceChecksByParameter, + subclassExhaustiveExceptionType ); } @@ -1954,7 +1961,8 @@ private BeanMappingMethod(Method method, MethodReference finalizerMethod, MappingReferences mappingReferences, List subclassMappings, - Map presenceChecksByParameter) { + Map presenceChecksByParameter, + Type subclassExhaustiveException) { super( method, annotations, @@ -1969,6 +1977,7 @@ private BeanMappingMethod(Method method, this.propertyMappings = propertyMappings; this.returnTypeBuilder = returnTypeBuilder; this.finalizerMethod = finalizerMethod; + this.subclassExhaustiveException = subclassExhaustiveException; if ( this.finalizerMethod != null ) { this.finalizedResultName = Strings.getSafeVariableName( getResultName() + "Result", existingVariableNames ); @@ -2017,6 +2026,10 @@ else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { this.subclassMappings = subclassMappings; } + public Type getSubclassExhaustiveException() { + return subclassExhaustiveException; + } + public List getConstantMappings() { return constantMappings; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java index ac27dfff00..73a4d7c12d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -11,6 +11,7 @@ import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.gem.BeanMappingGem; import org.mapstruct.ap.internal.gem.BuilderGem; @@ -182,6 +183,14 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { .orElse( next().getSubclassExhaustiveStrategy() ); } + @Override + public TypeMirror getSubclassExhaustiveException() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::subclassExhaustiveException ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .orElse( next().getSubclassExhaustiveException() ); + } + @Override public ReportingPolicyGem unmappedTargetPolicy() { return Optional.ofNullable( beanMapping ).map( BeanMappingGem::unmappedTargetPolicy ) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index c754d3c395..e1f04fd941 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -131,6 +131,10 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { return SubclassExhaustiveStrategyGem.valueOf( mapper.subclassExhaustiveStrategy().getDefaultValue() ); } + public TypeMirror getSubclassExhaustiveException() { + return mapper.subclassExhaustiveException().getDefaultValue(); + } + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { NullValueMappingStrategyGem nullValueIterableMappingStrategy = options.getNullValueIterableMappingStrategy(); if ( nullValueIterableMappingStrategy != null ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java index 50c1d84541..34478969fa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java @@ -106,6 +106,10 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { return next.getSubclassExhaustiveStrategy(); } + public TypeMirror getSubclassExhaustiveException() { + return next.getSubclassExhaustiveException(); + } + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { return next.getNullValueIterableMappingStrategy(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java index e3ca6162a5..d606658878 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -141,6 +141,12 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { next().getSubclassExhaustiveStrategy(); } + public TypeMirror getSubclassExhaustiveException() { + return mapperConfig.subclassExhaustiveException().hasValue() ? + mapperConfig.subclassExhaustiveException().get() : + next().getSubclassExhaustiveException(); + } + @Override public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { if ( mapperConfig.nullValueIterableMappingStrategy().hasValue() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java index ed1af34f79..9c2203efd5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -170,6 +170,13 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { next().getSubclassExhaustiveStrategy(); } + @Override + public TypeMirror getSubclassExhaustiveException() { + return mapper.subclassExhaustiveException().hasValue() ? + mapper.subclassExhaustiveException().get() : + next().getSubclassExhaustiveException(); + } + @Override public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { if ( mapper.nullValueIterableMappingStrategy().hasValue() ) { diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 3036e4a2c8..da590bb506 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -42,7 +42,7 @@ else { <#if isAbstractReturnType()> - throw new IllegalArgumentException("Not all subclasses are supported for this mapping. Missing for " + ${subclassMappings[0].sourceArgument}.getClass()); + throw new <@includeModel object=subclassExhaustiveException />("Not all subclasses are supported for this mapping. Missing for " + ${subclassMappings[0].sourceArgument}.getClass()); <#else> <#if !existingInstanceMapping> <#if hasConstructorMappings()> diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.java new file mode 100644 index 0000000000..52738ef6f4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomExceptionSubclassMapper.java @@ -0,0 +1,25 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CustomExceptionSubclassMapper { + CustomExceptionSubclassMapper INSTANCE = Mappers.getMapper( CustomExceptionSubclassMapper.class ); + + @BeanMapping(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + subclassExhaustiveException = CustomSubclassMappingException.class) + @SubclassMapping(source = Car.class, target = CarDto.class) + @SubclassMapping(source = Bike.class, target = BikeDto.class) + VehicleDto map(AbstractVehicle vehicle); + + VehicleCollectionDto mapInverse(VehicleCollection vehicles); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java new file mode 100644 index 0000000000..abb675bbb4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingException.java @@ -0,0 +1,12 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +public class CustomSubclassMappingException extends RuntimeException { + public CustomSubclassMappingException(String message) { + super( message ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java new file mode 100644 index 0000000000..472c007269 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/CustomSubclassMappingExceptionTest.java @@ -0,0 +1,76 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@IssueKey("3821") +@WithClasses({ + Bike.class, + BikeDto.class, + Car.class, + CarDto.class, + Motorcycle.class, + VehicleCollection.class, + VehicleCollectionDto.class, + AbstractVehicle.class, + VehicleDto.class, + CustomSubclassMappingException.class, + CustomExceptionSubclassMapper.class +}) +public class CustomSubclassMappingExceptionTest { + + private static final String EXPECTED_ERROR_MESSAGE = "Not all subclasses are supported for this mapping. " + + "Missing for class org.mapstruct.ap.test.subclassmapping.abstractsuperclass.Motorcycle"; + + @ProcessorTest + void customExceptionIsThrownForUnknownSubclass() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Motorcycle() ); // undefine subclass + + assertThatThrownBy( () -> CustomExceptionSubclassMapper.INSTANCE.mapInverse( vehicles ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } + + @ProcessorTest + void customExceptionIsThrownForSingleVehicle() { + AbstractVehicle vehicle = new Motorcycle(); // undefine subclass + + assertThatThrownBy( () -> CustomExceptionSubclassMapper.INSTANCE.map( vehicle ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } + + @ProcessorTest + @WithClasses({ MapperConfigSubclassMapper.class }) + void customExceptionIsThrownForMapperConfig() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Motorcycle() ); // undefined subclass + + assertThatThrownBy( () -> MapperConfigSubclassMapper.INSTANCE.mapInverse( vehicles ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } + + @ProcessorTest + @WithClasses({ MapperSubclassMapper.class }) + void customExceptionIsThrownForMapper() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Car() ); + vehicles.getVehicles().add( new Motorcycle() ); // undefined subclass + + assertThatThrownBy( () -> MapperSubclassMapper.INSTANCE.mapInverse( vehicles ) ) + .isInstanceOf( CustomSubclassMappingException.class ) + .hasMessage( EXPECTED_ERROR_MESSAGE ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.java new file mode 100644 index 0000000000..b3c1a66bb6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperConfigSubclassMapper.java @@ -0,0 +1,29 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +import org.mapstruct.Mapper; +import org.mapstruct.MapperConfig; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper(config = MapperConfigSubclassMapper.Config.class) +public interface MapperConfigSubclassMapper { + + MapperConfigSubclassMapper INSTANCE = Mappers.getMapper( MapperConfigSubclassMapper.class ); + + @MapperConfig(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + subclassExhaustiveException = CustomSubclassMappingException.class) + interface Config { + } + + @SubclassMapping(source = Car.class, target = CarDto.class) + @SubclassMapping(source = Bike.class, target = BikeDto.class) + VehicleDto map(AbstractVehicle vehicle); + + VehicleCollectionDto mapInverse(VehicleCollection vehicles); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.java new file mode 100644 index 0000000000..34ed3fde22 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/abstractsuperclass/MapperSubclassMapper.java @@ -0,0 +1,24 @@ +/* + * 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.subclassmapping.abstractsuperclass; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + subclassExhaustiveException = CustomSubclassMappingException.class) +public interface MapperSubclassMapper { + + MapperSubclassMapper INSTANCE = Mappers.getMapper( MapperSubclassMapper.class ); + + @SubclassMapping(source = Car.class, target = CarDto.class) + @SubclassMapping(source = Bike.class, target = BikeDto.class) + VehicleDto map(AbstractVehicle vehicle); + + VehicleCollectionDto mapInverse(VehicleCollection vehicles); +} From 05f27e96e2108344d69a2963b04ee973c83b0eb0 Mon Sep 17 00:00:00 2001 From: Dennis Melzer Date: Sun, 25 May 2025 14:58:23 +0200 Subject: [PATCH 296/363] #3852 Initialize Optionals with empty instead of null --- .../ap/internal/model/common/Type.java | 17 +++++ .../nullvalue/OptionalDefaultMapper.java | 20 ++++++ .../nullvalue/OptionalDefaultMapperTest.java | 53 ++++++++++++++++ .../nullvalue/SimpleConstructorMapper.java | 29 +++++++++ .../ap/test/optional/nullvalue/Source.java | 61 ++++++++++++++++++ .../ap/test/optional/nullvalue/Target.java | 63 +++++++++++++++++++ 6 files changed, 243 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 6520834066..d0f65ce97a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -17,6 +17,10 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -367,6 +371,15 @@ public boolean isArrayType() { return componentType != null; } + private boolean isType(Class type) { + return type.getName().equals( getFullyQualifiedName() ); + } + + private boolean isOptionalType() { + return isType( Optional.class ) || isType( OptionalInt.class ) || isType( OptionalDouble.class ) || + isType( OptionalLong.class ); + } + public boolean isTypeVar() { return (typeMirror.getKind() == TypeKind.TYPEVAR); } @@ -1166,6 +1179,10 @@ else if ( !method.getModifiers().contains( Modifier.PUBLIC ) ) { * FTL. */ public String getNull() { + if ( isOptionalType() ) { + return createReferenceName() + ".empty()"; + } + if ( !isPrimitive() || isArrayType() ) { return "null"; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.java new file mode 100644 index 0000000000..7397c2ccd1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapper.java @@ -0,0 +1,20 @@ +/* + * 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.optional.nullvalue; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Dennis Melzer + */ +@Mapper +public interface OptionalDefaultMapper { + + OptionalDefaultMapper INSTANCE = Mappers.getMapper( OptionalDefaultMapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java new file mode 100644 index 0000000000..78482831b9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/OptionalDefaultMapperTest.java @@ -0,0 +1,53 @@ +/* + * 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.optional.nullvalue; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Dennis Melzer + */ +@WithClasses({ + OptionalDefaultMapper.class, + Source.class, + Target.class +}) +@IssueKey("3852") +public class OptionalDefaultMapperTest { + + @ProcessorTest + public void shouldOptionalNotNull() { + Source source = new Source( null, null, null, null, null, null, null ); + Target target = OptionalDefaultMapper.INSTANCE.map( source ); + + assertThat( target.getSomeString() ).isEmpty(); + assertThat( target.getSomeInteger() ).isEmpty(); + assertThat( target.getSomeDouble() ).isEmpty(); + assertThat( target.getSomeBoolean() ).isEmpty(); + assertThat( target.getSomeIntValue() ).isEmpty(); + assertThat( target.getSomeDoubleValue() ).isEmpty(); + assertThat( target.getSomeLongValue() ).isEmpty(); + } + + @ProcessorTest + public void shouldMapOptional() { + Source source = new Source( "someString", 10, 11D, Boolean.TRUE, 10, 100D, 200L ); + Target target = OptionalDefaultMapper.INSTANCE.map( source ); + + assertThat( target.getSomeString() ).contains( "someString" ); + assertThat( target.getSomeInteger() ).contains( 10 ); + assertThat( target.getSomeDouble() ).contains( 11D ); + assertThat( target.getSomeBoolean() ).contains( Boolean.TRUE ); + assertThat( target.getSomeIntValue() ).hasValue( 10 ); + assertThat( target.getSomeDoubleValue() ).hasValue( 100 ); + assertThat( target.getSomeLongValue() ).hasValue( 200 ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java new file mode 100644 index 0000000000..158a01d3bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java @@ -0,0 +1,29 @@ +/* + * 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.optional.nullvalue; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.nestedproperties.simple._target.TargetObject; +import org.mapstruct.ap.test.nestedproperties.simple.source.SourceRoot; +import org.mapstruct.factory.Mappers; + +/** + * @author Dennis Melzer + */ +@Mapper( + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + injectionStrategy = InjectionStrategy.CONSTRUCTOR, unmappedTargetPolicy = ReportingPolicy.IGNORE +) +public interface SimpleConstructorMapper { + + SimpleConstructorMapper MAPPER = Mappers.getMapper( SimpleConstructorMapper.class ); + + TargetObject toTargetObject(SourceRoot sourceRoot); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java new file mode 100644 index 0000000000..9746d4a152 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Source.java @@ -0,0 +1,61 @@ +/* + * 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.optional.nullvalue; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +public class Source { + + private final String someString; + private final Integer someInteger; + private final Double someDouble; + private final Boolean someBoolean; + private final Integer someIntValue; + private final Double someDoubleValue; + private final Long someLongValue; + + public Source(String someString, Integer someInteger, Double someDouble, Boolean someBoolean, Integer someIntValue, + Double someDoubleValue, Long someLongValue) { + this.someString = someString; + this.someInteger = someInteger; + this.someDouble = someDouble; + this.someBoolean = someBoolean; + this.someIntValue = someIntValue; + this.someDoubleValue = someDoubleValue; + this.someLongValue = someLongValue; + } + + public Optional getSomeString() { + return Optional.ofNullable( someString ); + } + + public Optional getSomeInteger() { + return Optional.ofNullable( someInteger ); + } + + public Optional getSomeDouble() { + return Optional.ofNullable( someDouble ); + } + + public Optional getSomeBoolean() { + return Optional.ofNullable( someBoolean ); + } + + public OptionalDouble getSomeDoubleValue() { + return someDouble != null ? OptionalDouble.of( someDoubleValue ) : OptionalDouble.empty(); + } + + public OptionalInt getSomeIntValue() { + return someIntValue != null ? OptionalInt.of( someIntValue ) : OptionalInt.empty(); + } + + public OptionalLong getSomeLongValue() { + return someLongValue != null ? OptionalLong.of( someLongValue ) : OptionalLong.empty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java new file mode 100644 index 0000000000..5fd5d1730b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/Target.java @@ -0,0 +1,63 @@ +/* + * 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.optional.nullvalue; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional someString; + private final Optional someInteger; + private final Optional someDouble; + private final Optional someBoolean; + private final OptionalInt someIntValue; + private final OptionalDouble someDoubleValue; + private final OptionalLong someLongValue; + + public Target(Optional someString, Optional someInteger, Optional someDouble, + Optional someBoolean, OptionalInt someIntValue, OptionalDouble someDoubleValue, + OptionalLong someLongValue) { + this.someString = someString; + this.someInteger = someInteger; + this.someDouble = someDouble; + this.someBoolean = someBoolean; + this.someIntValue = someIntValue; + this.someDoubleValue = someDoubleValue; + this.someLongValue = someLongValue; + } + + public Optional getSomeString() { + return someString; + } + + public Optional getSomeInteger() { + return someInteger; + } + + public Optional getSomeDouble() { + return someDouble; + } + + public Optional getSomeBoolean() { + return someBoolean; + } + + public OptionalLong getSomeLongValue() { + return someLongValue; + } + + public OptionalDouble getSomeDoubleValue() { + return someDoubleValue; + } + + public OptionalInt getSomeIntValue() { + return someIntValue; + } +} From 42c87d1da99252e21699cde7d584538b5c961ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Eamil=20Can?= Date: Sun, 25 May 2025 15:22:47 +0200 Subject: [PATCH 297/363] #3848: Mark String to number as lossy conversion --- .../ap/internal/util/NativeTypes.java | 1 + .../conversion/lossy/CutleryInventoryDto.java | 9 +++++++++ .../lossy/CutleryInventoryEntity.java | 9 +++++++++ .../lossy/ErroneousKitchenDrawerMapper6.java | 20 +++++++++++++++++++ .../conversion/lossy/LossyConversionTest.java | 15 ++++++++++++++ .../lossy/OversizedKitchenDrawerDto.java | 8 ++++++++ .../lossy/RegularKitchenDrawerEntity.java | 8 ++++++++ 7 files changed, 70 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java index 0a4ca0cde0..ff01ae0ef3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java @@ -474,6 +474,7 @@ private NativeTypes() { tmp3.put( Double.class.getName(), 6 ); tmp3.put( BigInteger.class.getName(), 50 ); tmp3.put( BigDecimal.class.getName(), 51 ); + tmp3.put( String.class.getName(), 51 ); NARROWING_LUT = Collections.unmodifiableMap( tmp3 ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java index 5a96d6deb2..3bc144511e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java @@ -10,6 +10,7 @@ public class CutleryInventoryDto { private short numberOfKnifes; private int numberOfForks; private byte numberOfSpoons; + private int drawerId; private float approximateKnifeLength; @@ -44,4 +45,12 @@ public float getApproximateKnifeLength() { public void setApproximateKnifeLength(float approximateKnifeLength) { this.approximateKnifeLength = approximateKnifeLength; } + + public int getDrawerId() { + return drawerId; + } + + public void setDrawerId(int drawerId) { + this.drawerId = drawerId; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java index 9575ed99dc..755dbc3637 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java @@ -10,6 +10,7 @@ public class CutleryInventoryEntity { private int numberOfKnifes; private Long numberOfForks; private short numberOfSpoons; + private String drawerId; private double approximateKnifeLength; @@ -44,4 +45,12 @@ public double getApproximateKnifeLength() { public void setApproximateKnifeLength(double approximateKnifeLength) { this.approximateKnifeLength = approximateKnifeLength; } + + public String getDrawerId() { + return drawerId; + } + + public void setDrawerId(String drawerId) { + this.drawerId = drawerId; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java new file mode 100644 index 0000000000..c33f1bd662 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java @@ -0,0 +1,20 @@ +/* + * 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.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerMapper6 { + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "drawerId", source = "drawerId" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java index d27f04374a..629f727273 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java @@ -40,12 +40,14 @@ public void testNoErrorCase() { dto.setNumberOfKnifes( (short) 7 ); dto.setNumberOfSpoons( (byte) 3 ); dto.setApproximateKnifeLength( 3.7f ); + dto.setDrawerId( 1 ); CutleryInventoryEntity entity = CutleryInventoryMapper.INSTANCE.map( dto ); assertThat( entity.getNumberOfForks() ).isEqualTo( 5L ); assertThat( entity.getNumberOfKnifes() ).isEqualTo( 7 ); assertThat( entity.getNumberOfSpoons() ).isEqualTo( (short) 3 ); assertThat( entity.getApproximateKnifeLength() ).isCloseTo( 3.7d, withinPercentage( 0.0001d ) ); + assertThat( entity.getDrawerId() ).isEqualTo( "1" ); } @ProcessorTest @@ -74,6 +76,19 @@ public void testConversionFromLongToInt() { public void testConversionFromBigIntegerToInteger() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerMapper6.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"String drawerId\". It has a possibly lossy conversion from " + + "String to int.") + }) + public void testConversionFromStringToInt() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper3.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java index 110bb2f07c..c42b24cc57 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java @@ -21,6 +21,7 @@ public class OversizedKitchenDrawerDto { private Double depth; private BigDecimal length; private double height; + private String drawerId; public long getNumberOfForks() { return numberOfForks; @@ -70,4 +71,11 @@ public void setHeight(double height) { this.height = height; } + public String getDrawerId() { + return drawerId; + } + + public void setDrawerId(String drawerId) { + this.drawerId = drawerId; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java index d49a89052d..e3ae10bc1f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java @@ -17,6 +17,7 @@ public class RegularKitchenDrawerEntity { private float depth; private Float length; private VerySpecialNumber height; + private int drawerId; public int getNumberOfForks() { return numberOfForks; @@ -66,4 +67,11 @@ public void setHeight(VerySpecialNumber height) { this.height = height; } + public int getDrawerId() { + return drawerId; + } + + public void setDrawerId(int drawerId) { + this.drawerId = drawerId; + } } From 3a5c70224d60aaa834986c5ff88fcb568df9fc85 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 25 May 2025 21:40:43 +0800 Subject: [PATCH 298/363] #3809 Fix conditional mapping with `@TargetPropertyName` failing for nested update mappings Signed-off-by: TangYang --- .../model/assignment/UpdateWrapper.ftl | 15 +++- .../ap/test/bugs/_3809/Issue3809Mapper.java | 69 +++++++++++++++++++ .../ap/test/bugs/_3809/Issue3809Test.java | 20 ++++++ 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index 9cdd07c230..ea8eed2438 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -10,7 +10,7 @@ <@lib.handleExceptions> <#if includeSourceNullCheck> <@lib.sourceLocalVarAssignment/> - if ( <#if sourcePresenceCheckerReference?? ><@includeModel object=sourcePresenceCheckerReference /><#else><#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} != null ) { + if ( <@handleSourceReferenceNullCheck/> ) { <@assignToExistingTarget/> <@lib.handleAssignment/>; } @@ -32,3 +32,16 @@ ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.initTargetObject/>; } + +<#macro handleSourceReferenceNullCheck> + <@compress single_line=true> + <#if sourcePresenceCheckerReference?? > + <@includeModel object=sourcePresenceCheckerReference + targetPropertyName=ext.targetPropertyName + sourcePropertyName=ext.sourcePropertyName + targetType=ext.targetType/> + <#else> + <#if sourceLocalVarName??> ${sourceLocalVarName} <#else> ${sourceReference} != null + + + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java new file mode 100644 index 0000000000..10ff9e2628 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java @@ -0,0 +1,69 @@ +/* + * 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._3809; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetPropertyName; + +@Mapper +public interface Issue3809Mapper { + void updateMappingFails(Source source, @MappingTarget Target target); + + @Condition + default boolean canMap(Object source, @TargetPropertyName String propertyName) { + return true; + } + + class Source { + private NestedSource param; + + public NestedSource getParam() { + return param; + } + } + + class NestedSource { + private String param1; + + public String getParam1() { + return param1; + } + + public void setParam1(String param1) { + this.param1 = param1; + } + + } + + class Target { + + private NestedTarget param; + + public NestedTarget getParam() { + return param; + } + + public void setParam(NestedTarget param) { + this.param = param; + } + + } + + class NestedTarget { + private String param1; + + public String getParam1() { + return param1; + } + + public void setParam1(String param1) { + this.param1 = param1; + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java new file mode 100644 index 0000000000..23d97b877c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java @@ -0,0 +1,20 @@ +/* + * 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._3809; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@WithClasses(Issue3809Mapper.class) +@IssueKey("3809") +public class Issue3809Test { + + @ProcessorTest + public void shouldCompileNoError() { + + } +} From 0badba70038875cb2fdf6d9ff328b6a9ba31d245 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 25 May 2025 22:35:38 +0800 Subject: [PATCH 299/363] #3849: Resolve duplicate invocation of overloaded lifecycle methods with inheritance Add compiler option `mapstruct.disableLifecycleOverloadDeduplicateSelector` to disable the deduplication if needed. Signed-off-by: TangYang --- .../org/mapstruct/ap/MappingProcessor.java | 8 +- .../model/LifecycleMethodResolver.java | 2 +- .../model/ObjectFactoryMethodResolver.java | 7 +- .../model/PresenceCheckMethodResolver.java | 3 +- .../LifecycleOverloadDeduplicateSelector.java | 129 +++++++++++++++ .../source/selector/MethodSelectors.java | 16 +- .../mapstruct/ap/internal/option/Options.java | 9 +- .../creation/MappingResolverImpl.java | 2 +- .../mapstruct/ap/test/bugs/_3849/Child.java | 12 ++ .../ap/test/bugs/_3849/ChildDto.java | 16 ++ .../bugs/_3849/DeduplicateBySourceMapper.java | 69 ++++++++ .../bugs/_3849/DeduplicateByTargetMapper.java | 69 ++++++++ .../DeduplicateForCompileArgsMapper.java | 69 ++++++++ .../bugs/_3849/DeduplicateGenericMapper.java | 51 ++++++ .../ap/test/bugs/_3849/Issue3849Test.java | 150 ++++++++++++++++++ .../mapstruct/ap/test/bugs/_3849/Parent.java | 21 +++ .../ap/test/bugs/_3849/ParentDto.java | 22 +++ 17 files changed, 642 insertions(+), 13 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 407166f6fa..b34b133262 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -95,6 +95,7 @@ MappingProcessor.VERBOSE, MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY, + MappingProcessor.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR, }) public class MappingProcessor extends AbstractProcessor { @@ -115,6 +116,8 @@ public class MappingProcessor extends AbstractProcessor { protected static final String VERBOSE = "mapstruct.verbose"; protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy"; protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy"; + protected static final String DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR = + "mapstruct.disableLifecycleOverloadDeduplicateSelector"; private final Set additionalSupportedOptions; private final String additionalSupportedOptionsError; @@ -174,6 +177,8 @@ private Options createOptions() { String nullValueIterableMappingStrategy = processingEnv.getOptions() .get( NULL_VALUE_ITERABLE_MAPPING_STRATEGY ); String nullValueMapMappingStrategy = processingEnv.getOptions().get( NULL_VALUE_MAP_MAPPING_STRATEGY ); + String disableLifecycleOverloadDeduplicateSelector = processingEnv.getOptions() + .get( DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR ); return new Options( Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), @@ -189,7 +194,8 @@ private Options createOptions() { NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) : null, nullValueMapMappingStrategy != null ? - NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null + NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null, + Boolean.parseBoolean( disableLifecycleOverloadDeduplicateSelector ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java index b713fa5f0c..cfe4f9f8b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -133,7 +133,7 @@ private static List collectLifecycleCallbackMe MappingBuilderContext ctx, Set existingVariableNames) { MethodSelectors selectors = - new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() ); + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), ctx.getOptions() ); List> matchingMethods = selectors.getMatchingMethods( callbackMethods, 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 index 4cf653b46b..89f295981f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -5,12 +5,9 @@ */ package org.mapstruct.ap.internal.model; -import static org.mapstruct.ap.internal.util.Collections.first; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; - import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -26,6 +23,8 @@ import org.mapstruct.ap.internal.model.source.selector.SelectionContext; import org.mapstruct.ap.internal.util.Message; +import static org.mapstruct.ap.internal.util.Collections.first; + /** * * @author Sjaak Derksen @@ -126,7 +125,7 @@ public static List> getMatchingFactoryMethods( Meth MappingBuilderContext ctx) { MethodSelectors selectors = - new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() ); + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), null ); return selectors.getMatchingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 5906db8219..96826f8f66 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -119,7 +119,8 @@ private static List> findMatchingMethods( MethodSelectors selectors = new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), - ctx.getMessager() + ctx.getMessager(), + null ); return selectors.getMatchingMethods( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java new file mode 100644 index 0000000000..7ce23af186 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java @@ -0,0 +1,129 @@ +/* + * 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.internal.model.source.selector; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.stream.Collectors; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.source.Method; + +/** + * Selector for deduplicating overloaded lifecycle callback methods + * whose parameter signatures differ only by type hierarchy. + *

      + * In the context of lifecycle callback method selection + * (such as @BeforeMapping or @AfterMapping), it is possible to have multiple overloaded methods + * whose parameter lists are structurally identical except for the specific types, + * where those types are related by inheritance (e.g., one parameter is a superclass or subclass of another). + *

      + * This selector groups such methods by their effective parameter signature + * (ignoring differences only in type hierarchy), and, within each group, + * retains only the method whose parameter types have the closest inheritance distance + * to the actual invocation types. + * This ensures that, for each group of nearly identical overloads, + * only the most specific and appropriate method is selected. + *

      + * Example (see Issue3849Test): + * + *

      {@code
      + * @AfterMapping
      + * default void afterMapping(Parent source, @MappingTarget ParentDto target) { ... }
      + * @AfterMapping
      + * default void afterMapping(Parent source, @MappingTarget ChildDto target) { ... }
      + * }
      + * When mapping a Child to a ChildDto, + * only the method with ChildDto is selected, even though both methods match by signature + * except for the target type's inheritance relationship. + */ +public class LifecycleOverloadDeduplicateSelector implements MethodSelector { + @Override + public List> getMatchingMethods(List> methods, + SelectionContext context) { + if ( !context.getSelectionCriteria().isLifecycleCallbackRequired() || methods.size() <= 1 ) { + return methods; + } + Collection>> methodSignatureGroups = + methods.stream() + .collect( Collectors.groupingBy( + LifecycleOverloadDeduplicateSelector::buildSignatureKey, + LinkedHashMap::new, + Collectors.toList() + ) ) + .values(); + List> deduplicatedMethods = new ArrayList<>( methods.size() ); + for ( List> signatureGroup : methodSignatureGroups ) { + if ( signatureGroup.size() == 1 ) { + deduplicatedMethods.add( signatureGroup.get( 0 ) ); + continue; + } + SelectedMethod bestInheritanceMethod = signatureGroup.get( 0 ); + for ( int i = 1; i < signatureGroup.size(); i++ ) { + SelectedMethod candidateMethod = signatureGroup.get( i ); + if ( isInheritanceBetter( candidateMethod, bestInheritanceMethod ) ) { + bestInheritanceMethod = candidateMethod; + } + } + deduplicatedMethods.add( bestInheritanceMethod ); + } + return deduplicatedMethods; + } + + /** + * Builds a grouping key for a method based on its defining type, + * method name, and a detailed breakdown of each parameter binding. + *

      + * The key consists of: + *

        + *
      • The type that defines the method
      • + *
      • The method name
      • + *
      • parameter bindings
      • + *
      + * This ensures that methods are grouped together only if all these aspects match, + * except for differences in type hierarchy, which are handled separately. + */ + private static List buildSignatureKey(SelectedMethod method) { + List key = new ArrayList<>(); + key.add( method.getMethod().getDefiningType() ); + key.add( method.getMethod().getName() ); + for ( ParameterBinding binding : method.getParameterBindings() ) { + key.add( binding.getType() ); + key.add( binding.getVariableName() ); + } + return key; + } + + /** + * Compare the inheritance distance of parameters between two methods to determine if candidateMethod is better. + * Compares parameters in order, returns as soon as a better one is found. + */ + private boolean isInheritanceBetter(SelectedMethod candidateMethod, + SelectedMethod currentBestMethod) { + List candidateBindings = candidateMethod.getParameterBindings(); + List bestBindings = currentBestMethod.getParameterBindings(); + List candidateParams = candidateMethod.getMethod().getParameters(); + List bestParams = currentBestMethod.getMethod().getParameters(); + int paramCount = candidateBindings.size(); + + for ( int i = 0; i < paramCount; i++ ) { + int candidateDistance = candidateBindings.get( i ) + .getType() + .distanceTo( candidateParams.get( i ).getType() ); + int bestDistance = bestBindings.get( i ).getType().distanceTo( bestParams.get( i ).getType() ); + if ( candidateDistance < bestDistance ) { + return true; + } + else if ( candidateDistance > bestDistance ) { + return false; + } + } + return false; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java index c14729a90f..774f25a8c3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java @@ -10,6 +10,7 @@ import java.util.List; import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.TypeUtils; @@ -24,20 +25,27 @@ public class MethodSelectors { private final List selectors; public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, - FormattingMessager messager) { - selectors = Arrays.asList( + FormattingMessager messager, Options options) { + List selectorList = new ArrayList<>( Arrays.asList( new MethodFamilySelector(), new TypeSelector( messager ), new QualifierSelector( typeUtils, elementUtils ), new TargetTypeSelector( typeUtils ), new JavaxXmlElementDeclSelector( typeUtils ), new JakartaXmlElementDeclSelector( typeUtils ), - new InheritanceSelector(), + new InheritanceSelector() + ) ); + if ( options != null && !options.isDisableLifecycleOverloadDeduplicateSelector() ) { + selectorList.add( new LifecycleOverloadDeduplicateSelector() ); + } + + selectorList.addAll( Arrays.asList( new CreateOrUpdateSelector(), new SourceRhsSelector(), new FactoryParameterSelector(), new MostSpecificResultTypeSelector() - ); + ) ); + this.selectors = selectorList; } /** diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index a544374c0e..095c472c87 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -26,6 +26,7 @@ public class Options { private final boolean verbose; private final NullValueMappingStrategyGem nullValueIterableMappingStrategy; private final NullValueMappingStrategyGem nullValueMapMappingStrategy; + private final boolean disableLifecycleOverloadDeduplicateSelector; //CHECKSTYLE:OFF public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, @@ -36,7 +37,8 @@ public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVers boolean disableBuilders, boolean verbose, NullValueMappingStrategyGem nullValueIterableMappingStrategy, - NullValueMappingStrategyGem nullValueMapMappingStrategy + NullValueMappingStrategyGem nullValueMapMappingStrategy, + boolean disableLifecycleOverloadDeduplicateSelector ) { //CHECKSTYLE:ON this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; @@ -50,6 +52,7 @@ public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVers this.verbose = verbose; this.nullValueIterableMappingStrategy = nullValueIterableMappingStrategy; this.nullValueMapMappingStrategy = nullValueMapMappingStrategy; + this.disableLifecycleOverloadDeduplicateSelector = disableLifecycleOverloadDeduplicateSelector; } public boolean isSuppressGeneratorTimestamp() { @@ -95,4 +98,8 @@ public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { return nullValueMapMappingStrategy; } + + public boolean isDisableLifecycleOverloadDeduplicateSelector() { + return disableLifecycleOverloadDeduplicateSelector; + } } 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 d84ba974db..ba903b6048 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 @@ -116,7 +116,7 @@ public MappingResolverImpl(FormattingMessager messager, ElementUtils elementUtil this.conversions = new Conversions( typeFactory ); this.builtInMethods = new BuiltInMappingMethods( typeFactory ); - this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, messager ); + this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, messager, null ); this.verboseLogging = verboseLogging; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java new file mode 100644 index 0000000000..393cb16970 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java @@ -0,0 +1,12 @@ +/* + * 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._3849; + +public class Child extends Parent { + public Child() { + super(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java new file mode 100644 index 0000000000..e8cdae4d17 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java @@ -0,0 +1,16 @@ +/* + * 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._3849; + +public class ChildDto extends ParentDto { + public ChildDto(String value) { + super( value ); + } + + public void setValue(String value) { + super.setValue( value ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java new file mode 100644 index 0000000000..68450c7d9d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java @@ -0,0 +1,69 @@ +/* + * 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._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateBySourceMapper { + + DeduplicateBySourceMapper INSTANCE = Mappers.getMapper( DeduplicateBySourceMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ParentDto mapChild(Child source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateBySourceForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentSourceInOtherClass" ); + } + + @BeforeMapping + void deduplicateBySourceForBefore(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingChildSourceInOtherClass" ); + } + + @AfterMapping + void deduplicateBySource(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentSourceInOtherClass" ); + } + + @AfterMapping + void deduplicateBySource(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingChildSourceInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateBySourceForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentSource" ); + } + + @BeforeMapping + default void deduplicateBySourceForBefore(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingChildSource" ); + } + + @AfterMapping + default void deduplicateBySource(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentSource" ); + } + + @AfterMapping + default void deduplicateBySource(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingChildSource" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java new file mode 100644 index 0000000000..0aed8a2957 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java @@ -0,0 +1,69 @@ +/* + * 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._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateByTargetMapper { + + DeduplicateByTargetMapper INSTANCE = Mappers.getMapper( DeduplicateByTargetMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ChildDto mapChild(Parent source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTargetInOtherClass" ); + } + + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTargetInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTarget" ); + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTarget" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java new file mode 100644 index 0000000000..276a2c4352 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java @@ -0,0 +1,69 @@ +/* + * 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._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateForCompileArgsMapper { + + DeduplicateForCompileArgsMapper INSTANCE = Mappers.getMapper( DeduplicateForCompileArgsMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ChildDto mapChild(Parent source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTargetInOtherClass" ); + } + + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTargetInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTarget" ); + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTarget" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java new file mode 100644 index 0000000000..662137f1a9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java @@ -0,0 +1,51 @@ +/* + * 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._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateGenericMapper { + + DeduplicateGenericMapper INSTANCE = Mappers.getMapper( DeduplicateGenericMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source); + + ChildDto mapChild(Parent source); + + @BeforeMapping + default void deduplicateBefore(Parent source, @MappingTarget T target) { + INVOKED_METHODS.add( "beforeMappingParentGeneric" ); + } + + @BeforeMapping + default void deduplicateBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChild" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget T target) { + INVOKED_METHODS.add( "afterMappingGeneric" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParent" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChild" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java new file mode 100644 index 0000000000..a58c52c921 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java @@ -0,0 +1,150 @@ +/* + * 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._3849; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3849") +@WithClasses({ + Parent.class, + ParentDto.class, + Child.class, + ChildDto.class +}) +public class Issue3849Test { + + @ProcessorOption(name = "mapstruct.disableLifecycleOverloadDeduplicateSelector", value = "true") + @ProcessorTest() + @WithClasses(DeduplicateForCompileArgsMapper.class) + void lifecycleMappingOverloadSelectorDisableCompileArgs() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateForCompileArgsMapper.MappingContext mappingContext = + new DeduplicateForCompileArgsMapper.MappingContext(); + ParentDto parentDto = DeduplicateForCompileArgsMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateForCompileArgsMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentTargetInOtherClass", + "beforeMappingParentTarget", + "afterMappingParentTargetInOtherClass", + "afterMappingParentTarget" + ); + + DeduplicateForCompileArgsMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateForCompileArgsMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateForCompileArgsMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTarget", + "beforeMappingChildTarget", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTarget", + "afterMappingChildTarget" + ); + + DeduplicateForCompileArgsMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest() + @WithClasses( DeduplicateByTargetMapper.class ) + void lifecycleMappingOverloadByTarget() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateByTargetMapper.MappingContext mappingContext = new DeduplicateByTargetMapper.MappingContext(); + ParentDto parentDto = DeduplicateByTargetMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateByTargetMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentTargetInOtherClass", + "beforeMappingParentTarget", + "afterMappingParentTargetInOtherClass", + "afterMappingParentTarget" + ); + + DeduplicateByTargetMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateByTargetMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateByTargetMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTarget", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTarget" + ); + + DeduplicateByTargetMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest + @WithClasses( DeduplicateBySourceMapper.class ) + void lifecycleMappingOverloadBySource() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateBySourceMapper.MappingContext mappingContext = new DeduplicateBySourceMapper.MappingContext(); + ParentDto parentDto = DeduplicateBySourceMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateBySourceMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentSourceInOtherClass", + "beforeMappingParentSource", + "afterMappingParentSourceInOtherClass", + "afterMappingParentSource" + ); + + DeduplicateBySourceMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateBySourceMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateBySourceMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildSourceInOtherClass", + "beforeMappingChildSource", + "afterMappingChildSourceInOtherClass", + "afterMappingChildSource" + ); + + DeduplicateBySourceMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest + @WithClasses(DeduplicateGenericMapper.class) + void lifecycleMappingOverloadForGeneric() { + Child child = new Child(); + Parent parent = new Parent(); + + ParentDto parentDto = DeduplicateGenericMapper.INSTANCE.mapParent( parent ); + assertThat( DeduplicateGenericMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentGeneric", + "afterMappingParent" + ); + + DeduplicateGenericMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateGenericMapper.INSTANCE.mapChild( child ); + + assertThat( DeduplicateGenericMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChild", + "afterMappingChild" + ); + + DeduplicateGenericMapper.INVOKED_METHODS.clear(); + } +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java new file mode 100644 index 0000000000..4930677286 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java @@ -0,0 +1,21 @@ +/* + * 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._3849; + +public class Parent { + private String value; + + public Parent() { + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java new file mode 100644 index 0000000000..098917f001 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java @@ -0,0 +1,22 @@ +/* + * 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._3849; + +public class ParentDto { + private String value; + + public ParentDto(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} From 6b6600c370cb50b2fbf40f56412238974575ae85 Mon Sep 17 00:00:00 2001 From: Aleksey Ivashin Date: Sun, 25 May 2025 18:05:18 +0300 Subject: [PATCH 300/363] #1958: Add support for ignoring multiple target properties at once --- core/src/main/java/org/mapstruct/Ignored.java | 67 ++++++++++++ .../main/java/org/mapstruct/IgnoredList.java | 54 ++++++++++ core/src/main/java/org/mapstruct/Mapping.java | 3 + .../chapter-5-data-type-conversions.asciidoc | 7 ++ .../ap/internal/gem/GemGenerator.java | 4 + .../processor/MethodRetrievalProcessor.java | 61 ++++++++++- .../org/mapstruct/ap/test/ignored/Animal.java | 64 +++++++++++ .../mapstruct/ap/test/ignored/AnimalDto.java | 63 +++++++++++ .../ap/test/ignored/AnimalMapper.java | 20 ++++ .../ap/test/ignored/ErroneousMapper.java | 26 +++++ .../ap/test/ignored/IgnoredPropertyTest.java | 102 ++++++++++++++++++ .../org/mapstruct/ap/test/ignored/Zoo.java | 48 +++++++++ .../org/mapstruct/ap/test/ignored/ZooDto.java | 48 +++++++++ .../mapstruct/ap/test/ignored/ZooMapper.java | 23 ++++ 14 files changed, 589 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/org/mapstruct/Ignored.java create mode 100644 core/src/main/java/org/mapstruct/IgnoredList.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java diff --git a/core/src/main/java/org/mapstruct/Ignored.java b/core/src/main/java/org/mapstruct/Ignored.java new file mode 100644 index 0000000000..47e4961f43 --- /dev/null +++ b/core/src/main/java/org/mapstruct/Ignored.java @@ -0,0 +1,67 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Configures the ignored of one bean attribute. + * + *

      + * The name all attributes of for ignored is to be specified via {@link #targets()}. + *

      + * + *

      + * Example 1: Implicitly mapping fields with the same name: + *

      + * + *
      
      + * // We need ignored Human.name and Human.lastName
      + * // we can use @Ignored with parameters "name", "lastName" {@link #targets()}
      + * @Mapper
      + * public interface HumanMapper {
      + *    @Ignored( targets = { "name", "lastName" } )
      + *    HumanDto toHumanDto(Human human)
      + * }
      + * 
      + *
      
      + * // generates:
      + * @Override
      + * public HumanDto toHumanDto(Human human) {
      + *    humanDto.setFullName( human.getFullName() );
      + *    // ...
      + * }
      + * 
      + * + * @author Ivashin Aleksey + */ +@Repeatable(IgnoredList.class) +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +public @interface Ignored { + + /** + * Whether the specified properties should be ignored by the generated mapping method. + * This can be useful when certain attributes should not be propagated from source to target or when properties in + * the target object are populated using a decorator and thus would be reported as unmapped target property by + * default. + * + * @return The target names of the configured properties that should be ignored + */ + String[] targets(); + + /** + * The prefix that should be applied to all the properties specified via {@link #targets()}. + * + * @return The target prefix to be applied to the defined properties + */ + String prefix() default ""; + +} diff --git a/core/src/main/java/org/mapstruct/IgnoredList.java b/core/src/main/java/org/mapstruct/IgnoredList.java new file mode 100644 index 0000000000..e47db6bac7 --- /dev/null +++ b/core/src/main/java/org/mapstruct/IgnoredList.java @@ -0,0 +1,54 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Configures the ignored list for several bean attributes. + *

      + * TIP: When using Java 8 or later, you can omit the {@code @IgnoredList} + * wrapper annotation and directly specify several {@code @Ignored} annotations on one method. + * + *

      These two examples are equal. + *

      + *
      
      + * // before Java 8
      + * @Mapper
      + * public interface MyMapper {
      + *     @IgnoredList({
      + *         @Ignored(targets = { "firstProperty" } ),
      + *         @Ignored(targets = { "secondProperty" } )
      + *     })
      + *     HumanDto toHumanDto(Human human);
      + * }
      + * 
      + *
      
      + * // Java 8 and later
      + * @Mapper
      + * public interface MyMapper {
      + *     @Ignored(targets = { "firstProperty" } ),
      + *     @Ignored(targets = { "secondProperty" } )
      + *     HumanDto toHumanDto(Human human);
      + * }
      + * 
      + * + * @author Ivashin Aleksey + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +public @interface IgnoredList { + + /** + * The configuration of the bean attributes. + * + * @return The configuration of the bean attributes. + */ + Ignored[] value(); +} diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 6c95b0db2c..f15cfb75cc 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -306,6 +306,9 @@ * This can be useful when certain attributes should not be propagated from source to target or when properties in * the target object are populated using a decorator and thus would be reported as unmapped target property by * default. + *

      + * If you have multiple properties to ignore, + * you can use the {@link Ignored} annotation instead and group them all at once. * * @return {@code true} if the given property should be ignored, {@code false} otherwise */ diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index bc406cf0b0..ad49fe2960 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -280,6 +280,13 @@ This puts the configuration of the nested mapping into one place (method) where instead of re-configuring the same things on all of those upper methods. ==== +[TIP] +==== +When ignoring multiple properties instead of defining multiple `@Mapping` annotations, you can use the `@Ignored` annotation to group them together. +e.g. for the `FishTankMapperWithDocument` example above, you could write: +`@Ignored(targets = { "plant", "ornament", "material" })` +==== + [NOTE] ==== In some cases the `ReportingPolicy` that is going to be used for the generated nested method would be `IGNORE`. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index 2ed9bd9a09..a8b62babe9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -26,6 +26,8 @@ import org.mapstruct.Mapper; import org.mapstruct.MapperConfig; import org.mapstruct.Mapping; +import org.mapstruct.Ignored; +import org.mapstruct.IgnoredList; import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.mapstruct.Named; @@ -53,6 +55,8 @@ @GemDefinition(AnnotateWiths.class) @GemDefinition(Mapper.class) @GemDefinition(Mapping.class) +@GemDefinition(Ignored.class) +@GemDefinition(IgnoredList.class) @GemDefinition(Mappings.class) @GemDefinition(IterableMapping.class) @GemDefinition(BeanMapping.class) 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 c8c13e6bf9..6843ac5bbe 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 @@ -21,6 +21,8 @@ import org.mapstruct.ap.internal.gem.BeanMappingGem; import org.mapstruct.ap.internal.gem.ConditionGem; +import org.mapstruct.ap.internal.gem.IgnoredGem; +import org.mapstruct.ap.internal.gem.IgnoredListGem; import org.mapstruct.ap.internal.gem.IterableMappingGem; import org.mapstruct.ap.internal.gem.MapMappingGem; import org.mapstruct.ap.internal.gem.MappingGem; @@ -76,6 +78,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor getMappings(ExecutableElement method, BeanMappingOptions beanMapping) { - return new RepeatableMappings( beanMapping ).getProcessedAnnotations( method ); + Set processedAnnotations = new RepeatableMappings( beanMapping ) + .getProcessedAnnotations( method ); + processedAnnotations.addAll( new IgnoredConditions( processedAnnotations ) + .getProcessedAnnotations( method ) ); + return processedAnnotations; } /** @@ -823,4 +831,55 @@ protected void addInstance(ConditionGem gem, Element source, Set { + + protected final Set processedAnnotations; + + protected IgnoredConditions( Set processedAnnotations ) { + super( elementUtils, IGNORED_FQN, IGNORED_LIST_FQN ); + this.processedAnnotations = processedAnnotations; + } + + @Override + protected IgnoredGem singularInstanceOn(Element element) { + return IgnoredGem.instanceOn( element ); + } + + @Override + protected IgnoredListGem multipleInstanceOn(Element element) { + return IgnoredListGem.instanceOn( element ); + } + + @Override + protected void addInstance(IgnoredGem gem, Element method, Set mappings) { + IgnoredGem ignoredGem = IgnoredGem.instanceOn( method ); + if ( ignoredGem == null ) { + ignoredGem = gem; + } + String prefix = ignoredGem.prefix().get(); + for ( String target : ignoredGem.targets().get() ) { + String realTarget = target; + if ( !prefix.isEmpty() ) { + realTarget = prefix + "." + target; + } + MappingOptions mappingOptions = MappingOptions.forIgnore( realTarget ); + if ( processedAnnotations.contains( mappingOptions ) || mappings.contains( mappingOptions ) ) { + messager.printMessage( method, Message.PROPERTYMAPPING_DUPLICATE_TARGETS, realTarget ); + } + else { + mappings.add( mappingOptions ); + } + } + } + + @Override + protected void addInstances(IgnoredListGem gem, Element method, Set mappings) { + IgnoredListGem ignoredListGem = IgnoredListGem.instanceOn( method ); + for ( IgnoredGem ignoredGem : ignoredListGem.value().get() ) { + addInstance( ignoredGem, method, mappings ); + } + } + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java new file mode 100644 index 0000000000..223f99f705 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java @@ -0,0 +1,64 @@ +/* + * 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.ignored; + +public class Animal { + + //CHECKSTYLE:OFF + public Integer publicAge; + public String publicColour; + //CHECKSTYLE:OFN + private String colour; + private String name; + private int size; + private Integer age; + + // private String colour; + public Animal() { + } + + public Animal(String name, int size, Integer age, String colour) { + this.name = name; + this.size = size; + this.publicAge = age; + this.age = age; + this.publicColour = colour; + this.colour = colour; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getColour() { + return colour; + } + + public void setColour( String colour ) { + this.colour = colour; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java new file mode 100644 index 0000000000..1651f9b56b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java @@ -0,0 +1,63 @@ +/* + * 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.ignored; + +public class AnimalDto { + + //CHECKSTYLE:OFF + public Integer publicAge; + public String publicColor; + //CHECKSTYLE:ON + private String name; + private Integer size; + private Integer age; + private String color; + + public AnimalDto() { + + } + + public AnimalDto(String name, Integer size, Integer age, String color) { + this.name = name; + this.size = size; + this.publicAge = age; + this.age = age; + this.publicColor = color; + this.color = color; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java new file mode 100644 index 0000000000..2753b36070 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java @@ -0,0 +1,20 @@ +/* + * 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.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface AnimalMapper { + + AnimalMapper INSTANCE = Mappers.getMapper( AnimalMapper.class ); + + @Ignored( targets = { "publicAge", "age", "publicColor", "color" } ) + AnimalDto animalToDto( Animal animal ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java new file mode 100644 index 0000000000..b113e715aa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java @@ -0,0 +1,26 @@ +/* + * 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.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ErroneousMapper { + + ErroneousMapper INSTANCE = Mappers.getMapper( ErroneousMapper.class ); + + @Mapping(target = "name", ignore = true) + @Ignored(targets = { "name", "color", "publicColor" }) + AnimalDto ignoredAndMappingAnimalToDto( Animal animal ); + + @Mapping(target = "publicColor", source = "publicColour") + @Ignored(targets = { "publicColor", "color" }) + AnimalDto ignoredAndMappingAnimalToDtoMap( Animal animal ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java new file mode 100644 index 0000000000..bc6fc2f6b7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java @@ -0,0 +1,102 @@ +/* + * 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.ignored; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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 static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for ignoring properties during the mapping. + * + * @author Ivashin Aleksey + */ +@WithClasses({ Animal.class, AnimalDto.class, Zoo.class, ZooDto.class, ZooMapper.class}) +public class IgnoredPropertyTest { + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + + AnimalDto animalDto = AnimalMapper.INSTANCE.animalToDto( animal ); + + assertThat( animalDto ).isNotNull(); + assertThat( animalDto.getName() ).isEqualTo( "Bruno" ); + assertThat( animalDto.getSize() ).isEqualTo( 100 ); + assertThat( animalDto.getAge() ).isNull(); + assertThat( animalDto.publicAge ).isNull(); + assertThat( animalDto.getColor() ).isNull(); + assertThat( animalDto.publicColor ).isNull(); + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { ErroneousMapper.class } ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Target property \"name\" must not be mapped more than once." ), + @Diagnostic(type = ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 24, + message = "Target property \"publicColor\" must not be mapped more than once." ) + } + ) + public void shouldFailToGenerateMappings() { + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public void shouldNotPropagateIgnoredInnerPropertyGivenViaTargetAttribute() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + Zoo zoo = new Zoo(animal, "Test name", "test address"); + + ZooDto zooDto = ZooMapper.INSTANCE.zooToDto( zoo ); + + assertThat( zooDto ).isNotNull(); + assertThat( zooDto.getName() ).isEqualTo( "Test name" ); + assertThat( zooDto.getAddress() ).isEqualTo( "test address" ); + assertThat( zooDto.getAnimal() ).isNotNull(); + assertThat( zooDto.getAnimal().getName() ).isEqualTo( "Bruno" ); + assertThat( zooDto.getAnimal().getAge() ).isNull(); + assertThat( zooDto.getAnimal().publicAge ).isNull(); + assertThat( zooDto.getAnimal().getColor() ).isNull(); + assertThat( zooDto.getAnimal().publicColor ).isNull(); + assertThat( zooDto.getAnimal().getSize() ).isNull(); + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public void shouldNotPropagateIgnoredInnerPropertyGivenViaTargetAttribute2() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + Zoo zoo = new Zoo(animal, "Test name", "test address"); + + ZooDto zooDto = ZooMapper.INSTANCE.zooToDto2( zoo ); + + assertThat( zooDto ).isNotNull(); + assertThat( zooDto.getName() ).isEqualTo( "Test name" ); + assertThat( zooDto.getAddress() ).isNull(); + assertThat( zooDto.getAnimal() ).isNotNull(); + assertThat( zooDto.getAnimal().getName() ).isEqualTo( "Bruno" ); + assertThat( zooDto.getAnimal().getAge() ).isNull(); + assertThat( zooDto.getAnimal().publicAge ).isNull(); + assertThat( zooDto.getAnimal().getColor() ).isNull(); + assertThat( zooDto.getAnimal().publicColor ).isNull(); + assertThat( zooDto.getAnimal().getSize() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java new file mode 100644 index 0000000000..377e03b877 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java @@ -0,0 +1,48 @@ +/* + * 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.ignored; + +public class Zoo { + + private Animal animal; + + private String name; + + private String address; + + public Zoo() { + } + + public Zoo(Animal animal, String name, String address ) { + this.animal = animal; + this.name = name; + this.address = address; + } + + public Animal getAnimal() { + return animal; + } + + public void setAnimal(Animal animal) { + this.animal = animal; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java new file mode 100644 index 0000000000..7c062cab4c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java @@ -0,0 +1,48 @@ +/* + * 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.ignored; + +public class ZooDto { + + private AnimalDto animal; + + private String name; + + private String address; + + public ZooDto() { + } + + public ZooDto(AnimalDto animal, String name, String address) { + this.animal = animal; + this.name = name; + this.address = address; + } + + public AnimalDto getAnimal() { + return animal; + } + + public void setAnimal(AnimalDto animal) { + this.animal = animal; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java new file mode 100644 index 0000000000..f05e045b5f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java @@ -0,0 +1,23 @@ +/* + * 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.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ZooMapper { + + ZooMapper INSTANCE = Mappers.getMapper( ZooMapper.class ); + + @Ignored( prefix = "animal", targets = { "publicAge", "size", "publicColor", "age", "color" } ) + ZooDto zooToDto( Zoo zoo ); + + @Ignored( targets = { "address" } ) + @Ignored( prefix = "animal", targets = { "publicAge", "size", "publicColor", "age", "color" } ) + ZooDto zooToDto2( Zoo zoo ); +} From 5464c3cff805a235e908976fe5d55d0162d2a323 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 17:10:24 +0800 Subject: [PATCH 301/363] #3711: Support generic `@Context` Signed-off-by: TangYang --- .../processor/MethodRetrievalProcessor.java | 25 +++++++++------ .../ap/test/bugs/_3711/BaseMapper.java | 12 +++++++ .../ap/test/bugs/_3711/Issue3711Test.java | 31 +++++++++++++++++++ .../ap/test/bugs/_3711/JpaContext.java | 31 +++++++++++++++++++ .../ap/test/bugs/_3711/ParentDto.java | 18 +++++++++++ .../ap/test/bugs/_3711/ParentEntity.java | 18 +++++++++++ .../test/bugs/_3711/SourceTargetMapper.java | 16 ++++++++++ 7 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java 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 6843ac5bbe..d7a0329aeb 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 @@ -114,7 +114,12 @@ public List process(ProcessorContext context, TypeElement mapperTy } List prototypeMethods = retrievePrototypeMethods( mapperTypeElement, mapperOptions ); - return retrieveMethods( mapperTypeElement, mapperTypeElement, mapperOptions, prototypeMethods ); + return retrieveMethods( + typeFactory.getType( mapperTypeElement.asType() ), + mapperTypeElement, + mapperOptions, + prototypeMethods + ); } @Override @@ -166,19 +171,21 @@ private List retrievePrototypeMethods(TypeElement mapperTypeElemen /** * Retrieves the mapping methods declared by the given mapper type. * - * @param usedMapper The type of interest (either the mapper to implement or a used mapper via @uses annotation) + * @param usedMapperType The type of interest (either the mapper to implement, a used mapper via @uses annotation, + * or a parameter type annotated with @Context) * @param mapperToImplement the top level type (mapper) that requires implementation * @param mapperOptions the mapper config * @param prototypeMethods prototype methods defined in mapper config type * @return All mapping methods declared by the given type */ - private List retrieveMethods(TypeElement usedMapper, TypeElement mapperToImplement, + private List retrieveMethods(Type usedMapperType, TypeElement mapperToImplement, MapperOptions mapperOptions, List prototypeMethods) { List methods = new ArrayList<>(); + TypeElement usedMapper = usedMapperType.getTypeElement(); for ( ExecutableElement executable : elementUtils.getAllEnclosedExecutableElements( usedMapper ) ) { SourceMethod method = getMethod( - usedMapper, + usedMapperType, executable, mapperToImplement, mapperOptions, @@ -195,7 +202,7 @@ private List retrieveMethods(TypeElement usedMapper, TypeElement m TypeElement usesMapperElement = asTypeElement( mapper ); if ( !mapperToImplement.equals( usesMapperElement ) ) { methods.addAll( retrieveMethods( - usesMapperElement, + typeFactory.getType( mapper ), mapperToImplement, mapperOptions, prototypeMethods ) ); @@ -218,13 +225,13 @@ private TypeElement asTypeElement(DeclaredType type) { return (TypeElement) type.asElement(); } - private SourceMethod getMethod(TypeElement usedMapper, + private SourceMethod getMethod(Type usedMapperType, ExecutableElement method, TypeElement mapperToImplement, MapperOptions mapperOptions, List prototypeMethods) { - - ExecutableType methodType = typeFactory.getMethodType( (DeclaredType) usedMapper.asType(), method ); + TypeElement usedMapper = usedMapperType.getTypeElement(); + ExecutableType methodType = typeFactory.getMethodType( (DeclaredType) usedMapperType.getTypeMirror(), method ); List parameters = typeFactory.getParameters( methodType, method ); Type returnType = typeFactory.getReturnType( methodType ); @@ -357,7 +364,7 @@ private ParameterProvidedMethods retrieveContextProvidedMethods( continue; } List contextParamMethods = retrieveMethods( - contextParam.getType().getTypeElement(), + contextParam.getType(), mapperToImplement, mapperConfig, Collections.emptyList() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java new file mode 100644 index 0000000000..e2a04fe336 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java @@ -0,0 +1,12 @@ +/* + * 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._3711; + +import org.mapstruct.Context; + +interface BaseMapper { + E toEntity(T s, @Context JpaContext ctx); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java new file mode 100644 index 0000000000..7b141e9e14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java @@ -0,0 +1,31 @@ +/* + * 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._3711; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + ParentEntity.class, + ParentDto.class, + JpaContext.class, + SourceTargetMapper.class, + BaseMapper.class, +}) +@IssueKey("3711") +class Issue3711Test { + @ProcessorTest + void shouldGenerateContextMethod() { + JpaContext jpaContext = new JpaContext<>(); + SourceTargetMapper.INSTANCE.toEntity( new ParentDto(), jpaContext ); + + assertThat( jpaContext.getInvokedMethods() ) + .containsExactly( "beforeMapping", "afterMapping" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java new file mode 100644 index 0000000000..b21bf639d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java @@ -0,0 +1,31 @@ +/* + * 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._3711; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; + +public class JpaContext { + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + void beforeMapping(@MappingTarget T parentEntity) { + invokedMethods.add( "beforeMapping" ); + } + + @AfterMapping + void afterMapping(@MappingTarget T parentEntity) { + invokedMethods.add( "afterMapping" ); + } + + public List getInvokedMethods() { + return invokedMethods; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java new file mode 100644 index 0000000000..664d6e58ab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java @@ -0,0 +1,18 @@ +/* + * 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._3711; + +public class ParentDto { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java new file mode 100644 index 0000000000..aaefa949fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java @@ -0,0 +1,18 @@ +/* + * 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._3711; + +public class ParentEntity { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java new file mode 100644 index 0000000000..b09333fd56 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java @@ -0,0 +1,16 @@ +/* + * 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._3711; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceTargetMapper extends BaseMapper { + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + ParentEntity toDTO(ParentDto dto); +} From 8fc97f5f62743cc72280f5334dbebf9fdc39bfee Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 17:13:50 +0800 Subject: [PATCH 302/363] #3806: Properly apply `NullValuePropertyMappingStrategy.IGNORE` for collections / maps without setters Signed-off-by: TangYang --- .../model/CollectionAssignmentBuilder.java | 1 + .../GetterWrapperForCollectionsAndMaps.java | 17 ++++ .../GetterWrapperForCollectionsAndMaps.ftl | 5 +- .../ap/test/bugs/_3806/Issue3806Mapper.java | 63 ++++++++++++++ .../ap/test/bugs/_3806/Issue3806Test.java | 86 +++++++++++++++++++ 5 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java 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 9a0a025845..76e56cd976 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 @@ -240,6 +240,7 @@ else if ( hasNoArgsConstructor() ) { result, method.getThrownTypes(), targetType, + nvpms, targetAccessorType.isFieldAssignment() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java index d29a80b420..e1c0c8cb20 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java @@ -9,9 +9,12 @@ import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; + /** * This wrapper handles the situation were an assignment must be done via a target getter method because there * is no setter available. @@ -26,6 +29,14 @@ * @author Sjaak Derksen */ public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAndMaps { + private final boolean ignoreMapNull; + + public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, + List thrownTypesToExclude, + Type targetType, + boolean fieldAssignment) { + this( decoratedAssignment, thrownTypesToExclude, targetType, null, fieldAssignment ); + } /** * @param decoratedAssignment source RHS @@ -36,6 +47,7 @@ public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAnd public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, List thrownTypesToExclude, Type targetType, + NullValuePropertyMappingStrategyGem nvpms, boolean fieldAssignment) { super( @@ -44,6 +56,7 @@ public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, targetType, fieldAssignment ); + this.ignoreMapNull = nvpms == IGNORE; } @Override @@ -54,4 +67,8 @@ public Set getImportTypes() { } return imported; } + + public boolean isIgnoreMapNull() { + return ignoreMapNull; + } } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl index 4ef55f3b40..f47a37106e 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl @@ -10,10 +10,13 @@ <@lib.sourceLocalVarAssignment/> if ( ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing /> != null ) { <@lib.handleExceptions> - <#if ext.existingInstanceMapping> + <#if ext.existingInstanceMapping && !ignoreMapNull> ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.clear(); <@lib.handleLocalVarNullCheck needs_explicit_local_var=false> + <#if ext.existingInstanceMapping && ignoreMapNull> + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.clear(); + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.<#if ext.targetType.collectionType>addAll<#else>putAll( <@lib.handleWithAssignmentOrNullCheckVar/> ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java new file mode 100644 index 0000000000..89f325dfbb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java @@ -0,0 +1,63 @@ +/* + * 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._3806; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface Issue3806Mapper { + + Issue3806Mapper INSTANCE = Mappers.getMapper( Issue3806Mapper.class ); + + void update(@MappingTarget Target target, Target source); + + class Target { + + private final Collection authors; + private final Map booksByAuthor; + + protected Collection books; + protected Map booksByPublisher; + + public Target(Collection authors, Map booksByAuthor) { + this.authors = authors != null ? new ArrayList<>( authors ) : null; + this.booksByAuthor = booksByAuthor != null ? new HashMap<>( booksByAuthor ) : null; + } + + public Collection getAuthors() { + return authors; + } + + public Map getBooksByAuthor() { + return booksByAuthor; + } + + public Collection getBooks() { + return books; + } + + public void setBooks(Collection books) { + this.books = books; + } + + public Map getBooksByPublisher() { + return booksByPublisher; + } + + public void setBooksByPublisher(Map booksByPublisher) { + this.booksByPublisher = booksByPublisher; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java new file mode 100644 index 0000000000..1df3318c22 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java @@ -0,0 +1,86 @@ +/* + * 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._3806; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +@WithClasses(Issue3806Mapper.class) +@IssueKey("3806") +class Issue3806Test { + + @ProcessorTest + void shouldNotClearGetterOnlyCollectionsInUpdateMapping() { + Map booksByAuthor = new HashMap<>(); + booksByAuthor.put( "author1", "book1" ); + booksByAuthor.put( "author2", "book2" ); + List authors = new ArrayList<>(); + authors.add( "author1" ); + authors.add( "author2" ); + + List books = new ArrayList<>(); + books.add( "book1" ); + books.add( "book2" ); + Map booksByPublisher = new HashMap<>(); + booksByPublisher.put( "publisher1", "book1" ); + booksByPublisher.put( "publisher2", "book2" ); + Issue3806Mapper.Target target = new Issue3806Mapper.Target( authors, booksByAuthor ); + target.setBooks( books ); + target.setBooksByPublisher( booksByPublisher ); + + Issue3806Mapper.Target source = new Issue3806Mapper.Target( null, null ); + Issue3806Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAuthors() ).containsExactly( "author1", "author2" ); + assertThat( target.getBooksByAuthor() ) + .containsOnly( + entry( "author1", "book1" ), + entry( "author2", "book2" ) + ); + + assertThat( target.getBooks() ).containsExactly( "book1", "book2" ); + assertThat( target.getBooksByPublisher() ) + .containsOnly( + entry( "publisher1", "book1" ), + entry( "publisher2", "book2" ) + ); + + booksByAuthor = new HashMap<>(); + booksByAuthor.put( "author3", "book3" ); + authors = new ArrayList<>(); + authors.add( "author3" ); + + books = new ArrayList<>(); + books.add( "book3" ); + booksByPublisher = new HashMap<>(); + booksByPublisher.put( "publisher3", "book3" ); + source = new Issue3806Mapper.Target( authors, booksByAuthor ); + source.setBooks( books ); + source.setBooksByPublisher( booksByPublisher ); + Issue3806Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAuthors() ).containsExactly( "author3" ); + assertThat( target.getBooksByAuthor() ) + .containsOnly( + entry( "author3", "book3" ) + ); + + assertThat( target.getBooks() ).containsExactly( "book3" ); + assertThat( target.getBooksByPublisher() ) + .containsOnly( + entry( "publisher3", "book3" ) + ); + } +} From bff88297e367ec856a1c5a5f68fcc9a62662ee70 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 19:29:39 +0800 Subject: [PATCH 303/363] #3807: Properly recognize the type of public generic fields Signed-off-by: TangYang --- .../ap/internal/model/BeanMappingMethod.java | 4 +- .../mapstruct/ap/internal/util/Filters.java | 23 +++++---- .../util/accessor/AbstractAccessor.java | 41 ---------------- .../util/accessor/ElementAccessor.java | 46 ++++++++++++++---- .../accessor/ExecutableElementAccessor.java | 41 ---------------- .../accessor/ParameterElementAccessor.java | 48 ------------------- .../internal/util/accessor/ReadAccessor.java | 10 ++-- .../util/accessor/RecordElementAccessor.java | 38 --------------- .../ap/test/bugs/_3807/Issue3807Mapper.java | 48 +++++++++++++++++++ .../ap/test/bugs/_3807/Issue3807Test.java | 32 +++++++++++++ 10 files changed, 140 insertions(+), 191 deletions(-) delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java 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 34c1ea3ccb..b5208d302b 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 @@ -65,7 +65,7 @@ import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; -import org.mapstruct.ap.internal.util.accessor.ParameterElementAccessor; +import org.mapstruct.ap.internal.util.accessor.ElementAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; @@ -1067,7 +1067,7 @@ private Accessor createConstructorAccessor(Element element, TypeMirror accessedT existingVariableNames ); existingVariableNames.add( safeParameterName ); - return new ParameterElementAccessor( element, accessedType, safeParameterName ); + return new ElementAccessor( element, accessedType, safeParameterName ); } private boolean hasDefaultAnnotationFromAnyPackage(Element element) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java index 5f7fe74bf2..6492270705 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java @@ -12,7 +12,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.stream.Collectors; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; @@ -23,7 +23,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.util.accessor.Accessor; -import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; +import org.mapstruct.ap.internal.util.accessor.ElementAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; import static org.mapstruct.ap.internal.util.Collections.first; @@ -64,7 +64,7 @@ public Filters(AccessorNamingUtils accessorNaming, TypeUtils typeUtils, TypeMirr public List getterMethodsIn(List elements) { return elements.stream() .filter( accessorNaming::isGetterMethod ) - .map( method -> ReadAccessor.fromGetter( method, getReturnType( method ) ) ) + .map( method -> ReadAccessor.fromGetter( method, getReturnType( method ) ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } @@ -90,7 +90,10 @@ public Map recordAccessorsIn(Collection recordCom for ( Element recordComponent : recordComponents ) { recordAccessors.put( recordComponent.getSimpleName().toString(), - ReadAccessor.fromRecordComponent( recordComponent ) + ReadAccessor.fromRecordComponent( + recordComponent, + typeUtils.asMemberOf( (DeclaredType) typeMirror, recordComponent ) + ) ); } @@ -101,10 +104,10 @@ private TypeMirror getReturnType(ExecutableElement executableElement) { return getWithinContext( executableElement ).getReturnType(); } - public List fieldsIn(List accessors, Function creator) { + public List fieldsIn(List accessors, BiFunction creator) { return accessors.stream() .filter( Fields::isFieldAccessor ) - .map( creator ) + .map( variableElement -> creator.apply( variableElement, getWithinContext( variableElement ) ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } @@ -117,7 +120,7 @@ public List presenceCheckMethodsIn(List el public List setterMethodsIn(List elements) { return elements.stream() .filter( accessorNaming::isSetterMethod ) - .map( method -> new ExecutableElementAccessor( method, getFirstParameter( method ), SETTER ) ) + .map( method -> new ElementAccessor( method, getFirstParameter( method ), SETTER ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } @@ -129,10 +132,14 @@ private ExecutableType getWithinContext( ExecutableElement executableElement ) { return (ExecutableType) typeUtils.asMemberOf( (DeclaredType) typeMirror, executableElement ); } + private TypeMirror getWithinContext( VariableElement variableElement ) { + return typeUtils.asMemberOf( (DeclaredType) typeMirror, variableElement ); + } + public List adderMethodsIn(List elements) { return elements.stream() .filter( accessorNaming::isAdderMethod ) - .map( method -> new ExecutableElementAccessor( method, getFirstParameter( method ), ADDER ) ) + .map( method -> new ElementAccessor( method, getFirstParameter( method ), ADDER ) ) .collect( Collectors.toCollection( LinkedList::new ) ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java deleted file mode 100644 index 04c4c99992..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.internal.util.accessor; - -import java.util.Set; - -import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; - -/** - * This is an abstract implementation of an {@link Accessor} that provides the common implementation. - * - * @author Filip Hrisafov - */ -abstract class AbstractAccessor implements Accessor { - - protected final T element; - - AbstractAccessor(T element) { - this.element = element; - } - - @Override - public String getSimpleName() { - return element.getSimpleName().toString(); - } - - @Override - public Set getModifiers() { - return element.getModifiers(); - } - - @Override - public T getElement() { - return element; - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java index 24e71cc85f..4b363815b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java @@ -5,31 +5,61 @@ */ package org.mapstruct.ap.internal.util.accessor; +import java.util.Set; import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; /** - * An {@link Accessor} that wraps a {@link VariableElement}. - * + * An {@link Accessor} that wraps a {@link Element}. + * Used for getter, setter, filed, constructor, record-class, etc. * @author Filip Hrisafov + * @author Tang Yang */ -public class ElementAccessor extends AbstractAccessor { +public class ElementAccessor implements Accessor { + private final Element element; + private final String name; private final AccessorType accessorType; + private final TypeMirror accessedType; + + public ElementAccessor(VariableElement variableElement, TypeMirror accessedType) { + this( variableElement, accessedType, AccessorType.FIELD ); + } - public ElementAccessor(VariableElement variableElement) { - this( variableElement, AccessorType.FIELD ); + public ElementAccessor(Element element, TypeMirror accessedType, String name) { + this.element = element; + this.name = name; + this.accessedType = accessedType; + this.accessorType = AccessorType.PARAMETER; } - public ElementAccessor(Element element, AccessorType accessorType) { - super( element ); + public ElementAccessor(Element element, TypeMirror accessedType, AccessorType accessorType) { + this.element = element; + this.accessedType = accessedType; this.accessorType = accessorType; + this.name = null; } @Override public TypeMirror getAccessedType() { - return element.asType(); + return accessedType != null ? accessedType : element.asType(); + } + + @Override + public String getSimpleName() { + return name != null ? name : element.getSimpleName().toString(); + } + + @Override + public Set getModifiers() { + return element.getModifiers(); + } + + @Override + public Element getElement() { + return element; } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java deleted file mode 100644 index af37acbce1..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.internal.util.accessor; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps an {@link ExecutableElement}. - * - * @author Filip Hrisafov - */ -public class ExecutableElementAccessor extends AbstractAccessor { - - private final TypeMirror accessedType; - private final AccessorType accessorType; - - public ExecutableElementAccessor(ExecutableElement element, TypeMirror accessedType, AccessorType accessorType) { - super( element ); - this.accessedType = accessedType; - this.accessorType = accessorType; - } - - @Override - public TypeMirror getAccessedType() { - return accessedType; - } - - @Override - public String toString() { - return element.toString(); - } - - @Override - public AccessorType getAccessorType() { - return accessorType; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java deleted file mode 100644 index 9991370059..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ParameterElementAccessor.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.internal.util.accessor; - -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps a {@link VariableElement}. - * - * @author Filip Hrisafov - */ -public class ParameterElementAccessor extends AbstractAccessor { - - protected final String name; - protected final TypeMirror accessedType; - - public ParameterElementAccessor(Element element, TypeMirror accessedType, String name) { - super( element ); - this.name = name; - this.accessedType = accessedType; - } - - @Override - public String getSimpleName() { - return name != null ? name : super.getSimpleName(); - } - - @Override - public TypeMirror getAccessedType() { - return accessedType; - } - - @Override - public String toString() { - return element.toString(); - } - - @Override - public AccessorType getAccessorType() { - return AccessorType.PARAMETER; - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java index 5177bfc75b..31edf3a6da 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java @@ -17,8 +17,8 @@ public interface ReadAccessor extends Accessor { String getReadValueSource(); - static ReadAccessor fromField(VariableElement variableElement) { - return new ReadDelegateAccessor( new ElementAccessor( variableElement ) ) { + static ReadAccessor fromField(VariableElement variableElement, TypeMirror accessedType) { + return new ReadDelegateAccessor( new ElementAccessor( variableElement, accessedType ) ) { @Override public String getReadValueSource() { return getSimpleName(); @@ -26,8 +26,8 @@ public String getReadValueSource() { }; } - static ReadAccessor fromRecordComponent(Element element) { - return new ReadDelegateAccessor( new ElementAccessor( element, AccessorType.GETTER ) ) { + static ReadAccessor fromRecordComponent(Element element, TypeMirror accessedType) { + return new ReadDelegateAccessor( new ElementAccessor( element, accessedType, AccessorType.GETTER ) ) { @Override public String getReadValueSource() { return getSimpleName() + "()"; @@ -36,7 +36,7 @@ public String getReadValueSource() { } static ReadAccessor fromGetter(ExecutableElement element, TypeMirror accessedType) { - return new ReadDelegateAccessor( new ExecutableElementAccessor( element, accessedType, AccessorType.GETTER ) ) { + return new ReadDelegateAccessor( new ElementAccessor( element, accessedType, AccessorType.GETTER ) ) { @Override public String getReadValueSource() { return getSimpleName() + "()"; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java deleted file mode 100644 index d163f462f9..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/RecordElementAccessor.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.internal.util.accessor; - -import javax.lang.model.element.Element; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps a {@link VariableElement}. - * - * @author Filip Hrisafov - */ -public class RecordElementAccessor extends AbstractAccessor { - - public RecordElementAccessor(Element element) { - super( element ); - } - - @Override - public TypeMirror getAccessedType() { - return element.asType(); - } - - @Override - public String toString() { - return element.toString(); - } - - @Override - public AccessorType getAccessorType() { - return AccessorType.GETTER; - } - -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java new file mode 100644 index 0000000000..83ee1f32bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java @@ -0,0 +1,48 @@ +/* + * 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._3807; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3807Mapper { + Issue3807Mapper INSTANCE = Mappers.getMapper( Issue3807Mapper.class ); + + TargetWithoutSetter mapNoSetter(Source target); + + NormalTarget mapNormalSource(Source target); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + //CHECKSTYLE:OFF + class TargetWithoutSetter { + public T value; + } + //CHECKSTYLE:ON + + class NormalTarget { + private T value; + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java new file mode 100644 index 0000000000..3b895f3e11 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java @@ -0,0 +1,32 @@ +/* + * 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._3807; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses(Issue3807Mapper.class) +@IssueKey("3087") +class Issue3807Test { + + @ProcessorTest + void fieldAndSetterShouldWorkWithGeneric() { + Issue3807Mapper.Source source = new Issue3807Mapper.Source( "value" ); + Issue3807Mapper.TargetWithoutSetter targetWithoutSetter = + Issue3807Mapper.INSTANCE.mapNoSetter( source ); + + assertThat( targetWithoutSetter ).isNotNull(); + assertThat( targetWithoutSetter.value ).isEqualTo( "value" ); + + Issue3807Mapper.NormalTarget normalTarget = Issue3807Mapper.INSTANCE.mapNormalSource( source ); + + assertThat( normalTarget ).isNotNull(); + assertThat( normalTarget.getValue() ).isEqualTo( "value" ); + } +} From ce84c81de2ee809c1457dfb9078ec1ae785ad323 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sat, 31 May 2025 23:52:05 +0800 Subject: [PATCH 304/363] #3659: Support `@AnnotatedWith` on decorators Signed-off-by: TangYang --- .../ap/internal/model/Decorator.java | 20 ++- ...nnotationBasedComponentModelProcessor.java | 6 +- .../processor/JakartaComponentProcessor.java | 3 +- .../processor/Jsr330ComponentProcessor.java | 3 +- .../processor/MapperCreationProcessor.java | 5 + .../processor/SpringComponentProcessor.java | 130 ++++++++++++------ .../ap/test/decorator/AnnotatedMapper.java | 43 ++++++ .../decorator/AnnotatedMapperDecorator.java | 18 +++ .../DecoratedWithAnnotatedWithTest.java | 32 +++++ .../ap/test/decorator/TestAnnotation.java | 20 +++ .../JakartaAnnotateWithMapper.java | 40 ++++++ ...akartaAnnotateWithWithMapperDecorator.java | 31 +++++ .../JakartaDecoratorAnnotateWithTest.java | 59 ++++++++ .../decorator/jsr330/Jsr330DecoratorTest.java | 10 +- .../Jsr330AnnotateWithMapper.java | 28 ++++ .../Jsr330AnnotateWithMapperDecorator.java | 32 +++++ .../Jsr330DecoratorAnnotateWithTest.java | 95 +++++++++++++ .../spring/annotatewith/AnnotateMapper.java | 25 ++++ .../annotatewith/AnnotateMapperDecorator.java | 30 ++++ .../annotatewith/CustomAnnotateMapper.java | 25 ++++ .../CustomAnnotateMapperDecorator.java | 28 ++++ .../spring/annotatewith/CustomComponent.java | 20 +++ .../spring/annotatewith/CustomPrimary.java | 19 +++ .../SpringDecoratorAnnotateWithTest.java | 118 ++++++++++++++++ 24 files changed, 791 insertions(+), 49 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java index b7dc0effcb..df7940da31 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java @@ -7,14 +7,15 @@ import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.SortedSet; import javax.lang.model.element.TypeElement; +import org.mapstruct.ap.internal.gem.DecoratedWithGem; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.option.Options; -import org.mapstruct.ap.internal.gem.DecoratedWithGem; import org.mapstruct.ap.internal.version.VersionInformation; /** @@ -33,6 +34,7 @@ public static class Builder extends GeneratedTypeBuilder { private String implName; private String implPackage; private boolean suppressGeneratorTimestamp; + private Set customAnnotations; public Builder() { super( Builder.class ); @@ -68,6 +70,11 @@ public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) { return this; } + public Builder additionalAnnotations(Set customAnnotations) { + this.customAnnotations = customAnnotations; + return this; + } + public Decorator build() { String implementationName = implName.replace( Mapper.CLASS_NAME_PLACEHOLDER, Mapper.getFlatName( mapperElement ) ); @@ -95,7 +102,8 @@ public Decorator build() { suppressGeneratorTimestamp, Accessibility.fromModifiers( mapperElement.getModifiers() ), extraImportedTypes, - decoratorConstructor + decoratorConstructor, + customAnnotations ); } } @@ -110,7 +118,8 @@ private Decorator(TypeFactory typeFactory, String packageName, String name, Type Options options, VersionInformation versionInformation, boolean suppressGeneratorTimestamp, Accessibility accessibility, SortedSet extraImports, - DecoratorConstructor decoratorConstructor) { + DecoratorConstructor decoratorConstructor, + Set customAnnotations) { super( typeFactory, packageName, @@ -128,6 +137,11 @@ private Decorator(TypeFactory typeFactory, String packageName, String name, Type this.decoratorType = decoratorType; this.mapperType = mapperType; + + // Add custom annotations + if ( customAnnotations != null ) { + customAnnotations.forEach( this::addAnnotation ); + } } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java index 014ceda58e..bbd695412a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java @@ -14,6 +14,7 @@ import java.util.stream.Collectors; import javax.lang.model.element.TypeElement; +import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.model.AnnotatedConstructor; import org.mapstruct.ap.internal.model.AnnotatedSetter; import org.mapstruct.ap.internal.model.Annotation; @@ -24,7 +25,6 @@ import org.mapstruct.ap.internal.model.MapperReference; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.model.source.MapperOptions; /** @@ -88,7 +88,7 @@ else if ( injectionStrategy == InjectionStrategyGem.SETTER ) { protected void adjustDecorator(Mapper mapper, InjectionStrategyGem injectionStrategy) { Decorator decorator = mapper.getDecorator(); - for ( Annotation typeAnnotation : getDecoratorAnnotations() ) { + for ( Annotation typeAnnotation : getDecoratorAnnotations( decorator ) ) { decorator.addAnnotation( typeAnnotation ); } @@ -308,7 +308,7 @@ protected Field replacementMapperReference(Field originalReference, List getDecoratorAnnotations() { + protected List getDecoratorAnnotations(Decorator decorator) { return Collections.emptyList(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java index 999c0a51df..86c95d612b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java @@ -11,6 +11,7 @@ import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; @@ -39,7 +40,7 @@ protected List getTypeAnnotations(Mapper mapper) { } @Override - protected List getDecoratorAnnotations() { + protected List getDecoratorAnnotations(Decorator decorator) { return Arrays.asList( singleton(), named() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java index 00ba72a079..7770eff16c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java @@ -11,6 +11,7 @@ import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; @@ -42,7 +43,7 @@ protected List getTypeAnnotations(Mapper mapper) { } @Override - protected List getDecoratorAnnotations() { + protected List getDecoratorAnnotations(Decorator decorator) { return Arrays.asList( singleton(), named() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 3b38a05b59..4fbed6cbec 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -33,6 +33,7 @@ import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.model.AdditionalAnnotationsBuilder; +import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.BeanMappingMethod; import org.mapstruct.ap.internal.model.ContainerMappingMethod; import org.mapstruct.ap.internal.model.ContainerMappingMethodBuilder; @@ -287,6 +288,9 @@ else if ( constructor.getParameters().size() == 1 ) { messager.printMessage( element, decoratedWith.mirror(), Message.DECORATOR_CONSTRUCTOR ); } + // Get annotations from the decorator class + Set decoratorAnnotations = additionalAnnotationsBuilder.getProcessedAnnotations( decoratorElement ); + Decorator decorator = new Decorator.Builder() .elementUtils( elementUtils ) .typeFactory( typeFactory ) @@ -300,6 +304,7 @@ else if ( constructor.getParameters().size() == 1 ) { .implPackage( mapperOptions.implementationPackage() ) .extraImports( getExtraImports( element, mapperOptions ) ) .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) + .additionalAnnotations( decoratorAnnotations ) .build(); return decorator; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java index 84a672bcc9..c31ac51f06 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java @@ -9,20 +9,22 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; - import static javax.lang.model.element.ElementKind.PACKAGE; /** @@ -35,6 +37,9 @@ */ public class SpringComponentProcessor extends AnnotationBasedComponentModelProcessor { + private static final String SPRING_COMPONENT_ANNOTATION = "org.springframework.stereotype.Component"; + private static final String SPRING_PRIMARY_ANNOTATION = "org.springframework.context.annotation.Primary"; + @Override protected String getComponentModelIdentifier() { return MappingConstantsGem.ComponentModelGem.SPRING; @@ -54,12 +59,37 @@ protected List getTypeAnnotations(Mapper mapper) { return typeAnnotations; } + /** + * Returns the annotations that need to be added to the generated decorator, filtering out any annotations + * that are already present or represented as meta-annotations. + * + * @param decorator the decorator to process + * @return A list of annotations that should be added to the generated decorator. + */ @Override - protected List getDecoratorAnnotations() { - return Arrays.asList( - component(), - primary() - ); + protected List getDecoratorAnnotations(Decorator decorator) { + Set desiredAnnotationNames = new LinkedHashSet<>(); + desiredAnnotationNames.add( SPRING_COMPONENT_ANNOTATION ); + desiredAnnotationNames.add( SPRING_PRIMARY_ANNOTATION ); + List decoratorAnnotations = decorator.getAnnotations(); + if ( !decoratorAnnotations.isEmpty() ) { + Set handledElements = new HashSet<>(); + for ( Annotation annotation : decoratorAnnotations ) { + removeAnnotationsPresentOnElement( + annotation.getType().getTypeElement(), + desiredAnnotationNames, + handledElements + ); + if ( desiredAnnotationNames.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return Collections.emptyList(); + } + } + } + + return desiredAnnotationNames.stream() + .map( this::createAnnotation ) + .collect( Collectors.toList() ); } @Override @@ -82,8 +112,12 @@ protected boolean requiresGenerationOfDecoratorClass() { return true; } + private Annotation createAnnotation(String canonicalName) { + return new Annotation( getTypeFactory().getType( canonicalName ) ); + } + private Annotation autowired() { - return new Annotation( getTypeFactory().getType( "org.springframework.beans.factory.annotation.Autowired" ) ); + return createAnnotation( "org.springframework.beans.factory.annotation.Autowired" ); } private Annotation qualifierDelegate() { @@ -96,34 +130,51 @@ private Annotation qualifierDelegate() { ) ) ); } - private Annotation primary() { - return new Annotation( getTypeFactory().getType( "org.springframework.context.annotation.Primary" ) ); - } - private Annotation component() { - return new Annotation( getTypeFactory().getType( "org.springframework.stereotype.Component" ) ); + return createAnnotation( SPRING_COMPONENT_ANNOTATION ); } private boolean isAlreadyAnnotatedAsSpringStereotype(Mapper mapper) { - Set handledElements = new HashSet<>(); - return mapper.getAnnotations() - .stream() - .anyMatch( - annotation -> isOrIncludesComponentAnnotation( annotation, handledElements ) - ); - } + Set desiredAnnotationNames = new LinkedHashSet<>(); + desiredAnnotationNames.add( SPRING_COMPONENT_ANNOTATION ); + + List mapperAnnotations = mapper.getAnnotations(); + if ( !mapperAnnotations.isEmpty() ) { + Set handledElements = new HashSet<>(); + for ( Annotation annotation : mapperAnnotations ) { + removeAnnotationsPresentOnElement( + annotation.getType().getTypeElement(), + desiredAnnotationNames, + handledElements + ); + if ( desiredAnnotationNames.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return true; + } + } + } - private boolean isOrIncludesComponentAnnotation(Annotation annotation, Set handledElements) { - return isOrIncludesComponentAnnotation( - annotation.getType().getTypeElement(), handledElements - ); + return false; } - private boolean isOrIncludesComponentAnnotation(Element element, Set handledElements) { - if ( "org.springframework.stereotype.Component".equals( - ( (TypeElement) element ).getQualifiedName().toString() - )) { - return true; + /** + * Removes all the annotations and meta-annotations from {@code annotations} which are on the given element. + * + * @param element the element to check + * @param annotations the annotations to check for + * @param handledElements set of already handled elements to avoid infinite recursion + */ + private void removeAnnotationsPresentOnElement(Element element, Set annotations, + Set handledElements) { + if ( annotations.isEmpty() ) { + return; + } + if ( element instanceof TypeElement && + annotations.remove( ( (TypeElement) element ).getQualifiedName().toString() ) ) { + if ( annotations.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return; + } } for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { @@ -132,17 +183,16 @@ private boolean isOrIncludesComponentAnnotation(Element element, Set ha if ( !isAnnotationInPackage( annotationMirrorElement, "java.lang.annotation" ) && !handledElements.contains( annotationMirrorElement ) ) { handledElements.add( annotationMirrorElement ); - boolean isOrIncludesComponentAnnotation = isOrIncludesComponentAnnotation( - annotationMirrorElement, handledElements - ); - - if ( isOrIncludesComponentAnnotation ) { - return true; + if ( annotations.remove( ( (TypeElement) annotationMirrorElement ).getQualifiedName().toString() ) ) { + if ( annotations.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return; + } } + + removeAnnotationsPresentOnElement( element, annotations, handledElements ); } } - - return false; } private PackageElement getPackageOf( Element element ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java new file mode 100644 index 0000000000..6da0d70c93 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java @@ -0,0 +1,43 @@ +/* + * 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.decorator; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +@DecoratedWith(AnnotatedMapperDecorator.class) +public interface AnnotatedMapper { + + AnnotatedMapper INSTANCE = Mappers.getMapper( AnnotatedMapper.class ); + + Target toTarget(Source source); + + class Source { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Target { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java new file mode 100644 index 0000000000..6bf7a1fd66 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java @@ -0,0 +1,18 @@ +/* + * 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.decorator; + +import org.mapstruct.AnnotateWith; + +@AnnotateWith(value = TestAnnotation.class, elements = @AnnotateWith.Element(strings = "decoratorValue")) +public abstract class AnnotatedMapperDecorator implements AnnotatedMapper { + + private final AnnotatedMapper delegate; + + public AnnotatedMapperDecorator(AnnotatedMapper delegate) { + this.delegate = delegate; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java new file mode 100644 index 0000000000..1e3d574a6d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java @@ -0,0 +1,32 @@ +/* + * 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.decorator; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotatedWith on decorator classes. + */ +@IssueKey("3659") +@WithClasses({ + TestAnnotation.class, + AnnotatedMapper.class, + AnnotatedMapperDecorator.class +}) +public class DecoratedWithAnnotatedWithTest { + + @ProcessorTest + public void shouldApplyAnnotationFromDecorator() { + Class implementationClass = AnnotatedMapper.INSTANCE.getClass(); + + assertThat( implementationClass ).hasAnnotation( TestAnnotation.class ); + assertThat( implementationClass.getAnnotation( TestAnnotation.class ).value() ).isEqualTo( "decoratorValue" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java new file mode 100644 index 0000000000..742184d261 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java @@ -0,0 +1,20 @@ +/* + * 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.decorator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Test annotation for testing @AnnotatedWith on decorators. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestAnnotation { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java new file mode 100644 index 0000000000..a1a754158b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java @@ -0,0 +1,40 @@ +/* + * 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.decorator.jakarta.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +/** + * A mapper using Jakarta component model with a decorator. + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA) +@DecoratedWith(JakartaAnnotateWithWithMapperDecorator.class) +public interface JakartaAnnotateWithMapper { + + /** + * Maps a person to a person DTO. + * + * @param person the person to map + * @return the person DTO + */ + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + /** + * Maps an address to an address DTO. + * + * @param address the address to map + * @return the address DTO + */ + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java new file mode 100644 index 0000000000..89a42aaf6e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java @@ -0,0 +1,31 @@ +/* + * 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.decorator.jakarta.annotatewith; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; + +/** + * A decorator for {@link JakartaAnnotateWithMapper}. + */ +@AnnotateWith(value = TestAnnotation.class) +public abstract class JakartaAnnotateWithWithMapperDecorator implements JakartaAnnotateWithMapper { + + @Inject + @Named("org.mapstruct.ap.test.decorator.jakarta.annotatewith.JakartaAnnotateWithMapperImpl_") + private JakartaAnnotateWithMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..d98e26b217 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java @@ -0,0 +1,59 @@ +/* + * 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.decorator.jakarta.annotatewith; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test for the application of @AnnotateWith on decorator classes with Jakarta component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + JakartaAnnotateWithMapper.class, + TestAnnotation.class, + JakartaAnnotateWithWithMapperDecorator.class +}) +@WithJakartaInject +public class JakartaDecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void hasCorrectImports() { + // check the decorator + generatedSource.forMapper( JakartaAnnotateWithMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@TestAnnotation" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + // check the plain mapper + generatedSource.forDecoratedMapper( JakartaAnnotateWithMapper.class ).content() + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java index be1a67cf09..a29310b9ed 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java @@ -18,6 +18,7 @@ import org.mapstruct.ap.test.decorator.AddressDto; import org.mapstruct.ap.test.decorator.Person; import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.jsr330.annotatewith.Jsr330DecoratorAnnotateWithTest; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -27,6 +28,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; import static java.lang.System.lineSeparator; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +47,13 @@ PersonMapperDecorator.class }) @IssueKey("592") -@ComponentScan(basePackageClasses = Jsr330DecoratorTest.class) +@ComponentScan( + basePackageClasses = Jsr330DecoratorTest.class, + excludeFilters = @ComponentScan.Filter( + type = FilterType.ASSIGNABLE_TYPE, + classes = { Jsr330DecoratorAnnotateWithTest.class } + ) +) @Configuration @WithJavaxInject @DisabledOnJre(JRE.OTHER) diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java new file mode 100644 index 0000000000..7e6ab8b7bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java @@ -0,0 +1,28 @@ +/* + * 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.decorator.jsr330.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +/** + * A mapper using JSR-330 component model with a decorator. + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +@DecoratedWith(Jsr330AnnotateWithMapperDecorator.class) +public interface Jsr330AnnotateWithMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java new file mode 100644 index 0000000000..96cee9f824 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java @@ -0,0 +1,32 @@ +/* + * 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.decorator.jsr330.annotatewith; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; + +/** + * A decorator for {@link Jsr330AnnotateWithMapper}. + */ +@AnnotateWith(value = TestAnnotation.class) +public abstract class Jsr330AnnotateWithMapperDecorator implements Jsr330AnnotateWithMapper { + + @Inject + @Named("org.mapstruct.ap.test.decorator.jsr330.annotatewith.Jsr330AnnotateWithMapperImpl_") + private Jsr330AnnotateWithMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..19d77dd5cb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java @@ -0,0 +1,95 @@ +/* + * 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.decorator.jsr330.annotatewith; + +import java.util.Calendar; +import javax.inject.Inject; +import javax.inject.Named; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotateWith on decorator classes with JSR-330 component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + Jsr330AnnotateWithMapper.class, + Jsr330AnnotateWithMapperDecorator.class, + TestAnnotation.class +}) +@ComponentScan(basePackageClasses = Jsr330DecoratorAnnotateWithTest.class) +@Configuration +@WithJavaxInject +@DisabledOnJre(JRE.OTHER) +public class Jsr330DecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Inject + @Named + private Jsr330AnnotateWithMapper jsr330AnnotateWithMapper; + + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldContainCustomAnnotation() { + generatedSource.forMapper( Jsr330AnnotateWithMapper.class ) + .content() + .contains( "@TestAnnotation" ); + } + + @ProcessorTest + public void shouldInvokeDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = jsr330AnnotateWithMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java new file mode 100644 index 0000000000..0ba8e88299 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java @@ -0,0 +1,25 @@ +/* + * 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.decorator.spring.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +@DecoratedWith(AnnotateMapperDecorator.class) +public interface AnnotateMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java new file mode 100644 index 0000000000..cff0a4c148 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java @@ -0,0 +1,30 @@ +/* + * 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.decorator.spring.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +@AnnotateWith(value = Component.class, elements = @AnnotateWith.Element(strings = "decoratorComponent")) +@AnnotateWith(value = Primary.class) +public abstract class AnnotateMapperDecorator implements AnnotateMapper { + + @Autowired + @Qualifier("delegate") + private AnnotateMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java new file mode 100644 index 0000000000..9e2e35676c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java @@ -0,0 +1,25 @@ +/* + * 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.decorator.spring.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +@DecoratedWith(CustomAnnotateMapperDecorator.class) +public interface CustomAnnotateMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java new file mode 100644 index 0000000000..3668d98b04 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java @@ -0,0 +1,28 @@ +/* + * 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.decorator.spring.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +@AnnotateWith(value = CustomComponent.class, elements = @AnnotateWith.Element(strings = "customComponentDecorator")) +@AnnotateWith(value = CustomPrimary.class) +public abstract class CustomAnnotateMapperDecorator implements CustomAnnotateMapper { + + @Autowired + @Qualifier("delegate") + private CustomAnnotateMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java new file mode 100644 index 0000000000..be71249f2a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java @@ -0,0 +1,20 @@ +/* + * 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.decorator.spring.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.stereotype.Component; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Component +public @interface CustomComponent { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java new file mode 100644 index 0000000000..4e18bdbc7d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java @@ -0,0 +1,19 @@ +/* + * 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.decorator.spring.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Primary; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Primary +public @interface CustomPrimary { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..7b3911c22b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java @@ -0,0 +1,118 @@ +/* + * 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.decorator.spring.annotatewith; + +import java.util.Calendar; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotateWith on decorator classes with Spring component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + AnnotateMapper.class, + AnnotateMapperDecorator.class, + CustomComponent.class, + CustomPrimary.class, + CustomAnnotateMapper.class, + CustomAnnotateMapperDecorator.class +}) +@WithSpring +@ComponentScan(basePackageClasses = SpringDecoratorAnnotateWithTest.class) +@Configuration +public class SpringDecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private AnnotateMapper annotateMapper; + + @Autowired + private CustomAnnotateMapper customAnnotateMapper; + + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldNotDuplicateComponentAnnotation() { + generatedSource.forMapper( AnnotateMapper.class ) + .content() + .contains( "@Component(value = \"decoratorComponent\")", "@Primary" ) + .doesNotContain( "@Component" + System.lineSeparator() ); + } + + @ProcessorTest + public void shouldNotDuplicateCustomComponentAnnotation() { + generatedSource.forMapper( CustomAnnotateMapper.class ) + .content() + .contains( "@CustomComponent(value = \"customComponentDecorator\")", "@CustomPrimary" ) + .doesNotContain( "@Component" ); + } + + @ProcessorTest + public void shouldInvokeAnnotateDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = annotateMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest + public void shouldInvokeCustomAnnotateDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = customAnnotateMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} From 9847eaf195cb891c5a47df96db2b90a1b335b75b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 May 2025 18:14:13 +0200 Subject: [PATCH 305/363] #3876: Move Windows and Mac OS builds outside of the main workflow --- .github/workflows/macos.yml | 20 ++++++++++++++++++++ .github/workflows/main.yml | 24 ------------------------ .github/workflows/windows.yml | 20 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/macos.yml create mode 100644 .github/workflows/windows.yml diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000000..833bb6ba39 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,20 @@ +name: Mac OS CI + +on: push + +env: + MAVEN_ARGS: -V -B --no-transfer-progress -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 + +jobs: + mac: + name: 'Mac OS' + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: 'Set up JDK 21' + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: 21 + - name: 'Test' + run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 166466348d..b6a2ef371e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,27 +58,3 @@ jobs: java-version: ${{ matrix.java }} - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest - windows: - name: 'Windows' - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - name: 'Set up JDK 21' - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 21 - - name: 'Test' - run: ./mvnw %MAVEN_ARGS% install - mac: - name: 'Mac OS' - runs-on: macos-latest - steps: - - uses: actions/checkout@v3 - - name: 'Set up JDK 21' - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 21 - - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000000..bda38f8783 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,20 @@ +name: Windows CI + +on: push + +env: + MAVEN_ARGS: -V -B --no-transfer-progress -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 + +jobs: + windows: + name: 'Windows' + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - name: 'Set up JDK 21' + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: 21 + - name: 'Test' + run: ./mvnw %MAVEN_ARGS% install From 46ce011e4bd9b9fbcc0f0d337d5ad631f5d214b2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Jun 2025 07:52:18 +0200 Subject: [PATCH 306/363] Refactor options and add an enum (#3877) --- .../org/mapstruct/ap/MappingProcessor.java | 101 ++++++------------ .../internal/model/source/DefaultOptions.java | 20 ++-- .../ap/internal/option/MappingOption.java | 43 ++++++++ .../mapstruct/ap/internal/option/Options.java | 89 +++++++-------- 4 files changed, 128 insertions(+), 125 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index b34b133262..a66762d739 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -13,17 +13,16 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; @@ -35,9 +34,8 @@ import javax.tools.Diagnostic.Kind; import org.mapstruct.ap.internal.gem.MapperGem; -import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; -import org.mapstruct.ap.internal.gem.ReportingPolicyGem; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.option.MappingOption; import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.processor.DefaultModelElementProcessorContext; import org.mapstruct.ap.internal.processor.ModelElementProcessor; @@ -84,19 +82,6 @@ * @author Gunnar Morling */ @SupportedAnnotationTypes("org.mapstruct.Mapper") -@SupportedOptions({ - MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, - MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT, - MappingProcessor.UNMAPPED_TARGET_POLICY, - MappingProcessor.UNMAPPED_SOURCE_POLICY, - MappingProcessor.DEFAULT_COMPONENT_MODEL, - MappingProcessor.DEFAULT_INJECTION_STRATEGY, - MappingProcessor.DISABLE_BUILDERS, - MappingProcessor.VERBOSE, - MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, - MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY, - MappingProcessor.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR, -}) public class MappingProcessor extends AbstractProcessor { /** @@ -104,20 +89,32 @@ public class MappingProcessor extends AbstractProcessor { */ private static final boolean ANNOTATIONS_CLAIMED_EXCLUSIVELY = false; - protected static final String SUPPRESS_GENERATOR_TIMESTAMP = "mapstruct.suppressGeneratorTimestamp"; - protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT = - "mapstruct.suppressGeneratorVersionInfoComment"; - protected static final String UNMAPPED_TARGET_POLICY = "mapstruct.unmappedTargetPolicy"; - protected static final String UNMAPPED_SOURCE_POLICY = "mapstruct.unmappedSourcePolicy"; - protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel"; - protected static final String DEFAULT_INJECTION_STRATEGY = "mapstruct.defaultInjectionStrategy"; - protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; - protected static final String DISABLE_BUILDERS = "mapstruct.disableBuilders"; - protected static final String VERBOSE = "mapstruct.verbose"; - protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy"; - protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy"; - protected static final String DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR = - "mapstruct.disableLifecycleOverloadDeduplicateSelector"; + // CHECKSTYLE:OFF + // Deprecated options, kept for backwards compatibility. + // They will be removed in a future release. + @Deprecated + protected static final String SUPPRESS_GENERATOR_TIMESTAMP = MappingOption.SUPPRESS_GENERATOR_TIMESTAMP.getOptionName(); + @Deprecated + protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT = MappingOption.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT.getOptionName(); + @Deprecated + protected static final String UNMAPPED_TARGET_POLICY = MappingOption.UNMAPPED_TARGET_POLICY.getOptionName(); + @Deprecated + protected static final String UNMAPPED_SOURCE_POLICY = MappingOption.UNMAPPED_SOURCE_POLICY.getOptionName(); + @Deprecated + protected static final String DEFAULT_COMPONENT_MODEL = MappingOption.DEFAULT_COMPONENT_MODEL.getOptionName(); + @Deprecated + protected static final String DEFAULT_INJECTION_STRATEGY = MappingOption.DEFAULT_INJECTION_STRATEGY.getOptionName(); + @Deprecated + protected static final String ALWAYS_GENERATE_SERVICE_FILE = MappingOption.ALWAYS_GENERATE_SERVICE_FILE.getOptionName(); + @Deprecated + protected static final String DISABLE_BUILDERS = MappingOption.DISABLE_BUILDERS.getOptionName(); + @Deprecated + protected static final String VERBOSE = MappingOption.VERBOSE.getOptionName(); + @Deprecated + protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = MappingOption.NULL_VALUE_ITERABLE_MAPPING_STRATEGY.getOptionName(); + @Deprecated + protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = MappingOption.NULL_VALUE_MAP_MAPPING_STRATEGY.getOptionName(); + // CHECKSTYLE:ON private final Set additionalSupportedOptions; private final String additionalSupportedOptionsError; @@ -156,7 +153,7 @@ public MappingProcessor() { public synchronized void init(ProcessingEnvironment processingEnv) { super.init( processingEnv ); - options = createOptions(); + options = new Options( processingEnv.getOptions() ); annotationProcessorContext = new AnnotationProcessorContext( processingEnv.getElementUtils(), processingEnv.getTypeUtils(), @@ -171,34 +168,6 @@ public synchronized void init(ProcessingEnvironment processingEnv) { } } - private Options createOptions() { - String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY ); - String unmappedSourcePolicy = processingEnv.getOptions().get( UNMAPPED_SOURCE_POLICY ); - String nullValueIterableMappingStrategy = processingEnv.getOptions() - .get( NULL_VALUE_ITERABLE_MAPPING_STRATEGY ); - String nullValueMapMappingStrategy = processingEnv.getOptions().get( NULL_VALUE_MAP_MAPPING_STRATEGY ); - String disableLifecycleOverloadDeduplicateSelector = processingEnv.getOptions() - .get( DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR ); - - return new Options( - Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), - Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ), - unmappedTargetPolicy != null ? ReportingPolicyGem.valueOf( unmappedTargetPolicy.toUpperCase() ) : null, - unmappedSourcePolicy != null ? ReportingPolicyGem.valueOf( unmappedSourcePolicy.toUpperCase() ) : null, - processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ), - processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ), - Boolean.parseBoolean( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), - Boolean.parseBoolean( processingEnv.getOptions().get( DISABLE_BUILDERS ) ), - Boolean.parseBoolean( processingEnv.getOptions().get( VERBOSE ) ), - nullValueIterableMappingStrategy != null ? - NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) : - null, - nullValueMapMappingStrategy != null ? - NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null, - Boolean.parseBoolean( disableLifecycleOverloadDeduplicateSelector ) - ); - } - @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); @@ -259,13 +228,11 @@ else if ( !deferredMappers.isEmpty() ) { @Override public Set getSupportedOptions() { - Set supportedOptions = super.getSupportedOptions(); - if ( additionalSupportedOptions.isEmpty() ) { - return supportedOptions; - } - Set allSupportedOptions = new HashSet<>( supportedOptions ); - allSupportedOptions.addAll( additionalSupportedOptions ); - return allSupportedOptions; + return Stream.concat( + Stream.of( MappingOption.values() ).map( MappingOption::getOptionName ), + additionalSupportedOptions.stream() + ) + .collect( Collectors.toSet() ); } /** diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index e1f04fd941..8d76e65c75 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -56,16 +56,18 @@ public Set imports() { @Override public ReportingPolicyGem unmappedTargetPolicy() { - if ( options.getUnmappedTargetPolicy() != null ) { - return options.getUnmappedTargetPolicy(); + ReportingPolicyGem unmappedTargetPolicy = options.getUnmappedTargetPolicy(); + if ( unmappedTargetPolicy != null ) { + return unmappedTargetPolicy; } return ReportingPolicyGem.valueOf( mapper.unmappedTargetPolicy().getDefaultValue() ); } @Override public ReportingPolicyGem unmappedSourcePolicy() { - if ( options.getUnmappedSourcePolicy() != null ) { - return options.getUnmappedSourcePolicy(); + ReportingPolicyGem unmappedSourcePolicy = options.getUnmappedSourcePolicy(); + if ( unmappedSourcePolicy != null ) { + return unmappedSourcePolicy; } return ReportingPolicyGem.valueOf( mapper.unmappedSourcePolicy().getDefaultValue() ); } @@ -77,8 +79,9 @@ public ReportingPolicyGem typeConversionPolicy() { @Override public String componentModel() { - if ( options.getDefaultComponentModel() != null ) { - return options.getDefaultComponentModel(); + String defaultComponentModel = options.getDefaultComponentModel(); + if ( defaultComponentModel != null ) { + return defaultComponentModel; } return mapper.componentModel().getDefaultValue(); } @@ -97,8 +100,9 @@ public MappingInheritanceStrategyGem getMappingInheritanceStrategy() { @Override public InjectionStrategyGem getInjectionStrategy() { - if ( options.getDefaultInjectionStrategy() != null ) { - return InjectionStrategyGem.valueOf( options.getDefaultInjectionStrategy().toUpperCase() ); + String defaultInjectionStrategy = options.getDefaultInjectionStrategy(); + if ( defaultInjectionStrategy != null ) { + return InjectionStrategyGem.valueOf( defaultInjectionStrategy.toUpperCase() ); } return InjectionStrategyGem.valueOf( mapper.injectionStrategy().getDefaultValue() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java new file mode 100644 index 0000000000..d35b2c0dc6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java @@ -0,0 +1,43 @@ +/* + * 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.internal.option; + +/** + * @author Filip Hrisafov + */ +public enum MappingOption { + + // CHECKSTYLE:OFF + SUPPRESS_GENERATOR_TIMESTAMP( "mapstruct.suppressGeneratorTimestamp" ), + SUPPRESS_GENERATOR_VERSION_INFO_COMMENT( "mapstruct.suppressGeneratorVersionInfoComment" ), + UNMAPPED_TARGET_POLICY("mapstruct.unmappedTargetPolicy"), + UNMAPPED_SOURCE_POLICY("mapstruct.unmappedSourcePolicy"), + DEFAULT_COMPONENT_MODEL("mapstruct.defaultComponentModel"), + DEFAULT_INJECTION_STRATEGY("mapstruct.defaultInjectionStrategy"), + ALWAYS_GENERATE_SERVICE_FILE("mapstruct.alwaysGenerateServicesFile"), + DISABLE_BUILDERS("mapstruct.disableBuilders"), + VERBOSE("mapstruct.verbose"), + NULL_VALUE_ITERABLE_MAPPING_STRATEGY("mapstruct.nullValueIterableMappingStrategy"), + NULL_VALUE_MAP_MAPPING_STRATEGY("mapstruct.nullValueMapMappingStrategy"), + DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR("mapstruct.disableLifecycleOverloadDeduplicateSelector"), + ; + // CHECKSTYLE:ON + + private final String optionName; + + MappingOption(String optionName) { + this.optionName = optionName; + } + + /** + * Returns the name of the option, which can be used in the compiler arguments. + * + * @return the name of the option + */ + public String getOptionName() { + return optionName; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index 095c472c87..38474baf9c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -5,6 +5,9 @@ */ package org.mapstruct.ap.internal.option; +import java.util.Locale; +import java.util.Map; + import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem; @@ -13,93 +16,79 @@ * * @author Andreas Gudian * @author Gunnar Morling + * @author Filip Hrisafov */ public class Options { - private final boolean suppressGeneratorTimestamp; - private final boolean suppressGeneratorVersionComment; - private final ReportingPolicyGem unmappedTargetPolicy; - private final ReportingPolicyGem unmappedSourcePolicy; - private final boolean alwaysGenerateSpi; - private final String defaultComponentModel; - private final String defaultInjectionStrategy; - private final boolean disableBuilders; - private final boolean verbose; - private final NullValueMappingStrategyGem nullValueIterableMappingStrategy; - private final NullValueMappingStrategyGem nullValueMapMappingStrategy; - private final boolean disableLifecycleOverloadDeduplicateSelector; - - //CHECKSTYLE:OFF - public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, - ReportingPolicyGem unmappedTargetPolicy, - ReportingPolicyGem unmappedSourcePolicy, - String defaultComponentModel, String defaultInjectionStrategy, - boolean alwaysGenerateSpi, - boolean disableBuilders, - boolean verbose, - NullValueMappingStrategyGem nullValueIterableMappingStrategy, - NullValueMappingStrategyGem nullValueMapMappingStrategy, - boolean disableLifecycleOverloadDeduplicateSelector - ) { - //CHECKSTYLE:ON - this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; - this.suppressGeneratorVersionComment = suppressGeneratorVersionComment; - this.unmappedTargetPolicy = unmappedTargetPolicy; - this.unmappedSourcePolicy = unmappedSourcePolicy; - this.defaultComponentModel = defaultComponentModel; - this.defaultInjectionStrategy = defaultInjectionStrategy; - this.alwaysGenerateSpi = alwaysGenerateSpi; - this.disableBuilders = disableBuilders; - this.verbose = verbose; - this.nullValueIterableMappingStrategy = nullValueIterableMappingStrategy; - this.nullValueMapMappingStrategy = nullValueMapMappingStrategy; - this.disableLifecycleOverloadDeduplicateSelector = disableLifecycleOverloadDeduplicateSelector; + + private final Map options; + + public Options(Map options) { + this.options = options; } public boolean isSuppressGeneratorTimestamp() { - return suppressGeneratorTimestamp; + return parseBoolean( MappingOption.SUPPRESS_GENERATOR_TIMESTAMP ); } public boolean isSuppressGeneratorVersionComment() { - return suppressGeneratorVersionComment; + return parseBoolean( MappingOption.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ); } public ReportingPolicyGem getUnmappedTargetPolicy() { - return unmappedTargetPolicy; + return parseEnum( MappingOption.UNMAPPED_TARGET_POLICY, ReportingPolicyGem.class ); } public ReportingPolicyGem getUnmappedSourcePolicy() { - return unmappedSourcePolicy; + return parseEnum( MappingOption.UNMAPPED_SOURCE_POLICY, ReportingPolicyGem.class ); } public String getDefaultComponentModel() { - return defaultComponentModel; + return options.get( MappingOption.DEFAULT_COMPONENT_MODEL.getOptionName() ); } public String getDefaultInjectionStrategy() { - return defaultInjectionStrategy; + return options.get( MappingOption.DEFAULT_INJECTION_STRATEGY.getOptionName() ); } public boolean isAlwaysGenerateSpi() { - return alwaysGenerateSpi; + return parseBoolean( MappingOption.ALWAYS_GENERATE_SERVICE_FILE ); } public boolean isDisableBuilders() { - return disableBuilders; + return parseBoolean( MappingOption.DISABLE_BUILDERS ); } public boolean isVerbose() { - return verbose; + return parseBoolean( MappingOption.VERBOSE ); } public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { - return nullValueIterableMappingStrategy; + return parseEnum( MappingOption.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, NullValueMappingStrategyGem.class ); } public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { - return nullValueMapMappingStrategy; + return parseEnum( MappingOption.NULL_VALUE_MAP_MAPPING_STRATEGY, NullValueMappingStrategyGem.class ); } public boolean isDisableLifecycleOverloadDeduplicateSelector() { - return disableLifecycleOverloadDeduplicateSelector; + return parseBoolean( MappingOption.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR ); + } + + private boolean parseBoolean(MappingOption option) { + if ( options.isEmpty() ) { + return false; + } + return Boolean.parseBoolean( options.get( option.getOptionName() ) ); + } + + private > E parseEnum(MappingOption option, Class enumType) { + if ( options.isEmpty() ) { + return null; + } + String value = options.get( option.getOptionName() ); + if ( value == null ) { + return null; + } + return Enum.valueOf( enumType, value.toUpperCase( Locale.ROOT ) ); } } From d68819a233030b5d863e4d4645787a84e7db7d52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:02:23 +0000 Subject: [PATCH 307/363] Bump org.springframework:spring-context from 6.2.2 to 6.2.7 in /parent Bumps [org.springframework:spring-context](https://github.com/spring-projects/spring-framework) from 6.2.2 to 6.2.7. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.2...v6.2.7) --- updated-dependencies: - dependency-name: org.springframework:spring-context dependency-version: 6.2.7 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index f6ad60a22a..35fc24e96b 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -34,7 +34,7 @@ 3.4.1 3.2.2 3.1.0 - 6.2.2 + 6.2.7 1.6.0 8.36.1 5.10.1 From f4d18181719b821bc319f53314b7c8e19e929e16 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 14 Jun 2025 21:33:19 +0200 Subject: [PATCH 308/363] Fix issue key in Issue3807Test --- .../java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java index 3b895f3e11..db82472f98 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java @@ -12,7 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; @WithClasses(Issue3807Mapper.class) -@IssueKey("3087") +@IssueKey("3807") class Issue3807Test { @ProcessorTest From c90c93630e8580e330b09bcdd6f2bc99b08bdee6 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 14 Jun 2025 22:13:56 +0200 Subject: [PATCH 309/363] #3886: Records do not have property write accessors (apart from the record components) --- processor/pom.xml | 1 + .../ap/internal/model/common/Type.java | 4 +++ .../bugs/_3886/jdk21/Issue3886Mapper.java | 31 +++++++++++++++++++ .../test/bugs/_3886/jdk21/Issue3886Test.java | 25 +++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java diff --git a/processor/pom.xml b/processor/pom.xml index 2341c4ddb0..87c3517018 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -286,6 +286,7 @@ org.apache.maven.plugins maven-compiler-plugin + ${minimum.java.version} org.mapstruct.tools.gem diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index d0f65ce97a..24f23ecea8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -796,6 +796,10 @@ public Map getPropertyPresenceCheckers() { * @return an unmodifiable map of all write accessors indexed by property name */ public Map getPropertyWriteAccessors( CollectionMappingStrategyGem cmStrategy ) { + if ( isRecord() ) { + // Records do not have setters, so we return an empty map + return Collections.emptyMap(); + } // collect all candidate target accessors List candidates = new ArrayList<>( getSetters() ); candidates.addAll( getAlternativeTargetAccessors() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java new file mode 100644 index 0000000000..aba305a0bf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java @@ -0,0 +1,31 @@ +/* + * 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._3886.jdk21; + +import java.time.LocalDate; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface Issue3886Mapper { + + RangeRecord map(LocalDate validFrom); + + record RangeRecord(LocalDate validFrom) { + + public RangeRecord restrictTo(RangeRecord other) { + return null; + } + + public void setName(String name) { + // This method is here to ensure that MapStruct won't treat it as a setter + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java new file mode 100644 index 0000000000..93c068cbc7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java @@ -0,0 +1,25 @@ +/* + * 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._3886.jdk21; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.Compiler; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3886Mapper.class) +@IssueKey("3886") +class Issue3886Test { + + // The current version of the Eclipse compiler we use does not support records + @ProcessorTest(Compiler.JDK) + void shouldCompile() { + + } +} From e4bc1cdf1e39a3ff25f0a55b44bdb25837d49a69 Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Sun, 15 Jun 2025 14:29:45 +0800 Subject: [PATCH 310/363] #3884 Ensure `NullValuePropertyMappingStrategy.SET_TO_DEFAULT` initializes empty collection/map when target is null Signed-off-by: TangYang --- ...anceSetterWrapperForCollectionsAndMaps.ftl | 4 + .../ap/test/bugs/_3884/DestinationType.java | 22 ++++ .../ap/test/bugs/_3884/Issue3884Mapper.java | 21 ++++ .../ap/test/bugs/_3884/Issue3884Test.java | 116 ++++++++++++++++++ .../ap/test/bugs/_3884/SourceType.java | 37 ++++++ .../DomainDtoWithNvmsDefaultMapperImpl.java | 24 ++++ .../DomainDtoWithNvmsDefaultMapperImpl.java | 24 ++++ 7 files changed, 248 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl index cee722e2d3..95662c4c79 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl @@ -30,6 +30,10 @@ <@lib.handleLocalVarNullCheck needs_explicit_local_var=directAssignment> ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if directAssignment><@wrapLocalVarInCollectionInitializer/><#else><@lib.handleWithAssignmentOrNullCheckVar/>; + <#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && mapNullToDefault>else { + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.initTargetObject/>; + } + <#-- wraps the local variable in a collection initializer (new collection, or EnumSet.copyOf) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java new file mode 100644 index 0000000000..467b58990c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java @@ -0,0 +1,22 @@ +/* + * 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._3884; + +import java.util.List; +import java.util.Map; + +/** + * Destination type interface for testing null value property mapping strategy with Map properties. + */ +public interface DestinationType { + Map getAttributes(); + + void setAttributes(Map attributes); + + List getItems(); + + void setItems(List items); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java new file mode 100644 index 0000000000..6f6f4f924d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java @@ -0,0 +1,21 @@ +/* + * 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._3884; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * Mapper for testing null value property mapping strategy with Map properties. + */ +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) +public interface Issue3884Mapper { + Issue3884Mapper INSTANCE = Mappers.getMapper( Issue3884Mapper.class ); + + void update(@MappingTarget DestinationType destination, SourceType source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java new file mode 100644 index 0000000000..6c789a0c29 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java @@ -0,0 +1,116 @@ +/* + * 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._3884; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +/** + * Test for issue 3884: NullValuePropertyMappingStrategy.SET_TO_DEFAULT should set target Map/Collection to default + * when source and target are all null. + */ +@IssueKey("3884") +@WithClasses({ + DestinationType.class, + SourceType.class, + Issue3884Mapper.class +}) +public class Issue3884Test { + + @ProcessorTest + public void shouldSetTargetToDefaultWhenBothSourceAndTargetAreNull() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + assertThat( source.getAttributes() ).isNull(); + assertThat( target.getAttributes() ).isNull(); + assertThat( source.getItems() ).isNull(); + assertThat( target.getItems() ).isNull(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).isEmpty(); + assertThat( target.getItems() ).isEmpty(); + } + + @ProcessorTest + public void shouldClearTargetWhenSourceIsNullAndTargetIsInitialized() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + Map targetAttributes = new HashMap<>(); + targetAttributes.put( "targetKey", "targetValue" ); + target.setAttributes( targetAttributes ); + + List targetItems = new ArrayList<>(); + targetItems.add( "targetItem" ); + target.setItems( targetItems ); + + assertThat( source.getAttributes() ).isNull(); + assertThat( target.getAttributes() ).isNotEmpty(); + assertThat( source.getItems() ).isNull(); + assertThat( target.getItems() ).isNotEmpty(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).isEmpty(); + assertThat( target.getItems() ).isEmpty(); + } + + @ProcessorTest + public void shouldCopySourceToTargetWhenSourceIsInitializedAndTargetIsNull() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + source.setAttributes( Map.of( "sourceKey", "sourceValue" ) ); + source.setItems( List.of( "sourceItem" ) ); + + assertThat( source.getAttributes() ).isNotEmpty(); + assertThat( target.getAttributes() ).isNull(); + assertThat( source.getItems() ).isNotEmpty(); + assertThat( target.getItems() ).isNull(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).containsOnly( entry( "sourceKey", "sourceValue" ) ); + assertThat( target.getItems() ).containsExactly( "sourceItem" ); + } + + @ProcessorTest + public void shouldCopySourceToTargetWhenBothSourceAndTargetAreInitialized() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + source.setAttributes( Map.of( "sourceKey", "sourceValue" ) ); + source.setItems( List.of( "sourceItem" ) ); + + Map targetAttributes = new HashMap<>(); + targetAttributes.put( "targetKey", "targetValue" ); + target.setAttributes( targetAttributes ); + List targetItems = new ArrayList<>(); + targetItems.add( "targetItem" ); + target.setItems( targetItems ); + + assertThat( source.getAttributes() ).isNotEmpty(); + assertThat( target.getAttributes() ).isNotEmpty(); + assertThat( source.getItems() ).isNotEmpty(); + assertThat( target.getItems() ).isNotEmpty(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).containsOnly( entry( "sourceKey", "sourceValue" ) ); + assertThat( target.getItems() ).containsExactly( "sourceItem" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java new file mode 100644 index 0000000000..be5c19e01c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java @@ -0,0 +1,37 @@ +/* + * 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._3884; + +import java.util.List; +import java.util.Map; + +/** + * Source type class implementing DestinationType for testing null value property mapping strategy with Map properties. + */ +public class SourceType implements DestinationType { + private Map attributes; + private List items; + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + @Override + public List getItems() { + return items; + } + + @Override + public void setItems(List items) { + this.items = items; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java index 76de94378b..f36e2e437d 100644 --- a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java @@ -67,6 +67,9 @@ public void update(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -83,6 +86,9 @@ public void update(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -99,6 +105,9 @@ public void update(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -115,6 +124,9 @@ public void update(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); @@ -157,6 +169,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -173,6 +188,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -189,6 +207,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -205,6 +226,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java index f1bf0fa78f..87d2fccb26 100644 --- a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapperImpl.java @@ -67,6 +67,9 @@ public void update(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -83,6 +86,9 @@ public void update(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -99,6 +105,9 @@ public void update(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -115,6 +124,9 @@ public void update(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); @@ -157,6 +169,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list != null ) { target.setStrings( new LinkedHashSet( list ) ); } + else { + target.setStrings( new LinkedHashSet() ); + } } if ( target.getLongs() != null ) { Set set = stringListToLongSet( source.getStrings() ); @@ -173,6 +188,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set != null ) { target.setLongs( set ); } + else { + target.setLongs( new LinkedHashSet() ); + } } if ( target.getStringsInitialized() != null ) { List list1 = source.getStringsInitialized(); @@ -189,6 +207,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( list1 != null ) { target.setStringsInitialized( new LinkedHashSet( list1 ) ); } + else { + target.setStringsInitialized( new LinkedHashSet() ); + } } if ( target.getLongsInitialized() != null ) { Set set1 = stringListToLongSet( source.getStringsInitialized() ); @@ -205,6 +226,9 @@ public Domain updateWithReturn(Dto source, Domain target) { if ( set1 != null ) { target.setLongsInitialized( set1 ); } + else { + target.setLongsInitialized( new LinkedHashSet() ); + } } if ( target.getStringsWithDefault() != null ) { List list2 = source.getStringsWithDefault(); From 46a9c29fcabc7ade34f39a92741449c874d7547a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=88=98?= Date: Thu, 31 Jul 2025 04:37:50 +0900 Subject: [PATCH 311/363] #3902: Validate unknown properties in @Ignored --- .../ap/internal/model/BeanMappingMethod.java | 60 +++++++++------- .../mapstruct/ap/internal/util/Message.java | 1 + .../bugs/_3902/ErroneousIssue3902Mapper.java | 69 +++++++++++++++++++ .../ap/test/bugs/_3902/Issue3902Test.java | 57 +++++++++++++++ 4 files changed, 164 insertions(+), 23 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java 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 b5208d302b..c81fc51800 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 @@ -1280,40 +1280,54 @@ else if ( inheritContext.isReversed() ) { return false; } } - Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); - String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); Message msg; String[] args; - if ( targetRef.getPathProperties().isEmpty() ) { - msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; + Element elementForMessage = mapping.getElement(); + if ( elementForMessage == null ) { + elementForMessage = method.getExecutable(); + } + + if ( mapping.isIgnored() && mapping.getElement() == null ) { + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED; args = new String[] { targetPropertyName, - resultTypeToMap.describe(), - mostSimilarProperty + resultTypeToMap.describe() }; } else { - List pathProperties = new ArrayList<>( targetRef.getPathProperties() ); - pathProperties.add( mostSimilarProperty ); - msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE; - args = new String[] { - targetPropertyName, - resultTypeToMap.describe(), - mapping.getTargetName(), - Strings.join( pathProperties, "." ) - }; + Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); + String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); + + if ( targetRef.getPathProperties().isEmpty() ) { + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; + args = new String[] { + targetPropertyName, + resultTypeToMap.describe(), + mostSimilarProperty + }; + } + else { + List pathProperties = new ArrayList<>( targetRef.getPathProperties() ); + pathProperties.add( mostSimilarProperty ); + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE; + args = new String[] { + targetPropertyName, + resultTypeToMap.describe(), + mapping.getTargetName(), + Strings.join( pathProperties, "." ) + }; + } } - ctx.getMessager() - .printMessage( - mapping.getElement(), - mapping.getMirror(), - mapping.getTargetAnnotationValue(), - msg, - args - ); + ctx.getMessager().printMessage( + elementForMessage, + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + msg, + args + ); return true; } else if ( mapping.getInheritContext() != null && mapping.getInheritContext().isReversed() ) { 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 883b3e3792..8534758285 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 @@ -45,6 +45,7 @@ public enum Message { BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ), BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ), + BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("No property named \"%s\" exists in @Ignored for target type \"%s\""), CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ), CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java new file mode 100644 index 0000000000..07f94ce6fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java @@ -0,0 +1,69 @@ +/* + * 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._3902; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * Mapper for testing bug #3902. + * + * @author znight1020 + */ +@Mapper +public interface ErroneousIssue3902Mapper { + + ErroneousIssue3902Mapper INSTANCE = Mappers.getMapper( ErroneousIssue3902Mapper.class ); + + @Ignored(targets = {"name", "foo", "bar"}) + ZooDto mapWithOneKnownAndMultipleUnknowns(Zoo source); + + @Ignored(targets = {"name", "address", "foo"}) + ZooDto mapWithMultipleKnownAndOneUnknown(Zoo source); + + class Zoo { + private String name; + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } + + class ZooDto { + private String name; + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java new file mode 100644 index 0000000000..f1332bbdc6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java @@ -0,0 +1,57 @@ +/* + * 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._3902; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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; + +/** + * Verifies that using an unknown property in {@code @Ignored} yields a proper + * compile error instead of an internal processor error. + * + * @author znight1020 + */ +@WithClasses({ErroneousIssue3902Mapper.class}) +@IssueKey("3902") +public class Issue3902Test { + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + // Test case: mapWithOneKnownAndMultipleUnknowns + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 23, + message = "No property named \"foo\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\"" + ), + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 23, + message = "No property named \"bar\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\"" + ), + + // Test case: mapWithMultipleKnownAndOneUnknown + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 26, + message = "No property named \"foo\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\"" + ) + } + ) + public void shouldFailOnUnknownPropertiesInIgnored() { + } +} From 29036542b85ff93a05fc5851793438ede8713f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=88=98?= Date: Mon, 4 Aug 2025 15:08:22 +0900 Subject: [PATCH 312/363] #3908: Add similar suggestion for unknown property in `@Ignored` --- .../ap/internal/model/BeanMappingMethod.java | 22 ++++++++++--------- .../mapstruct/ap/internal/util/Message.java | 2 +- .../bugs/_3902/ErroneousIssue3902Mapper.java | 3 +++ .../ap/test/bugs/_3902/Issue3902Test.java | 15 ++++++++++--- 4 files changed, 28 insertions(+), 14 deletions(-) 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 c81fc51800..7973ece427 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 @@ -1284,6 +1284,9 @@ else if ( inheritContext.isReversed() ) { Message msg; String[] args; + Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); + String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); + Element elementForMessage = mapping.getElement(); if ( elementForMessage == null ) { elementForMessage = method.getExecutable(); @@ -1293,13 +1296,11 @@ else if ( inheritContext.isReversed() ) { msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED; args = new String[] { targetPropertyName, - resultTypeToMap.describe() + resultTypeToMap.describe(), + mostSimilarProperty }; } else { - Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); - String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); - if ( targetRef.getPathProperties().isEmpty() ) { msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; args = new String[] { @@ -1321,12 +1322,13 @@ else if ( inheritContext.isReversed() ) { } } - ctx.getMessager().printMessage( - elementForMessage, - mapping.getMirror(), - mapping.getTargetAnnotationValue(), - msg, - args + ctx.getMessager() + .printMessage( + elementForMessage, + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + msg, + args ); return true; } 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 8534758285..60aa55eb38 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 @@ -45,7 +45,7 @@ public enum Message { BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ), BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ), - BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("No property named \"%s\" exists in @Ignored for target type \"%s\""), + BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("No property named \"%s\" exists in @Ignored for target type \"%s\". Did you mean \"%s\"?"), CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ), CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java index 07f94ce6fe..b646f1a08e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java @@ -25,6 +25,9 @@ public interface ErroneousIssue3902Mapper { @Ignored(targets = {"name", "address", "foo"}) ZooDto mapWithMultipleKnownAndOneUnknown(Zoo source); + @Ignored(targets = {"name", "addres"}) + ZooDto mapWithTypo(Zoo source); + class Zoo { private String name; private String address; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java index f1332bbdc6..44247d8c27 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java @@ -32,14 +32,14 @@ public class Issue3902Test { kind = javax.tools.Diagnostic.Kind.ERROR, line = 23, message = "No property named \"foo\" exists in @Ignored for target type " + - "\"ErroneousIssue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" ), @Diagnostic( type = ErroneousIssue3902Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 23, message = "No property named \"bar\" exists in @Ignored for target type " + - "\"ErroneousIssue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" ), // Test case: mapWithMultipleKnownAndOneUnknown @@ -48,7 +48,16 @@ public class Issue3902Test { kind = javax.tools.Diagnostic.Kind.ERROR, line = 26, message = "No property named \"foo\" exists in @Ignored for target type " + - "\"ErroneousIssue3902Mapper.ZooDto\"" + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" + ), + + // Test case: mapWithTypo + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 29, + message = "No property named \"addres\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"address\"?" ) } ) From fe43563c8184dcf0e96a9ac9bfb40a8ace436e6f Mon Sep 17 00:00:00 2001 From: Ritesh Chopade <94113981+codeswithritesh@users.noreply.github.com> Date: Sun, 24 Aug 2025 19:47:30 +0530 Subject: [PATCH 313/363] #3837 Add warning/error for redundant ignoreUnmappedSourceProperties entries (#3906) Adds compiler warning / error when properties listed in `ignoreUnmappedSourceProperties` are actually mapped. Respects `unmappedSourcePolicy` and includes tests for redundant and valid ignore cases. --- .../ap/internal/model/BeanMappingMethod.java | 44 +++++- .../mapstruct/ap/internal/util/Message.java | 2 + .../IgnoredMappedPropertyTest.java | 136 ++++++++++++++++++ .../ap/test/ignoreunmapped/UserDto.java | 31 ++++ .../ap/test/ignoreunmapped/UserEntity.java | 40 ++++++ .../ignoreunmapped/mapper/UserMapper.java | 19 +++ ...rMapperWithIgnorePolicyInMapperConfig.java | 24 ++++ .../UserMapperWithIgnoreSourcePolicy.java | 21 +++ ...serMapperWithWarnPolicyInMapperConfig.java | 24 ++++ .../UserMapperWithWarnSourcePolicy.java | 21 +++ ...serMapperWithWarnSourcePolicyInMapper.java | 20 +++ .../mapper/UserMapperWithoutBeanMapping.java | 17 +++ ...erMapperWithErrorPolicyInMapperConfig.java | 24 ++++ .../UserMapperWithErrorSourcePolicy.java | 21 +++ ...erMapperWithErrorSourcePolicyInMapper.java | 20 +++ .../erroneous/UserMapperWithMultiMapping.java | 22 +++ 16 files changed, 485 insertions(+), 1 deletion(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/IgnoredMappedPropertyTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnorePolicyInMapperConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnoreSourcePolicy.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnPolicyInMapperConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicy.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicyInMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithoutBeanMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorPolicyInMapperConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicy.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicyInMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithMultiMapping.java 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 7973ece427..05e0424a21 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 @@ -114,6 +114,7 @@ public static class Builder extends AbstractMappingMethodBuilder unprocessedTargetProperties; private Map unprocessedSourceProperties; private Set missingIgnoredSourceProperties; + private Set redundantIgnoredSourceProperties; private Set targetProperties; private final List propertyMappings = new ArrayList<>(); private final Set unprocessedSourceParameters = new HashSet<>(); @@ -278,11 +279,23 @@ else if ( !method.isUpdateMethod() ) { // get bean mapping (when specified as annotation ) this.missingIgnoredSourceProperties = new HashSet<>(); - if ( beanMapping != null ) { + this.redundantIgnoredSourceProperties = new HashSet<>(); + if ( beanMapping != null && !beanMapping.getIgnoreUnmappedSourceProperties().isEmpty() ) { + // Get source properties explicitly mapped using @Mapping annotations + Set mappedSourceProperties = method.getOptions().getMappings().stream() + .map( MappingOptions::getSourceName ) + .filter( Objects::nonNull ) + .collect( Collectors.toSet() ); + for ( String ignoreUnmapped : beanMapping.getIgnoreUnmappedSourceProperties() ) { + // Track missing ignored properties (i.e. not in source class) if ( unprocessedSourceProperties.remove( ignoreUnmapped ) == null ) { missingIgnoredSourceProperties.add( ignoreUnmapped ); } + // Track redundant ignored properties (actually mapped despite being ignored) + if ( mappedSourceProperties.contains( ignoreUnmapped ) ) { + redundantIgnoredSourceProperties.add( ignoreUnmapped ); + } } } @@ -331,6 +344,7 @@ else if ( !method.isUpdateMethod() ) { } reportErrorForMissingIgnoredSourceProperties(); reportErrorForUnusedSourceParameters(); + reportErrorForRedundantIgnoredSourceProperties(); // mapNullToDefault boolean mapNullToDefault = method.getOptions() @@ -1914,6 +1928,34 @@ private void reportErrorForMissingIgnoredSourceProperties() { } } + private void reportErrorForRedundantIgnoredSourceProperties() { + if ( !redundantIgnoredSourceProperties.isEmpty() ) { + ReportingPolicyGem unmappedSourcePolicy = getUnmappedSourcePolicy(); + if ( unmappedSourcePolicy == ReportingPolicyGem.IGNORE ) { //don't show warning + return; + } + + Message message = Message.BEANMAPPING_REDUNDANT_IGNORED_SOURCES_WARNING; + if ( unmappedSourcePolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ) { + message = Message.BEANMAPPING_REDUNDANT_IGNORED_SOURCES_ERROR; + } + + Object[] args = new Object[] { + MessageFormat.format( + "{0,choice,1#property|1 Date: Thu, 28 Aug 2025 22:31:52 +0200 Subject: [PATCH 314/363] docs: reference gh discussions --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6064004d73..14443592e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ You love MapStruct but miss a certain feature? You found a bug and want to repor * Source code: [http://github.com/mapstruct/mapstruct](http://github.com/mapstruct/mapstruct) * Issue tracker: [https://github.com/mapstruct/mapstruct/issues](https://github.com/mapstruct/mapstruct/issues) -* Discussions: Join the [mapstruct-users](https://groups.google.com/forum/?fromgroups#!forum/mapstruct-users) Google group +* Discussions: [https://github.com/mapstruct/mapstruct/discussions](https://github.com/mapstruct/mapstruct/discussions) * CI build: [https://github.com/mapstruct/mapstruct/actions/](https://github.com/mapstruct/mapstruct/actions) MapStruct follows the _Fork & Pull_ development approach. To get started just fork the [MapStruct repository](http://github.com/mapstruct/mapstruct) to your GitHub account and create a new topic branch for each change. Once you are done with your change, submit a [pull request](https://help.github.com/articles/using-pull-requests) against the MapStruct repo. @@ -15,7 +15,7 @@ When doing changes, keep the following best practices in mind: * Use the code formatter for your IDE * [IntelliJ Formatter](https://github.com/mapstruct/mapstruct/blob/master/etc/mapstruct.xml) * Update the [reference documentation](mapstruct.org/documentation) on [mapstruct.org](mapstruct.org) where required -* Discuss new features you'd like to implement at the [Google group](https://groups.google.com/forum/?fromgroups#!forum/mapstruct-users) before getting started +* Discuss new features you'd like to implement in the [discussions](https://github.com/mapstruct/mapstruct/discussions) before getting started * Create one pull request per feature * Provide a meaningful history, e.g. squash intermediary commits before submitting a pull request From 0f7f5434295093d1f452d95d3d72c10c7f37103e Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 29 Aug 2025 15:57:31 +0200 Subject: [PATCH 315/363] Update Distribution management to point to new Maven Central Portal --- parent/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index 35fc24e96b..f90b066774 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -87,12 +87,12 @@ sonatype-nexus-staging Nexus Release Repository - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/ sonatype-nexus-snapshots Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots/ + https://central.sonatype.com/repository/maven-snapshots/ From 4e1720c2a899252de448c38eb10942f9fbbf5276 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 29 Aug 2025 16:11:26 +0200 Subject: [PATCH 316/363] #3905: Using custom class Override should compile --- .../model/AbstractMappingMethodBuilder.java | 9 ++++++- .../model/NormalTypeMappingMethod.java | 6 ----- .../ap/internal/model/BeanMappingMethod.ftl | 1 - .../internal/model/IterableMappingMethod.ftl | 1 - .../ap/internal/model/MapMappingMethod.ftl | 1 - .../ap/internal/model/StreamMappingMethod.ftl | 1 - .../ap/test/bugs/_3905/Issue3905Mapper.java | 17 ++++++++++++ .../ap/test/bugs/_3905/Issue3905Test.java | 26 +++++++++++++++++++ .../ap/test/bugs/_3905/Override.java | 22 ++++++++++++++++ .../ap/test/bugs/_3905/OverrideDto.java | 22 ++++++++++++++++ 10 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index 7329df5ab7..efb5887c95 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -130,7 +130,14 @@ public List getMethodAnnotations() { ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() ); - return new ArrayList<>( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); + List annotations = new ArrayList<>( + additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) + ); + + if ( method.overridesMethod() ) { + annotations.add( new Annotation( ctx.getTypeFactory().getType( Override.class ) ) ); + } + return annotations; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java index 79cd0e0566..89ee1baab2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java @@ -21,7 +21,6 @@ */ public abstract class NormalTypeMappingMethod extends MappingMethod { private final MethodReference factoryMethod; - private final boolean overridden; private final boolean mapNullToDefault; private final List annotations; @@ -33,7 +32,6 @@ public abstract class NormalTypeMappingMethod extends MappingMethod { List afterMappingReferences) { super( method, existingVariableNames, beforeMappingReferences, afterMappingReferences ); this.factoryMethod = factoryMethod; - this.overridden = method.overridesMethod(); this.mapNullToDefault = mapNullToDefault; this.annotations = annotations; } @@ -59,10 +57,6 @@ public boolean isMapNullToDefault() { return mapNullToDefault; } - public boolean isOverridden() { - return overridden; - } - public MethodReference getFactoryMethod() { return this.factoryMethod; } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index da590bb506..059e2d77d4 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#assign targetType = resultType /> <#if !existingInstanceMapping> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl index 4d08c44b38..7c3d3071ac 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list beforeMappingReferencesWithoutMappingTarget as callback> <@includeModel object=callback targetBeanName=resultName targetType=resultType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl index 1227dbd27a..957dc712ee 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType /> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list beforeMappingReferencesWithoutMappingTarget as callback> <@includeModel object=callback targetBeanName=resultName targetType=resultType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl index 7d0080a1d2..2269892b8a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl @@ -9,7 +9,6 @@ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#if overridden>@Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#--TODO does it even make sense to do a callback if the result is a Stream, as they are immutable--> <#list beforeMappingReferencesWithoutMappingTarget as callback> diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java new file mode 100644 index 0000000000..27e3ac9960 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java @@ -0,0 +1,17 @@ +/* + * 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._3905; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3905Mapper { + + OverrideDto map(Override override); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java new file mode 100644 index 0000000000..f15271362d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java @@ -0,0 +1,26 @@ +/* + * 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._3905; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3905") +@WithClasses({ + Issue3905Mapper.class, + Override.class, + OverrideDto.class +}) +class Issue3905Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java new file mode 100644 index 0000000000..dedc6b28dc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java @@ -0,0 +1,22 @@ +/* + * 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._3905; + +/** + * @author Filip Hrisafov + */ +public class Override { + + private final String name; + + public Override(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java new file mode 100644 index 0000000000..531fab505b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java @@ -0,0 +1,22 @@ +/* + * 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._3905; + +/** + * @author Filip Hrisafov + */ +public class OverrideDto { + + private final String name; + + public OverrideDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} From a0552131a5bf89f8192acb154b81bbd03d334ba4 Mon Sep 17 00:00:00 2001 From: hdulme Date: Wed, 31 Dec 2025 17:23:53 +0100 Subject: [PATCH 317/363] skip coverage upload to codecov on contributors repositories --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b6a2ef371e..c4e6092723 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: if: matrix.java == 21 run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' - if: matrix.java == 21 + if: matrix.java == 21 && github.repository == 'mapstruct/mapstruct' uses: codecov/codecov-action@v2 - name: 'Publish Snapshots' if: matrix.java == 21 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' From 1c8b9107f99e986e5e28cff66ad604ca89975a8c Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:01:18 +0100 Subject: [PATCH 318/363] Update codecov-action to v5 and use token (#3967) --- .github/workflows/main.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4e6092723..c4def2a89d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,10 @@ jobs: run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' if: matrix.java == 21 && github.repository == 'mapstruct/mapstruct' - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v5 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} - name: 'Publish Snapshots' if: matrix.java == 21 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' run: ./mvnw -s etc/ci-settings.xml -DskipTests=true -DskipDistribution=true deploy From 7bcab2824d0d17fe0a68d520e0c73f83a0e82588 Mon Sep 17 00:00:00 2001 From: ro-otgo <116685946+ro-otgo@users.noreply.github.com> Date: Sat, 24 Jan 2026 15:44:21 +0100 Subject: [PATCH 319/363] Fix example 5 code comment. (#3968) Example 5: Mapping with default value should be using fullName instead of just name in order to map the property. --- core/src/main/java/org/mapstruct/Mapping.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index f15cfb75cc..c1923eb41a 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -118,7 +118,7 @@ * // we can use {@link #defaultValue()} or {@link #defaultExpression()} for it * @Mapper * public interface HumanMapper { - * @Mapping(source="name", target="name", defaultValue="Somebody") + * @Mapping(source="name", target="fullName", defaultValue="Somebody") * HumanDto toHumanDto(Human human) * } * From 57790791abd0193cf0ff42d33524c69ab0eb6215 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 24 Jan 2026 15:46:36 +0100 Subject: [PATCH 320/363] BeanMappingMethod and NestedPropertyMappingMethod simplification (#3970) * Move `returnTypeBuilder` and `userDefinedReturnType` in `BeanMappingMethod#build` instead of doing it earlier * Move source parameter presence check resolving into `PresenceCheckMethodResolver` * Simplify `NestedPropertyMappingMethod.ftl` by moving logic into java instead of freemarker --- .../internal/model/AbstractBaseBuilder.java | 4 +- .../model/AbstractMappingMethodBuilder.java | 7 +- .../ap/internal/model/BeanMappingMethod.java | 32 +++----- .../model/NestedPropertyMappingMethod.java | 79 ++++++++++++------- .../model/PresenceCheckMethodResolver.java | 4 + .../ap/internal/model/PropertyMapping.java | 2 +- .../AnyPresenceChecksPresenceCheck.java | 64 +++++++++++++++ .../processor/MapperCreationProcessor.java | 14 ---- .../model/NestedPropertyMappingMethod.ftl | 16 +--- .../AnyPresenceChecksPresenceCheck.ftl | 16 ++++ 10 files changed, 152 insertions(+), 86 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java index 56a1831d52..584e0260bc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java @@ -9,7 +9,6 @@ import javax.lang.model.element.AnnotationMirror; import org.mapstruct.ap.internal.model.common.Assignment; -import org.mapstruct.ap.internal.model.common.BuilderType; import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; @@ -73,7 +72,7 @@ private boolean isDisableSubMappingMethodsGeneration() { * * @return See above */ - Assignment createForgedAssignment(SourceRHS sourceRHS, BuilderType builderType, ForgedMethod forgedMethod) { + Assignment createForgedAssignment(SourceRHS sourceRHS, ForgedMethod forgedMethod) { Supplier forgedMappingMethodCreator; if ( MappingMethodUtils.isEnumMapping( forgedMethod ) ) { @@ -87,7 +86,6 @@ Assignment createForgedAssignment(SourceRHS sourceRHS, BuilderType builderType, else { forgedMappingMethodCreator = () -> new BeanMappingMethod.Builder() .forgedMethod( forgedMethod ) - .returnTypeBuilder( builderType ) .mappingContext( ctx ) .build(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index efb5887c95..3b6e3c1686 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.internal.model; -import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.SourceRHS; @@ -94,12 +93,8 @@ private Assignment forgeMapping(SourceRHS sourceRHS, Type sourceType, Type targe ForgedMethod forgedMethod = forgeMethodCreator.createMethod( name, sourceType, targetType, method, description, true ); - BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); - return createForgedAssignment( - sourceRHS, - ctx.getTypeFactory().builderTypeFor( targetType, builder ), - forgedMethod ); + return createForgedAssignment( sourceRHS, forgedMethod ); } private String getName(Type sourceType, Type targetType) { 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 05e0424a21..39222efc88 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 @@ -32,6 +32,7 @@ import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic; +import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.ReportingPolicyGem; import org.mapstruct.ap.internal.model.PropertyMapping.ConstantMappingBuilder; @@ -52,7 +53,6 @@ import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder; -import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.source.BeanMappingOptions; import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; @@ -106,8 +106,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { public static class Builder extends AbstractMappingMethodBuilder { - private Type userDefinedReturnType; - /* returnType to construct can have a builder */ private BuilderType returnTypeBuilder; private Map unprocessedConstructorProperties; @@ -135,16 +133,6 @@ protected boolean shouldUsePropertyNamesInHistory() { return true; } - public Builder userDefinedReturnType(Type userDefinedReturnType) { - this.userDefinedReturnType = userDefinedReturnType; - return this; - } - - public Builder returnTypeBuilder( BuilderType returnTypeBuilder ) { - this.returnTypeBuilder = returnTypeBuilder; - return this; - } - public Builder sourceMethod(SourceMethod sourceMethod) { method( sourceMethod ); return this; @@ -181,7 +169,17 @@ public BeanMappingMethod build() { // determine which return type to construct boolean cannotConstructReturnType = false; if ( !method.getReturnType().isVoid() ) { - Type returnTypeImpl = null; + BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); + Type returnTypeImpl; + Type userDefinedReturnType = null; + if ( selectionParameters != null && selectionParameters.getResultType() != null ) { + // This is a user-defined return type, which means we need to do some extra checks for it + userDefinedReturnType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ); + returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( userDefinedReturnType, builder ); + } + else { + returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( method.getReturnType(), builder ); + } if ( isBuilderRequired() ) { // the userDefinedReturn type can also require a builder. That buildertype is already set returnTypeImpl = returnTypeBuilder.getBuilder(); @@ -446,12 +444,6 @@ else if ( !method.isUpdateMethod() ) { if ( parameterPresenceCheck != null ) { presenceChecksByParameter.put( sourceParameter.getName(), parameterPresenceCheck ); } - else if ( !sourceParameter.getType().isPrimitive() ) { - presenceChecksByParameter.put( - sourceParameter.getName(), - new NullPresenceCheck( sourceParameter.getName() ) - ); - } } 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 da73e1783f..25c9f8fc03 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 @@ -6,6 +6,7 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Set; @@ -14,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.presence.AnyPresenceChecksPresenceCheck; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; @@ -68,9 +71,32 @@ public NestedPropertyMappingMethod build() { } String previousPropertyName = sourceParameter.getName(); - for ( PropertyEntry propertyEntry : propertyEntries ) { + for ( int i = 0; i < propertyEntries.size(); i++ ) { + PropertyEntry propertyEntry = propertyEntries.get( i ); + PresenceCheck presenceCheck = getPresenceCheck( propertyEntry, previousPropertyName ); + + if ( i > 0 ) { + // If this is not the first property entry, + // then we might need to combine the presence check with a null check of the previous property + if ( presenceCheck != null ) { + presenceCheck = new AnyPresenceChecksPresenceCheck( Arrays.asList( + new NullPresenceCheck( previousPropertyName, true ), + presenceCheck + ) ); + } + else { + presenceCheck = new NullPresenceCheck( previousPropertyName, true ); + } + } + String safeName = Strings.getSafeVariableName( propertyEntry.getName(), existingVariableNames ); - safePropertyEntries.add( new SafePropertyEntry( propertyEntry, safeName, previousPropertyName ) ); + String source = previousPropertyName + "." + propertyEntry.getReadAccessor().getReadValueSource(); + safePropertyEntries.add( new SafePropertyEntry( + propertyEntry.getType(), + safeName, + source, + presenceCheck + ) ); existingVariableNames.add( safeName ); thrownTypes.addAll( ctx.getTypeFactory().getThrownTypes( propertyEntry.getReadAccessor() ) ); @@ -79,6 +105,18 @@ public NestedPropertyMappingMethod build() { method.addThrownTypes( thrownTypes ); return new NestedPropertyMappingMethod( method, safePropertyEntries ); } + + private PresenceCheck getPresenceCheck(PropertyEntry propertyEntry, String previousPropertyName) { + PresenceCheckAccessor propertyPresenceChecker = propertyEntry.getPresenceChecker(); + if ( propertyPresenceChecker != null ) { + return new SuffixPresenceCheck( + previousPropertyName, + propertyPresenceChecker.getPresenceCheckSuffix(), + true + ); + } + return null; + } } private NestedPropertyMappingMethod( ForgedMethod method, List sourcePropertyEntries ) { @@ -157,44 +195,29 @@ public boolean equals( Object obj ) { public static class SafePropertyEntry { private final String safeName; - private final String readAccessorName; + private final String source; private final PresenceCheck presenceChecker; - private final String previousPropertyName; private final Type type; - public SafePropertyEntry(PropertyEntry entry, String safeName, String previousPropertyName) { + public SafePropertyEntry(Type type, String safeName, String source, PresenceCheck presenceCheck) { this.safeName = safeName; - this.readAccessorName = entry.getReadAccessor().getReadValueSource(); - PresenceCheckAccessor presenceChecker = entry.getPresenceChecker(); - if ( presenceChecker != null ) { - this.presenceChecker = new SuffixPresenceCheck( - previousPropertyName, - presenceChecker.getPresenceCheckSuffix() - ); - } - else { - this.presenceChecker = null; - } - this.previousPropertyName = previousPropertyName; - this.type = entry.getType(); + this.source = source; + this.presenceChecker = presenceCheck; + this.type = type; } public String getName() { return safeName; } - public String getAccessorName() { - return readAccessorName; + public String getSource() { + return source; } public PresenceCheck getPresenceChecker() { return presenceChecker; } - public String getPreviousPropertyName() { - return previousPropertyName; - } - public Type getType() { return type; } @@ -210,7 +233,7 @@ public boolean equals(Object o) { SafePropertyEntry that = (SafePropertyEntry) o; - if ( !Objects.equals( readAccessorName, that.readAccessorName ) ) { + if ( !Objects.equals( source, that.source ) ) { return false; } @@ -218,10 +241,6 @@ public boolean equals(Object o) { return false; } - if ( !Objects.equals( previousPropertyName, that.previousPropertyName ) ) { - return false; - } - if ( !Objects.equals( type, that.type ) ) { return false; } @@ -231,7 +250,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = readAccessorName != null ? readAccessorName.hashCode() : 0; + int result = source != null ? source.hashCode() : 0; result = 31 * result + ( presenceChecker != null ? presenceChecker.hashCode() : 0 ); result = 31 * result + ( type != null ? type.hashCode() : 0 ); return result; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 96826f8f66..049e4caf37 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -12,6 +12,7 @@ import org.mapstruct.ap.internal.gem.ConditionStrategyGem; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; import org.mapstruct.ap.internal.model.source.SelectionParameters; @@ -86,6 +87,9 @@ public static PresenceCheck getPresenceCheckForSourceParameter( ); if ( matchingMethods.isEmpty() ) { + if ( !sourceParameter.getType().isPrimitive() ) { + return new NullPresenceCheck( sourceParameter.getName() ); + } return null; } 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 db14ccbd8e..08e8adbd1b 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 @@ -829,7 +829,7 @@ private Assignment forgeMapping(Type sourceType, Type targetType, SourceRHS sour forgeMethodWithMappingReferences, forgedNamedBased ); - return createForgedAssignment( sourceRHS, targetBuilderType, forgedMethod ); + return createForgedAssignment( sourceRHS, forgedMethod ); } private ForgedMethodHistory getForgedMethodHistory(SourceRHS sourceRHS) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java new file mode 100644 index 0000000000..0b5770e42f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java @@ -0,0 +1,64 @@ +/* + * 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.internal.model.presence; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.NegatePresenceCheck; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * @author Filip Hrisafov + */ +public class AnyPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck { + + private final Collection presenceChecks; + + public AnyPresenceChecksPresenceCheck(Collection presenceChecks) { + this.presenceChecks = presenceChecks; + } + + public Collection getPresenceChecks() { + return presenceChecks; + } + + @Override + public PresenceCheck negate() { + return new NegatePresenceCheck( this ); + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>(); + for ( PresenceCheck presenceCheck : presenceChecks ) { + importTypes.addAll( presenceCheck.getImportTypes() ); + } + + return importTypes; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + AnyPresenceChecksPresenceCheck that = (AnyPresenceChecksPresenceCheck) o; + return Objects.equals( presenceChecks, that.presenceChecks ); + } + + @Override + public int hashCode() { + return Objects.hash( presenceChecks ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 4fbed6cbec..ba24e868aa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -24,7 +24,6 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; -import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.DecoratedWithGem; import org.mapstruct.ap.internal.gem.InheritConfigurationGem; import org.mapstruct.ap.internal.gem.InheritInverseConfigurationGem; @@ -420,15 +419,10 @@ else if ( method.isStreamMapping() ) { } else { this.messager.note( 1, Message.BEANMAPPING_CREATE_NOTE, method ); - BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); - Type userDefinedReturnType = getUserDesiredReturnType( method ); - Type builderBaseType = userDefinedReturnType != null ? userDefinedReturnType : method.getReturnType(); BeanMappingMethod.Builder beanMappingBuilder = new BeanMappingMethod.Builder(); BeanMappingMethod beanMappingMethod = beanMappingBuilder .mappingContext( mappingContext ) .sourceMethod( method ) - .userDefinedReturnType( userDefinedReturnType ) - .returnTypeBuilder( typeFactory.builderTypeFor( builderBaseType, builder ) ) .build(); // We can consider that the bean mapping method can always be constructed. If there is a problem @@ -466,14 +460,6 @@ private Javadoc getJavadoc(TypeElement element) { return javadoc; } - private Type getUserDesiredReturnType(SourceMethod method) { - SelectionParameters selectionParameters = method.getOptions().getBeanMapping().getSelectionParameters(); - if ( selectionParameters != null && selectionParameters.getResultType() != null ) { - return typeFactory.getType( selectionParameters.getResultType() ); - } - return null; - } - private M createWithElementMappingMethod(SourceMethod method, MappingMethodOptions mappingMethodOptions, ContainerMappingMethodBuilder builder) { diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl index 8eabe23a16..04d385a9dc 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl @@ -9,22 +9,14 @@ <#lt>private <@includeModel object=returnType.typeBound/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list propertyEntries as entry> <#if entry.presenceChecker?? > - if ( <#if entry_index != 0>${entry.previousPropertyName} == null || !<@includeModel object=entry.presenceChecker /> ) { + if ( <@includeModel object=entry.presenceChecker /> ) { return ${returnType.null}; } - <#if !entry_has_next> - return ${entry.previousPropertyName}.${entry.accessorName}; - <#if entry_has_next> - <@includeModel object=entry.type.typeBound/> ${entry.name} = ${entry.previousPropertyName}.${entry.accessorName}; - <#if !entry.presenceChecker?? > - <#if !entry.type.primitive> - if ( ${entry.name} == null ) { - return ${returnType.null}; - } - - + <@includeModel object=entry.type.typeBound/> ${entry.name} = ${entry.source}; + <#else> + return ${entry.source}; } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl new file mode 100644 index 0000000000..3df10348ad --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl @@ -0,0 +1,16 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.AnyPresenceChecksPresenceCheck" --> +<@compress single_line=true> +<#list presenceChecks as presenceCheck> + <#if presenceCheck_index != 0> + || + + <@includeModel object=presenceCheck /> + + \ No newline at end of file From 9d75a48df5e40c1ed87d2f68d96fbb35807e3b91 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 26 Jan 2026 11:35:18 +0100 Subject: [PATCH 321/363] Block plexus.snapshots repository in GitHub actions #3972 --- .github/workflows/java-ea.yml | 3 +++ .github/workflows/macos.yml | 3 +++ .github/workflows/main.yml | 6 ++++++ .github/workflows/release.yml | 4 ++++ .github/workflows/windows.yml | 3 +++ 5 files changed, 19 insertions(+) diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index fc3a4a0fb1..70b81c40f1 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -17,5 +17,8 @@ jobs: with: website: jdk.java.net release: EA + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=true install -DskipDistribution=true diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 833bb6ba39..98b4e156e0 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -16,5 +16,8 @@ jobs: with: distribution: 'zulu' java-version: 21 + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4def2a89d..ca66a22291 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,6 +23,9 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 21 }} install -DskipDistribution=${{ matrix.java != 21 }} - name: 'Generate coverage report' @@ -59,5 +62,8 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Run integration tests' run: ./mvnw ${MAVEN_ARGS} verify -pl integrationtest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 61f347af3c..b4dffe0586 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,6 +26,10 @@ jobs: distribution: 'zulu' cache: maven + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' + - name: Set release version id: version run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index bda38f8783..fc37af6a9d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -16,5 +16,8 @@ jobs: with: distribution: 'zulu' java-version: 21 + - uses: s4u/maven-settings-action@v4.0.0 + with: + mirrors: '[{"id": "block-plexus-snapshots", "name": "Block Plexus Snapshots", "mirrorOf": "plexus.snapshots", "url": "http://localhost"}]' - name: 'Test' run: ./mvnw %MAVEN_ARGS% install From 99e865f9861bba3d8cc24d90b89821e8bb191164 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 10:27:59 +0100 Subject: [PATCH 322/363] Improve testing support for Kotlin as part of the regular processor testing (#3977) --- .../itest/tests/MavenIntegrationTest.java | 6 + .../test/resources/fullFeatureTest/pom.xml | 1 + .../resources/kotlinFullFeatureTest/pom.xml | 143 ++++++++++++++++++ parent/pom.xml | 13 ++ processor/pom.xml | 68 +++++++++ .../ap/test/kotlin/data/CustomerDto.kt | 11 ++ .../ap/test/kotlin/data/CustomerEntity.java | 31 ++++ .../ap/test/kotlin/data/CustomerMapper.java | 27 ++++ .../ap/test/kotlin/data/KotlinDataTest.java | 54 +++++++ .../ap/testutil/WithKotlinSources.java | 32 ++++ .../model/CompilationOutcomeDescriptor.java | 31 ++++ .../model/DiagnosticDescriptor.java | 34 +++++ .../testutil/runner/CompilationRequest.java | 12 +- .../testutil/runner/CompilingExtension.java | 102 ++++++++++++- .../runner/EclipseCompilingExtension.java | 11 +- .../runner/JdkCompilingExtension.java | 15 +- 16 files changed, 579 insertions(+), 12 deletions(-) create mode 100644 integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index ab43cc4cba..70e1f948b9 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -159,6 +159,12 @@ void expressionTextBlocksTest() { void kotlinDataTest() { } + @ProcessorTest(baseDir = "kotlinFullFeatureTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC_WITH_PATHS + }) + void kotlinFullFeatureTest() { + } + @ProcessorTest(baseDir = "simpleTest") void simpleTest() { } diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index b5ddae9a79..670d5cff92 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -47,6 +47,7 @@ **/*Test.java **/testutil/**/*.java **/spi/**/*.java + **/kotlin/**/*.java ${additionalExclude0} ${additionalExclude1} ${additionalExclude2} diff --git a/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml b/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml new file mode 100644 index 0000000000..ba18457f42 --- /dev/null +++ b/integrationtest/src/test/resources/kotlinFullFeatureTest/pom.xml @@ -0,0 +1,143 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + kotlinFullFeatureTest + jar + + + 2.3.0 + + + + + generate-via-compiler-plugin-with-annotation-processor-paths + + false + + + + + maven-resources-plugin + + + filter-kotlin-patterns + generate-sources + + copy-resources + + + \${project.build.directory}/kotlin-sources + + + ../../../../../processor/src/test/java + + **/kotlin/**/*.kt + + + + true + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + kotlin-compile + compile + + compile + + + + \${project.build.directory}/kotlin-sources + + + + ${project.groupId} + mapstruct-processor + ${mapstruct.version} + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + + default-compile + none + + + default-testCompile + none + + + + + java-compile + compile + + compile + + + + ../../../../../processor/src/test/java + + + **/kotlin/**/*.java + + + **/erroneous/**/*.java + **/*Erroneous*.java + **/*Test.java + **/testutil/**/*.java + + + + + java-test-compile + test-compile + + testCompile + + + + + + + + + + + + org.jetbrains.kotlin + kotlin-stdlib + + + + diff --git a/parent/pom.xml b/parent/pom.xml index f90b066774..5c3a73da88 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -52,6 +52,7 @@ 1.8 3.25.5 2.3.2 + 2.3.0 @@ -151,6 +152,13 @@ ${com.puppycrawl.tools.checkstyle.version} + + org.jetbrains.kotlin + kotlin-bom + ${kotlin.version} + pom + import + org.junit junit-bom @@ -490,6 +498,11 @@ maven-shade-plugin 3.2.0 + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + com.mycila.maven-license-plugin maven-license-plugin diff --git a/processor/pom.xml b/processor/pom.xml index 87c3517018..9f24281e33 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -42,6 +42,11 @@ mapstruct provided + + org.jetbrains.kotlin + kotlin-compiler-embeddable + test + org.eclipse.tycho tycho-compiler-jdt @@ -282,6 +287,42 @@ + + org.jetbrains.kotlin + + kotlin-maven-plugin + + false + + + + kotlin-compile + compile + + compile + + + + src/main/kotlin + + src/main/java + + + + + kotlin-test-compile + test-compile + + test-compile + + + + src/test/java + + + + + org.apache.maven.plugins maven-compiler-plugin @@ -296,6 +337,33 @@ + + + + default-compile + none + + + default-testCompile + none + + + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + + testCompile + + + org.apache.maven.plugins diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt new file mode 100644 index 0000000000..3fbd48b0b1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt @@ -0,0 +1,11 @@ +/* + * 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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class CustomerDto(var name: String?, var email: String?) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java new file mode 100644 index 0000000000..7ddd41bda0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java @@ -0,0 +1,31 @@ +/* + * 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.kotlin.data; + +/** + * @author Filip Hrisafov + */ +public class CustomerEntity { + + private String name; + private String mail; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMail() { + return mail; + } + + public void setMail(String mail) { + this.mail = mail; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java new file mode 100644 index 0000000000..1830a9c583 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java @@ -0,0 +1,27 @@ +/* + * 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.kotlin.data; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CustomerMapper { + + CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class ); + + @Mapping(target = "mail", source = "email") + CustomerEntity fromData(CustomerDto record); + + @InheritInverseConfiguration + CustomerDto toData(CustomerEntity entity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java new file mode 100644 index 0000000000..310039363d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -0,0 +1,54 @@ +/* + * 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.kotlin.data; + +import org.junit.jupiter.api.Nested; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlinSources; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +class KotlinDataTest { + + @Nested + @WithClasses({ + CustomerEntity.class, + CustomerMapper.class + }) + @WithKotlinSources("CustomerDto.kt") + class Standard { + + @ProcessorTest + void shouldMapData() { + CustomerEntity customer = CustomerMapper.INSTANCE.fromData( new CustomerDto( + "Kermit", + "kermit@test.com" + ) ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getMail() ).isEqualTo( "kermit@test.com" ); + } + + @ProcessorTest + void shouldMapIntoData() { + CustomerEntity entity = new CustomerEntity(); + entity.setName( "Kermit" ); + entity.setMail( "kermit@test.com" ); + + CustomerDto customer = CustomerMapper.INSTANCE.toData( entity ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getEmail() ).isEqualTo( "kermit@test.com" ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java new file mode 100644 index 0000000000..656dc9bc5f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java @@ -0,0 +1,32 @@ +/* + * 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.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.intellij.lang.annotations.Language; + +/** + * Specifies the kotlin sources to compile during an annotation processor test. + * If given both on the class-level and the method-level for a given test, all the given sources will be compiled. + * + * @author Filip Hrisafov + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface WithKotlinSources { + + /** + * The classes to be compiled for the annotated test class or method. + * + * @return the classes to be compiled for the annotated test class or method + */ + @Language(value = "file-reference", prefix = "", suffix = ".kt") + String[] value(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java index b9f7bb8425..b5a4dc535d 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java @@ -18,6 +18,8 @@ import org.codehaus.plexus.compiler.CompilerMessage; import org.codehaus.plexus.compiler.CompilerResult; +import org.jetbrains.kotlin.cli.common.ExitCode; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedNote; @@ -104,6 +106,25 @@ public static CompilationOutcomeDescriptor forResult(String sourceDir, CompilerR return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors, Collections.emptyList() ); } + public static CompilationOutcomeDescriptor forResult(String sourceDir, ExitCode exitCode, + List messages) { + List notes = new ArrayList<>(); + List diagnosticDescriptors = new ArrayList<>( messages.size() ); + for ( MessageCollectorImpl.Message message : messages ) { + //ignore notes created by the compiler + DiagnosticDescriptor descriptor = DiagnosticDescriptor.forCompilerMessage( sourceDir, message ); + if ( descriptor.getKind() != Kind.NOTE ) { + diagnosticDescriptors.add( descriptor ); + } + else { + notes.add( descriptor.getMessage() ); + } + } + CompilationResult compilationResult = + exitCode == ExitCode.OK ? CompilationResult.SUCCEEDED : CompilationResult.FAILED; + return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors, notes ); + } + public CompilationResult getCompilationResult() { return compilationResult; } @@ -116,6 +137,16 @@ public List getNotes() { return notes; } + public CompilationOutcomeDescriptor merge(CompilationOutcomeDescriptor other) { + CompilationResult compilationResult = + this.compilationResult == CompilationResult.SUCCEEDED ? other.compilationResult : this.compilationResult; + List diagnostics = new ArrayList<>( this.diagnostics ); + diagnostics.addAll( other.diagnostics ); + List notes = new ArrayList<>( this.notes ); + notes.addAll( other.notes ); + return new CompilationOutcomeDescriptor( compilationResult, diagnostics, notes ); + } + @Override public int hashCode() { final int prime = 31; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java index ecfec14a16..68e2cb00fe 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java @@ -15,6 +15,9 @@ import javax.tools.JavaFileObject; import org.codehaus.plexus.compiler.CompilerMessage; +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity; +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; /** @@ -100,6 +103,37 @@ private static Kind toJavaxKind(CompilerMessage.Kind kind) { } } + public static DiagnosticDescriptor forCompilerMessage(String sourceDir, MessageCollectorImpl.Message message) { + CompilerMessageSourceLocation location = message.getLocation(); + String sourceFileName; + Long line; + if ( location != null ) { + sourceFileName = location.getPath(); + line = (long) location.getLine(); + } + else { + sourceFileName = null; + line = null; + } + return new DiagnosticDescriptor( + sourceFileName, + toJavaxKind( message.getSeverity() ), + line, + message.getMessage(), + null + ); + } + + private static Kind toJavaxKind(CompilerMessageSeverity severity) { + return switch ( severity ) { + case ERROR, EXCEPTION -> Kind.ERROR; + case WARNING, FIXED_WARNING -> Kind.WARNING; + case STRONG_WARNING -> Kind.MANDATORY_WARNING; + case INFO, LOGGING -> Kind.NOTE; + case OUTPUT -> Kind.OTHER; + }; + } + private static String getSourceName(String sourceDir, javax.tools.Diagnostic diagnostic) { if ( diagnostic.getSource() == null ) { return null; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java index 9f1b78caa0..8622fbe92c 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java @@ -19,14 +19,17 @@ public class CompilationRequest { private final Map, Class> services; private final List processorOptions; private final Collection testDependencies; + private final Collection kotlinSources; CompilationRequest(Compiler compiler, Set> sourceClasses, Map, Class> services, - List processorOptions, Collection testDependencies) { + List processorOptions, Collection testDependencies, + Collection kotlinSources) { this.compiler = compiler; this.sourceClasses = sourceClasses; this.services = services; this.processorOptions = processorOptions; this.testDependencies = testDependencies; + this.kotlinSources = kotlinSources; } @Override @@ -58,7 +61,8 @@ public boolean equals(Object obj) { && processorOptions.equals( other.processorOptions ) && services.equals( other.services ) && testDependencies.equals( other.testDependencies ) - && sourceClasses.equals( other.sourceClasses ); + && sourceClasses.equals( other.sourceClasses ) + && kotlinSources.equals( other.kotlinSources ); } public Set> getSourceClasses() { @@ -77,6 +81,10 @@ public Collection getTestDependencies() { return testDependencies; } + public Collection getKotlinSources() { + return kotlinSources; + } + public Compiler getCompiler() { return compiler; } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index 95874e4b3d..7ff78753a6 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; import com.puppycrawl.tools.checkstyle.Checker; import com.puppycrawl.tools.checkstyle.ConfigurationLoader; @@ -30,9 +31,15 @@ import com.puppycrawl.tools.checkstyle.PropertiesExpander; import com.puppycrawl.tools.checkstyle.api.AutomaticBean; import org.apache.commons.io.output.NullOutputStream; +import org.jetbrains.kotlin.cli.common.ExitCode; +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler; +import org.jetbrains.kotlin.config.Services; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlinSources; import org.mapstruct.ap.testutil.WithServiceImplementation; import org.mapstruct.ap.testutil.WithTestDependency; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; @@ -455,6 +462,20 @@ protected static Set getSourceFiles(Collection> classes) { return sourceFiles; } + private Collection getKotlinSources(ExtensionContext context) { + Collection kotlinSources = new HashSet<>(); + Method testMethod = context.getRequiredTestMethod(); + Class testClass = context.getRequiredTestClass(); + + findAnnotation( testMethod, WithKotlinSources.class ) + .ifPresent( withKotlinSources -> Collections.addAll( kotlinSources, withKotlinSources.value() ) ); + + findAnnotation( testClass, WithKotlinSources.class ) + .ifPresent( withKotlinSources -> Collections.addAll( kotlinSources, withKotlinSources.value() ) ); + + return kotlinSources; + } + private CompilationOutcomeDescriptor compile(ExtensionContext context) { Method testMethod = context.getRequiredTestMethod(); Class testClass = context.getRequiredTestClass(); @@ -464,7 +485,8 @@ private CompilationOutcomeDescriptor compile(ExtensionContext context) { getTestClasses( testMethod, testClass ), getServices( testMethod, testClass ), getProcessorOptions( testMethod, testClass ), - getAdditionalTestDependencies( testMethod, testClass ) + getAdditionalTestDependencies( testMethod, testClass ), + getKotlinSources( context ) ); ExtensionContext.Store rootStore = context.getRoot().getStore( NAMESPACE ); @@ -484,7 +506,8 @@ private CompilationOutcomeDescriptor compile(ExtensionContext context) { boolean needsAdditionalCompilerClasspath = prepareServices( compilationRequest ); CompilationOutcomeDescriptor resultHolder; - resultHolder = compileWithSpecificCompiler( + resultHolder = compile( + context, compilationRequest, sourceOutputDir, classOutputDir, @@ -503,6 +526,81 @@ protected Object loadAndInstantiate(ClassLoader processorClassloader, Class c } } + protected CompilationOutcomeDescriptor compile( + ExtensionContext context, + CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir, + String additionalCompilerClasspath) { + CompilationOutcomeDescriptor kotlinCompilationOutcome = compileWithKotlin( + context, + compilationRequest, + sourceOutputDir, + classOutputDir + ); + + if ( kotlinCompilationOutcome != null && + kotlinCompilationOutcome.getCompilationResult() == CompilationResult.FAILED ) { + return kotlinCompilationOutcome; + } + + CompilationOutcomeDescriptor javaCompilationOutcome = compileWithSpecificCompiler( + compilationRequest, + sourceOutputDir, + classOutputDir, + additionalCompilerClasspath + ); + return kotlinCompilationOutcome == null ? javaCompilationOutcome : + kotlinCompilationOutcome.merge( javaCompilationOutcome ); + } + + private CompilationOutcomeDescriptor compileWithKotlin( + ExtensionContext context, + CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir + ) { + CompilationOutcomeDescriptor kotlinCompilationOutcome = null; + if ( !compilationRequest.getKotlinSources().isEmpty() ) { + K2JVMCompiler k2JvmCompiler = new K2JVMCompiler(); + MessageCollectorImpl messageCollector = new MessageCollectorImpl(); + K2JVMCompilerArguments k2JvmArguments = new K2JVMCompilerArguments(); + k2JvmArguments.setClasspath( + String.join( + File.pathSeparator, filterBootClassPath( List.of( + "kotlin-stdlib", + "kotlin-reflect" + ) ) + ) + ); + k2JvmArguments.setNoStdlib( true ); + k2JvmArguments.setNoReflect( true ); + String packageName = context.getRequiredTestClass() + .getPackageName(); + String sourcePrefix = + SOURCE_DIR + File.separator + packageName.replace( ".", File.separator ) + File.separator; + k2JvmArguments.setFreeArgs( Arrays.asList( + compilationRequest.getKotlinSources() + .stream() + .map( kotlinSource -> sourcePrefix + kotlinSource ) + .collect( Collectors.joining( File.pathSeparator ) ) + ) ); + k2JvmArguments.setVerbose( true ); + k2JvmArguments.setSuppressWarnings( false ); + k2JvmArguments.setDestination( classOutputDir ); + + ExitCode kotlinExitCode = k2JvmCompiler.exec( messageCollector, Services.EMPTY, k2JvmArguments ); + + kotlinCompilationOutcome = CompilationOutcomeDescriptor.forResult( + sourceOutputDir, + kotlinExitCode, + messageCollector.getMessages() + ); + + } + return kotlinCompilationOutcome; + } + protected abstract CompilationOutcomeDescriptor compileWithSpecificCompiler( CompilationRequest compilationRequest, String sourceOutputDir, diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java index c356347664..4755df0bd1 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java @@ -68,24 +68,27 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe return clHelper.compileInOtherClassloader( compilationRequest, - getTestCompilationClasspath( compilationRequest ), + getTestCompilationClasspath( compilationRequest, classOutputDir ), getSourceFiles( compilationRequest.getSourceClasses() ), SOURCE_DIR, sourceOutputDir, classOutputDir ); } - private static List getTestCompilationClasspath(CompilationRequest request) { + private static List getTestCompilationClasspath(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() ) { + if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { return TEST_COMPILATION_CLASSPATH; } List testCompilationPaths = new ArrayList<>( - TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() ); + TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() + 1 ); testCompilationPaths.addAll( TEST_COMPILATION_CLASSPATH ); testCompilationPaths.addAll( filterBootClassPath( testDependencies ) ); + if ( !request.getKotlinSources().isEmpty() ) { + testCompilationPaths.add( classOutputDir ); + } return testCompilationPaths; } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index 5185bfb7ed..b6d8afd3bf 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -59,7 +59,10 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.getSourceClasses() ) ); try { - fileManager.setLocation( StandardLocation.CLASS_PATH, getCompilerClasspathFiles( compilationRequest ) ); + fileManager.setLocation( + StandardLocation.CLASS_PATH, + getCompilerClasspathFiles( compilationRequest, classOutputDir ) + ); fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( new File( classOutputDir ) ) ); fileManager.setLocation( StandardLocation.SOURCE_OUTPUT, Arrays.asList( new File( sourceOutputDir ) ) ); } @@ -99,20 +102,24 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe diagnostics.getDiagnostics() ); } - private static List getCompilerClasspathFiles(CompilationRequest request) { + private static List getCompilerClasspathFiles(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() ) { + if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { return COMPILER_CLASSPATH_FILES; } List compilerClasspathFiles = new ArrayList<>( - COMPILER_CLASSPATH_FILES.size() + testDependencies.size() ); + COMPILER_CLASSPATH_FILES.size() + testDependencies.size() + 1 ); compilerClasspathFiles.addAll( COMPILER_CLASSPATH_FILES ); for ( String testDependencyPath : filterBootClassPath( testDependencies ) ) { compilerClasspathFiles.add( new File( testDependencyPath ) ); } + if ( !request.getKotlinSources().isEmpty() ) { + compilerClasspathFiles.add( new File( classOutputDir ) ); + } + return compilerClasspathFiles; } From d3006640938014a1d8fa193d735cee934771309b Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 12:30:13 +0100 Subject: [PATCH 323/363] Improve support for Kotlin Data Classes (#3978) * Properly handle single field data classes * Properly handle multi constructor data classes * Properly handle all defaults constructor data classes Fixes #2281, #2577, #3031 --- NEXT_RELEASE_CHANGELOG.md | 6 + distribution/pom.xml | 5 + .../main/asciidoc/chapter-2-set-up.asciidoc | 71 +++- parent/pom.xml | 5 + processor/pom.xml | 5 + .../ap/internal/model/BeanMappingMethod.java | 22 ++ .../ap/internal/model/common/Type.java | 156 ++++++++ .../internal/util/kotlin/KotlinMetadata.java | 21 + .../ap/spi/DefaultAccessorNamingStrategy.java | 68 +++- .../test/kotlin/data/AllDefaultsProperty.kt | 11 + .../data/AllDefaultsPropertyMapper.java | 39 ++ .../ap/test/kotlin/data/KotlinDataTest.java | 370 ++++++++++++++++++ .../kotlin/data/MultiConstructorProperty.kt | 13 + .../data/MultiConstructorPropertyMapper.java | 45 +++ .../data/MultiDefaultConstructorProperty.kt | 18 + ...MultiDefaultConstructorPropertyMapper.java | 45 +++ .../data/MultiSimilarConstructorProperty.kt | 50 +++ ...MultiSimilarConstructorPropertyMapper.java | 57 +++ .../ap/test/kotlin/data/SingleProperty.kt | 11 + .../kotlin/data/SinglePropertyMapper.java | 32 ++ .../org/mapstruct/ap/testutil/WithKotlin.java | 29 ++ .../testutil/WithProcessorDependencies.java | 22 ++ .../ap/testutil/WithProcessorDependency.java | 28 ++ .../testutil/runner/CompilationRequest.java | 8 + .../testutil/runner/CompilingExtension.java | 22 ++ .../runner/EclipseCompilingExtension.java | 18 +- .../runner/JdkCompilingExtension.java | 31 +- 27 files changed, 1186 insertions(+), 22 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java create mode 100644 processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 2d0555cb24..f279193f6a 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,6 +1,12 @@ ### Features * Support for Java 21 Sequenced Collections (#3240) +* Improved support for Kotlin. Requires use of `org.jetbrains.kotlin:kotlin-metadata-jvm`. + - Data Classes (#2281, #2577, #3031) - MapStruct now properly handles: + - Single field data classes + - Proper primary constructor detection + - Data classes with multiple constructors + - Data classes with all default parameters ### Enhancements diff --git a/distribution/pom.xml b/distribution/pom.xml index db654236e4..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -39,6 +39,11 @@ org.mapstruct.tools.gem gem-api + + + org.jetbrains.kotlin + kotlin-metadata-jvm + jakarta.xml.bind diff --git a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc index 58759ff8e0..86ed345630 100644 --- a/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -35,10 +35,10 @@ For Maven based projects add the following to your POM file in order to use MapS org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 - 1.8 - 1.8 + ${java.version} + ${java.version} org.mapstruct @@ -141,10 +141,10 @@ When invoking javac directly, these options are passed to the compiler in the fo org.apache.maven.plugins maven-compiler-plugin - 3.5.1 + 3.14.1 - 1.8 - 1.8 + ${java.version} + ${java.version} org.mapstruct @@ -323,3 +323,62 @@ Some features include: * Code completion in `target` and `source` * Quick Fixes + +[[kotlin-setup]] +=== Kotlin Support + +MapStruct provides support for Kotlin interoperability with Java. + +When using MapStruct with Kotlin, it's recommended to add the `org.jetbrains.kotlin:kotlin-metadata-jvm` library to enable proper introspection of Kotlin metadata: + +.Maven configuration for Kotlin support +==== +[source, xml, linenums] +[subs="verbatim,attributes"] +---- +... + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.1 + + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + org.jetbrains.kotlin + kotlin-metadata-jvm + ${kotlin.version} + + + + + + +---- +==== + +.Gradle configuration for Kotlin support +==== +[source, groovy, linenums] +[subs="verbatim,attributes"] +---- +dependencies { + implementation "org.mapstruct:mapstruct:${mapstructVersion}" + + annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + annotationProcessor "org.jetbrains.kotlin:kotlin-metadata-jvm:${kotlinVersion}" +} +---- +==== + +[NOTE] +==== +The `org.jetbrains.kotlin:kotlin-metadata-jvm` dependency is optional but highly recommended when working with Kotlin and Java. +Without it, MapStruct will still work but may not handle complex Kotlin scenarios optimally. +==== \ No newline at end of file diff --git a/parent/pom.xml b/parent/pom.xml index 5c3a73da88..8c1266ed19 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -159,6 +159,11 @@ pom import + + org.jetbrains.kotlin + kotlin-metadata-jvm + ${kotlin.version} + org.junit junit-bom diff --git a/processor/pom.xml b/processor/pom.xml index 9f24281e33..c51c2bca26 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -42,6 +42,11 @@ mapstruct provided + + org.jetbrains.kotlin + kotlin-metadata-jvm + provided + org.jetbrains.kotlin kotlin-compiler-embeddable 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 39222efc88..619272a175 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 @@ -68,6 +68,7 @@ import org.mapstruct.ap.internal.util.accessor.ElementAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; +import org.mapstruct.ap.internal.util.kotlin.KotlinMetadata; import static org.mapstruct.ap.internal.model.beanmapping.MappingReferences.forSourceMethod; import static org.mapstruct.ap.internal.util.Collections.first; @@ -904,6 +905,27 @@ private ConstructorAccessor getConstructorAccessor(Type type) { return new ConstructorAccessor( parameterBindings, constructorAccessors ); } + KotlinMetadata kotlinMetadata = type.getKotlinMetadata(); + if ( kotlinMetadata != null && kotlinMetadata.isDataClass() ) { + List constructors = ElementFilter.constructorsIn( type.getTypeElement() + .getEnclosedElements() ); + + for ( ExecutableElement constructor : constructors ) { + if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + continue; + } + + // prefer constructor annotated with @Default + if ( hasDefaultAnnotationFromAnyPackage( constructor ) ) { + return getConstructorAccessor( type, constructor ); + } + } + + ExecutableElement primaryConstructor = kotlinMetadata.determinePrimaryConstructor( constructors ); + + return primaryConstructor != null ? getConstructorAccessor( type, primaryConstructor ) : null; + } + List constructors = ElementFilter.constructorsIn( type.getTypeElement() .getEnclosedElements() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 24f23ecea8..313ff38125 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -43,6 +43,13 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.SimpleTypeVisitor8; +import kotlin.Metadata; +import kotlin.metadata.Attributes; +import kotlin.metadata.KmClass; +import kotlin.metadata.KmConstructor; +import kotlin.metadata.jvm.JvmExtensionsKt; +import kotlin.metadata.jvm.JvmMethodSignature; +import kotlin.metadata.jvm.KotlinClassMetadata; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.util.AccessorNamingUtils; import org.mapstruct.ap.internal.util.ElementUtils; @@ -58,6 +65,7 @@ import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; +import org.mapstruct.ap.internal.util.kotlin.KotlinMetadata; import static java.util.Collections.emptyList; import static org.mapstruct.ap.internal.util.Collections.first; @@ -75,6 +83,7 @@ */ public class Type extends ModelElement implements Comparable { private static final Method SEALED_PERMITTED_SUBCLASSES_METHOD; + private static final boolean KOTLIN_METADATA_JVM_PRESENT; static { Method permittedSubclassesMethod; @@ -85,6 +94,16 @@ public class Type extends ModelElement implements Comparable { permittedSubclassesMethod = null; } SEALED_PERMITTED_SUBCLASSES_METHOD = permittedSubclassesMethod; + + boolean kotlinMetadataJvmPresent; + try { + Class.forName( "kotlin.metadata.jvm.KotlinClassMetadata", false, ModelElement.class.getClassLoader() ); + kotlinMetadataJvmPresent = true; + } + catch ( ClassNotFoundException e ) { + kotlinMetadataJvmPresent = false; + } + KOTLIN_METADATA_JVM_PRESENT = kotlinMetadataJvmPresent; } private final TypeUtils typeUtils; @@ -139,6 +158,8 @@ public class Type extends ModelElement implements Comparable { private Type boxedEquivalent = null; private Boolean hasAccessibleConstructor; + private KotlinMetadata kotlinMetadata; + private boolean kotlinMetadataInitialized; private final Filters filters; @@ -1370,6 +1391,23 @@ public boolean hasAccessibleConstructor() { return hasAccessibleConstructor; } + public KotlinMetadata getKotlinMetadata() { + if ( !kotlinMetadataInitialized ) { + kotlinMetadataInitialized = true; + if ( typeElement != null && KOTLIN_METADATA_JVM_PRESENT ) { + Metadata metadataAnnotation = typeElement.getAnnotation( Metadata.class ); + if ( metadataAnnotation != null ) { + KotlinClassMetadata classMetadata = KotlinClassMetadata.readLenient( metadataAnnotation ); + if ( classMetadata instanceof KotlinClassMetadata.Class ) { + kotlinMetadata = new KotlinMetadataImpl( (KotlinClassMetadata.Class) classMetadata ); + } + } + } + } + + return kotlinMetadata; + } + /** * Returns the direct supertypes of a type. The interface types, if any, * will appear last in the list. @@ -1831,4 +1869,122 @@ public List getPermittedSubclasses() { } } + private class KotlinMetadataImpl implements KotlinMetadata { + + private final KotlinClassMetadata.Class kotlinClassMetadata; + + private KotlinMetadataImpl(KotlinClassMetadata.Class kotlinClassMetadata) { + this.kotlinClassMetadata = kotlinClassMetadata; + } + + @Override + public boolean isDataClass() { + return Attributes.isData( kotlinClassMetadata.getKmClass() ); + } + + @Override + public ExecutableElement determinePrimaryConstructor(List constructors) { + if ( constructors.size() == 1 ) { + // If we have one constructor, that this constructor is the primary one + return constructors.get( 0 ); + } + KmClass kmClass = kotlinClassMetadata.getKmClass(); + KmConstructor primaryKmConstructor = null; + for ( KmConstructor constructor : kmClass.getConstructors() ) { + if ( !Attributes.isSecondary( constructor ) ) { + primaryKmConstructor = constructor; + } + + } + + if ( primaryKmConstructor == null ) { + return null; + } + + List sameParametersSizeConstructors = new ArrayList<>(); + for ( ExecutableElement constructor : constructors ) { + if ( constructor.getParameters().size() == primaryKmConstructor.getValueParameters().size() ) { + sameParametersSizeConstructors.add( constructor ); + } + } + + if ( sameParametersSizeConstructors.size() == 1 ) { + return sameParametersSizeConstructors.get( 0 ); + } + + JvmMethodSignature signature = JvmExtensionsKt.getSignature( primaryKmConstructor ); + if ( signature == null ) { + return null; + } + + String signatureDescriptor = signature.getDescriptor(); + for ( ExecutableElement constructor : constructors ) { + String constructorDescriptor = buildJvmConstructorDescriptor( constructor ); + if ( signatureDescriptor.equals( constructorDescriptor ) ) { + return constructor; + } + } + + return null; + } + + private String buildJvmConstructorDescriptor(ExecutableElement constructor) { + StringBuilder signature = new StringBuilder( "(" ); + + for ( VariableElement param : constructor.getParameters() ) { + signature.append( getJvmTypeDescriptor( param.asType() ) ); + } + + signature.append( ")V" ); + return signature.toString(); + } + + private String getJvmTypeDescriptor(TypeMirror type) { + return type.accept( + new SimpleTypeVisitor8() { + @Override + public String visitPrimitive(PrimitiveType t, Void p) { + switch ( t.getKind() ) { + case BOOLEAN: + return "Z"; + case BYTE: + return "B"; + case SHORT: + return "S"; + case INT: + return "I"; + case LONG: + return "J"; + case CHAR: + return "C"; + case FLOAT: + return "F"; + case DOUBLE: + return "D"; + default: + return ""; + } + } + + @Override + public String visitDeclared(DeclaredType t, Void p) { + TypeElement element = (TypeElement) t.asElement(); + String binaryName = elementUtils.getBinaryName( element ).toString(); + return "L" + binaryName.replace( '.', '/' ) + ";"; + } + + @Override + public String visitArray(ArrayType t, Void p) { + return "[" + getJvmTypeDescriptor( t.getComponentType() ); + } + + @Override + protected String defaultAction(TypeMirror e, Void p) { + return ""; + } + }, null + ); + } + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java new file mode 100644 index 0000000000..d58ab75c79 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java @@ -0,0 +1,21 @@ +/* + * 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.internal.util.kotlin; + +import java.util.List; +import javax.lang.model.element.ExecutableElement; + +/** + * Information about a type in case it's a Kotlin type. + * + * @author Filip Hrisafov + */ +public interface KotlinMetadata { + + boolean isDataClass(); + + ExecutableElement determinePrimaryConstructor(List constructors); +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index 82ecb4e17b..511504c937 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -7,6 +7,7 @@ import java.util.regex.Pattern; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; @@ -17,6 +18,9 @@ import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; +import kotlin.Metadata; +import kotlin.metadata.Attributes; +import kotlin.metadata.jvm.KotlinClassMetadata; import org.mapstruct.ap.spi.util.IntrospectorUtils; /** @@ -28,6 +32,24 @@ public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" ); + private static final boolean KOTLIN_METADATA_JVM_PRESENT; + + static { + boolean kotlinMetadataJvmPresent; + try { + Class.forName( + "kotlin.metadata.jvm.KotlinClassMetadata", + false, + AccessorNamingStrategy.class.getClassLoader() + ); + kotlinMetadataJvmPresent = true; + } + catch ( ClassNotFoundException e ) { + kotlinMetadataJvmPresent = false; + } + KOTLIN_METADATA_JVM_PRESENT = kotlinMetadataJvmPresent; + } + protected Elements elementUtils; protected Types typeUtils; @@ -102,10 +124,48 @@ public boolean isSetterMethod(ExecutableElement method) { } protected boolean isFluentSetter(ExecutableElement method) { - return method.getParameters().size() == 1 && - !JAVA_JAVAX_PACKAGE.matcher( method.getEnclosingElement().asType().toString() ).matches() && - !isAdderWithUpperCase4thCharacter( method ) && - typeUtils.isAssignable( method.getReturnType(), method.getEnclosingElement().asType() ); + if ( method.getParameters().size() != 1 ) { + return false; + } + Element methodOwnerElement = method.getEnclosingElement(); + if ( !canMethodOwnerBeUsedInFluentSetter( methodOwnerElement ) ) { + return false; + } + return !isAdderWithUpperCase4thCharacter( method ) && + typeUtils.isAssignable( method.getReturnType(), methodOwnerElement.asType() ); + } + + private boolean canMethodOwnerBeUsedInFluentSetter(Element methodOwnerElement) { + if ( !( methodOwnerElement instanceof TypeElement ) ) { + return false; + } + + TypeElement typeElement = (TypeElement) methodOwnerElement; + if ( JAVA_JAVAX_PACKAGE.matcher( typeElement.getQualifiedName().toString() ).matches() ) { + return false; + } + if ( isKotlinDataClass( typeElement ) ) { + return false; + } + + return true; + } + + private boolean isKotlinDataClass(TypeElement typeElement) { + if ( !KOTLIN_METADATA_JVM_PRESENT ) { + return false; + } + + Metadata kotlinMetadata = typeElement.getAnnotation( Metadata.class ); + if ( kotlinMetadata == null ) { + return false; + } + KotlinClassMetadata classMetadata = KotlinClassMetadata.readLenient( kotlinMetadata ); + if ( classMetadata instanceof KotlinClassMetadata.Class ) { + return Attributes.isData( ( (KotlinClassMetadata.Class) classMetadata ).getKmClass() ); + } + + return false; } /** diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt new file mode 100644 index 0000000000..e452102f6e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt @@ -0,0 +1,11 @@ +/* + * 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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class AllDefaultsProperty(val firstName: String? = null, val lastName: String? = null) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java new file mode 100644 index 0000000000..1f8aab4624 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java @@ -0,0 +1,39 @@ +/* + * 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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface AllDefaultsPropertyMapper { + + AllDefaultsPropertyMapper INSTANCE = Mappers.getMapper( AllDefaultsPropertyMapper.class ); + + AllDefaultsProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + + public Source(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java index 310039363d..1daa19c231 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -8,7 +8,11 @@ import org.junit.jupiter.api.Nested; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlin; import org.mapstruct.ap.testutil.WithKotlinSources; +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 static org.assertj.core.api.Assertions.assertThat; @@ -51,4 +55,370 @@ void shouldMapIntoData() { } } + + @Nested + @WithClasses({ + SinglePropertyMapper.class, + }) + @WithKotlinSources("SingleProperty.kt") + class SingleData { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + SingleProperty property = SinglePropertyMapper.INSTANCE.map( new SinglePropertyMapper.Source( "test" ) ); + assertThat( property ).isNotNull(); + assertThat( property.getValue() ).isEqualTo( "test" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = SinglePropertyMapper.class, + line = 19, + message = "Unmapped target property: \"copy\"." + ) + } + ) + void shouldCompileWithWarnings() { + + SingleProperty property = SinglePropertyMapper.INSTANCE.map( new SinglePropertyMapper.Source( "test" ) ); + assertThat( property ).isNotNull(); + assertThat( property.getValue() ).isEqualTo( "test" ); + } + } + + @Nested + @WithClasses({ + AllDefaultsPropertyMapper.class, + }) + @WithKotlinSources("AllDefaultsProperty.kt") + class AllDefaults { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + AllDefaultsProperty property = AllDefaultsPropertyMapper.INSTANCE.map( new AllDefaultsPropertyMapper.Source( + "Kermit", + "the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = AllDefaultsPropertyMapper.class, + line = 19, + message = "No target property found for target \"AllDefaultsProperty\"." + ) + } + ) + void shouldCompileWithWarnings() { + + AllDefaultsProperty property = AllDefaultsPropertyMapper.INSTANCE.map( new AllDefaultsPropertyMapper.Source( + "Kermit", + "the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + } + } + + @Nested + @WithClasses({ + MultiConstructorPropertyMapper.class, + }) + @WithKotlinSources("MultiConstructorProperty.kt") + class MultiConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + MultiConstructorProperty property = MultiConstructorPropertyMapper.INSTANCE + .map( new MultiConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isEqualTo( "Kermit the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiConstructorPropertyMapper.class, + line = 19, + messageRegExp = "Ambiguous constructors found for creating .*MultiConstructorProperty: " + + "MultiConstructorProperty\\(java.lang.String, java.lang.String.*\\), " + + "MultiConstructorProperty\\(java.lang.String, java.lang.String.*\\)\\. " + + "Either declare parameterless constructor or annotate the default constructor with an " + + "annotation named @Default\\." + ) + } + ) + void shouldFailToCompile() { + + } + } + + @Nested + @WithClasses({ + MultiSimilarConstructorPropertyMapper.class, + }) + @WithKotlinSources("MultiSimilarConstructorProperty.kt") + class MultiSimilarConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + PrimaryString primaryString = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + "Kermit the Frog" + ); + assertThat( primaryString ).isNotNull(); + assertThat( primaryString.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryString.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryString.getDisplayName() ).isEqualTo( "Kermit the Frog" ); + + PrimaryInt primaryInt = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42 + ); + assertThat( primaryInt ).isNotNull(); + assertThat( primaryInt.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryInt.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryInt.getAge() ).isEqualTo( 42 ); + + PrimaryLong primaryLong = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42L + ); + assertThat( primaryLong ).isNotNull(); + assertThat( primaryLong.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryLong.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryLong.getAge() ).isEqualTo( 42 ); + + PrimaryBoolean primaryBoolean = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + true + ); + assertThat( primaryBoolean ).isNotNull(); + assertThat( primaryBoolean.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryBoolean.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryBoolean.getActive() ).isTrue(); + + PrimaryByte primaryByte = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + (byte) 4 + ); + assertThat( primaryByte ).isNotNull(); + assertThat( primaryByte.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryByte.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryByte.getB() ).isEqualTo( (byte) 4 ); + + PrimaryShort primaryShort = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + (short) 4 + ); + assertThat( primaryShort ).isNotNull(); + assertThat( primaryShort.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryShort.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryShort.getAge() ).isEqualTo( (short) 4 ); + + PrimaryChar primaryChar = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 't' + ); + assertThat( primaryChar ).isNotNull(); + assertThat( primaryChar.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryChar.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryChar.getC() ).isEqualTo( 't' ); + + PrimaryFloat primaryFloat = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42.2f + ); + assertThat( primaryFloat ).isNotNull(); + assertThat( primaryFloat.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryFloat.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryFloat.getPrice() ).isEqualTo( 42.2f ); + + PrimaryDouble primaryDouble = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42.2 + ); + assertThat( primaryDouble ).isNotNull(); + assertThat( primaryDouble.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryDouble.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryDouble.getPrice() ).isEqualTo( 42.2d ); + + PrimaryArray primaryArray = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + new String[] { "Kermit", "the Frog" } + ); + assertThat( primaryArray ).isNotNull(); + assertThat( primaryArray.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryArray.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryArray.getElements() ).containsExactly( "Kermit", "the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 19, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryString" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 21, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryInt" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 23, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryLong" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 25, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryBoolean" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 27, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryByte" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 29, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryShort" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 31, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryChar" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 33, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryFloat" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 35, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryDouble" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 37, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryArray" + ) + + + } + ) + void shouldFailToCompile() { + + } + } + + @Nested + @WithClasses({ + MultiDefaultConstructorPropertyMapper.class, + }) + @WithKotlinSources("MultiDefaultConstructorProperty.kt") + class MultiDefaultConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + MultiDefaultConstructorProperty property = MultiDefaultConstructorPropertyMapper.INSTANCE + .map( new MultiDefaultConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isNull(); + } + + @ProcessorTest + void shouldCompileWithoutKotlin() { + MultiDefaultConstructorProperty property = MultiDefaultConstructorPropertyMapper.INSTANCE + .map( new MultiDefaultConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isNull(); + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt new file mode 100644 index 0000000000..46cb8034e9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt @@ -0,0 +1,13 @@ +/* + * 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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class MultiConstructorProperty(var firstName: String?, var lastName: String?, var displayName: String?) { + constructor(firstName: String?, lastName: String?) : this(firstName, lastName, null) +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java new file mode 100644 index 0000000000..3186226a57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java @@ -0,0 +1,45 @@ +/* + * 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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiConstructorPropertyMapper { + + MultiConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiConstructorPropertyMapper.class ); + + MultiConstructorProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + private final String displayName; + + public Source(String firstName, String lastName, String displayName) { + this.firstName = firstName; + this.lastName = lastName; + this.displayName = displayName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getDisplayName() { + return displayName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt new file mode 100644 index 0000000000..712cf1151b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt @@ -0,0 +1,18 @@ +/* + * 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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class MultiDefaultConstructorProperty(val firstName: String?, val lastName: String?, val displayName: String?) { + @Default + constructor(firstName: String?, lastName: String?) : this(firstName, lastName, null) +} + +@Target(AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.BINARY) +annotation class Default diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java new file mode 100644 index 0000000000..da04c01fb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java @@ -0,0 +1,45 @@ +/* + * 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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiDefaultConstructorPropertyMapper { + + MultiDefaultConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiDefaultConstructorPropertyMapper.class ); + + MultiDefaultConstructorProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + private final String displayName; + + public Source(String firstName, String lastName, String displayName) { + this.firstName = firstName; + this.lastName = lastName; + this.displayName = displayName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getDisplayName() { + return displayName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt new file mode 100644 index 0000000000..e122169075 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt @@ -0,0 +1,50 @@ +/* + * 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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class PrimaryString(var firstName: String?, var lastName: String?, var displayName: String?) { + constructor(firstName: String?, lastName: String?, age: Int) : this(firstName, lastName, null as String?) +} + +data class PrimaryInt(var firstName: String?, var lastName: String?, var age: Int) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, -1) +} + +data class PrimaryLong(var firstName: String?, var lastName: String?, var age: Long) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, -1) +} + +data class PrimaryBoolean(var firstName: String?, var lastName: String?, var active: Boolean) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, false) +} + +data class PrimaryByte(var firstName: String?, var lastName: String?, var b: Byte) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0) +} + +data class PrimaryShort(var firstName: String?, var lastName: String?, var age: Short) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0) +} + +data class PrimaryChar(var firstName: String?, var lastName: String?, var c: Char) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 'a') +} + +data class PrimaryFloat(var firstName: String?, var lastName: String?, var price: Float) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0.0f) +} + +data class PrimaryDouble(var firstName: String?, var lastName: String?, var price: Double) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0.0) +} + +@Suppress("ArrayInDataClass") +data class PrimaryArray(var firstName: String?, var lastName: String?, var elements: Array) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, emptyArray()) +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java new file mode 100644 index 0000000000..86021cd6d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java @@ -0,0 +1,57 @@ +/* + * 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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiSimilarConstructorPropertyMapper { + + MultiSimilarConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiSimilarConstructorPropertyMapper.class ); + + PrimaryString map(Source source, String displayName); + + PrimaryInt map(Source source, int age); + + PrimaryLong map(Source source, long age); + + PrimaryBoolean map(Source source, boolean active); + + PrimaryByte map(Source source, byte b); + + PrimaryShort map(Source source, short age); + + PrimaryChar map(Source source, char c); + + PrimaryFloat map(Source source, float price); + + PrimaryDouble map(Source source, double price); + + PrimaryArray map(Source source, String[] elements); + + class Source { + + private final String firstName; + private final String lastName; + + public Source(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt new file mode 100644 index 0000000000..1bb63c410d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt @@ -0,0 +1,11 @@ +/* + * 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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class SingleProperty(val value: String?) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java new file mode 100644 index 0000000000..d3d70f261c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java @@ -0,0 +1,32 @@ +/* + * 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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SinglePropertyMapper { + + SinglePropertyMapper INSTANCE = Mappers.getMapper( SinglePropertyMapper.class ); + + SingleProperty map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java new file mode 100644 index 0000000000..f4bd2c5970 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java @@ -0,0 +1,29 @@ +/* + * 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.testutil; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithProcessorDependency({ + "kotlin-stdlib", + "kotlin-metadata-jvm", +}) +@WithTestDependency("kotlin-stdlib") +public @interface WithKotlin { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java new file mode 100644 index 0000000000..cccb30b6b4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java @@ -0,0 +1,22 @@ +/* + * 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.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Filip Hrisafov + * @see WithProcessorDependency + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +public @interface WithProcessorDependencies { + + WithProcessorDependency[] value(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java new file mode 100644 index 0000000000..8dbf8c8acd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java @@ -0,0 +1,28 @@ +/* + * 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.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies the group id of the additional dependencies that should be added to the processor path + * + * @author Filip Hrisafov + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Repeatable(WithProcessorDependencies.class) +public @interface WithProcessorDependency { + /** + * @return The group ids of the additional dependencies for the processor path + */ + String[] value(); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java index 8622fbe92c..c9718c6c21 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java @@ -19,16 +19,19 @@ public class CompilationRequest { private final Map, Class> services; private final List processorOptions; private final Collection testDependencies; + private final Collection processorDependencies; private final Collection kotlinSources; CompilationRequest(Compiler compiler, Set> sourceClasses, Map, Class> services, List processorOptions, Collection testDependencies, + Collection processorDependencies, Collection kotlinSources) { this.compiler = compiler; this.sourceClasses = sourceClasses; this.services = services; this.processorOptions = processorOptions; this.testDependencies = testDependencies; + this.processorDependencies = processorDependencies; this.kotlinSources = kotlinSources; } @@ -61,6 +64,7 @@ public boolean equals(Object obj) { && processorOptions.equals( other.processorOptions ) && services.equals( other.services ) && testDependencies.equals( other.testDependencies ) + && processorDependencies.equals( other.processorDependencies ) && sourceClasses.equals( other.sourceClasses ) && kotlinSources.equals( other.kotlinSources ); } @@ -81,6 +85,10 @@ public Collection getTestDependencies() { return testDependencies; } + public Collection getProcessorDependencies() { + return processorDependencies; + } + public Collection getKotlinSources() { return kotlinSources; } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index 7ff78753a6..d8ed3b953d 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -40,6 +40,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithKotlinSources; +import org.mapstruct.ap.testutil.WithProcessorDependency; import org.mapstruct.ap.testutil.WithServiceImplementation; import org.mapstruct.ap.testutil.WithTestDependency; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; @@ -152,6 +153,15 @@ protected static List filterBootClassPath(Collection whitelist) return classpath; } + protected static Collection getProcessorClasspathDependencies(CompilationRequest request, + String additionalCompilerClasspath) { + List processClassPaths = filterBootClassPath( request.getProcessorDependencies() ); + if ( additionalCompilerClasspath != null ) { + processClassPaths.add( additionalCompilerClasspath ); + } + return processClassPaths; + } + private static boolean isWhitelisted(String path, Collection whitelist) { return whitelist.stream().anyMatch( path::contains ); } @@ -392,6 +402,17 @@ private Collection getAdditionalTestDependencies(Method testMethod, Clas return testDependencies; } + private Collection getAdditionalProcessorDependencies(Method testMethod, Class testClass) { + Collection processorDependencies = new HashSet<>(); + findRepeatableAnnotations( testMethod, WithProcessorDependency.class ) + .forEach( annotation -> Collections.addAll( processorDependencies, annotation.value() ) ); + + findRepeatableAnnotations( testClass, WithProcessorDependency.class ) + .forEach( annotation -> Collections.addAll( processorDependencies, annotation.value() ) ); + + return processorDependencies; + } + private void addServices(Map, Class> services, List withImplementations) { for ( WithServiceImplementation withImplementation : withImplementations ) { addService( services, withImplementation ); @@ -486,6 +507,7 @@ private CompilationOutcomeDescriptor compile(ExtensionContext context) { getServices( testMethod, testClass ), getProcessorOptions( testMethod, testClass ), getAdditionalTestDependencies( testMethod, testClass ), + getAdditionalProcessorDependencies( testMethod, testClass ), getKotlinSources( context ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java index 4755df0bd1..6b13036be3 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java @@ -47,8 +47,12 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe String sourceOutputDir, String classOutputDir, String additionalCompilerClasspath) { + Collection processorClassPaths = getProcessorClasspathDependencies( + compilationRequest, + additionalCompilerClasspath + ); ClassLoader compilerClassloader; - if ( additionalCompilerClasspath == null ) { + if ( processorClassPaths.isEmpty() ) { compilerClassloader = DEFAULT_ECLIPSE_COMPILER_CLASSLOADER; } else { @@ -59,7 +63,7 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe compilerClassloader = loader.withPaths( ECLIPSE_COMPILER_CLASSPATH ) .withPaths( PROCESSOR_CLASSPATH ) .withOriginOf( ClassLoaderExecutor.class ) - .withPath( additionalCompilerClasspath ) + .withPaths( processorClassPaths ) .withOriginsOf( compilationRequest.getServices().values() ); } @@ -77,16 +81,19 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe private static List getTestCompilationClasspath(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { + Collection processorDependencies = request.getProcessorDependencies(); + Collection kotlinSources = request.getKotlinSources(); + if ( testDependencies.isEmpty() && processorDependencies.isEmpty() && kotlinSources.isEmpty() ) { return TEST_COMPILATION_CLASSPATH; } List testCompilationPaths = new ArrayList<>( - TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() + 1 ); + TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() + processorDependencies.size() + 1 ); testCompilationPaths.addAll( TEST_COMPILATION_CLASSPATH ); testCompilationPaths.addAll( filterBootClassPath( testDependencies ) ); - if ( !request.getKotlinSources().isEmpty() ) { + testCompilationPaths.addAll( filterBootClassPath( processorDependencies ) ); + if ( !kotlinSources.isEmpty() ) { testCompilationPaths.add( classOutputDir ); } return testCompilationPaths; @@ -96,6 +103,7 @@ private static FilteringParentClassLoader newFilteringClassLoaderForEclipse() { return new FilteringParentClassLoader( // reload eclipse compiler classes "org.eclipse.", + "kotlin.", // reload mapstruct processor classes "org.mapstruct.ap.internal.", "org.mapstruct.ap.spi.", diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index b6d8afd3bf..115618382c 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -39,7 +39,7 @@ class JdkCompilingExtension extends CompilingExtension { private static final List COMPILER_CLASSPATH_FILES = asFiles( TEST_COMPILATION_CLASSPATH ); private static final ClassLoader DEFAULT_PROCESSOR_CLASSLOADER = - new ModifiableURLClassLoader( new FilteringParentClassLoader( "org.mapstruct." ) ) + new ModifiableURLClassLoader( newFilteringClassLoaderForJdk() ) .withPaths( PROCESSOR_CLASSPATH ); JdkCompilingExtension() { @@ -70,15 +70,21 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe throw new RuntimeException( e ); } + Collection processorClassPaths = getProcessorClasspathDependencies( + compilationRequest, + additionalCompilerClasspath + ); ClassLoader processorClassloader; - if ( additionalCompilerClasspath == null ) { + if ( processorClassPaths.isEmpty() ) { processorClassloader = DEFAULT_PROCESSOR_CLASSLOADER; } else { processorClassloader = new ModifiableURLClassLoader( - new FilteringParentClassLoader( "org.mapstruct." ) ) + newFilteringClassLoaderForJdk() + .hidingClasses( compilationRequest.getServices().values() ) + ) .withPaths( PROCESSOR_CLASSPATH ) - .withPath( additionalCompilerClasspath ) + .withPaths( processorClassPaths ) .withOriginsOf( compilationRequest.getServices().values() ); } @@ -104,19 +110,21 @@ protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRe private static List getCompilerClasspathFiles(CompilationRequest request, String classOutputDir) { Collection testDependencies = request.getTestDependencies(); - if ( testDependencies.isEmpty() && request.getKotlinSources().isEmpty() ) { + Collection processorDependencies = request.getProcessorDependencies(); + Collection kotlinSources = request.getKotlinSources(); + if ( testDependencies.isEmpty() && processorDependencies.isEmpty() && kotlinSources.isEmpty() ) { return COMPILER_CLASSPATH_FILES; } List compilerClasspathFiles = new ArrayList<>( - COMPILER_CLASSPATH_FILES.size() + testDependencies.size() + 1 ); + COMPILER_CLASSPATH_FILES.size() + testDependencies.size() + processorDependencies.size() + 1 ); compilerClasspathFiles.addAll( COMPILER_CLASSPATH_FILES ); for ( String testDependencyPath : filterBootClassPath( testDependencies ) ) { compilerClasspathFiles.add( new File( testDependencyPath ) ); } - if ( !request.getKotlinSources().isEmpty() ) { + if ( !kotlinSources.isEmpty() ) { compilerClasspathFiles.add( new File( classOutputDir ) ); } @@ -161,4 +169,13 @@ protected List filterExpectedDiagnostics(List Date: Thu, 29 Jan 2026 15:38:08 +0100 Subject: [PATCH 324/363] Add test case with Kotlin unsigned type (#3979) --- .../ap/internal/model/BeanMappingMethod.java | 5 +- .../mapstruct/ap/test/kotlin/data/Default.kt | 10 ++++ .../ap/test/kotlin/data/KotlinDataTest.java | 50 ++++++++++++++++++- .../data/MultiDefaultConstructorProperty.kt | 3 -- .../ap/test/kotlin/data/UnsignedProperty.kt | 17 +++++++ .../kotlin/data/UnsignedPropertyMapper.java | 35 +++++++++++++ .../testutil/runner/CompilingExtension.java | 11 ++-- 7 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java 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 619272a175..086773dbe9 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 @@ -910,8 +910,11 @@ private ConstructorAccessor getConstructorAccessor(Type type) { List constructors = ElementFilter.constructorsIn( type.getTypeElement() .getEnclosedElements() ); - for ( ExecutableElement constructor : constructors ) { + Iterator constructorIterator = constructors.iterator(); + while ( constructorIterator.hasNext() ) { + ExecutableElement constructor = constructorIterator.next(); if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + constructorIterator.remove(); continue; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt new file mode 100644 index 0000000000..58ebbd128d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt @@ -0,0 +1,10 @@ +/* + * 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.kotlin.data + +@Target(AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.BINARY) +annotation class Default diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java index 1daa19c231..f55cb42535 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -10,6 +10,7 @@ import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithKotlin; import org.mapstruct.ap.testutil.WithKotlinSources; +import org.mapstruct.ap.testutil.WithTestDependency; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; @@ -388,7 +389,10 @@ void shouldFailToCompile() { @WithClasses({ MultiDefaultConstructorPropertyMapper.class, }) - @WithKotlinSources("MultiDefaultConstructorProperty.kt") + @WithKotlinSources({ + "MultiDefaultConstructorProperty.kt", + "Default.kt" + }) class MultiDefaultConstructor { @ProcessorTest @@ -421,4 +425,48 @@ void shouldCompileWithoutKotlin() { assertThat( property.getDisplayName() ).isNull(); } } + + @Nested + @WithClasses({ + UnsignedPropertyMapper.class, + }) + @WithKotlinSources("UnsignedProperty.kt") + class Unsigned { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + UnsignedProperty property = UnsignedPropertyMapper.INSTANCE.map( new UnsignedPropertyMapper.Source( 10 ) ); + assertThat( property ).isNotNull(); + assertThat( property.getAge() ).isEqualTo( 10 ); + + UnsignedPropertyMapper.Source source = UnsignedPropertyMapper.INSTANCE.map( new UnsignedProperty( 20 ) ); + assertThat( source ).isNotNull(); + assertThat( source.getAge() ).isEqualTo( 20 ); + } + + @ProcessorTest + @WithTestDependency( "kotlin-stdlib" ) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = UnsignedPropertyMapper.class, + line = 19, + messageRegExp = "Unmapped target property: \"copy-.*\"\\." + ) + } + ) + void shouldCompileWithoutKotlinJvmMetadata() { + UnsignedProperty property = UnsignedPropertyMapper.INSTANCE.map( new UnsignedPropertyMapper.Source( 10 ) ); + assertThat( property ).isNotNull(); + assertThat( property.getAge() ).isEqualTo( 10 ); + + UnsignedPropertyMapper.Source source = UnsignedPropertyMapper.INSTANCE.map( new UnsignedProperty( 20 ) ); + assertThat( source ).isNotNull(); + assertThat( source.getAge() ).isEqualTo( 20 ); + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt index 712cf1151b..9f9af16053 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt @@ -13,6 +13,3 @@ data class MultiDefaultConstructorProperty(val firstName: String?, val lastName: constructor(firstName: String?, lastName: String?) : this(firstName, lastName, null) } -@Target(AnnotationTarget.CONSTRUCTOR) -@Retention(AnnotationRetention.BINARY) -annotation class Default diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt new file mode 100644 index 0000000000..c52426b3af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt @@ -0,0 +1,17 @@ +/* + * 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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class UnsignedProperty(val age: UInt?) { + // Java-friendly secondary constructor + constructor(age: Int?) : this(age?.toUInt()) + + @JvmName("getAge") + fun getAgeAsLong(): Long? = age?.toLong() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java new file mode 100644 index 0000000000..4f055c7b41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java @@ -0,0 +1,35 @@ +/* + * 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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface UnsignedPropertyMapper { + + UnsignedPropertyMapper INSTANCE = Mappers.getMapper( UnsignedPropertyMapper.class ); + + UnsignedProperty map(Source source); + + Source map(UnsignedProperty property); + + class Source { + + private final int age; + + public Source(int age) { + this.age = age; + } + + public int getAge() { + return age; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index d8ed3b953d..08056ca697 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -601,12 +601,11 @@ File.pathSeparator, filterBootClassPath( List.of( .getPackageName(); String sourcePrefix = SOURCE_DIR + File.separator + packageName.replace( ".", File.separator ) + File.separator; - k2JvmArguments.setFreeArgs( Arrays.asList( - compilationRequest.getKotlinSources() - .stream() - .map( kotlinSource -> sourcePrefix + kotlinSource ) - .collect( Collectors.joining( File.pathSeparator ) ) - ) ); + k2JvmArguments.setFreeArgs( compilationRequest.getKotlinSources() + .stream() + .map( kotlinSource -> sourcePrefix + kotlinSource ) + .collect( Collectors.toList() ) + ); k2JvmArguments.setVerbose( true ); k2JvmArguments.setSuppressWarnings( false ); k2JvmArguments.setDestination( classOutputDir ); From 3f20144b2f0529e048eee06cdcd93015686544c8 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 15:54:38 +0100 Subject: [PATCH 325/363] Add test case for Kotlin property named default (#3980) --- .../ap/test/kotlin/data/DefaultProperty.kt | 16 ++++++ .../kotlin/data/DefaultPropertyMapper.java | 22 +++++++++ .../ap/test/kotlin/data/KotlinDataTest.java | 49 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt new file mode 100644 index 0000000000..f5ff27d3d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt @@ -0,0 +1,16 @@ +/* + * 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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class DefaultPropertySource(val default: Boolean, val identifier: String?) + +class DefaultPropertyTarget( + var default: Boolean, + var identifier: String? +) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java new file mode 100644 index 0000000000..ce08e26e7e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java @@ -0,0 +1,22 @@ +/* + * 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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface DefaultPropertyMapper { + + DefaultPropertyMapper INSTANCE = Mappers.getMapper( DefaultPropertyMapper.class ); + + DefaultPropertyTarget map(DefaultPropertySource source); + + DefaultPropertySource map(DefaultPropertyTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java index f55cb42535..94d0d90d75 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -469,4 +469,53 @@ void shouldCompileWithoutKotlinJvmMetadata() { assertThat( source.getAge() ).isEqualTo( 20 ); } } + + @Nested + @WithClasses({ + DefaultPropertyMapper.class, + }) + @WithKotlinSources("DefaultProperty.kt") + class Default { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + DefaultPropertyTarget target = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertySource( + true, + "test" + ) ); + assertThat( target ).isNotNull(); + assertThat( target.getDefault() ).isTrue(); + assertThat( target.getIdentifier() ).isEqualTo( "test" ); + + DefaultPropertySource source = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertyTarget( + false, + "private" + ) ); + assertThat( source ).isNotNull(); + assertThat( source.getDefault() ).isFalse(); + assertThat( source.getIdentifier() ).isEqualTo( "private" ); + + } + + @ProcessorTest + void shouldCompileWithoutKotlin() { + DefaultPropertyTarget target = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertySource( + true, + "test" + ) ); + assertThat( target ).isNotNull(); + assertThat( target.getDefault() ).isTrue(); + assertThat( target.getIdentifier() ).isEqualTo( "test" ); + + DefaultPropertySource source = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertyTarget( + false, + "private" + ) ); + assertThat( source ).isNotNull(); + assertThat( source.getDefault() ).isFalse(); + assertThat( source.getIdentifier() ).isEqualTo( "private" ); + } + } } From ec1e5f9317d399b3af420191bc5d6d025726073d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 16:07:57 +0100 Subject: [PATCH 326/363] Add tests for sealed classes in processor module --- ...eatureCompilationExclusionCliEnhancer.java | 6 ++ .../subclassmapping/jdk17/sealed/Motor.java | 42 ++++++++++ .../jdk17/sealed/MotorDto.java | 42 ++++++++++ .../jdk17/sealed/SealedSubclassMapper.java | 31 +++++++ .../jdk17/sealed/SealedSubclassTest.java | 80 +++++++++++++++++++ .../subclassmapping/jdk17/sealed/Vehicle.java | 51 ++++++++++++ .../jdk17/sealed/VehicleCollection.java | 17 ++++ .../jdk17/sealed/VehicleCollectionDto.java | 17 ++++ .../jdk17/sealed/VehicleDto.java | 51 ++++++++++++ 9 files changed, 337 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index 0cc1a87818..7d397c9499 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -35,6 +35,8 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/annotatewith/deprecated/jdk11/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" ); if ( processorType == ProcessorTest.ProcessorType.ECLIPSE_JDT ) { additionalExcludes.add( "org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java" ); @@ -43,10 +45,14 @@ public Collection getAdditionalCommandLineArguments(ProcessorTest.Proces case JAVA_9: // TODO find out why this fails: additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); break; case JAVA_11: additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); break; case JAVA_17: diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.java new file mode 100644 index 0000000000..3498bb09e0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Motor.java @@ -0,0 +1,42 @@ +/* + * 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.subclassmapping.jdk17.sealed; + +public abstract sealed class Motor extends Vehicle { + private int cc; + + public int getCc() { + return cc; + } + + public void setCc(int cc) { + this.cc = cc; + } + + public static final class Davidson extends Motor { + private int numberOfExhausts; + + public int getNumberOfExhausts() { + return numberOfExhausts; + } + + public void setNumberOfExhausts(int numberOfExhausts) { + this.numberOfExhausts = numberOfExhausts; + } + } + + public static final class Harley extends Motor { + private int engineDb; + + public int getEngineDb() { + return engineDb; + } + + public void setEngineDb(int engineDb) { + this.engineDb = engineDb; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.java new file mode 100644 index 0000000000..a7ec0be4a4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/MotorDto.java @@ -0,0 +1,42 @@ +/* + * 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.subclassmapping.jdk17.sealed; + +public abstract sealed class MotorDto extends VehicleDto { + private int cc; + + public int getCc() { + return cc; + } + + public void setCc(int cc) { + this.cc = cc; + } + + public static final class DavidsonDto extends MotorDto { + private int numberOfExhausts; + + public int getNumberOfExhausts() { + return numberOfExhausts; + } + + public void setNumberOfExhausts(int numberOfExhausts) { + this.numberOfExhausts = numberOfExhausts; + } + } + + public static final class HarleyDto extends MotorDto { + private int engineDb; + + public int getEngineDb() { + return engineDb; + } + + public void setEngineDb(int engineDb) { + this.engineDb = engineDb; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java new file mode 100644 index 0000000000..806eec78a4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassMapper.java @@ -0,0 +1,31 @@ +/* + * 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.subclassmapping.jdk17.sealed; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SealedSubclassMapper { + SealedSubclassMapper INSTANCE = Mappers.getMapper( SealedSubclassMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + @SubclassMapping( source = Vehicle.Car.class, target = VehicleDto.CarDto.class ) + @SubclassMapping( source = Vehicle.Bike.class, target = VehicleDto.BikeDto.class ) + @SubclassMapping( source = Motor.Harley.class, target = MotorDto.HarleyDto.class ) + @SubclassMapping( source = Motor.Davidson.class, target = MotorDto.DavidsonDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker") + VehicleDto map(Vehicle vehicle); + + VehicleCollection mapInverse(VehicleCollectionDto vehicles); + + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java new file mode 100644 index 0000000000..f9f59c45ec --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/SealedSubclassTest.java @@ -0,0 +1,80 @@ +/* + * 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.subclassmapping.jdk17.sealed; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.Compiler; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Motor.class, + MotorDto.class, + SealedSubclassMapper.class, + Vehicle.class, + VehicleCollection.class, + VehicleCollectionDto.class, + VehicleDto.class +}) +public class SealedSubclassTest { + + @ProcessorTest(Compiler.JDK) + public void mappingIsDoneUsingSubclassMapping() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Vehicle.Car() ); + vehicles.getVehicles().add( new Vehicle.Bike() ); + vehicles.getVehicles().add( new Motor.Harley() ); + vehicles.getVehicles().add( new Motor.Davidson() ); + + VehicleCollectionDto result = SealedSubclassMapper.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + VehicleDto.CarDto.class, + VehicleDto.BikeDto.class, + MotorDto.HarleyDto.class, + MotorDto.DavidsonDto.class + ); + } + + @ProcessorTest(Compiler.JDK) + public void inverseMappingIsDoneUsingSubclassMapping() { + VehicleCollectionDto vehicles = new VehicleCollectionDto(); + vehicles.getVehicles().add( new VehicleDto.CarDto() ); + vehicles.getVehicles().add( new VehicleDto.BikeDto() ); + vehicles.getVehicles().add( new MotorDto.HarleyDto() ); + vehicles.getVehicles().add( new MotorDto.DavidsonDto() ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + Vehicle.Car.class, + Vehicle.Bike.class, + Motor.Harley.class, + Motor.Davidson.class + ); + } + + @ProcessorTest(Compiler.JDK) + public void subclassMappingInheritsInverseMapping() { + VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); + VehicleDto.CarDto carDto = new VehicleDto.CarDto(); + carDto.setMaker( "BenZ" ); + vehiclesDto.getVehicles().add( carDto ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehiclesDto ); + + assertThat( result.getVehicles() ) + .extracting( Vehicle::getVehicleManufacturingCompany ) + .containsExactly( "BenZ" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java new file mode 100644 index 0000000000..c558ec877e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/Vehicle.java @@ -0,0 +1,51 @@ +/* + * 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.subclassmapping.jdk17.sealed; + +public abstract sealed class Vehicle permits Motor, Vehicle.Bike, Vehicle.Car { + private String name; + private String vehicleManufacturingCompany; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVehicleManufacturingCompany() { + return vehicleManufacturingCompany; + } + + public void setVehicleManufacturingCompany(String vehicleManufacturingCompany) { + this.vehicleManufacturingCompany = vehicleManufacturingCompany; + } + + public static final class Bike extends Vehicle { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } + } + + public static final class Car extends Vehicle { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java new file mode 100644 index 0000000000..2285789baa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollection.java @@ -0,0 +1,17 @@ +/* + * 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.subclassmapping.jdk17.sealed; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollection { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java new file mode 100644 index 0000000000..6c9dc7d382 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleCollectionDto.java @@ -0,0 +1,17 @@ +/* + * 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.subclassmapping.jdk17.sealed; + +import java.util.ArrayList; +import java.util.Collection; + +public class VehicleCollectionDto { + private Collection vehicles = new ArrayList<>(); + + public Collection getVehicles() { + return vehicles; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java new file mode 100644 index 0000000000..40cbb0df60 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/subclassmapping/jdk17/sealed/VehicleDto.java @@ -0,0 +1,51 @@ +/* + * 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.subclassmapping.jdk17.sealed; + +public abstract sealed class VehicleDto permits VehicleDto.CarDto, VehicleDto.BikeDto, MotorDto { + private String name; + private String maker; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMaker() { + return maker; + } + + public void setMaker(String maker) { + this.maker = maker; + } + + public static final class BikeDto extends VehicleDto { + private int numberOfGears; + + public int getNumberOfGears() { + return numberOfGears; + } + + public void setNumberOfGears(int numberOfGears) { + this.numberOfGears = numberOfGears; + } + } + + public static final class CarDto extends VehicleDto { + private boolean manual; + + public boolean isManual() { + return manual; + } + + public void setManual(boolean manual) { + this.manual = manual; + } + } +} From 49963e34dec5cebc7a3dceda03a568d96939c2f1 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 17:42:00 +0100 Subject: [PATCH 327/363] Upgrade to Checkstyle 13.0.0 Fix errors which are now reported. Run only on Java 21 or later for integration tests module --- integrationtest/pom.xml | 36 ++++++++++++------- parent/pom.xml | 4 +-- .../ap/internal/model/common/Assignment.java | 1 - .../ap/internal/model/source/Method.java | 1 - .../processor/ModelElementProcessor.java | 2 +- .../bugs/_3485/ErroneousIssue3485Mapper.java | 1 + .../ap/test/bugs/_3485/Issue3485Test.java | 2 +- .../java8time/SourceTargetMapper.java | 1 + .../java/MultiLineExpressionMapper.java | 1 - .../expressions/java/SourceTargetMapper.java | 1 - .../compilation/annotation/ExpectedNote.java | 2 +- .../testutil/runner/CompilingExtension.java | 8 ++--- 12 files changed, 34 insertions(+), 26 deletions(-) diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 7c91837e79..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -107,19 +107,6 @@ - - org.apache.maven.plugins - maven-checkstyle-plugin - - - check-style - verify - - checkstyle - - - - @@ -138,5 +125,28 @@ + + jdk-21-or-newer + + [21 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + check-style + verify + + checkstyle + + + + + + + diff --git a/parent/pom.xml b/parent/pom.xml index 8c1266ed19..f7d26dfd22 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -36,7 +36,7 @@ 3.1.0 6.2.7 1.6.0 - 8.36.1 + 13.0.0 5.10.1 2.2.0 1.12.0 @@ -376,7 +376,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.1.1 + 3.6.0 build-config/checkstyle.xml true diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java index 293df9516b..7ed0140d81 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java @@ -147,7 +147,6 @@ public boolean isConverted() { */ void setSourceLoopVarName(String sourceLoopVarName); - /** * Returns whether the type of assignment * 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 bea4d56101..ba32b12b46 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 @@ -97,7 +97,6 @@ public interface Method { */ Parameter getTargetTypeParameter(); - /** * Returns the {@link Accessibility} of this method. * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java index d43b52953d..4a30b776b7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java @@ -40,7 +40,7 @@ public interface ModelElementProcessor { * * @author Gunnar Morling */ - public interface ProcessorContext { + interface ProcessorContext { Filer getFiler(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java index 5714e04684..dc1558907c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java @@ -17,6 +17,7 @@ public interface ErroneousIssue3485Mapper { ErroneousIssue3485Mapper INSTANCE = Mappers.getMapper( ErroneousIssue3485Mapper.class ); + class Target { private final String value; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java index 7427b63a4e..964de36ec8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java @@ -24,7 +24,7 @@ public class Issue3485Test { diagnostics = { @Diagnostic(type = ErroneousIssue3485Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 33, + line = 34, message = "Using @Mapping( target = \".\") requires a source property. Expression or " + "constant cannot be used as a source.") }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java index 37fc7d6cbe..f2351f4175 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java @@ -26,6 +26,7 @@ public interface SourceTargetMapper { String LOCAL_TIME_FORMAT = "HH:mm"; SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + @Mappings( { @Mapping( target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT ), @Mapping( target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT ), @Mapping( target = "localDate", dateFormat = LOCAL_DATE_FORMAT ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java index 50d0b4b2cd..76286bfa3a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/MultiLineExpressionMapper.java @@ -17,7 +17,6 @@ @Mapper(imports = TimeAndFormat.class) public interface MultiLineExpressionMapper { - MultiLineExpressionMapper INSTANCE = Mappers.getMapper( MultiLineExpressionMapper.class ); @Mappings({ diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java index 0e462ab3b3..56809494a3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/expressions/java/SourceTargetMapper.java @@ -18,7 +18,6 @@ @Mapper( imports = TimeAndFormat.class ) public interface SourceTargetMapper { - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @Mappings( { diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java index 1cb95f52c2..2070c028c7 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java @@ -29,7 +29,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) - public @interface ExpectedNotes { + @interface ExpectedNotes { /** * Regexp for the note to match. diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index 08056ca697..bcfc707d68 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -25,11 +25,11 @@ import java.util.Set; import java.util.stream.Collectors; +import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean; import com.puppycrawl.tools.checkstyle.Checker; import com.puppycrawl.tools.checkstyle.ConfigurationLoader; import com.puppycrawl.tools.checkstyle.DefaultLogger; import com.puppycrawl.tools.checkstyle.PropertiesExpander; -import com.puppycrawl.tools.checkstyle.api.AutomaticBean; import org.apache.commons.io.output.NullOutputStream; import org.jetbrains.kotlin.cli.common.ExitCode; import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments; @@ -225,10 +225,10 @@ private void assertCheckstyleRules() throws Exception { ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); checker.addListener( new DefaultLogger( - NullOutputStream.NULL_OUTPUT_STREAM, - AutomaticBean.OutputStreamOptions.CLOSE, + NullOutputStream.INSTANCE, + AbstractAutomaticBean.OutputStreamOptions.CLOSE, errorStream, - AutomaticBean.OutputStreamOptions.CLOSE + AbstractAutomaticBean.OutputStreamOptions.CLOSE ) ); From 60151132145a606990224376d35be6eaff9e250f Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 29 Jan 2026 22:33:00 +0100 Subject: [PATCH 328/363] #3404 Add support for Kotlin Sealed classes --- NEXT_RELEASE_CHANGELOG.md | 1 + .../ap/internal/model/common/Type.java | 26 ++++++ .../internal/util/kotlin/KotlinMetadata.java | 5 ++ .../mapstruct/ap/test/kotlin/sealed/Dtos.kt | 36 +++++++++ .../ap/test/kotlin/sealed/Entities.kt | 36 +++++++++ .../kotlin/sealed/SealedSubclassMapper.java | 31 +++++++ .../kotlin/sealed/SealedSubclassTest.java | 80 +++++++++++++++++++ 7 files changed, 215 insertions(+) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index f279193f6a..1f7026c127 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -7,6 +7,7 @@ - Proper primary constructor detection - Data classes with multiple constructors - Data classes with all default parameters + - Sealed Classes (#3404) - Subclass exhaustiveness is now checked for Kotlin sealed classes ### Enhancements diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 313ff38125..8fb10090c2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -47,6 +47,7 @@ import kotlin.metadata.Attributes; import kotlin.metadata.KmClass; import kotlin.metadata.KmConstructor; +import kotlin.metadata.Modality; import kotlin.metadata.jvm.JvmExtensionsKt; import kotlin.metadata.jvm.JvmMethodSignature; import kotlin.metadata.jvm.KotlinClassMetadata; @@ -1850,6 +1851,10 @@ public boolean isEnumSet() { * return true if this type is a java 17+ sealed class */ public boolean isSealed() { + KotlinMetadata kotlinMetadata = getKotlinMetadata(); + if ( kotlinMetadata != null ) { + return kotlinMetadata.isSealedClass(); + } return typeElement.getModifiers().stream().map( Modifier::name ).anyMatch( "SEALED"::equals ); } @@ -1858,6 +1863,10 @@ public boolean isSealed() { */ @SuppressWarnings( "unchecked" ) public List getPermittedSubclasses() { + KotlinMetadata kotlinMetadata = getKotlinMetadata(); + if ( kotlinMetadata != null ) { + return kotlinMetadata.getPermittedSubclasses(); + } if (SEALED_PERMITTED_SUBCLASSES_METHOD == null) { return emptyList(); } @@ -1882,6 +1891,11 @@ public boolean isDataClass() { return Attributes.isData( kotlinClassMetadata.getKmClass() ); } + @Override + public boolean isSealedClass() { + return Attributes.getModality( kotlinClassMetadata.getKmClass() ) == Modality.SEALED; + } + @Override public ExecutableElement determinePrimaryConstructor(List constructors) { if ( constructors.size() == 1 ) { @@ -1928,6 +1942,18 @@ public ExecutableElement determinePrimaryConstructor(List con return null; } + @Override + public List getPermittedSubclasses() { + List sealedSubclassNames = kotlinClassMetadata.getKmClass().getSealedSubclasses(); + List permittedSubclasses = new ArrayList<>( sealedSubclassNames.size() ); + for ( String sealedSubclassName : sealedSubclassNames ) { + Type subclassType = typeFactory.getType( sealedSubclassName.replace( '/', '.' ) ); + permittedSubclasses.add( subclassType.getTypeMirror() ); + } + + return permittedSubclasses; + } + private String buildJvmConstructorDescriptor(ExecutableElement constructor) { StringBuilder signature = new StringBuilder( "(" ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java index d58ab75c79..66a6fafc02 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java @@ -7,6 +7,7 @@ import java.util.List; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; /** * Information about a type in case it's a Kotlin type. @@ -17,5 +18,9 @@ public interface KotlinMetadata { boolean isDataClass(); + boolean isSealedClass(); + ExecutableElement determinePrimaryConstructor(List constructors); + + List getPermittedSubclasses(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt new file mode 100644 index 0000000000..5a9084a685 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt @@ -0,0 +1,36 @@ +/* + * 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.kotlin.sealed + +sealed class VehicleDto { + var name: String? = null + var maker: String? = null + + class BikeDto : VehicleDto() { + var numberOfGears: Int = 0 + } + + class CarDto : VehicleDto() { + var manual: Boolean = false + } + +} + +sealed class MotorDto : VehicleDto() { + var cc: Int = 0 + + class DavidsonDto : MotorDto() { + var numberOfExhausts: Int = 0 + } + + class HarleyDto : MotorDto() { + var engineDb: Int = 0 + } +} + +class VehicleCollectionDto { + var vehicles: MutableList = mutableListOf() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt new file mode 100644 index 0000000000..b380bac231 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt @@ -0,0 +1,36 @@ +/* + * 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.kotlin.sealed + +sealed class Vehicle { + var name: String? = null + var vehicleManufacturingCompany: String? = null + + class Bike : Vehicle() { + var numberOfGears: Int = 0 + } + + class Car : Vehicle() { + var manual: Boolean = false + } + +} + +sealed class Motor : Vehicle() { + var cc: Int = 0 + + class Davidson : Motor() { + var numberOfExhausts: Int = 0 + } + + class Harley : Motor() { + var engineDb: Int = 0 + } +} + +class VehicleCollection { + var vehicles: MutableList = mutableListOf() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java new file mode 100644 index 0000000000..bde7dc24cf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java @@ -0,0 +1,31 @@ +/* + * 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.kotlin.sealed; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SealedSubclassMapper { + SealedSubclassMapper INSTANCE = Mappers.getMapper( SealedSubclassMapper.class ); + + VehicleCollectionDto map(VehicleCollection vehicles); + + @SubclassMapping( source = Vehicle.Car.class, target = VehicleDto.CarDto.class ) + @SubclassMapping( source = Vehicle.Bike.class, target = VehicleDto.BikeDto.class ) + @SubclassMapping( source = Motor.Harley.class, target = MotorDto.HarleyDto.class ) + @SubclassMapping( source = Motor.Davidson.class, target = MotorDto.DavidsonDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker") + VehicleDto map(Vehicle vehicle); + + VehicleCollection mapInverse(VehicleCollectionDto vehicles); + + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java new file mode 100644 index 0000000000..f7162ec3a2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java @@ -0,0 +1,80 @@ +/* + * 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.kotlin.sealed; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlin; +import org.mapstruct.ap.testutil.WithKotlinSources; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + SealedSubclassMapper.class, +}) +@WithKotlinSources({ + "Dtos.kt", + "Entities.kt" +}) +@WithKotlin +public class SealedSubclassTest { + + @ProcessorTest + public void mappingIsDoneUsingSubclassMapping() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Vehicle.Car() ); + vehicles.getVehicles().add( new Vehicle.Bike() ); + vehicles.getVehicles().add( new Motor.Harley() ); + vehicles.getVehicles().add( new Motor.Davidson() ); + + VehicleCollectionDto result = SealedSubclassMapper.INSTANCE.map( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + VehicleDto.CarDto.class, + VehicleDto.BikeDto.class, + MotorDto.HarleyDto.class, + MotorDto.DavidsonDto.class + ); + } + + @ProcessorTest + public void inverseMappingIsDoneUsingSubclassMapping() { + VehicleCollectionDto vehicles = new VehicleCollectionDto(); + vehicles.getVehicles().add( new VehicleDto.CarDto() ); + vehicles.getVehicles().add( new VehicleDto.BikeDto() ); + vehicles.getVehicles().add( new MotorDto.HarleyDto() ); + vehicles.getVehicles().add( new MotorDto.DavidsonDto() ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehicles ); + + assertThat( result.getVehicles() ).doesNotContainNull(); + assertThat( result.getVehicles() ) // remove generic so that test works. + .extracting( vehicle -> (Class) vehicle.getClass() ) + .containsExactly( + Vehicle.Car.class, + Vehicle.Bike.class, + Motor.Harley.class, + Motor.Davidson.class + ); + } + + @ProcessorTest + public void subclassMappingInheritsInverseMapping() { + VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); + VehicleDto.CarDto carDto = new VehicleDto.CarDto(); + carDto.setMaker( "BenZ" ); + vehiclesDto.getVehicles().add( carDto ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehiclesDto ); + + assertThat( result.getVehicles() ) + .extracting( Vehicle::getVehicleManufacturingCompany ) + .containsExactly( "BenZ" ); + } +} From 9923f78d91a8dae76f210c5de2ac0013baa078b2 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 30 Jan 2026 12:25:56 +0100 Subject: [PATCH 329/363] #674 Add support for `Optional` (#3971) MapStruct now fully supports Optional as both source and target types: * `Optional` to `Optional` - Both source and target wrapped in `Optional` * `Optional` to Non-`Optional` - Unwrapping `Optional` values * Non-`Optional` to `Optional` - Wrapping values in `Optional` * Always assume that optionals are not `null`, i.e. we can always call `isPresent()` / `isEmpty()` on an `Optional` * Add support for conversions e.g. `Optional` -> `String`, `Optional` -> `String` etc. --------- Co-authored-by: Ken Wang --- NEXT_RELEASE_CHANGELOG.md | 5 + copyright.txt | 3 +- .../mapstruct/NullValueMappingStrategy.java | 6 +- .../NullValuePropertyMappingStrategy.java | 5 +- ...apter-10-advanced-mapping-options.asciidoc | 17 +- .../chapter-3-defining-a-mapper.asciidoc | 415 +++++++++++ .../chapter-5-data-type-conversions.asciidoc | 8 + .../ap/internal/conversion/Conversions.java | 35 + .../OptionalWrapperConversionProvider.java | 97 +++ .../conversion/TypeToOptionalConversion.java | 44 ++ .../ap/internal/model/BeanMappingMethod.java | 160 +++- .../model/FromOptionalTypeConversion.java | 113 +++ .../model/LifecycleMethodResolver.java | 33 +- .../internal/model/MappingBuilderContext.java | 11 + .../ap/internal/model/MappingMethod.java | 6 +- .../model/NestedPropertyMappingMethod.java | 63 +- .../model/PresenceCheckMethodResolver.java | 6 +- .../ap/internal/model/PropertyMapping.java | 16 +- .../model/ToOptionalTypeConversion.java | 119 +++ .../model/assignment/OptionalGetWrapper.java | 35 + .../model/beanmapping/SourceReference.java | 10 + .../model/common/ConversionContext.java | 7 + .../common/DefaultConversionContext.java | 5 + .../ap/internal/model/common/Parameter.java | 26 +- .../ap/internal/model/common/Type.java | 23 +- .../model/presence/OptionalPresenceCheck.java | 77 ++ .../ap/internal/model/source/Method.java | 9 - .../source/selector/SelectionContext.java | 21 +- .../processor/DefaultVersionInformation.java | 7 + .../processor/MapperCreationProcessor.java | 1 + .../creation/MappingResolverImpl.java | 13 +- .../internal/version/VersionInformation.java | 2 + .../ap/internal/model/BeanMappingMethod.ftl | 81 +- .../model/FromOptionalTypeConversion.ftl | 16 + .../model/ToOptionalTypeConversion.ftl | 21 + .../model/assignment/OptionalGetWrapper.ftl | 15 + .../model/assignment/UpdateWrapper.ftl | 4 +- .../ap/internal/model/macro/CommonMacros.ftl | 28 +- .../model/presence/OptionalPresenceCheck.ftl | 19 + .../OptionalEnumToIntegerConversionTest.java | 73 ++ .../_enum/OptionalEnumToIntegerMapper.java | 19 + .../_enum/OptionalEnumToIntegerSource.java | 31 + .../bignumbers/BigDecimalOptionalMapper.java | 19 + .../bignumbers/BigDecimalOptionalSource.java | 139 ++++ .../bignumbers/BigIntegerOptionalMapper.java | 19 + .../bignumbers/BigIntegerOptionalSource.java | 131 ++++ .../bignumbers/BigNumbersConversionTest.java | 150 ++++ .../Java8OptionalTimeConversionTest.java | 466 ++++++++++++ .../conversion/java8time/OptionalSource.java | 160 ++++ .../java8time/OptionalSourceTargetMapper.java | 70 ++ ...ErroneousKitchenDrawerOptionalMapper1.java | 23 + ...ErroneousKitchenDrawerOptionalMapper3.java | 23 + ...ErroneousKitchenDrawerOptionalMapper4.java | 23 + ...ErroneousKitchenDrawerOptionalMapper5.java | 23 + ...ErroneousKitchenDrawerOptionalMapper6.java | 20 + .../lossy/KitchenDrawerOptionalMapper2.java | 23 + .../lossy/KitchenDrawerOptionalMapper6.java | 23 + .../conversion/lossy/LossyConversionTest.java | 94 +++ .../OversizedKitchenDrawerOptionalDto.java | 84 +++ .../lossy/VerySpecialNumberMapper.java | 6 + .../ByteWrapperOptionalSource.java | 121 +++ .../nativetypes/DoubleOptionalSource.java | 121 +++ .../DoubleWrapperOptionalSource.java | 121 +++ .../FloatWrapperOptionalSource.java | 121 +++ .../nativetypes/IntOptionalSource.java | 121 +++ .../nativetypes/IntWrapperOptionalSource.java | 121 +++ .../nativetypes/LongOptionalSource.java | 121 +++ .../LongWrapperOptionalSource.java | 121 +++ .../NumberOptionalConversionTest.java | 357 +++++++++ .../OptionalNumberConversionMapper.java | 52 ++ .../ShortWrapperOptionalSource.java | 121 +++ .../OptionalBeforeAfterMapper.java | 76 ++ .../beforeafter/OptionalBeforeAfterTest.java | 25 + .../ap/test/optional/beforeafter/Source.java | 87 +++ .../ap/test/optional/beforeafter/Target.java | 86 +++ .../optional/builder/OptionalBuilderTest.java | 31 + .../builder/SimpleOptionalBuilderMapper.java | 67 ++ .../optional/custom/CustomOptionalMapper.java | 63 ++ .../optional/custom/CustomOptionalTest.java | 60 ++ .../OptionalDifferentTypesMapper.java | 18 + .../OptionalDifferentTypesTest.java | 204 +++++ .../test/optional/differenttypes/Source.java | 89 +++ .../test/optional/differenttypes/Target.java | 84 +++ .../optional/lifecycle/MappingContext.java | 108 +++ .../lifecycle/OptionalLifecycleTest.java | 165 +++++ .../lifecycle/OptionalToOptionalMapper.java | 27 + .../OptionalToOptionalWithBuilderMapper.java | 24 + .../lifecycle/OptionalToTypeMapper.java | 27 + .../OptionalToTypeMultiSourceMapper.java | 27 + .../ap/test/optional/lifecycle/Source.java | 21 + .../ap/test/optional/lifecycle/Target.java | 39 + .../ap/test/optional/nested/Artist.java | 81 ++ .../optional/nested/OptionalNestedMapper.java | 23 + ...ptionalNestedPresenceCheckFirstMapper.java | 52 ++ .../OptionalNestedPresenceCheckMapper.java | 44 ++ .../optional/nested/OptionalNestedTest.java | 148 ++++ .../ap/test/optional/nested/Source.java | 76 ++ .../ap/test/optional/nested/Target.java | 48 ++ .../test/optional/nested/TargetAggregate.java | 49 ++ .../OptionalNullCheckAlwaysMapper.java | 19 + .../OptionalNullCheckAlwaysTest.java | 84 +++ .../test/optional/nullcheckalways/Source.java | 40 + .../test/optional/nullcheckalways/Target.java | 40 + .../nullvalue/OptionalDefaultMapperTest.java | 2 - .../nullvalue/SimpleConstructorMapper.java | 29 - .../sametype/OptionalSameTypeMapper.java | 18 + .../sametype/OptionalSameTypeTest.java | 186 +++++ .../ap/test/optional/sametype/Source.java | 74 ++ .../ap/test/optional/sametype/Target.java | 70 ++ .../optional/simple/OptionalSimpleTest.java | 110 +++ .../simple/OptionalToOptionalMapper.java | 23 + ...ionalToOptionalNullValueDefaultMapper.java | 25 + .../optional/simple/OptionalToTypeMapper.java | 23 + .../OptionalToTypeNullValueDefaultMapper.java | 24 + .../OptionalToTypeWithMappingMapper.java | 67 ++ .../ap/test/optional/simple/Source.java | 21 + .../ap/test/optional/simple/Target.java | 22 + .../optional/simple/TypeToOptionalMapper.java | 23 + .../optional/update/OptionalUpdateMapper.java | 21 + ...alUpdateNullValuePropertyIgnoreMapper.java | 23 + ...pdateNullValuePropertyToDefaultMapper.java | 23 + .../optional/update/OptionalUpdateTest.java | 117 +++ .../ap/test/optional/update/Source.java | 37 + .../ap/test/optional/update/Target.java | 37 + .../testutil/assertions/JavaFileAssert.java | 2 +- .../OptionalSourceTargetMapperImpl.java | 701 ++++++++++++++++++ .../OptionalBeforeAfterMapperImpl.java | 113 +++ .../OptionalDifferentTypesMapperImpl.java | 89 +++ .../nested/OptionalNestedMapperImpl.java | 71 ++ ...nalNestedPresenceCheckFirstMapperImpl.java | 117 +++ ...OptionalNestedPresenceCheckMapperImpl.java | 100 +++ .../OptionalNullCheckAlwaysMapperImpl.java | 38 + .../sametype/OptionalSameTypeMapperImpl.java | 55 ++ 133 files changed, 8860 insertions(+), 132 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckFirstMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/OptionalNestedTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nested/TargetAggregate.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/OptionalNullCheckAlwaysMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/OptionalNullCheckAlwaysTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullcheckalways/Target.java delete mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/nullvalue/SimpleConstructorMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/OptionalSameTypeMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/OptionalSameTypeTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/sametype/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalSimpleTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToOptionalNullValueDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToTypeMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToTypeNullValueDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/OptionalToTypeWithMappingMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/simple/TypeToOptionalMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateNullValuePropertyIgnoreMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateNullValuePropertyToDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/OptionalUpdateTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/optional/update/Target.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nested/OptionalNestedMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckFirstMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nested/OptionalNestedPresenceCheckMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/nullcheckalways/OptionalNullCheckAlwaysMapperImpl.java create mode 100644 processor/src/test/resources/fixtures/org/mapstruct/ap/test/optional/sametype/OptionalSameTypeMapperImpl.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 1f7026c127..59fed4fed3 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,6 +1,11 @@ ### Features * Support for Java 21 Sequenced Collections (#3240) +* Native support for `java.util.Optional` mapping (#674) - MapStruct now fully supports Optional as both source and target types: + - `Optional` to `Optional` - Both source and target wrapped in `Optional` + - `Optional` to Non-`Optional` - Unwrapping `Optional` values + - Non-`Optional` to `Optional` - Wrapping values in `Optional` + - `Optional` properties in beans with automatic presence checks. Note, there is no null check done for `Optional` properties. * Improved support for Kotlin. Requires use of `org.jetbrains.kotlin:kotlin-metadata-jvm`. - Data Classes (#2281, #2577, #3031) - MapStruct now properly handles: - Single field data classes diff --git a/copyright.txt b/copyright.txt index 71d47a796a..bd0034b076 100644 --- a/copyright.txt +++ b/copyright.txt @@ -38,6 +38,7 @@ Joshua Spoerri - https://github.com/spoerri Jude Niroshan - https://github.com/JudeNiroshan Justyna Kubica-Ledzion - https://github.com/JKLedzion Kemal Özcan - https://github.com/yekeoe +Ken Wang - https://github.com/ro0sterjam Kevin Grüneberg - https://github.com/kevcodez Lukas Lazar - https://github.com/LukeLaz Nikolas Charalambidis - https://github.com/Nikolas-Charalambidis @@ -70,4 +71,4 @@ Tomek Gubala - https://github.com/vgtworld Valentin Kulesh - https://github.com/unshare Vincent Alexander Beelte - https://github.com/grandmasterpixel Winter Andreas - https://github.dev/wandi34 -Xiu Hong Kooi - https://github.com/kooixh +Xiu Hong Kooi - https://github.com/kooixh \ No newline at end of file diff --git a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java index 519a28bd81..4cece83031 100644 --- a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java @@ -13,8 +13,9 @@ public enum NullValueMappingStrategy { /** - * If {@code null} is passed to a mapping method, {@code null} will be returned. That's the default behavior if no - * alternative strategy is configured globally, for given mapper or method. + * If {@code null} is passed to a mapping method, {@code null} will be returned, unless the return type is + * {@link java.util.Optional Optional}, in which case an empty optional will be returned. + * That's the default behavior if no alternative strategy is configured globally, for given mapper or method. */ RETURN_NULL, @@ -28,6 +29,7 @@ public enum NullValueMappingStrategy { * case. *

    12. For iterable mapping methods an empty collection will be returned.
    13. *
    14. For map mapping methods an empty map will be returned.
    15. + *
    16. For optional mapping methods an empty optional will be returned.
    17. * */ RETURN_DEFAULT; diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java index 556d4253b1..a58eed8a2a 100644 --- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -27,7 +27,9 @@ public enum NullValuePropertyMappingStrategy { /** - * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null}. + * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null} + * or {@link java.util.Optional#empty() Optional.empty()} in case the target type is an + * {@link java.util.Optional Optional}. */ SET_TO_NULL, @@ -36,6 +38,7 @@ public enum NullValuePropertyMappingStrategy { *

      * This means: *

        + *
      1. For {@code Optional} MapStruct generates an {@code Optional.empty()}
      2. *
      3. For {@code List} MapStruct generates an {@code ArrayList}
      4. *
      5. For {@code Map} a {@code HashMap}
      6. *
      7. For arrays an empty array
      8. diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index b5c116ffd3..43f8fc38a0 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -204,7 +204,8 @@ The mechanism is also present on iterable mapping and map mapping. `@IterableMap [[mapping-result-for-null-arguments]] === Controlling mapping result for 'null' arguments -MapStruct offers control over the object to create when the source argument of the mapping method equals `null`. By default `null` will be returned. +MapStruct offers control over the object to create when the source argument of the mapping method equals `null`. +By default `null` will be returned, or `Optional.empty()` if the return type is `Optional`. However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT` on `@BeanMapping`, `@IterableMapping`, `@MapMapping`, or globally on `@Mapper` or `@MapperConfig`, the mapping result can be altered to return empty *default* values. This means for: @@ -243,7 +244,7 @@ How the value of the `NullValueMappingStrategy` is applied is the same as in <> for detailed examples and behavior with Optional types. +==== + [[checking-source-property-for-null-arguments]] === Controlling checking result for 'null' properties in bean mapping diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc index 81980a35ac..96c76fc20d 100644 --- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc @@ -660,6 +660,421 @@ public class PersonMapperImpl implements PersonMapper { ---- ==== +[[mapping-with-optional]] +=== Using Optional + +MapStruct provides comprehensive support for Java's `Optional` type, allowing you to use `Optional` as both source and target types in your mappings. +MapStruct handles all common `Optional` mapping scenarios, including conversions, nested properties, update mappings, and integration with builders and constructors. + +[[optional-basic-behavior]] +==== Basic Optional Behavior + +When using `Optional` as a source type, MapStruct performs presence checks using `Optional.isEmpty()` / `!Optional.isPresent()` instead of `null` checks. +When using `Optional` as a target type, MapStruct returns `Optional.empty()` instead of `null` when the source is absent. + +[[optional-mapping-scenarios]] +==== Optional Mapping Scenarios + +MapStruct supports three primary mapping scenarios with `Optional`: + +1. `Optional` to `Optional` - Both source and target are wrapped in `Optional` +2. `Optional` to Non-`Optional` - Source is `Optional`, target is a regular type +3. Non-`Optional` to `Optional` - Source is a regular type, target is `Optional` + +.Example mapper showing all Optional scenarios +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface ProductMapper { + + Optional map(Optional product); + + ProductDto unwrap(Optional product); + + Optional wrap(Product product); +} +---- +==== + +.Generated code for `Optional` to `Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public Optional map(Optional product) { + if ( product.isEmpty() ) { + return Optional.empty(); + } + + Product productValue = product.get(); + + ProductDto productDto = new ProductDto(); + + productDto.setName( productValue.getName() ); + productDto.setPrice( productValue.getPrice() ); + + return Optional.of( productDto ); +} +---- +==== + +.Generated code for `Optional` to Non-`Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public ProductDto unwrap(Optional product) { + if ( product.isEmpty() ) { + return null; + } + + Product productValue = product.get(); + + ProductDto productDto = new ProductDto(); + + productDto.setName( productValue.getName() ); + productDto.setPrice( productValue.getPrice() ); + + return productDto; +} +---- +==== + +.Generated code for Non-`Optional` to `Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public Optional wrap(Product product) { + if ( product == null ) { + return Optional.empty(); + } + + ProductDto productDto = new ProductDto(); + + productDto.setName( product.getName() ); + productDto.setPrice( product.getPrice() ); + + return Optional.of( productDto ); +} +---- +==== + +[[optional-properties]] +==== Mapping Optional Properties + +MapStruct handles `Optional` properties within your beans seamlessly. +When mapping `Optional` properties, MapStruct generates appropriate presence checks before accessing the values. + +.Example with `Optional` properties +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class Car { + private Optional make; + private Optional numberOfSeats; + private Person driver; + + // getters omitted for brevity +} + +public class CarDto { + private String manufacturer; + private int seatCount; + private PersonDto driver; + + // getters and setters omitted for brevity +} + +@Mapper +public interface CarMapper { + + @Mapping(target = "manufacturer", source = "make") + @Mapping(target = "seatCount", source = "numberOfSeats") + CarDto carToCarDto(Car car); +} +---- +==== + +.Code generated by MapStruct for `Optional` properties +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class CarMapperImpl implements CarMapper { + + @Override + public CarDto carToCarDto(Car car) { + if ( car == null ) { + return null; + } + + CarDto carDto = new CarDto(); + + if ( car.getMake().isPresent() ) { + carDto.setManufacturer( car.getMake().get() ); + } + if ( car.getNumberOfSeats().isPresent() ) { + carDto.setSeatCount( car.getNumberOfSeats().get() ); + } + carDto.setDriver( personToPersonDto( car.getDriver() ) ); + + return carDto; + } + + protected PersonDto personToPersonDto(Person person) { + //... + } +} +---- +==== + +[[optional-update-mappings]] +==== Update Mappings with Optional + +`Optional` works seamlessly with update mappings using `@MappingTarget`. +When the source `Optional` is present, the target object is updated with the unwrapped value. +When the source `Optional` is empty, the behavior depends on the null value property mapping strategy. + +.Update mapping with an `Optional` source +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface ProductUpdateMapper { + + void updateProduct(ProductUpdateDto dto, @MappingTarget Product product); +} + +public class ProductUpdateDto { + private Optional name; + private Optional price; + + // getters omitted +} + +public class Product { + private String name; + private BigDecimal price; + + // getters and setters omitted +} +---- +==== + +.Generated update method +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public void updateProduct(ProductUpdateDto dto, Product product) { + if ( dto == null ) { + return; + } + + if ( dto.getName().isPresent() ) { + product.setName( dto.getName().get() ); + } + if ( dto.getPrice().isPresent() ) { + product.setPrice( dto.getPrice().get() ); + } +} +---- +==== + +[[optional-null-value-strategies]] +==== Null Value Mapping Strategies + +When working with `Optional` types, MapStruct's null value mapping strategies behave as follows: + +* For `Optional` return types: `NullValueMappingStrategy.RETURN_DEFAULT` will return `Optional.empty()` instead of `null` +* For `Optional` properties: When a source property `Optional` is empty, `NullValuePropertyMappingStrategy` determines whether to skip the property, set it to null/empty, or use a default value +* `IGNORE` strategy: When a source `Optional` is empty, the target property is not updated +* `SET_TO_NULL` strategy: When a source `Optional` is empty, the target property is set to `null` (for non-`Optional` targets) or `Optional.empty()` (for `Optional` targets) +* `SET_TO_DEFAULT` strategy: When a source `Optional` is empty, the target property is set to its default value + +.Using null value strategies with `Optional` +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface ProductMapper { + + void updateProduct(ProductUpdateDto dto, @MappingTarget Product product); +} +---- +==== + +With the `IGNORE` strategy, when Optional properties are empty, they won't overwrite existing target values. + +[[optional-builders-constructors]] +==== Optional with Builders and Constructors + +MapStruct's Optional support works seamlessly with both builder patterns and constructor-based mappings. + +.Optional with builders +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface PersonMapper { + Optional map(Optional person); +} + +public class PersonDto { + private final String name; + private final Integer age; + + private PersonDto(Builder builder) { + this.name = builder.name; + this.age = builder.age; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String name; + private Integer age; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder age(Integer age) { + this.age = age; + return this; + } + + public PersonDto build() { + return new PersonDto(this); + } + } + + // getters omitted +} +---- +==== + +.Generated code using builder +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +@Override +public Optional map(Optional person) { + if ( person.isEmpty() ) { + return Optional.empty(); + } + + Person personValue = person.get(); + + PersonDto.Builder builder = PersonDto.builder(); + + builder.name( personValue.getName() ); + builder.age( personValue.getAge() ); + + return Optional.of( builder.build() ); +} +---- +==== + +.Optional with constructors +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class PersonDto { + private final String name; + private final Integer age; + + public PersonDto(String name, Integer age) { + this.name = name; + this.age = age; + } + + // getters omitted +} +---- +==== + +When the target type uses constructors and has `Optional` properties, MapStruct will properly handle the `Optional` parameters. + +[[optional-complex-mappings]] +==== Complex Optional Mappings + +MapStruct can handle complex scenarios where `Optional` is combined with nested object mappings, collections, and custom mapping methods. + +.Complex nested Optional mappings +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper +public interface OrderMapper { + + OrderDto map(Order order); + + // MapStruct will automatically use this for nested mappings + CustomerDto map(Customer customer); +} + +public class Order { + private Optional customer; + private Optional
        shippingAddress; + + // getters omitted +} + +public class OrderDto { + private Optional customer; + private AddressDto shippingAddress; + + // getters and setters omitted +} +---- +==== + +When mapping nested objects that are wrapped in Optional, MapStruct will: + +1. Check if the source `Optional` is present +2. Extract the value from the `Optional` +3. Map the extracted value to the target type +4. Wrap the result in an `Optional` if the target is also Optional + +[[optional-best-practices]] +==== Best Practices and Limitations + +[NOTE] +==== +While MapStruct fully supports `Optional` for target properties (public fields or setter parameters), the Java community generally recommends using `Optional` only for return types (source properties / getters). +Consider your team's coding standards when deciding whether to use `Optional` for target properties. +==== + +[WARNING] +==== +Avoid using `null` when returning `Optional` values to prevent `NullPointerException`. +MapStruct assumes that if you're using `Optional`, the `Optional` itself is not null. +==== + [[mapping-map-to-bean]] === Mapping Map to Bean diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index ad49fe2960..9da7ff75c1 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -133,6 +133,14 @@ public interface CarMapper { * Between `java.util.Locale` and `String`. ** When converting from a `Locale`, the resulting `String` will be a well-formed IETF BCP 47 language tag representing the locale. When converting from a `String`, the locale that best represents the language tag will be returned. See https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#forLanguageTag-java.lang.String-[Locale.forLanguageTag()] and https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#toLanguageTag--[Locale.toLanguageTag()] for more information. +[NOTE] +==== +All the above conversions also work when the source or target type is wrapped in `java.util.Optional`, `java.util.OptionalDouble`, `java.util.OptionalLong` or `java.util.OptionalInt`. +For example, `Optional` can be converted to `String`, `int` can be converted to `Optional`, +or `Optional` can be converted to `Integer`. +The same conversion rules apply, with MapStruct automatically handling the wrapping and unwrapping of `Optional` values. +==== + [[mapping-object-references]] === Mapping object references diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java index 5090c903f3..076cd2f42e 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/Conversions.java @@ -333,6 +333,41 @@ private void register(String sourceTypeName, Class targetClass, ConversionPro } public ConversionProvider getConversion(Type sourceType, Type targetType) { + if ( sourceType.isOptionalType() ) { + if ( targetType.isOptionalType() ) { + // We cannot convert optional to optional + return null; + } + Type sourceBaseType = sourceType.getOptionalBaseType(); + if ( sourceBaseType.equals( targetType ) ) { + // Optional -> Type + return TypeToOptionalConversion.OPTIONAL_TO_TYPE_CONVERSION; + } + + ConversionProvider conversionProvider = getInternalConversion( sourceBaseType, targetType ); + if ( conversionProvider != null ) { + return inverse( new OptionalWrapperConversionProvider( conversionProvider ) ); + } + + } + else if ( targetType.isOptionalType() ) { + // Type -> Optional + Type targetBaseType = targetType.getOptionalBaseType(); + if ( targetBaseType.equals( sourceType )) { + return TypeToOptionalConversion.TYPE_TO_OPTIONAL_CONVERSION; + } + ConversionProvider conversionProvider = getInternalConversion( sourceType, targetBaseType ); + if ( conversionProvider != null ) { + return new OptionalWrapperConversionProvider( conversionProvider ); + } + return null; + + } + + return getInternalConversion( sourceType, targetType ); + } + + private ConversionProvider getInternalConversion(Type sourceType, Type targetType) { if ( sourceType.isEnumType() && ( targetType.equals( stringType ) || targetType.getBoxedEquivalent().equals( integerType ) ) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java new file mode 100644 index 0000000000..2b3e1bd960 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java @@ -0,0 +1,97 @@ +/* + * 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.internal.conversion; + +import java.util.List; + +import org.mapstruct.ap.internal.model.FromOptionalTypeConversion; +import org.mapstruct.ap.internal.model.HelperMethod; +import org.mapstruct.ap.internal.model.ToOptionalTypeConversion; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.FieldReference; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; + +/** + * @author Filip Hrisafov + */ +public class OptionalWrapperConversionProvider implements ConversionProvider { + + private final ConversionProvider conversionProvider; + + public OptionalWrapperConversionProvider(ConversionProvider conversionProvider) { + this.conversionProvider = conversionProvider; + } + + @Override + public Assignment to(ConversionContext conversionContext) { + Assignment assignment = conversionProvider.to( new OptionalConversionContext( conversionContext ) ); + return new ToOptionalTypeConversion( conversionContext.getTargetType(), assignment ); + } + + @Override + public Assignment from(ConversionContext conversionContext) { + Assignment assignment = conversionProvider.to( new OptionalConversionContext( conversionContext ) ); + return new FromOptionalTypeConversion( conversionContext.getSourceType(), assignment ); + } + + @Override + public List getRequiredHelperMethods(ConversionContext conversionContext) { + return conversionProvider.getRequiredHelperMethods( conversionContext ); + } + + @Override + public List getRequiredHelperFields(ConversionContext conversionContext) { + return conversionProvider.getRequiredHelperFields( conversionContext ); + } + + private static class OptionalConversionContext implements ConversionContext { + + private final ConversionContext delegate; + + private OptionalConversionContext(ConversionContext delegate) { + this.delegate = delegate; + } + + @Override + public Type getTargetType() { + return resolveType( delegate.getTargetType() ); + } + + @Override + public Type getSourceType() { + return resolveType( delegate.getSourceType() ); + } + + private Type resolveType(Type type) { + if ( type.isOptionalType() ) { + return type.getOptionalBaseType(); + } + return type; + } + + @Override + public String getDateFormat() { + return delegate.getDateFormat(); + } + + @Override + public String getNumberFormat() { + return delegate.getNumberFormat(); + } + + @Override + public String getLocale() { + return delegate.getLocale(); + } + + @Override + public TypeFactory getTypeFactory() { + return delegate.getTypeFactory(); + } + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java new file mode 100644 index 0000000000..ad488d1a8f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java @@ -0,0 +1,44 @@ +/* + * 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.internal.conversion; + +import java.util.Collections; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Strings; + +import static org.mapstruct.ap.internal.conversion.ReverseConversion.inverse; + +/** + * @author Filip Hrisafov + */ +public class TypeToOptionalConversion extends SimpleConversion { + + static final TypeToOptionalConversion TYPE_TO_OPTIONAL_CONVERSION = new TypeToOptionalConversion(); + static final ConversionProvider OPTIONAL_TO_TYPE_CONVERSION = inverse( TYPE_TO_OPTIONAL_CONVERSION ); + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().asRawType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.singleton( conversionContext.getTargetType().asRawType() ); + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + StringBuilder sb = new StringBuilder(".get"); + Type optionalBaseType = conversionContext.getSourceType().getOptionalBaseType(); + if ( optionalBaseType.isPrimitive() ) { + sb.append( "As" ).append( Strings.capitalize( optionalBaseType.getName() ) ); + } + return sb.append( "()" ).toString(); + } +} 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 086773dbe9..c6fc7753f2 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 @@ -20,6 +20,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -99,9 +100,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { private final BuilderType returnTypeBuilder; private final MethodReference finalizerMethod; private final String finalizedResultName; + private final String optionalResultName; private final List beforeMappingReferencesWithFinalizedReturnType; private final List afterMappingReferencesWithFinalizedReturnType; + private final List afterMappingReferencesWithOptionalReturnType; private final Type subclassExhaustiveException; + private final Map sourceParametersReassignments; private final MappingReferences mappingReferences; @@ -119,6 +123,7 @@ public static class Builder extends AbstractMappingMethodBuilder unprocessedSourceParameters = new HashSet<>(); private final Set existingVariableNames = new HashSet<>(); private final Map> unprocessedDefinedTargets = new LinkedHashMap<>(); + private final Map sourceParametersReassignments = new HashMap<>(); private MappingReferences mappingReferences; private List targetThisReferences; @@ -176,10 +181,18 @@ public BeanMappingMethod build() { if ( selectionParameters != null && selectionParameters.getResultType() != null ) { // This is a user-defined return type, which means we need to do some extra checks for it userDefinedReturnType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ); + returnTypeImpl = userDefinedReturnType; returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( userDefinedReturnType, builder ); } else { - returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( method.getReturnType(), builder ); + Type methodReturnType = method.getReturnType(); + if ( methodReturnType.isOptionalType() ) { + returnTypeImpl = methodReturnType.getOptionalBaseType(); + } + else { + returnTypeImpl = methodReturnType; + } + returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( returnTypeImpl, builder ); } if ( isBuilderRequired() ) { // the userDefinedReturn type can also require a builder. That buildertype is already set @@ -195,7 +208,6 @@ public BeanMappingMethod build() { } } else if ( userDefinedReturnType != null ) { - returnTypeImpl = userDefinedReturnType; initializeFactoryMethod( returnTypeImpl, selectionParameters ); if ( factoryMethod != null || canResultTypeFromBeanMappingBeConstructed( returnTypeImpl ) ) { returnTypeToConstruct = returnTypeImpl; @@ -205,7 +217,6 @@ else if ( userDefinedReturnType != null ) { } } else if ( !method.isUpdateMethod() ) { - returnTypeImpl = method.getReturnType(); initializeFactoryMethod( returnTypeImpl, selectionParameters ); if ( factoryMethod != null || allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed( returnTypeImpl ) @@ -266,12 +277,25 @@ else if ( !method.isUpdateMethod() ) { for ( Parameter sourceParameter : method.getSourceParameters() ) { unprocessedSourceParameters.add( sourceParameter ); - if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() || - sourceParameter.getType().isMapType() ) { + Type sourceParameterType = sourceParameter.getType(); + if ( sourceParameterType.isOptionalType() ) { + String sourceParameterValueName = Strings.getSafeVariableName( + sourceParameter.getName() + "Value", + existingVariableNames + ); + existingVariableNames.add( sourceParameterValueName ); + sourceParameterType = sourceParameterType.getOptionalBaseType(); + sourceParametersReassignments.put( + sourceParameter.getName(), + new Parameter( sourceParameterValueName, sourceParameter.getName(), sourceParameterType ) + ); + } + if ( sourceParameterType.isPrimitive() || sourceParameterType.isArrayType() || + sourceParameterType.isMapType() ) { continue; } - Map readAccessors = sourceParameter.getType().getPropertyReadAccessors(); + Map readAccessors = sourceParameterType.getPropertyReadAccessors(); unprocessedSourceProperties.putAll( readAccessors ); } @@ -362,12 +386,24 @@ else if ( !method.isUpdateMethod() ) { ctx, existingVariableNames ); + + Supplier> additionalAfterMappingParameterBindingsProvider = () -> + sourceParametersReassignments.values() + .stream() + .map( parameter -> method.getSourceParameters().size() == 1 ? + ParameterBinding.fromParameter( parameter ) : + ParameterBinding.fromTypeAndName( + parameter.getType(), + parameter.getOriginalName() + ".get()" + ) ) + .collect( Collectors.toList() ); List afterMappingMethods = LifecycleMethodResolver.afterMappingMethods( method, resultTypeToMap, selectionParameters, ctx, - existingVariableNames + existingVariableNames, + Collections::emptyList ); if ( method instanceof ForgedMethod ) { @@ -409,12 +445,15 @@ else if ( !method.isUpdateMethod() ) { if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) { finalizeMethod = getFinalizerMethod(); - Type actualReturnType = method.getReturnType(); + Type finalizerReturnType = method.getReturnType(); + if ( finalizerReturnType.isOptionalType() ) { + finalizerReturnType = finalizerReturnType.getOptionalBaseType(); + } beforeMappingReferencesWithFinalizedReturnType.addAll( filterMappingTarget( LifecycleMethodResolver.beforeMappingMethods( method, - actualReturnType, + finalizerReturnType, selectionParameters, ctx, existingVariableNames @@ -424,14 +463,33 @@ else if ( !method.isUpdateMethod() ) { afterMappingReferencesWithFinalizedReturnType.addAll( LifecycleMethodResolver.afterMappingMethods( method, - actualReturnType, + finalizerReturnType, + selectionParameters, + ctx, + existingVariableNames, + additionalAfterMappingParameterBindingsProvider + ) ); + + keepMappingReferencesUsingTarget( beforeMappingReferencesWithFinalizedReturnType, finalizerReturnType ); + keepMappingReferencesUsingTarget( afterMappingReferencesWithFinalizedReturnType, finalizerReturnType ); + } + + List afterMappingReferencesWithOptionalReturnType = new ArrayList<>(); + if ( method.getReturnType().isOptionalType() ) { + afterMappingReferencesWithOptionalReturnType.addAll( LifecycleMethodResolver.afterMappingMethods( + method, + method.getReturnType(), selectionParameters, ctx, - existingVariableNames + existingVariableNames, + additionalAfterMappingParameterBindingsProvider ) ); - keepMappingReferencesUsingTarget( beforeMappingReferencesWithFinalizedReturnType, actualReturnType ); - keepMappingReferencesUsingTarget( afterMappingReferencesWithFinalizedReturnType, actualReturnType ); + keepMappingReferencesUsingTarget( + afterMappingReferencesWithOptionalReturnType, + method.getReturnType() + ); + } Map presenceChecksByParameter = new LinkedHashMap<>(); @@ -461,11 +519,13 @@ else if ( !method.isUpdateMethod() ) { afterMappingMethods, beforeMappingReferencesWithFinalizedReturnType, afterMappingReferencesWithFinalizedReturnType, + afterMappingReferencesWithOptionalReturnType, finalizeMethod, mappingReferences, subclasses, presenceChecksByParameter, - subclassExhaustiveExceptionType + subclassExhaustiveExceptionType, + sourceParametersReassignments ); } @@ -609,8 +669,17 @@ private void initializeMappingReferencesIfNeeded(Type resultTypeToMap) { * builder is not assignable to the return type (so without building). */ private boolean isBuilderRequired() { - return returnTypeBuilder != null - && ( !method.isUpdateMethod() || !method.isMappingTargetAssignableToReturnType() ); + if ( returnTypeBuilder == null ) { + return false; + } + if ( method.isUpdateMethod() ) { + // when @MappingTarget annotated parameter is the same type as the return type. + return !method.getResultType().isAssignableTo( method.getReturnType() ); + } + else { + // For non-update methods a builder is required when returnTypeBuilder is set + return true; + } } private boolean shouldCallFinalizerMethod(Type returnTypeToConstruct ) { @@ -1510,6 +1579,7 @@ else if ( mapping.getJavaExpression() != null ) { if ( sourceRef != null ) { // sourceRef == null is not considered an error here if ( sourceRef.isValid() ) { + Parameter sourceParameter = sourceRef.getParameter(); // targetProperty == null can occur: we arrived here because we want as many errors // as possible before we stop analysing @@ -1518,7 +1588,8 @@ else if ( mapping.getJavaExpression() != null ) { .sourceMethod( method ) .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .sourcePropertyName( mapping.getSourceName() ) - .sourceReference( sourceRef ) + .sourceReference( sourceRef.withParameter( + sourceParametersReassignments.get( sourceParameter.getName() ) ) ) .selectionParameters( mapping.getSelectionParameters() ) .formattingParameters( mapping.getFormattingParameters() ) .existingVariableNames( existingVariableNames ) @@ -1530,7 +1601,6 @@ else if ( mapping.getJavaExpression() != null ) { .options( mapping ) .build(); handledTargets.add( targetPropertyName ); - Parameter sourceParameter = sourceRef.getParameter(); unprocessedSourceParameters.remove( sourceParameter ); // If the source parameter was directly mapped if ( sourceRef.getPropertyEntries().isEmpty() ) { @@ -1743,20 +1813,26 @@ private SourceReference getSourceRefByTargetName(Parameter sourceParameter, Stri SourceReference sourceRef = null; - if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() ) { + Type sourceParameterType = sourceParameter.getType(); + Parameter sourceParameterToUse = sourceParameter; + if ( sourceParameterType.isOptionalType() ) { + sourceParameterType = sourceParameterType.getOptionalBaseType(); + sourceParameterToUse = sourceParametersReassignments.get( sourceParameter.getName() ); + } + if ( sourceParameterType.isPrimitive() || sourceParameterType.isArrayType() ) { return sourceRef; } - ReadAccessor sourceReadAccessor = sourceParameter.getType() + ReadAccessor sourceReadAccessor = sourceParameterType .getReadAccessor( targetPropertyName, method.getSourceParameters().size() == 1 ); if ( sourceReadAccessor != null ) { // property mapping PresenceCheckAccessor sourcePresenceChecker = - sourceParameter.getType().getPresenceChecker( targetPropertyName ); + sourceParameterType.getPresenceChecker( targetPropertyName ); - DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); + DeclaredType declaredSourceType = (DeclaredType) sourceParameterType.getTypeMirror(); Type returnType = ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ); - sourceRef = new SourceReference.BuilderFromProperty().sourceParameter( sourceParameter ) + sourceRef = new SourceReference.BuilderFromProperty().sourceParameter( sourceParameterToUse ) .type( returnType ) .readAccessor( sourceReadAccessor ) .presenceChecker( sourcePresenceChecker ) @@ -2033,11 +2109,14 @@ private BeanMappingMethod(Method method, List afterMappingReferences, List beforeMappingReferencesWithFinalizedReturnType, List afterMappingReferencesWithFinalizedReturnType, + List afterMappingReferencesWithOptionalReturnType, MethodReference finalizerMethod, MappingReferences mappingReferences, List subclassMappings, Map presenceChecksByParameter, - Type subclassExhaustiveException) { + Type subclassExhaustiveException, + Map sourceParametersReassignments + ) { super( method, annotations, @@ -2057,14 +2136,21 @@ private BeanMappingMethod(Method method, this.finalizedResultName = Strings.getSafeVariableName( getResultName() + "Result", existingVariableNames ); existingVariableNames.add( this.finalizedResultName ); + this.optionalResultName = + Strings.getSafeVariableName( getResultName() + "ResultOptional", existingVariableNames ); + existingVariableNames.add( this.optionalResultName ); } else { this.finalizedResultName = null; + this.optionalResultName = + Strings.getSafeVariableName( getResultName() + "Optional", existingVariableNames ); + existingVariableNames.add( this.optionalResultName ); } this.mappingReferences = mappingReferences; this.beforeMappingReferencesWithFinalizedReturnType = beforeMappingReferencesWithFinalizedReturnType; this.afterMappingReferencesWithFinalizedReturnType = afterMappingReferencesWithFinalizedReturnType; + this.afterMappingReferencesWithOptionalReturnType = afterMappingReferencesWithOptionalReturnType; // initialize constant mappings as all mappings, but take out the ones that can be contributed to a // parameter mapping. @@ -2099,6 +2185,7 @@ else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { } this.returnTypeToConstruct = returnTypeToConstruct; this.subclassMappings = subclassMappings; + this.sourceParametersReassignments = sourceParametersReassignments; } public Type getSubclassExhaustiveException() { @@ -2121,6 +2208,18 @@ public String getFinalizedResultName() { return finalizedResultName; } + public Type getFinalizedReturnType() { + Type returnType = getReturnType(); + if ( returnType.isOptionalType() ) { + return returnType.getOptionalBaseType(); + } + return returnType; + } + + public String getOptionalResultName() { + return optionalResultName; + } + public List getBeforeMappingReferencesWithFinalizedReturnType() { return beforeMappingReferencesWithFinalizedReturnType; } @@ -2129,6 +2228,10 @@ public List getAfterMappingReferencesWithFinal return afterMappingReferencesWithFinalizedReturnType; } + public List getAfterMappingReferencesWithOptionalReturnType() { + return afterMappingReferencesWithOptionalReturnType; + } + public List propertyMappingsByParameter(Parameter parameter) { // issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value return mappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() ); @@ -2188,6 +2291,10 @@ public Set getImportTypes() { types.addAll( reference.getImportTypes() ); } + for ( LifecycleCallbackMethodReference reference : afterMappingReferencesWithOptionalReturnType ) { + types.addAll( reference.getImportTypes() ); + } + return types; } @@ -2215,6 +2322,10 @@ public List getSourceParametersNotNeedingPresenceCheck() { .collect( Collectors.toList() ); } + public Parameter getSourceParameterReassignment(Parameter parameter) { + return sourceParametersReassignments.get( parameter.getName() ); + } + private boolean needsPresenceCheck(Parameter parameter) { if ( !presenceChecksByParameter.containsKey( parameter.getName() ) ) { return false; @@ -2277,5 +2388,4 @@ public boolean equals(Object obj) { return true; } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java new file mode 100644 index 0000000000..f8c596a368 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java @@ -0,0 +1,113 @@ +/* + * 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.internal.model; + +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.assignment.OptionalGetWrapper; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * @author Filip Hrisafov + */ +public class FromOptionalTypeConversion extends ModelElement implements Assignment { + + private final Assignment conversionAssignment; + private final Type optionalType; + + public FromOptionalTypeConversion(Type optionalType, Assignment conversionAssignment) { + this.conversionAssignment = conversionAssignment; + this.optionalType = optionalType; + } + + @Override + public Set getImportTypes() { + return conversionAssignment.getImportTypes(); + } + + @Override + public List getThrownTypes() { + return conversionAssignment.getThrownTypes(); + } + + public Assignment getAssignment() { + return conversionAssignment; + } + + @Override + public String getSourceReference() { + return conversionAssignment.getSourceReference(); + } + + @Override + public boolean isSourceReferenceParameter() { + return conversionAssignment.isSourceReferenceParameter(); + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return conversionAssignment.getSourcePresenceCheckerReference(); + } + + @Override + public Type getSourceType() { + return conversionAssignment.getSourceType(); + } + + @Override + public String createUniqueVarName(String desiredName) { + return conversionAssignment.createUniqueVarName( desiredName ); + } + + @Override + public String getSourceLocalVarName() { + return conversionAssignment.getSourceLocalVarName(); + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + conversionAssignment.setSourceLocalVarName( sourceLocalVarName ); + } + + @Override + public String getSourceLoopVarName() { + return conversionAssignment.getSourceLoopVarName(); + } + + @Override + public void setSourceLoopVarName(String sourceLoopVarName) { + conversionAssignment.setSourceLoopVarName( sourceLoopVarName ); + } + + @Override + public String getSourceParameterName() { + return conversionAssignment.getSourceParameterName(); + } + + @Override + public void setAssignment(Assignment assignment) { + this.conversionAssignment.setAssignment( new OptionalGetWrapper( assignment, optionalType ) ); + } + + @Override + public AssignmentType getType() { + return conversionAssignment.getType(); + } + + @Override + public boolean isCallingUpdateMethod() { + return false; + } + + @Override + public String toString() { + return conversionAssignment.toString(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java index cfe4f9f8b7..ada9dac470 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -6,11 +6,14 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; @@ -63,13 +66,16 @@ public static List afterMappingMethods(Method Type alternativeTarget, SelectionParameters selectionParameters, MappingBuilderContext ctx, - Set existingVariableNames) { + Set existingVariableNames, + Supplier> parameterBindingsProvider) { return collectLifecycleCallbackMethods( method, alternativeTarget, selectionParameters, filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ), ctx, - existingVariableNames ); + existingVariableNames, + parameterBindingsProvider + ); } /** @@ -131,13 +137,34 @@ private static List getAllAvailableMethods(Method method, List collectLifecycleCallbackMethods( Method method, Type targetType, SelectionParameters selectionParameters, List callbackMethods, MappingBuilderContext ctx, Set existingVariableNames) { + return collectLifecycleCallbackMethods( + method, + targetType, + selectionParameters, + callbackMethods, + ctx, + existingVariableNames, + Collections::emptyList + ); + } + + private static List collectLifecycleCallbackMethods( + Method method, Type targetType, SelectionParameters selectionParameters, List callbackMethods, + MappingBuilderContext ctx, Set existingVariableNames, + Supplier> parameterBindingsProvider) { MethodSelectors selectors = new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), ctx.getOptions() ); List> matchingMethods = selectors.getMatchingMethods( callbackMethods, - SelectionContext.forLifecycleMethods( method, targetType, selectionParameters, ctx.getTypeFactory() ) + SelectionContext.forLifecycleMethods( + method, + targetType, + selectionParameters, + ctx.getTypeFactory(), + parameterBindingsProvider + ) ); return toLifecycleCallbackMethodRefs( 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 95c229c6e6..1ebf99ffeb 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 @@ -30,6 +30,7 @@ import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Services; import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.ap.internal.version.VersionInformation; import org.mapstruct.ap.spi.EnumMappingStrategy; import org.mapstruct.ap.spi.EnumTransformationStrategy; import org.mapstruct.ap.spi.MappingExclusionProvider; @@ -109,6 +110,7 @@ Assignment getTargetAssignment(Method mappingMethod, ForgedMethodHistory descrip private final ElementUtils elementUtils; private final TypeUtils typeUtils; private final FormattingMessager messager; + private final VersionInformation versionInformation; private final AccessorNamingUtils accessorNaming; private final EnumMappingStrategy enumMappingStrategy; private final Map enumTransformationStrategies; @@ -126,6 +128,7 @@ public MappingBuilderContext(TypeFactory typeFactory, ElementUtils elementUtils, TypeUtils typeUtils, FormattingMessager messager, + VersionInformation versionInformation, AccessorNamingUtils accessorNaming, EnumMappingStrategy enumMappingStrategy, Map enumTransformationStrategies, @@ -138,6 +141,7 @@ public MappingBuilderContext(TypeFactory typeFactory, this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.messager = messager; + this.versionInformation = versionInformation; this.accessorNaming = accessorNaming; this.enumMappingStrategy = enumMappingStrategy; this.enumTransformationStrategies = enumTransformationStrategies; @@ -190,6 +194,10 @@ public FormattingMessager getMessager() { return messager; } + public VersionInformation getVersionInformation() { + return versionInformation; + } + public AccessorNamingUtils getAccessorNaming() { return accessorNaming; } @@ -264,6 +272,9 @@ public boolean canGenerateAutoSubMappingBetween(Type sourceType, Type targetType * @return {@code true} if the type is not excluded from the {@link MappingExclusionProvider} */ private boolean canGenerateAutoSubMappingFor(Type type) { + if ( "java.util.Optional".equals( type.getFullyQualifiedName() ) ) { + return !SUB_MAPPING_EXCLUSION_PROVIDER.isExcluded( type.getOptionalBaseType().getTypeElement() ); + } return type.getTypeElement() != null && !SUB_MAPPING_EXCLUSION_PROVIDER.isExcluded( type.getTypeElement() ); } 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 599ddf1d35..0d04f5b6d5 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 @@ -97,7 +97,11 @@ else if ( getResultType().isArrayType() ) { return name; } else { - String name = getSafeVariableName( getResultType().getName(), existingVarNames ); + Type resultType = getResultType(); + if ( resultType.isOptionalType() ) { + resultType = resultType.getOptionalBaseType(); + } + String name = getSafeVariableName( resultType.getName(), existingVarNames ); existingVarNames.add( name ); return name; } 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 25c9f8fc03..ed841ba5bc 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 @@ -17,6 +17,7 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.presence.AnyPresenceChecksPresenceCheck; import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; @@ -71,21 +72,58 @@ public NestedPropertyMappingMethod build() { } String previousPropertyName = sourceParameter.getName(); + Type previousPropertyType = sourceParameter.getType(); for ( int i = 0; i < propertyEntries.size(); i++ ) { PropertyEntry propertyEntry = propertyEntries.get( i ); - PresenceCheck presenceCheck = getPresenceCheck( propertyEntry, previousPropertyName ); - - if ( i > 0 ) { - // If this is not the first property entry, - // then we might need to combine the presence check with a null check of the previous property - if ( presenceCheck != null ) { - presenceCheck = new AnyPresenceChecksPresenceCheck( Arrays.asList( - new NullPresenceCheck( previousPropertyName, true ), - presenceCheck - ) ); + PresenceCheck presenceCheck; + + if ( previousPropertyType.isOptionalType() ) { + String optionalValueSafeName = Strings.getSafeVariableName( + previousPropertyName + "Value", + existingVariableNames + ); + existingVariableNames.add( optionalValueSafeName ); + + presenceCheck = getPresenceCheck( propertyEntry, optionalValueSafeName ); + + String optionalValueSource = previousPropertyName + ".get()"; + boolean doesNotNeedFollowUpProperty = false; + if ( i == propertyEntries.size() - 1 ) { + // If this is the last property, and we do not have a presence check, + // then we do not need to assign the optional value + // e.g., we need to generate .get().getXxx(); + doesNotNeedFollowUpProperty = presenceCheck == null; + if ( doesNotNeedFollowUpProperty ) { + optionalValueSource += "." + propertyEntry.getReadAccessor().getReadValueSource(); + } } - else { - presenceCheck = new NullPresenceCheck( previousPropertyName, true ); + Type optionalBaseType = previousPropertyType.getOptionalBaseType(); + safePropertyEntries.add( new SafePropertyEntry( + optionalBaseType, + optionalValueSafeName, + optionalValueSource, + new OptionalPresenceCheck( previousPropertyName, ctx.getVersionInformation(), true ) + ) ); + if ( doesNotNeedFollowUpProperty ) { + break; + } + previousPropertyName = optionalValueSafeName; + + } + else { + presenceCheck = getPresenceCheck( propertyEntry, previousPropertyName ); + if ( i > 0 ) { + // If this is not the first property entry, + // then we might need to combine the presence check with a null check of the previous property + if ( presenceCheck != null ) { + presenceCheck = new AnyPresenceChecksPresenceCheck( Arrays.asList( + new NullPresenceCheck( previousPropertyName, true ), + presenceCheck + ) ); + } + else { + presenceCheck = new NullPresenceCheck( previousPropertyName, true ); + } } } @@ -101,6 +139,7 @@ public NestedPropertyMappingMethod build() { thrownTypes.addAll( ctx.getTypeFactory().getThrownTypes( propertyEntry.getReadAccessor() ) ); previousPropertyName = safeName; + previousPropertyType = propertyEntry.getType(); } method.addThrownTypes( thrownTypes ); return new NestedPropertyMappingMethod( method, safePropertyEntries ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 049e4caf37..784342b8d7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -13,6 +13,7 @@ import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; import org.mapstruct.ap.internal.model.source.SelectionParameters; @@ -87,7 +88,10 @@ public static PresenceCheck getPresenceCheckForSourceParameter( ); if ( matchingMethods.isEmpty() ) { - if ( !sourceParameter.getType().isPrimitive() ) { + if ( sourceParameter.getType().isOptionalType() ) { + return new OptionalPresenceCheck( sourceParameter.getName(), ctx.getVersionInformation() ); + } + else if ( !sourceParameter.getType().isPrimitive() ) { return new NullPresenceCheck( sourceParameter.getName() ); } return null; 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 08e8adbd1b..622ba9cef0 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 @@ -38,6 +38,7 @@ import org.mapstruct.ap.internal.model.presence.AllPresenceChecksPresenceCheck; import org.mapstruct.ap.internal.model.presence.JavaExpressionPresenceCheck; import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; import org.mapstruct.ap.internal.model.source.DelegatingOptions; import org.mapstruct.ap.internal.model.source.MappingControl; @@ -290,7 +291,7 @@ else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.get return new PropertyMapping( sourcePropertyName, targetPropertyName, - rightHandSide.getSourceParameterName(), + sourceReference.getParameter().getOriginalName(), targetWriteAccessor.getSimpleName(), targetReadAccessor, targetType, @@ -703,15 +704,26 @@ private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReferenc String variableName = sourceParam.getName() + "." + propertyEntry.getReadAccessor().getReadValueSource(); + Type variableType = propertyEntry.getType(); for (int i = 1; i < sourceReference.getPropertyEntries().size(); i++) { PropertyEntry entry = sourceReference.getPropertyEntries().get( i ); if (entry.getPresenceChecker() != null && entry.getReadAccessor() != null) { - presenceChecks.add( new NullPresenceCheck( variableName ) ); + if ( variableType.isOptionalType() ) { + presenceChecks.add( new OptionalPresenceCheck( + variableName, + ctx.getVersionInformation() + ) ); + variableName = variableName + ".get()"; + } + else { + presenceChecks.add( new NullPresenceCheck( variableName ) ); + } presenceChecks.add( new SuffixPresenceCheck( variableName, entry.getPresenceChecker().getPresenceCheckSuffix() ) ); variableName = variableName + "." + entry.getReadAccessor().getSimpleName() + "()"; + variableType = entry.getType(); } else { break; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java new file mode 100644 index 0000000000..5925540e10 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java @@ -0,0 +1,119 @@ +/* + * 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.internal.model; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * @author Filip Hrisafov + */ +public class ToOptionalTypeConversion extends ModelElement implements Assignment { + + private final Assignment conversionAssignment; + private final Type targetType; + + public ToOptionalTypeConversion(Type targetType, Assignment conversionAssignment) { + this.conversionAssignment = conversionAssignment; + this.targetType = targetType; + } + + public Type getTargetType() { + return targetType; + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>( conversionAssignment.getImportTypes() ); + importTypes.add( targetType ); + return importTypes; + } + + @Override + public List getThrownTypes() { + return conversionAssignment.getThrownTypes(); + } + + public Assignment getAssignment() { + return conversionAssignment; + } + + @Override + public String getSourceReference() { + return conversionAssignment.getSourceReference(); + } + + @Override + public boolean isSourceReferenceParameter() { + return conversionAssignment.isSourceReferenceParameter(); + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return conversionAssignment.getSourcePresenceCheckerReference(); + } + + @Override + public Type getSourceType() { + return conversionAssignment.getSourceType(); + } + + @Override + public String createUniqueVarName(String desiredName) { + return conversionAssignment.createUniqueVarName( desiredName ); + } + + @Override + public String getSourceLocalVarName() { + return conversionAssignment.getSourceLocalVarName(); + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + conversionAssignment.setSourceLocalVarName( sourceLocalVarName ); + } + + @Override + public String getSourceLoopVarName() { + return conversionAssignment.getSourceLoopVarName(); + } + + @Override + public void setSourceLoopVarName(String sourceLoopVarName) { + conversionAssignment.setSourceLoopVarName( sourceLoopVarName ); + } + + @Override + public String getSourceParameterName() { + return conversionAssignment.getSourceParameterName(); + } + + @Override + public void setAssignment(Assignment assignment) { + conversionAssignment.setAssignment( assignment ); + } + + @Override + public AssignmentType getType() { + return conversionAssignment.getType(); + } + + @Override + public boolean isCallingUpdateMethod() { + return false; + } + + @Override + public String toString() { + return targetType.getName() + ".of( " + conversionAssignment.toString() + " )"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java new file mode 100644 index 0000000000..9064640bb4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java @@ -0,0 +1,35 @@ +/* + * 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.internal.model.assignment; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Strings; + +/** + * @author Filip Hrisafov + */ +public class OptionalGetWrapper extends AssignmentWrapper { + + private final Type optionalType; + + public OptionalGetWrapper(Assignment decoratedAssignment, Type optionalType) { + super( decoratedAssignment, false ); + this.optionalType = optionalType; + } + + public Type getOptionalType() { + return optionalType; + } + + @Override + public String toString() { + if ( optionalType.getFullyQualifiedName().equals( "java.util.Optional" ) ) { + return getAssignment() + ".get()"; + } + return getAssignment() + ".getAs" + Strings.capitalize( optionalType.getOptionalBaseType().getName() ) + "()"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 73ec84b6b7..c37b12e311 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -325,6 +325,9 @@ private List matchWithSourceAccessorTypes(Type type, String[] ent for ( int i = 0; i < entryNames.length; i++ ) { boolean matchFound = false; Type noBoundsType = newType.withoutBounds(); + if ( noBoundsType.isOptionalType() ) { + noBoundsType = noBoundsType.getOptionalBaseType(); + } ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i], i > 0 || allowedMapToBean ); if ( readAccessor != null ) { PresenceCheckAccessor presenceChecker = noBoundsType.getPresenceChecker( entryNames[i] ); @@ -465,4 +468,11 @@ public List push(TypeFactory typeFactory, FormattingMessager me return result; } + public SourceReference withParameter(Parameter parameter) { + if ( parameter == null || getParameter() == parameter ) { + return this; + } + return new SourceReference( parameter, getPropertyEntries(), isValid() ); + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java index 96d3d6fe78..d3a29e30a4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java @@ -21,6 +21,13 @@ public interface ConversionContext { */ Type getTargetType(); + /** + * Returns the source type of this conversion. + * + * @return The source type of this conversion. + */ + Type getSourceType(); + /** * Returns the date format if this conversion or built-in method is from String to a date type (e.g. {@link Date}) * or vice versa. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java index 159f1663e2..4e5eed47a8 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java @@ -61,6 +61,11 @@ public Type getTargetType() { return targetType; } + @Override + public Type getSourceType() { + return sourceType; + } + @Override public String getNumberFormat() { return numberFormat; 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 aaab7f46ca..c5091869a6 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 @@ -51,12 +51,13 @@ private Parameter(Element element, Type type, boolean varArgs) { this.varArgs = varArgs; } - private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext, + private Parameter(String name, String originalName, Type type, boolean mappingTarget, boolean targetType, + boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName, boolean varArgs) { this.element = null; this.name = name; - this.originalName = name; + this.originalName = originalName; this.type = type; this.mappingTarget = mappingTarget; this.targetType = targetType; @@ -67,7 +68,11 @@ private Parameter(String name, Type type, boolean mappingTarget, boolean targetT } public Parameter(String name, Type type) { - this( name, type, false, false, false, false, false, false ); + this( name, name, type ); + } + + public Parameter(String name, String originalName, Type type) { + this( name, originalName, type, false, false, false, false, false, false ); } public Element getElement() { @@ -141,6 +146,20 @@ public boolean isSourceParameter() { !isTargetPropertyName(); } + public Parameter withName(String name) { + return new Parameter( + name, + this.name, + type, + mappingTarget, + targetType, + mappingContext, + sourcePropertyName, + targetPropertyName, + varArgs + ); + } + @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; @@ -176,6 +195,7 @@ public static Parameter forElementAndType(VariableElement element, Type paramete public static Parameter forForgedMappingTarget(Type parameterType) { return new Parameter( + "mappingTarget", "mappingTarget", parameterType, true, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 8fb10090c2..29bb3cd906 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -397,11 +397,32 @@ private boolean isType(Class type) { return type.getName().equals( getFullyQualifiedName() ); } - private boolean isOptionalType() { + public boolean isOptionalType() { return isType( Optional.class ) || isType( OptionalInt.class ) || isType( OptionalDouble.class ) || isType( OptionalLong.class ); } + public Type getOptionalBaseType() { + if ( isType( Optional.class ) ) { + return getTypeParameters().get( 0 ); + } + + if ( isType( OptionalInt.class ) ) { + return typeFactory.getType( int.class ); + } + + if ( isType( OptionalDouble.class ) ) { + return typeFactory.getType( double.class ); + } + + if ( isType( OptionalLong.class ) ) { + return typeFactory.getType( long.class ); + } + + throw new IllegalStateException( "getOptionalBaseType should only be called for Optional types." ); + + } + public boolean isTypeVar() { return (typeMirror.getKind() == TypeKind.TYPEVAR); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java new file mode 100644 index 0000000000..d727c30f24 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java @@ -0,0 +1,77 @@ +/* + * 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.internal.model.presence; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.version.VersionInformation; + +/** + * Presence checker for {@link java.util.Optional} types. + * + * @author Ken Wang + */ +public class OptionalPresenceCheck extends ModelElement implements PresenceCheck { + + private final String sourceReference; + private final VersionInformation versionInformation; + private final boolean negate; + + public OptionalPresenceCheck(String sourceReference, VersionInformation versionInformation) { + this( sourceReference, versionInformation, false ); + } + + public OptionalPresenceCheck(String sourceReference, VersionInformation versionInformation, boolean negate) { + this.sourceReference = sourceReference; + this.versionInformation = versionInformation; + this.negate = negate; + } + + public String getSourceReference() { + return sourceReference; + } + + public VersionInformation getVersionInformation() { + return versionInformation; + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + + public boolean isNegate() { + return negate; + } + + @Override + public PresenceCheck negate() { + return new OptionalPresenceCheck( sourceReference, versionInformation, !negate ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + OptionalPresenceCheck that = (OptionalPresenceCheck) o; + return Objects.equals( sourceReference, that.sourceReference ); + } + + @Override + public int hashCode() { + return Objects.hash( sourceReference ); + } + +} 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 ba32b12b46..d9c9ce41fc 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 @@ -182,15 +182,6 @@ default ConditionMethodOptions getConditionOptions() { return ConditionMethodOptions.empty(); } - /** - * - * @return true when @MappingTarget annotated parameter is the same type as the return type. The method has - * to be an update method in order for this to be true. - */ - default boolean isMappingTargetAssignableToReturnType() { - return isUpdateMethod() && getResultType().isAssignableTo( getReturnType() ); - } - /** * @return the first source type, intended for mapping methods from single source to target */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java index 84bd04d7db..79351ae868 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java @@ -106,8 +106,9 @@ public static SelectionContext forMappingMethods(Method mappingMethod, Type sour } public static SelectionContext forLifecycleMethods(Method mappingMethod, Type targetType, - SelectionParameters selectionParameters, - TypeFactory typeFactory) { + SelectionParameters selectionParameters, + TypeFactory typeFactory, + Supplier> additionalParameterBindingsProvider) { SelectionCriteria criteria = SelectionCriteria.forLifecycleMethods( selectionParameters ); return new SelectionContext( null, @@ -115,12 +116,16 @@ public static SelectionContext forLifecycleMethods(Method mappingMethod, Type ta mappingMethod, targetType, mappingMethod.getResultType(), - () -> getAvailableParameterBindingsFromMethod( - mappingMethod, - targetType, - criteria.getSourceRHS(), - typeFactory - ) + () -> { + List parameterBindings = getAvailableParameterBindingsFromMethod( + mappingMethod, + targetType, + criteria.getSourceRHS(), + typeFactory + ); + parameterBindings.addAll( additionalParameterBindingsProvider.get() ); + return parameterBindings; + } ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java index 055bfe6095..0457a66da4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java @@ -41,6 +41,7 @@ public class DefaultVersionInformation implements VersionInformation { private final String runtimeVendor; private final String compiler; private final boolean sourceVersionAtLeast9; + private final boolean sourceVersionAtLeast11; private final boolean sourceVersionAtLeast19; private final boolean eclipseJDT; private final boolean javac; @@ -54,6 +55,7 @@ public class DefaultVersionInformation implements VersionInformation { this.javac = compiler.startsWith( COMPILER_NAME_JAVAC ); // If the difference between the source version and RELEASE_6 is more that 2 than we are at least on 9 this.sourceVersionAtLeast9 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 2; + this.sourceVersionAtLeast11 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 4; this.sourceVersionAtLeast19 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 12; } @@ -82,6 +84,11 @@ public boolean isSourceVersionAtLeast9() { return sourceVersionAtLeast9; } + @Override + public boolean isSourceVersionAtLeast11() { + return sourceVersionAtLeast11; + } + @Override public boolean isSourceVersionAtLeast19() { return sourceVersionAtLeast19; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index ba24e868aa..03e8bf977f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -119,6 +119,7 @@ public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, L elementUtils, typeUtils, messager, + versionInformation, accessorNaming, context.getEnumMappingStrategy(), context.getEnumTransformationStrategies(), 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 ba903b6048..2a36f6b890 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 @@ -464,7 +464,7 @@ private ConversionAssignment resolveViaConversion(Type sourceType, Type targetTy Assignment conversion = conversionProvider.to( ctx ); if ( conversion != null ) { - return new ConversionAssignment( sourceType, targetType, conversionProvider.to( ctx ) ); + return new ConversionAssignment( sourceType, targetType, conversion ); } return null; } @@ -673,7 +673,16 @@ Assignment getAssignment() { void reportMessageWhenNarrowing(FormattingMessager messager, ResolvingAttempt attempt) { - if ( NativeTypes.isNarrowing( sourceType.getFullyQualifiedName(), targetType.getFullyQualifiedName() ) ) { + Type source = sourceType; + if ( sourceType.isOptionalType() ) { + source = sourceType.getOptionalBaseType(); + } + + Type target = targetType; + if ( targetType.isOptionalType() ) { + target = targetType.getOptionalBaseType(); + } + if ( NativeTypes.isNarrowing( source.getFullyQualifiedName(), target.getFullyQualifiedName() ) ) { ReportingPolicyGem policy = attempt.mappingMethod.getOptions().getMapper().typeConversionPolicy(); if ( policy == ReportingPolicyGem.WARN ) { report( messager, attempt, Message.CONVERSION_LOSSY_WARNING ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java index 5e1972fcae..411e11bf9e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java @@ -21,6 +21,8 @@ public interface VersionInformation { boolean isSourceVersionAtLeast9(); + boolean isSourceVersionAtLeast11(); + boolean isSourceVersionAtLeast19(); boolean isEclipseJDTCompiler(); diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 059e2d77d4..4ad6ae20db 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -21,14 +21,22 @@ <#list beforeMappingReferencesWithFinalizedReturnType as callback> - <@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/> + <@includeModel object=callback targetBeanName=finalizedResultName targetType=finalizedReturnType/> <#if !callback_has_next> <#if !mapNullToDefault && !sourcePresenceChecks.empty> if ( <#list sourcePresenceChecks as sourcePresenceCheck><@includeModel object=sourcePresenceCheck.negate() /><#if sourcePresenceCheck_has_next> && ) { - return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /><#else>null; + <#if returnType.name == "void"> + return; + <#else> + <#if existingInstanceMapping> + <@createReturn applyOptionalAfterMapping=false>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /> + <#else> + return ${returnType.null}; + + } @@ -52,6 +60,11 @@ <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParam)!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParam.name}.get(); + + <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -71,6 +84,11 @@ <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParameters[0])!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParameters[0].name}.get(); + + <#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -102,6 +120,11 @@ <#list sourceParametersNeedingPresenceCheck as sourceParam> <#if (propertyMappingsByParameter(sourceParam)?size > 0)> if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParam)!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParam.name}.get(); + + <#list propertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -117,6 +140,11 @@ <#elseif !propertyMappingsByParameter(sourceParameters[0]).empty> <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParameters[0])!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParameters[0].name}.get(); + + <#list propertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> @@ -133,24 +161,24 @@ <#if returnType.name != "void"> - <#if finalizerMethod??> - <#if (afterMappingReferencesWithFinalizedReturnType?size > 0)> - <@includeModel object=returnType /> ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; + <#if finalizerMethod??> + <#if (afterMappingReferencesWithFinalizedReturnType?size > 0)> + <@includeModel object=finalizedReturnType /> ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; - <#list afterMappingReferencesWithFinalizedReturnType as callback> - <#if callback_index = 0> + <#list afterMappingReferencesWithFinalizedReturnType as callback> + <#if callback_index = 0> - - <@includeModel object=callback targetBeanName=finalizedResultName targetType=returnType/> - + + <@includeModel object=callback targetBeanName=finalizedResultName targetType=finalizedReturnType/> + - return ${finalizedResultName}; + <@createReturn>${finalizedResultName} + <#else> + <@createReturn>${resultName}.<@includeModel object=finalizerMethod /> + <#else> - return ${resultName}.<@includeModel object=finalizerMethod />; + <@createReturn>${resultName} - <#else> - return ${resultName}; - <#if hasSubclassMappings()> @@ -164,4 +192,27 @@ <#if exceptionType_has_next>, <#t> + +<#macro createReturn applyOptionalAfterMapping=true> +<#-- <@compress single_line=true>--> + <#if returnType.optionalType> + <#if (afterMappingReferencesWithOptionalReturnType?size > 0)> + <@includeModel object=returnType /> ${optionalResultName} = <@includeModel object=returnType.asRawType()/>.of( <#nested/> ); + + <#list afterMappingReferencesWithOptionalReturnType as callback> + <#if callback_index = 0> + + + <@includeModel object=callback targetBeanName=optionalResultName targetType=returnType/> + + + return ${optionalResultName}; + <#else> + return <@includeModel object=returnType.asRawType()/>.of( <#nested/> ); + + <#else> + return <#nested/>; + +<#-- --> + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl new file mode 100644 index 0000000000..50ab97e5b4 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl @@ -0,0 +1,16 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.FromOptionalTypeConversion" --> +<@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl new file mode 100644 index 0000000000..48e20e7017 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl @@ -0,0 +1,21 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.ToOptionalTypeConversion" --> +<@compress single_line=true> +<@includeModel object=targetType.asRawType() />.of( <@_assignment/> ) +<#macro _assignment> + <@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> + + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl new file mode 100644 index 0000000000..7bbc089d7c --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl @@ -0,0 +1,15 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.OptionalGetWrapper" --> +<@compress single_line=true> +<#if optionalType.optionalBaseType.isPrimitive()> +${assignment}.getAs${optionalType.optionalBaseType.name?cap_first}() +<#else> +${assignment}.get() + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index ea8eed2438..d8ed4b53f6 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -6,6 +6,8 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.UpdateWrapper" --> +<#-- @ftlvariable name="ext" type="java.util.Map" --> +<#-- @ftlvariable name="ext.targetType" type="org.mapstruct.ap.internal.model.common.Type" --> <#import '../macro/CommonMacros.ftl' as lib > <@lib.handleExceptions> <#if includeSourceNullCheck> @@ -16,7 +18,7 @@ } <#if setExplicitlyToDefault || setExplicitlyToNull> else { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>null; + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; } <#else> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index 278b441aa9..9c4ad26e03 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -13,6 +13,8 @@ requires: caller to implement boolean:getIncludeSourceNullCheck() --> +<#-- @ftlvariable name="ext" type="java.util.Map" --> +<#-- @ftlvariable name="ext.targetType" type="org.mapstruct.ap.internal.model.common.Type" --> <#macro handleSourceReferenceNullCheck> <#if sourcePresenceCheckerReference??> if ( <@includeModel object=sourcePresenceCheckerReference @@ -23,7 +25,7 @@ } <@elseDefaultAssignment/> <#elseif includeSourceNullCheck || ext.defaultValueAssignment??> - if ( <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} != null ) { + if ( <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference}<#if sourceType.optionalType>.isPresent()<#else> != null ) { <#nested> } <@elseDefaultAssignment/> @@ -43,7 +45,7 @@ } <#elseif setExplicitlyToDefault || setExplicitlyToNull> else { - <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>null; + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; } @@ -156,7 +158,7 @@ Performs a default assignment with a default value. <#if factoryMethod??> <@includeModel object=factoryMethod targetType=ext.targetType/> <#else> - <@constructTargetObject/> + <@constructTargetObject targetType=ext.targetType/> <#-- @@ -164,15 +166,18 @@ Performs a default assignment with a default value. purpose: Either call the constructor of the target object directly or of the implementing type. --> -<#macro constructTargetObject><@compress single_line=true> - <#if ext.targetType.implementationType??> - new <@includeModel object=ext.targetType.implementationType/>() - <#elseif ext.targetType.arrayType> - new <@includeModel object=ext.targetType.componentType/>[0] - <#elseif ext.targetType.sensibleDefault??> - ${ext.targetType.sensibleDefault} +<#-- @ftlvariable name="targetType" type="org.mapstruct.ap.internal.model.common.Type" --> +<#macro constructTargetObject targetType><@compress single_line=true> + <#if targetType.implementationType??> + new <@includeModel object=targetType.implementationType/>() + <#elseif targetType.arrayType> + new <@includeModel object=targetType.componentType/>[0] + <#elseif targetType.sensibleDefault??> + ${targetType.sensibleDefault} + <#elseif targetType.optionalType> + <@includeModel object=targetType.asRawType()/>.of( <@constructTargetObject targetType=targetType.optionalBaseType/> ) <#else> - new <@includeModel object=ext.targetType/>() + new <@includeModel object=targetType/>() <#-- @@ -181,6 +186,7 @@ Performs a default assignment with a default value. purpose: assignment for source local variables. The sourceLocalVarName replaces the sourceReference in the assignmentcall. --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.Assignment" --> <#macro sourceLocalVarAssignment> <#if sourceLocalVarName??> <@includeModel object=sourceType/> ${sourceLocalVarName} = ${sourceReference}; diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl new file mode 100644 index 0000000000..c321ce20ec --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl @@ -0,0 +1,19 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck" --> +<@compress single_line=true> + <#if isNegate()> + <#if versionInformation.isSourceVersionAtLeast11()> + ${sourceReference}.isEmpty() + <#else> + !${sourceReference}.isPresent() + + <#else> + ${sourceReference}.isPresent() + + \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java new file mode 100644 index 0000000000..e1e557a897 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java @@ -0,0 +1,73 @@ +/* + * 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.conversion._enum; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@WithClasses({ + OptionalEnumToIntegerSource.class, + EnumToIntegerTarget.class, + OptionalEnumToIntegerMapper.class, + EnumToIntegerEnum.class +}) +public class OptionalEnumToIntegerConversionTest { + + @ProcessorTest + public void shouldApplyEnumToIntegerConversion() { + OptionalEnumToIntegerSource source = new OptionalEnumToIntegerSource(); + + for ( EnumToIntegerEnum value : EnumToIntegerEnum.values() ) { + source.setEnumValue( Optional.of( value ) ); + + EnumToIntegerTarget target = OptionalEnumToIntegerMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getEnumValue() ).isEqualTo( value.ordinal() ); + } + } + + @ProcessorTest + public void shouldApplyReverseEnumToIntegerConversion() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + EnumToIntegerEnum[] enumValues = EnumToIntegerEnum.values(); + int numberOfValues = enumValues.length; + for ( int value = 0; value < numberOfValues; value++ ) { + target.setEnumValue( value ); + + OptionalEnumToIntegerSource source = OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).contains( enumValues[target.getEnumValue()] ); + } + } + + @ProcessorTest + public void shouldHandleOutOfBoundsEnumOrdinal() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + target.setInvalidEnumValue( EnumToIntegerEnum.values().length + 1 ); + + assertThatThrownBy( () -> OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( ArrayIndexOutOfBoundsException.class ); + } + + @ProcessorTest + public void shouldHandleNullIntegerValue() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + OptionalEnumToIntegerSource source = OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).isEmpty(); + assertThat( source.getInvalidEnumValue() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java new file mode 100644 index 0000000000..d07d9b879c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java @@ -0,0 +1,19 @@ +/* + * 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.conversion._enum; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalEnumToIntegerMapper { + OptionalEnumToIntegerMapper INSTANCE = Mappers.getMapper( OptionalEnumToIntegerMapper.class ); + + EnumToIntegerTarget sourceToTarget(OptionalEnumToIntegerSource source); + + OptionalEnumToIntegerSource targetToSource(EnumToIntegerTarget target); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java new file mode 100644 index 0000000000..700c3198bc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java @@ -0,0 +1,31 @@ +/* + * 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.conversion._enum; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OptionalEnumToIntegerSource { + private Optional enumValue = Optional.empty(); + + private Optional invalidEnumValue = Optional.empty(); + + public Optional getEnumValue() { + return enumValue; + } + + public void setEnumValue(Optional enumValue) { + this.enumValue = enumValue; + } + + public Optional getInvalidEnumValue() { + return invalidEnumValue; + } + + public void setInvalidEnumValue(Optional invalidEnumValue) { + this.invalidEnumValue = invalidEnumValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java new file mode 100644 index 0000000000..986de6a0ed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java @@ -0,0 +1,19 @@ +/* + * 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.conversion.bignumbers; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BigDecimalOptionalMapper { + + BigDecimalOptionalMapper INSTANCE = Mappers.getMapper( BigDecimalOptionalMapper.class ); + + BigDecimalTarget sourceToTarget(BigDecimalOptionalSource source); + + BigDecimalOptionalSource targetToSource(BigDecimalTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java new file mode 100644 index 0000000000..bf4e53e5d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java @@ -0,0 +1,139 @@ +/* + * 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.conversion.bignumbers; + +import java.math.BigDecimal; +import java.util.Optional; + +public class BigDecimalOptionalSource { + + private Optional b; + private Optional bb; + private Optional s; + private Optional ss; + private Optional i; + private Optional ii; + private Optional l; + private Optional ll; + private Optional f; + private Optional ff; + private Optional d; + private Optional dd; + private Optional string; + private Optional bigInteger; + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } + + public Optional getString() { + return string; + } + + public void setString(Optional string) { + this.string = string; + } + + public Optional getBigInteger() { + return bigInteger; + } + + public void setBigInteger(Optional bigInteger) { + this.bigInteger = bigInteger; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java new file mode 100644 index 0000000000..d39b48c7e5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java @@ -0,0 +1,19 @@ +/* + * 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.conversion.bignumbers; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BigIntegerOptionalMapper { + + BigIntegerOptionalMapper INSTANCE = Mappers.getMapper( BigIntegerOptionalMapper.class ); + + BigIntegerTarget sourceToTarget(BigIntegerOptionalSource source); + + BigIntegerOptionalSource targetToSource(BigIntegerTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java new file mode 100644 index 0000000000..f4bb0f03c8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java @@ -0,0 +1,131 @@ +/* + * 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.conversion.bignumbers; + +import java.math.BigInteger; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class BigIntegerOptionalSource { + + private Optional b; + private Optional bb; + private Optional s; + private Optional ss; + private Optional i; + private Optional ii; + private Optional l; + private Optional ll; + private Optional f; + private Optional ff; + private Optional d; + private Optional dd; + private Optional string; + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } + + public Optional getString() { + return string; + } + + public void setString(Optional string) { + this.string = string; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java index 69f87f3980..2ddddec02e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Optional; import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.testutil.IssueKey; @@ -100,6 +101,79 @@ public void shouldApplyReverseBigIntegerConversions() { assertThat( source.getString() ).isEqualTo( new BigInteger( "13" ) ); } + @ProcessorTest + @WithClasses({ BigIntegerOptionalSource.class, BigIntegerTarget.class, BigIntegerOptionalMapper.class }) + public void shouldApplyOptionalBigIntegerConversions() { + BigIntegerOptionalSource source = new BigIntegerOptionalSource(); + source.setB( Optional.of( new BigInteger( "1" ) ) ); + source.setBb( Optional.of( new BigInteger( "2" ) ) ); + source.setS( Optional.of( new BigInteger( "3" ) ) ); + source.setSs( Optional.of( new BigInteger( "4" ) ) ); + source.setI( Optional.of( new BigInteger( "5" ) ) ); + source.setIi( Optional.of( new BigInteger( "6" ) ) ); + source.setL( Optional.of( new BigInteger( "7" ) ) ); + source.setLl( Optional.of( new BigInteger( "8" ) ) ); + source.setF( Optional.of( new BigInteger( "9" ) ) ); + source.setFf( Optional.of( new BigInteger( "10" ) ) ); + source.setD( Optional.of( new BigInteger( "11" ) ) ); + source.setDd( Optional.of( new BigInteger( "12" ) ) ); + source.setString( Optional.of( new BigInteger( "13" ) ) ); + + BigIntegerTarget target = BigIntegerOptionalMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( (byte) 2 ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( (short) 4 ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( 6 ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( 8 ); + assertThat( target.getF() ).isEqualTo( 9.0f ); + assertThat( target.getFf() ).isEqualTo( 10.0f ); + assertThat( target.getD() ).isEqualTo( 11.0d ); + assertThat( target.getDd() ).isEqualTo( 12.0d ); + assertThat( target.getString() ).isEqualTo( "13" ); + } + + @ProcessorTest + @IssueKey("21") + @WithClasses({ BigIntegerOptionalSource.class, BigIntegerTarget.class, BigIntegerOptionalMapper.class }) + public void shouldApplyReverseOptionalBigIntegerConversions() { + BigIntegerTarget target = new BigIntegerTarget(); + target.setB( (byte) 1 ); + target.setBb( (byte) 2 ); + target.setS( (short) 3 ); + target.setSs( (short) 4 ); + target.setI( 5 ); + target.setIi( 6 ); + target.setL( 7 ); + target.setLl( 8L ); + target.setF( 9.0f ); + target.setFf( 10.0f ); + target.setD( 11.0d ); + target.setDd( 12.0d ); + target.setString( "13" ); + + BigIntegerOptionalSource source = BigIntegerOptionalMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getB() ).contains( new BigInteger( "1" ) ); + assertThat( source.getBb() ).contains( new BigInteger( "2" ) ); + assertThat( source.getS() ).contains( new BigInteger( "3" ) ); + assertThat( source.getSs() ).contains( new BigInteger( "4" ) ); + assertThat( source.getI() ).contains( new BigInteger( "5" ) ); + assertThat( source.getIi() ).contains( new BigInteger( "6" ) ); + assertThat( source.getL() ).contains( new BigInteger( "7" ) ); + assertThat( source.getLl() ).contains( new BigInteger( "8" ) ); + assertThat( source.getF() ).contains( new BigInteger( "9" ) ); + assertThat( source.getFf() ).contains( new BigInteger( "10" ) ); + assertThat( source.getD() ).contains( new BigInteger( "11" ) ); + assertThat( source.getDd() ).contains( new BigInteger( "12" ) ); + assertThat( source.getString() ).contains( new BigInteger( "13" ) ); + } + @ProcessorTest @IssueKey("21") @WithClasses({ BigDecimalSource.class, BigDecimalTarget.class, BigDecimalMapper.class }) @@ -178,6 +252,82 @@ public void shouldApplyReverseBigDecimalConversions() { assertThat( source.getBigInteger() ).isEqualTo( new BigDecimal( "14" ) ); } + @ProcessorTest + @WithClasses({ BigDecimalOptionalSource.class, BigDecimalTarget.class, BigDecimalOptionalMapper.class }) + public void shouldApplyOptionalBigDecimalConversions() { + BigDecimalOptionalSource source = new BigDecimalOptionalSource(); + source.setB( Optional.of( new BigDecimal( "1.45" ) ) ); + source.setBb( Optional.of( new BigDecimal( "2.45" ) ) ); + source.setS( Optional.of( new BigDecimal( "3.45" ) ) ); + source.setSs( Optional.of( new BigDecimal( "4.45" ) ) ); + source.setI( Optional.of( new BigDecimal( "5.45" ) ) ); + source.setIi( Optional.of( new BigDecimal( "6.45" ) ) ); + source.setL( Optional.of( new BigDecimal( "7.45" ) ) ); + source.setLl( Optional.of( new BigDecimal( "8.45" ) ) ); + source.setF( Optional.of( new BigDecimal( "9.45" ) ) ); + source.setFf( Optional.of( new BigDecimal( "10.45" ) ) ); + source.setD( Optional.of( new BigDecimal( "11.45" ) ) ); + source.setDd( Optional.of( new BigDecimal( "12.45" ) ) ); + source.setString( Optional.of( new BigDecimal( "13.45" ) ) ); + source.setBigInteger( Optional.of( new BigDecimal( "14.45" ) ) ); + + BigDecimalTarget target = BigDecimalOptionalMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( (byte) 2 ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( (short) 4 ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( 6 ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( 8 ); + assertThat( target.getF() ).isEqualTo( 9.45f ); + assertThat( target.getFf() ).isEqualTo( 10.45f ); + assertThat( target.getD() ).isEqualTo( 11.45d ); + assertThat( target.getDd() ).isEqualTo( 12.45d ); + assertThat( target.getString() ).isEqualTo( "13.45" ); + assertThat( target.getBigInteger() ).isEqualTo( new BigInteger( "14" ) ); + } + + @ProcessorTest + @WithClasses({ BigDecimalOptionalSource.class, BigDecimalTarget.class, BigDecimalOptionalMapper.class }) + public void shouldApplyReverseOptionalBigDecimalConversions() { + BigDecimalTarget target = new BigDecimalTarget(); + target.setB( (byte) 1 ); + target.setBb( (byte) 2 ); + target.setS( (short) 3 ); + target.setSs( (short) 4 ); + target.setI( 5 ); + target.setIi( 6 ); + target.setL( 7 ); + target.setLl( 8L ); + target.setF( 9.0f ); + target.setFf( 10.0f ); + target.setD( 11.0d ); + target.setDd( 12.0d ); + target.setString( "13.45" ); + target.setBigInteger( new BigInteger( "14" ) ); + + BigDecimalOptionalSource source = BigDecimalOptionalMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getB() ).contains( new BigDecimal( "1" ) ); + assertThat( source.getBb() ).contains( new BigDecimal( "2" ) ); + assertThat( source.getS() ).contains( new BigDecimal( "3" ) ); + assertThat( source.getSs() ).contains( new BigDecimal( "4" ) ); + assertThat( source.getI() ).contains( new BigDecimal( "5" ) ); + assertThat( source.getIi() ).contains( new BigDecimal( "6" ) ); + assertThat( source.getL() ).contains( new BigDecimal( "7" ) ); + assertThat( source.getLl() ).contains( new BigDecimal( "8" ) ); + assertThat( source.getF() ).contains( new BigDecimal( "9.0" ) ); + assertThat( source.getFf() ).contains( new BigDecimal( "10.0" ) ); + assertThat( source.getD() ).contains( new BigDecimal( "11.0" ) ); + assertThat( source.getDd() ).contains( new BigDecimal( "12.0" ) ); + assertThat( source.getString() ).contains( new BigDecimal( "13.45" ) ); + assertThat( source.getBigInteger() ).contains( new BigDecimal( "14" ) ); + } + @ProcessorTest @IssueKey("1009") @WithClasses({ BigIntegerSource.class, BigIntegerTarget.class, BigIntegerMapper.class }) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java new file mode 100644 index 0000000000..1a9bb50b67 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java @@ -0,0 +1,466 @@ +/* + * 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.conversion.java8time; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.Optional; +import java.util.TimeZone; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.DefaultTimeZone; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for conversions to/from Java 8 date and time types wrapped in {@link Optional}. + */ +@WithClasses({ OptionalSource.class, Target.class, OptionalSourceTargetMapper.class }) +public class Java8OptionalTimeConversionTest { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalSourceTargetMapper.class ); + } + + @ProcessorTest + public void testDateTimeToString() { + OptionalSource src = new OptionalSource(); + src.setZonedDateTime( Optional.of( ZonedDateTime.of( + LocalDateTime.of( 2014, 1, 1, 0, 0 ), + ZoneId.of( "UTC" ) + ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDateTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + } + + @ProcessorTest + public void testLocalDateTimeToString() { + OptionalSource src = new OptionalSource(); + src.setLocalDateTime( Optional.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalDateTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + } + + @ProcessorTest + public void testLocalDateToString() { + OptionalSource src = new OptionalSource(); + src.setLocalDate( Optional.of( LocalDate.of( 2014, 1, 1 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalDateMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + } + + @ProcessorTest + public void testLocalTimeToString() { + OptionalSource src = new OptionalSource(); + src.setLocalTime( Optional.ofNullable( LocalTime.of( 0, 0 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + } + + @ProcessorTest + public void testSourceToTargetMappingForStrings() { + OptionalSource src = new OptionalSource(); + src.setLocalTime( Optional.ofNullable( LocalTime.of( 0, 0 ) ) ); + src.setLocalDate( Optional.of( LocalDate.of( 2014, 1, 1 ) ) ); + src.setLocalDateTime( Optional.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ) ); + src.setZonedDateTime( Optional.of( ZonedDateTime.of( + LocalDateTime.of( 2014, 1, 1, 0, 0 ), + ZoneId.of( "UTC" ) + ) ) ); + + // with given format + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( src ); + + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + + // and now with default mappings + target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( src ); + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + } + + @ProcessorTest + public void testStringToDateTime() { + String dateTimeAsString = "01.01.2014 00:00 UTC"; + Target target = new Target(); + target.setZonedDateTime( dateTimeAsString ); + ZonedDateTime sourceDateTime = + ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceDateTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getZonedDateTime() ).contains( sourceDateTime ); + } + + @ProcessorTest + public void testStringToLocalDateTime() { + String dateTimeAsString = "01.01.2014 00:00"; + Target target = new Target(); + target.setLocalDateTime( dateTimeAsString ); + LocalDateTime sourceDateTime = + LocalDateTime.of( 2014, 1, 1, 0, 0, 0 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalDateTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalDateTime() ).contains( sourceDateTime ); + } + + @ProcessorTest + public void testStringToLocalDate() { + String dateTimeAsString = "01.01.2014"; + Target target = new Target(); + target.setLocalDate( dateTimeAsString ); + LocalDate sourceDate = + LocalDate.of( 2014, 1, 1 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalDateMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalDate() ).contains( sourceDate ); + } + + @ProcessorTest + public void testStringToLocalTime() { + String dateTimeAsString = "00:00"; + Target target = new Target(); + target.setLocalTime( dateTimeAsString ); + LocalTime sourceTime = + LocalTime.of( 0, 0 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalTime() ).contains( sourceTime ); + } + + @ProcessorTest + public void testTargetToSourceNullMapping() { + Target target = new Target(); + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( src ).isNotNull(); + assertThat( src.getZonedDateTime() ).isEmpty(); + assertThat( src.getLocalDate() ).isEmpty(); + assertThat( src.getLocalDateTime() ).isEmpty(); + assertThat( src.getLocalTime() ).isEmpty(); + } + + @ProcessorTest + public void testTargetToSourceMappingForStrings() { + Target target = new Target(); + + target.setZonedDateTime( "01.01.2014 00:00 UTC" ); + target.setLocalDateTime( "01.01.2014 00:00" ); + target.setLocalDate( "01.01.2014" ); + target.setLocalTime( "00:00" ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( src.getZonedDateTime() ).contains( + ZonedDateTime.of( + LocalDateTime.of( + 2014, + 1, + 1, + 0, + 0 + ), ZoneId.of( "UTC" ) + ) ); + assertThat( src.getLocalDateTime() ).contains( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); + assertThat( src.getLocalDate() ).contains( LocalDate.of( 2014, 1, 1 ) ); + assertThat( src.getLocalTime() ).contains( LocalTime.of( 0, 0 ) ); + } + + @ProcessorTest + public void testCalendarMapping() { + OptionalSource source = new OptionalSource(); + ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + source.setForCalendarConversion( Optional.of( dateTime ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target.getForCalendarConversion() ).isNotNull(); + assertThat( target.getForCalendarConversion().getTimeZone() ).isEqualTo( + TimeZone.getTimeZone( + "UTC" ) ); + assertThat( target.getForCalendarConversion().get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( target.getForCalendarConversion().get( Calendar.MONTH ) ).isEqualTo( + dateTime.getMonthValue() - 1 ); + assertThat( target.getForCalendarConversion().get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( target.getForCalendarConversion().get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( target.getForCalendarConversion().get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForCalendarConversion() ).contains( dateTime ); + } + + @ProcessorTest + @DefaultTimeZone("UTC") + public void testZonedDateTimeToDateMapping() { + OptionalSource source = new OptionalSource(); + ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + source.setForDateConversionWithZonedDateTime( Optional.of( dateTime ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithZonedDateTime() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithZonedDateTime().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( dateTime.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( instance.get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( instance.get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithZonedDateTime() ).contains( dateTime ); + } + + @ProcessorTest + public void testInstantToDateMapping() { + Instant instant = Instant.ofEpochMilli( 1539366615000L ); + + OptionalSource source = new OptionalSource(); + source.setForDateConversionWithInstant( Optional.ofNullable( instant ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + Date date = target.getForDateConversionWithInstant(); + assertThat( date ).isNotNull(); + assertThat( date.getTime() ).isEqualTo( 1539366615000L ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDateConversionWithInstant() ).contains( instant ); + } + + @ProcessorTest + public void testLocalDateTimeToLocalDateMapping() { + LocalDate localDate = LocalDate.of( 2014, 1, 1 ); + + OptionalSource source = new OptionalSource(); + source.setForLocalDateTimeConversionWithLocalDate( Optional.of( localDate ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + LocalDateTime localDateTime = target.getForLocalDateTimeConversionWithLocalDate(); + assertThat( localDateTime ).isNotNull(); + assertThat( localDateTime ).isEqualTo( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForLocalDateTimeConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateTimeToDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDateTime dateTime = LocalDateTime.of( 2014, 1, 1, 0, 0 ); + source.setForDateConversionWithLocalDateTime( Optional.of( dateTime ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithLocalDateTime() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithLocalDateTime().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( dateTime.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( instance.get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( instance.get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithLocalDateTime() ).contains( dateTime ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateToDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDate localDate = LocalDate.of( 2016, 3, 1 ); + source.setForDateConversionWithLocalDate( Optional.of( localDate ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithLocalDate() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithLocalDate().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( localDate.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( localDate.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( localDate.getDayOfMonth() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateToSqlDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDate localDate = LocalDate.of( 2016, 3, 1 ); + source.setForSqlDateConversionWithLocalDate( Optional.of( localDate ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForSqlDateConversionWithLocalDate() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForSqlDateConversionWithLocalDate().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( localDate.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( localDate.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( localDate.getDayOfMonth() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForSqlDateConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + public void testInstantToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForInstantConversionWithString( Optional.ofNullable( Instant.ofEpochSecond( 42L ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isEqualTo( "1970-01-01T00:00:42Z" ); + } + + @ProcessorTest + public void testInstantToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForInstantConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isNull(); + } + + @ProcessorTest + public void testStringToInstantMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( "1970-01-01T00:00:00.000Z" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForInstantConversionWithString() ).contains( Instant.EPOCH ); + } + + @ProcessorTest + public void testStringToInstantNullMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForInstantConversionWithString() ).isEmpty(); + } + + @ProcessorTest + public void testPeriodToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForPeriodConversionWithString( Optional.ofNullable( Period.ofDays( 42 ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isEqualTo( "P42D" ); + } + + @ProcessorTest + public void testPeriodToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForPeriodConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isNull(); + } + + @ProcessorTest + public void testStringToPeriodMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( "P1Y2M3D" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForPeriodConversionWithString() ).contains( Period.of( 1, 2, 3 ) ); + } + + @ProcessorTest + public void testStringToPeriodNullMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForPeriodConversionWithString() ).isEmpty(); + } + + @ProcessorTest + public void testDurationToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForDurationConversionWithString( Optional.ofNullable( Duration.ofMinutes( 42L ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isEqualTo( "PT42M" ); + } + + @ProcessorTest + public void testDurationToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForDurationConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isNull(); + } + + @ProcessorTest + public void testStringToDurationMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( "PT20.345S" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDurationConversionWithString() ).contains( Duration.ofSeconds( 20L, 345000000L ) ); + } + + @ProcessorTest + public void testStringToDurationNullMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDurationConversionWithString() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java new file mode 100644 index 0000000000..cf94f31869 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java @@ -0,0 +1,160 @@ +/* + * 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.conversion.java8time; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZonedDateTime; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OptionalSource { + + private Optional zonedDateTime = Optional.empty(); + + private Optional localDateTime = Optional.empty(); + + private Optional localDate = Optional.empty(); + + private Optional localTime = Optional.empty(); + + private Optional forCalendarConversion = Optional.empty(); + + private Optional forDateConversionWithZonedDateTime = Optional.empty(); + + private Optional forDateConversionWithLocalDateTime = Optional.empty(); + + private Optional forDateConversionWithLocalDate = Optional.empty(); + + private Optional forSqlDateConversionWithLocalDate = Optional.empty(); + + private Optional forDateConversionWithInstant = Optional.empty(); + + private Optional forLocalDateTimeConversionWithLocalDate = Optional.empty(); + + private Optional forInstantConversionWithString = Optional.empty(); + + private Optional forPeriodConversionWithString = Optional.empty(); + + private Optional forDurationConversionWithString = Optional.empty(); + + public Optional getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(Optional dateTime) { + this.zonedDateTime = dateTime; + } + + public Optional getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(Optional localDateTime) { + this.localDateTime = localDateTime; + } + + public Optional getLocalDate() { + return localDate; + } + + public void setLocalDate(Optional localDate) { + this.localDate = localDate; + } + + public Optional getLocalTime() { + return localTime; + } + + public void setLocalTime(Optional localTime) { + this.localTime = localTime; + } + + public Optional getForCalendarConversion() { + return forCalendarConversion; + } + + public void setForCalendarConversion(Optional forCalendarConversion) { + this.forCalendarConversion = forCalendarConversion; + } + + public Optional getForDateConversionWithZonedDateTime() { + return forDateConversionWithZonedDateTime; + } + + public void setForDateConversionWithZonedDateTime(Optional forDateConversionWithZonedDateTime) { + this.forDateConversionWithZonedDateTime = forDateConversionWithZonedDateTime; + } + + public Optional getForDateConversionWithLocalDateTime() { + return forDateConversionWithLocalDateTime; + } + + public void setForDateConversionWithLocalDateTime(Optional forDateConversionWithLocalDateTime) { + this.forDateConversionWithLocalDateTime = forDateConversionWithLocalDateTime; + } + + public Optional getForDateConversionWithLocalDate() { + return forDateConversionWithLocalDate; + } + + public void setForDateConversionWithLocalDate(Optional forDateConversionWithLocalDate) { + this.forDateConversionWithLocalDate = forDateConversionWithLocalDate; + } + + public Optional getForSqlDateConversionWithLocalDate() { + return forSqlDateConversionWithLocalDate; + } + + public void setForSqlDateConversionWithLocalDate(Optional forSqlDateConversionWithLocalDate) { + this.forSqlDateConversionWithLocalDate = forSqlDateConversionWithLocalDate; + } + + public Optional getForDateConversionWithInstant() { + return forDateConversionWithInstant; + } + + public void setForDateConversionWithInstant(Optional forDateConversionWithInstant) { + this.forDateConversionWithInstant = forDateConversionWithInstant; + } + + public Optional getForLocalDateTimeConversionWithLocalDate() { + return forLocalDateTimeConversionWithLocalDate; + } + + public void setForLocalDateTimeConversionWithLocalDate( + Optional forLocalDateTimeConversionWithLocalDate) { + this.forLocalDateTimeConversionWithLocalDate = forLocalDateTimeConversionWithLocalDate; + } + + public Optional getForInstantConversionWithString() { + return forInstantConversionWithString; + } + + public void setForInstantConversionWithString(Optional forInstantConversionWithString) { + this.forInstantConversionWithString = forInstantConversionWithString; + } + + public Optional getForPeriodConversionWithString() { + return forPeriodConversionWithString; + } + + public void setForPeriodConversionWithString(Optional forPeriodConversionWithString) { + this.forPeriodConversionWithString = forPeriodConversionWithString; + } + + public Optional getForDurationConversionWithString() { + return forDurationConversionWithString; + } + + public void setForDurationConversionWithString(Optional forDurationConversionWithString) { + this.forDurationConversionWithString = forDurationConversionWithString; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java new file mode 100644 index 0000000000..a266309b61 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java @@ -0,0 +1,70 @@ +/* + * 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.conversion.java8time; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalSourceTargetMapper { + + String DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm z"; + + String LOCAL_DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm"; + + String LOCAL_DATE_FORMAT = "dd.MM.yyyy"; + + String LOCAL_TIME_FORMAT = "HH:mm"; + + OptionalSourceTargetMapper INSTANCE = Mappers.getMapper( OptionalSourceTargetMapper.class ); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTarget(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTargetDefaultMapping(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + Target sourceToTargetDateTimeMapped(OptionalSource source); + + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + Target sourceToTargetLocalDateTimeMapped(OptionalSource source); + + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + Target sourceToTargetLocalDateMapped(OptionalSource source); + + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTargetLocalTimeMapped(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + OptionalSource targetToSource(Target target); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + OptionalSource targetToSourceDateTimeMapped(Target target); + + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + OptionalSource targetToSourceLocalDateTimeMapped(Target target); + + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + OptionalSource targetToSourceLocalDateMapped(Target target); + + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + OptionalSource targetToSourceLocalTimeMapped(Target target); + + @InheritInverseConfiguration(name = "sourceToTarget") + OptionalSource targetToSourceDefaultMapping(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java new file mode 100644 index 0000000000..d83b368bce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java @@ -0,0 +1,23 @@ +/* + * 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.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper1 { + + ErroneousKitchenDrawerOptionalMapper1 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper1.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfForks", source = "numberOfForks" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java new file mode 100644 index 0000000000..7d1824a5b1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java @@ -0,0 +1,23 @@ +/* + * 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.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR, uses = VerySpecialNumberMapper.class ) +public interface ErroneousKitchenDrawerOptionalMapper3 { + + ErroneousKitchenDrawerOptionalMapper3 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper3.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfSpoons", source = "numberOfSpoons" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java new file mode 100644 index 0000000000..edd33c71d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java @@ -0,0 +1,23 @@ +/* + * 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.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper4 { + + ErroneousKitchenDrawerOptionalMapper4 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper4.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "depth", source = "depth" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java new file mode 100644 index 0000000000..bad921a7c2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java @@ -0,0 +1,23 @@ +/* + * 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.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper5 { + + ErroneousKitchenDrawerOptionalMapper5 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper5.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "length", source = "length" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java new file mode 100644 index 0000000000..acd2e0876f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java @@ -0,0 +1,20 @@ +/* + * 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.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper6 { + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "drawerId", source = "drawerId" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java new file mode 100644 index 0000000000..92862d3320 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java @@ -0,0 +1,23 @@ +/* + * 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.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.WARN ) +public interface KitchenDrawerOptionalMapper2 { + + KitchenDrawerOptionalMapper2 INSTANCE = Mappers.getMapper( KitchenDrawerOptionalMapper2.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfKnifes", source = "numberOfKnifes" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java new file mode 100644 index 0000000000..2b3e982584 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java @@ -0,0 +1,23 @@ +/* + * 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.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.WARN, uses = VerySpecialNumberMapper.class ) +public interface KitchenDrawerOptionalMapper6 { + + KitchenDrawerOptionalMapper6 INSTANCE = Mappers.getMapper( KitchenDrawerOptionalMapper6.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "height", source = "height" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java index 629f727273..604ff9bffa 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java @@ -22,6 +22,7 @@ */ @WithClasses({ OversizedKitchenDrawerDto.class, + OversizedKitchenDrawerOptionalDto.class, RegularKitchenDrawerEntity.class, VerySpecialNumber.class, VerySpecialNumberMapper.class, @@ -63,6 +64,19 @@ public void testNoErrorCase() { public void testConversionFromLongToInt() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper1.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper1.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"OptionalLong numberOfForks\". It has a possibly lossy conversion from " + + "OptionalLong to int.") + }) + public void testConversionFromOptionalLongToInt() { + } + @ProcessorTest @WithClasses(KitchenDrawerMapper2.class) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, @@ -76,6 +90,19 @@ public void testConversionFromLongToInt() { public void testConversionFromBigIntegerToInteger() { } + @ProcessorTest + @WithClasses(KitchenDrawerOptionalMapper2.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = KitchenDrawerOptionalMapper2.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 20, + message = "property \"Optional numberOfKnifes\" has a possibly lossy conversion " + + "from Optional to Integer.") + }) + public void testConversionFromOptionalBigIntegerToInteger() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper6.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -89,6 +116,19 @@ public void testConversionFromBigIntegerToInteger() { public void testConversionFromStringToInt() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper6.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"Optional drawerId\". It has a possibly lossy conversion from " + + "Optional to int.") + }) + public void testConversionFromOptionalStringToInt() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper3.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -102,6 +142,19 @@ public void testConversionFromStringToInt() { public void test2StepConversionFromBigIntegerToLong() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper3.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper3.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"Optional numberOfSpoons\". " + + "It has a possibly lossy conversion from BigInteger to Long.") + }) + public void test2StepConversionFromOptionalBigIntegerToLong() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper4.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -115,6 +168,20 @@ public void test2StepConversionFromBigIntegerToLong() { public void testConversionFromDoubleToFloat() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper4.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper4.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = + "Can't map property \"Optional depth\". " + + "It has a possibly lossy conversion from Optional to float.") + }) + public void testConversionFromOptionalDoubleToFloat() { + } + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper5.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -128,6 +195,20 @@ public void testConversionFromDoubleToFloat() { public void testConversionFromBigDecimalToFloat() { } + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper5.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper5.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = + "Can't map property \"Optional length\". " + + "It has a possibly lossy conversion from Optional to Float.") + }) + public void testConversionFromOptionalBigDecimalToFloat() { + } + @ProcessorTest @WithClasses(KitchenDrawerMapper6.class) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, @@ -140,6 +221,19 @@ public void testConversionFromBigDecimalToFloat() { public void test2StepConversionFromDoubleToFloat() { } + @ProcessorTest + @WithClasses(KitchenDrawerOptionalMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = KitchenDrawerOptionalMapper6.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 20, + message = "property \"OptionalDouble height\" has a possibly lossy conversion from " + + "OptionalDouble to float.") + }) + public void test2StepConversionFromOptionalDoubleToFloat() { + } + @ProcessorTest @WithClasses(ListMapper.class) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java new file mode 100644 index 0000000000..f214fecd1d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java @@ -0,0 +1,84 @@ +/* + * 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.conversion.lossy; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalLong; + +/** + * @author Filip Hrisafov + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OversizedKitchenDrawerOptionalDto { + + /* yes, its a big drawer */ + private OptionalLong numberOfForks; + private Optional numberOfKnifes; + private Optional numberOfSpoons; + private Optional depth; + private Optional length; + private OptionalDouble height; + private Optional drawerId; + + public OptionalLong getNumberOfForks() { + return numberOfForks; + } + + public void setNumberOfForks(OptionalLong numberOfForks) { + this.numberOfForks = numberOfForks; + } + + public Optional getNumberOfKnifes() { + return numberOfKnifes; + } + + public void setNumberOfKnifes(Optional numberOfKnifes) { + this.numberOfKnifes = numberOfKnifes; + } + + public Optional getNumberOfSpoons() { + return numberOfSpoons; + } + + public void setNumberOfSpoons(Optional numberOfSpoons) { + this.numberOfSpoons = numberOfSpoons; + } + + public Optional getDepth() { + return depth; + } + + public void setDepth(Optional depth) { + this.depth = depth; + } + + public Optional getLength() { + return length; + } + + public void setLength(Optional length) { + this.length = length; + } + + public OptionalDouble getHeight() { + return height; + } + + public void setHeight(OptionalDouble height) { + this.height = height; + } + + public Optional getDrawerId() { + return drawerId; + } + + public void setDrawerId(Optional drawerId) { + this.drawerId = drawerId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java index d49a263deb..00b543c728 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.conversion.lossy; import java.math.BigInteger; +import java.util.Optional; /** * @author Sjaak Derksen @@ -19,4 +20,9 @@ VerySpecialNumber fromFloat(float f) { BigInteger toBigInteger(VerySpecialNumber v) { return new BigInteger( "10" ); } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + BigInteger toBigInteger(Optional v) { + return new BigInteger( "10" ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java new file mode 100644 index 0000000000..a92b029b14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * 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.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class ByteWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java new file mode 100644 index 0000000000..e469c44390 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java @@ -0,0 +1,121 @@ +/* + * 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.conversion.nativetypes; + +import java.util.OptionalDouble; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class DoubleOptionalSource { + + private OptionalDouble b = OptionalDouble.empty(); + private OptionalDouble bb = OptionalDouble.empty(); + private OptionalDouble s = OptionalDouble.empty(); + private OptionalDouble ss = OptionalDouble.empty(); + private OptionalDouble i = OptionalDouble.empty(); + private OptionalDouble ii = OptionalDouble.empty(); + private OptionalDouble l = OptionalDouble.empty(); + private OptionalDouble ll = OptionalDouble.empty(); + private OptionalDouble f = OptionalDouble.empty(); + private OptionalDouble ff = OptionalDouble.empty(); + private OptionalDouble d = OptionalDouble.empty(); + private OptionalDouble dd = OptionalDouble.empty(); + + public OptionalDouble getB() { + return b; + } + + public void setB(OptionalDouble b) { + this.b = b; + } + + public OptionalDouble getBb() { + return bb; + } + + public void setBb(OptionalDouble bb) { + this.bb = bb; + } + + public OptionalDouble getS() { + return s; + } + + public void setS(OptionalDouble s) { + this.s = s; + } + + public OptionalDouble getSs() { + return ss; + } + + public void setSs(OptionalDouble ss) { + this.ss = ss; + } + + public OptionalDouble getI() { + return i; + } + + public void setI(OptionalDouble i) { + this.i = i; + } + + public OptionalDouble getIi() { + return ii; + } + + public void setIi(OptionalDouble ii) { + this.ii = ii; + } + + public OptionalDouble getL() { + return l; + } + + public void setL(OptionalDouble l) { + this.l = l; + } + + public OptionalDouble getLl() { + return ll; + } + + public void setLl(OptionalDouble ll) { + this.ll = ll; + } + + public OptionalDouble getF() { + return f; + } + + public void setF(OptionalDouble f) { + this.f = f; + } + + public OptionalDouble getFf() { + return ff; + } + + public void setFf(OptionalDouble ff) { + this.ff = ff; + } + + public OptionalDouble getD() { + return d; + } + + public void setD(OptionalDouble d) { + this.d = d; + } + + public OptionalDouble getDd() { + return dd; + } + + public void setDd(OptionalDouble dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java new file mode 100644 index 0000000000..ecf0d9bc41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * 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.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class DoubleWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java new file mode 100644 index 0000000000..f20e6fa5d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * 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.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class FloatWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java new file mode 100644 index 0000000000..6543b7bc0b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java @@ -0,0 +1,121 @@ +/* + * 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.conversion.nativetypes; + +import java.util.OptionalInt; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class IntOptionalSource { + + private OptionalInt b = OptionalInt.empty(); + private OptionalInt bb = OptionalInt.empty(); + private OptionalInt s = OptionalInt.empty(); + private OptionalInt ss = OptionalInt.empty(); + private OptionalInt i = OptionalInt.empty(); + private OptionalInt ii = OptionalInt.empty(); + private OptionalInt l = OptionalInt.empty(); + private OptionalInt ll = OptionalInt.empty(); + private OptionalInt f = OptionalInt.empty(); + private OptionalInt ff = OptionalInt.empty(); + private OptionalInt d = OptionalInt.empty(); + private OptionalInt dd = OptionalInt.empty(); + + public OptionalInt getB() { + return b; + } + + public void setB(OptionalInt b) { + this.b = b; + } + + public OptionalInt getBb() { + return bb; + } + + public void setBb(OptionalInt bb) { + this.bb = bb; + } + + public OptionalInt getS() { + return s; + } + + public void setS(OptionalInt s) { + this.s = s; + } + + public OptionalInt getSs() { + return ss; + } + + public void setSs(OptionalInt ss) { + this.ss = ss; + } + + public OptionalInt getI() { + return i; + } + + public void setI(OptionalInt i) { + this.i = i; + } + + public OptionalInt getIi() { + return ii; + } + + public void setIi(OptionalInt ii) { + this.ii = ii; + } + + public OptionalInt getL() { + return l; + } + + public void setL(OptionalInt l) { + this.l = l; + } + + public OptionalInt getLl() { + return ll; + } + + public void setLl(OptionalInt ll) { + this.ll = ll; + } + + public OptionalInt getF() { + return f; + } + + public void setF(OptionalInt f) { + this.f = f; + } + + public OptionalInt getFf() { + return ff; + } + + public void setFf(OptionalInt ff) { + this.ff = ff; + } + + public OptionalInt getD() { + return d; + } + + public void setD(OptionalInt d) { + this.d = d; + } + + public OptionalInt getDd() { + return dd; + } + + public void setDd(OptionalInt dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java new file mode 100644 index 0000000000..b1243d9c1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * 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.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class IntWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java new file mode 100644 index 0000000000..1df7baaec9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java @@ -0,0 +1,121 @@ +/* + * 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.conversion.nativetypes; + +import java.util.OptionalLong; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class LongOptionalSource { + + private OptionalLong b = OptionalLong.empty(); + private OptionalLong bb = OptionalLong.empty(); + private OptionalLong s = OptionalLong.empty(); + private OptionalLong ss = OptionalLong.empty(); + private OptionalLong i = OptionalLong.empty(); + private OptionalLong ii = OptionalLong.empty(); + private OptionalLong l = OptionalLong.empty(); + private OptionalLong ll = OptionalLong.empty(); + private OptionalLong f = OptionalLong.empty(); + private OptionalLong ff = OptionalLong.empty(); + private OptionalLong d = OptionalLong.empty(); + private OptionalLong dd = OptionalLong.empty(); + + public OptionalLong getB() { + return b; + } + + public void setB(OptionalLong b) { + this.b = b; + } + + public OptionalLong getBb() { + return bb; + } + + public void setBb(OptionalLong bb) { + this.bb = bb; + } + + public OptionalLong getS() { + return s; + } + + public void setS(OptionalLong s) { + this.s = s; + } + + public OptionalLong getSs() { + return ss; + } + + public void setSs(OptionalLong ss) { + this.ss = ss; + } + + public OptionalLong getI() { + return i; + } + + public void setI(OptionalLong i) { + this.i = i; + } + + public OptionalLong getIi() { + return ii; + } + + public void setIi(OptionalLong ii) { + this.ii = ii; + } + + public OptionalLong getL() { + return l; + } + + public void setL(OptionalLong l) { + this.l = l; + } + + public OptionalLong getLl() { + return ll; + } + + public void setLl(OptionalLong ll) { + this.ll = ll; + } + + public OptionalLong getF() { + return f; + } + + public void setF(OptionalLong f) { + this.f = f; + } + + public OptionalLong getFf() { + return ff; + } + + public void setFf(OptionalLong ff) { + this.ff = ff; + } + + public OptionalLong getD() { + return d; + } + + public void setD(OptionalLong d) { + this.d = d; + } + + public OptionalLong getDd() { + return dd; + } + + public void setDd(OptionalLong dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java new file mode 100644 index 0000000000..bed8ab6b59 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * 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.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class LongWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java new file mode 100644 index 0000000000..0857b20473 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java @@ -0,0 +1,357 @@ +/* + * 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.conversion.nativetypes; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + ByteTarget.class, + ByteWrapperOptionalSource.class, + ByteWrapperTarget.class, + ShortTarget.class, + ShortWrapperOptionalSource.class, + ShortWrapperTarget.class, + IntOptionalSource.class, + IntTarget.class, + IntWrapperOptionalSource.class, + IntWrapperTarget.class, + LongOptionalSource.class, + LongTarget.class, + LongWrapperOptionalSource.class, + LongWrapperTarget.class, + FloatWrapperOptionalSource.class, + FloatWrapperTarget.class, + DoubleOptionalSource.class, + DoubleTarget.class, + DoubleWrapperOptionalSource.class, + DoubleWrapperTarget.class, + OptionalNumberConversionMapper.class +}) +public class NumberOptionalConversionTest { + + @ProcessorTest + public void shouldApplyByteWrapperConversions() { + ByteWrapperOptionalSource source = new ByteWrapperOptionalSource(); + source.setB( Optional.of( (byte) 1 ) ); + source.setBb( Optional.of( (byte) 2 ) ); + source.setS( Optional.of( (byte) 3 ) ); + source.setSs( Optional.of( (byte) 4 ) ); + source.setI( Optional.of( (byte) 5 ) ); + source.setIi( Optional.of( (byte) 6 ) ); + source.setL( Optional.of( (byte) 7 ) ); + source.setLl( Optional.of( (byte) 8 ) ); + source.setF( Optional.of( (byte) 9 ) ); + source.setFf( Optional.of( (byte) 10 ) ); + source.setD( Optional.of( (byte) 11 ) ); + source.setDd( Optional.of( (byte) 12 ) ); + + ByteWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyShortWrapperConversions() { + ShortWrapperOptionalSource source = new ShortWrapperOptionalSource(); + source.setB( Optional.of( (short) 1 ) ); + source.setBb( Optional.of( (short) 2 ) ); + source.setS( Optional.of( (short) 3 ) ); + source.setSs( Optional.of( (short) 4 ) ); + source.setI( Optional.of( (short) 5 ) ); + source.setIi( Optional.of( (short) 6 ) ); + source.setL( Optional.of( (short) 7 ) ); + source.setLl( Optional.of( (short) 8 ) ); + source.setF( Optional.of( (short) 9 ) ); + source.setFf( Optional.of( (short) 10 ) ); + source.setD( Optional.of( (short) 11 ) ); + source.setDd( Optional.of( (short) 12 ) ); + + ShortWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyIntConversions() { + IntOptionalSource source = new IntOptionalSource(); + source.setB( OptionalInt.of( 1 ) ); + source.setBb( OptionalInt.of( 2 ) ); + source.setS( OptionalInt.of( 3 ) ); + source.setSs( OptionalInt.of( 4 ) ); + source.setI( OptionalInt.of( 5 ) ); + source.setIi( OptionalInt.of( 6 ) ); + source.setL( OptionalInt.of( 7 ) ); + source.setLl( OptionalInt.of( 8 ) ); + source.setF( OptionalInt.of( 9 ) ); + source.setFf( OptionalInt.of( 10 ) ); + source.setD( OptionalInt.of( 11 ) ); + source.setDd( OptionalInt.of( 12 ) ); + + IntTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyIntWrapperConversions() { + IntWrapperOptionalSource source = new IntWrapperOptionalSource(); + source.setB( Optional.of( 1 ) ); + source.setBb( Optional.of( 2 ) ); + source.setS( Optional.of( 3 ) ); + source.setSs( Optional.of( 4 ) ); + source.setI( Optional.of( 5 ) ); + source.setIi( Optional.of( 6 ) ); + source.setL( Optional.of( 7 ) ); + source.setLl( Optional.of( 8 ) ); + source.setF( Optional.of( 9 ) ); + source.setFf( Optional.of( 10 ) ); + source.setD( Optional.of( 11 ) ); + source.setDd( Optional.of( 12 ) ); + + IntWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyLongConversions() { + LongOptionalSource source = new LongOptionalSource(); + source.setB( OptionalLong.of( 1 ) ); + source.setBb( OptionalLong.of( 2 ) ); + source.setS( OptionalLong.of( 3 ) ); + source.setSs( OptionalLong.of( 4 ) ); + source.setI( OptionalLong.of( 5 ) ); + source.setIi( OptionalLong.of( 6 ) ); + source.setL( OptionalLong.of( 7 ) ); + source.setLl( OptionalLong.of( 8 ) ); + source.setF( OptionalLong.of( 9 ) ); + source.setFf( OptionalLong.of( 10 ) ); + source.setD( OptionalLong.of( 11 ) ); + source.setDd( OptionalLong.of( 12 ) ); + + LongTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyLongWrapperConversions() { + LongWrapperOptionalSource source = new LongWrapperOptionalSource(); + source.setB( Optional.of( (long) 1 ) ); + source.setBb( Optional.of( (long) 2 ) ); + source.setS( Optional.of( (long) 3 ) ); + source.setSs( Optional.of( (long) 4 ) ); + source.setI( Optional.of( (long) 5 ) ); + source.setIi( Optional.of( (long) 6 ) ); + source.setL( Optional.of( (long) 7 ) ); + source.setLl( Optional.of( (long) 8 ) ); + source.setF( Optional.of( (long) 9 ) ); + source.setFf( Optional.of( (long) 10 ) ); + source.setD( Optional.of( (long) 11 ) ); + source.setDd( Optional.of( (long) 12 ) ); + + LongWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyFloatWrapperConversions() { + FloatWrapperOptionalSource source = new FloatWrapperOptionalSource(); + source.setB( Optional.of( 1f ) ); + source.setBb( Optional.of( 2f ) ); + source.setS( Optional.of( 3f ) ); + source.setSs( Optional.of( 4f ) ); + source.setI( Optional.of( 5f ) ); + source.setIi( Optional.of( 6f ) ); + source.setL( Optional.of( 7f ) ); + source.setLl( Optional.of( 8f ) ); + source.setF( Optional.of( 9f ) ); + source.setFf( Optional.of( 10f ) ); + source.setD( Optional.of( 11f ) ); + source.setDd( Optional.of( 12f ) ); + + FloatWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyDoubleConversions() { + DoubleOptionalSource source = new DoubleOptionalSource(); + source.setB( OptionalDouble.of( 1 ) ); + source.setBb( OptionalDouble.of( 2 ) ); + source.setS( OptionalDouble.of( 3 ) ); + source.setSs( OptionalDouble.of( 4 ) ); + source.setI( OptionalDouble.of( 5 ) ); + source.setIi( OptionalDouble.of( 6 ) ); + source.setL( OptionalDouble.of( 7 ) ); + source.setLl( OptionalDouble.of( 8 ) ); + source.setF( OptionalDouble.of( 9 ) ); + source.setFf( OptionalDouble.of( 10 ) ); + source.setD( OptionalDouble.of( 11 ) ); + source.setDd( OptionalDouble.of( 12 ) ); + + DoubleTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyDoubleWrapperConversions() { + DoubleWrapperOptionalSource source = new DoubleWrapperOptionalSource(); + source.setB( Optional.of( 1d ) ); + source.setBb( Optional.of( 2d ) ); + source.setS( Optional.of( 3d ) ); + source.setSs( Optional.of( 4d ) ); + source.setI( Optional.of( 5d ) ); + source.setIi( Optional.of( 6d ) ); + source.setL( Optional.of( 7d ) ); + source.setLl( Optional.of( 8d ) ); + source.setF( Optional.of( 9d ) ); + source.setFf( Optional.of( 10d ) ); + source.setD( Optional.of( 11d ) ); + source.setDd( Optional.of( 12d ) ); + + DoubleWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + @IssueKey("229") + public void wrapperToPrimitiveIsNullSafe() { + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new ByteWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new DoubleWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new ShortWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new IntWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new FloatWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new LongWrapperOptionalSource() ) ) + .isNotNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java new file mode 100644 index 0000000000..94472586fa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java @@ -0,0 +1,52 @@ +/* + * 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.conversion.nativetypes; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalNumberConversionMapper { + + OptionalNumberConversionMapper INSTANCE = Mappers.getMapper( OptionalNumberConversionMapper.class ); + + ByteWrapperTarget sourceToTarget(ByteWrapperOptionalSource source); + + ByteWrapperOptionalSource targetToSource(ByteWrapperTarget target); + + ShortWrapperTarget sourceToTarget(ShortWrapperOptionalSource source); + + ShortWrapperOptionalSource targetToSource(ShortWrapperTarget target); + + IntTarget sourceToTarget(IntOptionalSource source); + + IntOptionalSource targetToSource(IntTarget target); + + IntWrapperTarget sourceToTarget(IntWrapperOptionalSource source); + + IntWrapperOptionalSource targetToSource(IntWrapperTarget target); + + LongTarget sourceToTarget(LongOptionalSource source); + + LongOptionalSource targetToSource(LongTarget target); + + LongWrapperTarget sourceToTarget(LongWrapperOptionalSource source); + + LongWrapperOptionalSource targetToSource(LongWrapperTarget target); + + FloatWrapperTarget sourceToTarget(FloatWrapperOptionalSource source); + + FloatWrapperOptionalSource targetToSource(FloatWrapperTarget target); + + DoubleTarget sourceToTarget(DoubleOptionalSource source); + + DoubleOptionalSource targetToSource(DoubleTarget target); + + DoubleWrapperTarget sourceToTarget(DoubleWrapperOptionalSource source); + + DoubleWrapperOptionalSource targetToSource(DoubleWrapperTarget target); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java new file mode 100644 index 0000000000..16acf6c69b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java @@ -0,0 +1,121 @@ +/* + * 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.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class ShortWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java new file mode 100644 index 0000000000..30dca2cf39 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java @@ -0,0 +1,76 @@ +/* + * 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.optional.beforeafter; + +import java.util.Optional; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalBeforeAfterMapper { + + OptionalBeforeAfterMapper INSTANCE = Mappers.getMapper( OptionalBeforeAfterMapper.class ); + + Target toTarget(Source source); + + @BeforeMapping + default void beforeDeepOptionalSourceWithNoTargetType(Optional source) { + } + + @BeforeMapping + default void beforeDeepOptionalSourceWithNonOptionalTargetType(@TargetType Class targetType, + Optional source) { + } + + @AfterMapping + default void afterDeepOptionalSourceWithNoTarget(Optional source) { + + } + + @AfterMapping + default void afterDeepOptionalSourceWithNonOptionalTarget(@MappingTarget Target.SubType target, + Optional source) { + } + + @AfterMapping + default void afterDeepOptionalSourceWithOptionalTarget(@MappingTarget Optional target, + Optional source) { + } + + @AfterMapping + default void afterDeepNonOptionalSourceOptionalTarget(@MappingTarget Optional target, + Source.SubType source) { + } + + @BeforeMapping + default void beforeShallowOptionalSourceWithNoTargetType(Optional source) { + } + + @BeforeMapping + default void beforeShallowOptionalSourceWithNonOptionalTargetType(@TargetType Class targetType, + Optional source) { + } + + @AfterMapping + default void afterShallowOptionalSourceWithNoTarget(Optional source) { + + } + + @AfterMapping + default void afterShallowOptionalSourceWithNonOptionalTarget(@MappingTarget String target, + Optional source) { + } + + @AfterMapping + default void afterShallowNonOptionalSourceOptionalTarget(@MappingTarget Optional target, String source) { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java new file mode 100644 index 0000000000..ba1a7b09d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java @@ -0,0 +1,25 @@ +/* + * 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.optional.beforeafter; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +@WithClasses({ + OptionalBeforeAfterMapper.class, Source.class, Target.class +}) +class OptionalBeforeAfterTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalBeforeAfterMapper.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java new file mode 100644 index 0000000000..1284bca7ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java @@ -0,0 +1,87 @@ +/* + * 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.optional.beforeafter; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional deepOptionalToOptional; + private final Optional deepOptionalToNonOptional; + private final SubType deepNonOptionalToOptional; + + private final Optional shallowOptionalToOptional; + private final Optional shallowOptionalToNonOptional; + private final String shallowNonOptionalToOptional; + + public Source(Optional deepOptionalToOptional, Optional deepOptionalToNonOptional, + SubType deepNonOptionalToOptional, Optional shallowOptionalToOptional, + Optional shallowOptionalToNonOptional, String shallowNonOptionalToOptional) { + this.deepOptionalToOptional = deepOptionalToOptional; + this.deepOptionalToNonOptional = deepOptionalToNonOptional; + this.deepNonOptionalToOptional = deepNonOptionalToOptional; + this.shallowOptionalToOptional = shallowOptionalToOptional; + this.shallowOptionalToNonOptional = shallowOptionalToNonOptional; + this.shallowNonOptionalToOptional = shallowNonOptionalToOptional; + } + + public Optional getDeepOptionalToOptional() { + return deepOptionalToOptional; + } + + public Optional getDeepOptionalToNonOptional() { + return deepOptionalToNonOptional; + } + + public SubType getDeepNonOptionalToOptional() { + return deepNonOptionalToOptional; + } + + public Optional getShallowOptionalToOptional() { + return shallowOptionalToOptional; + } + + public Optional getShallowOptionalToNonOptional() { + return shallowOptionalToNonOptional; + } + + public String getShallowNonOptionalToOptional() { + return shallowNonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java new file mode 100644 index 0000000000..592e3338fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java @@ -0,0 +1,86 @@ +/* + * 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.optional.beforeafter; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional deepOptionalToOptional; + private final SubType deepOptionalToNonOptional; + private final Optional deepNonOptionalToOptional; + + private final Optional shallowOptionalToOptional; + private final String shallowOptionalToNonOptional; + private final Optional shallowNonOptionalToOptional; + + public Target(Optional deepOptionalToOptional, SubType deepOptionalToNonOptional, + Optional deepNonOptionalToOptional, Optional shallowOptionalToOptional, + String shallowOptionalToNonOptional, Optional shallowNonOptionalToOptional) { + this.deepOptionalToOptional = deepOptionalToOptional; + this.deepOptionalToNonOptional = deepOptionalToNonOptional; + this.deepNonOptionalToOptional = deepNonOptionalToOptional; + this.shallowOptionalToOptional = shallowOptionalToOptional; + this.shallowOptionalToNonOptional = shallowOptionalToNonOptional; + this.shallowNonOptionalToOptional = shallowNonOptionalToOptional; + } + + public Optional getDeepOptionalToOptional() { + return deepOptionalToOptional; + } + + public SubType getDeepOptionalToNonOptional() { + return deepOptionalToNonOptional; + } + + public Optional getDeepNonOptionalToOptional() { + return deepNonOptionalToOptional; + } + + public Optional getShallowOptionalToOptional() { + return shallowOptionalToOptional; + } + + public String getShallowOptionalToNonOptional() { + return shallowOptionalToNonOptional; + } + + public Optional getShallowNonOptionalToOptional() { + return shallowNonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java new file mode 100644 index 0000000000..9dd4b01256 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java @@ -0,0 +1,31 @@ +/* + * 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.optional.builder; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +class OptionalBuilderTest { + + @ProcessorTest + @WithClasses(SimpleOptionalBuilderMapper.class) + void simpleOptionalBuilder() { + Optional targetOpt = SimpleOptionalBuilderMapper.INSTANCE.map( null ); + assertThat( targetOpt ).isEmpty(); + + targetOpt = SimpleOptionalBuilderMapper.INSTANCE.map( new SimpleOptionalBuilderMapper.Source( "test" ) ); + assertThat( targetOpt ).isNotEmpty(); + SimpleOptionalBuilderMapper.Target target = targetOpt.get(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java new file mode 100644 index 0000000000..5f24aa0cae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java @@ -0,0 +1,67 @@ +/* + * 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.optional.builder; + +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SimpleOptionalBuilderMapper { + + SimpleOptionalBuilderMapper INSTANCE = Mappers.getMapper( SimpleOptionalBuilderMapper.class ); + + Optional map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final String value; + + private Target(TargetBuilder builder) { + this.value = builder.value; + } + + public String getValue() { + return value; + } + + public static TargetBuilder builder() { + return new TargetBuilder(); + } + + } + + class TargetBuilder { + + private String value; + + public TargetBuilder value(String value) { + this.value = value; + return this; + } + + public Target build() { + return new Target(this); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java new file mode 100644 index 0000000000..a6f8c155d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java @@ -0,0 +1,63 @@ +/* + * 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.optional.custom; + +import java.util.Optional; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +@SuppressWarnings("OptionalAssignedToNull") +public interface CustomOptionalMapper { + + CustomOptionalMapper INSTANCE = Mappers.getMapper( CustomOptionalMapper.class ); + + Target map(Source source); + + void update(@MappingTarget Target target, Source source); + + @Condition + default boolean isPresent(Optional optional) { + return optional != null; + } + + default E unwrapFromOptional(Optional optional) { + return optional == null + ? null + : optional.orElse( null ); + } + + class Target { + private String value = "initial"; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Source { + private final Optional value; + + public Source(Optional value) { + this.value = value; + } + + public Optional getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java new file mode 100644 index 0000000000..51991ac478 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java @@ -0,0 +1,60 @@ +/* + * 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.optional.custom; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(CustomOptionalMapper.class) +class CustomOptionalTest { + + @ProcessorTest + void shouldUseCustomMethodsWhenMapping() { + CustomOptionalMapper.Target target = CustomOptionalMapper.INSTANCE. + map( new CustomOptionalMapper.Source( null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "initial" ); + + target = CustomOptionalMapper.INSTANCE.map( new CustomOptionalMapper.Source( Optional.empty() ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + + target = CustomOptionalMapper.INSTANCE.map( new CustomOptionalMapper.Source( Optional.of( "test" ) ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } + + @ProcessorTest + void shouldUseCustomMethodsWhenUpdating() { + CustomOptionalMapper.Target target = new CustomOptionalMapper.Target(); + + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "initial" ); + + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( Optional.empty() ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + + target = new CustomOptionalMapper.Target(); + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( Optional.of( "test" ) ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java new file mode 100644 index 0000000000..5a276b4745 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java @@ -0,0 +1,18 @@ +/* + * 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.optional.differenttypes; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalDifferentTypesMapper { + + OptionalDifferentTypesMapper INSTANCE = Mappers.getMapper( OptionalDifferentTypesMapper.class ); + + Target toTarget(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java new file mode 100644 index 0000000000..74ab85e174 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java @@ -0,0 +1,204 @@ +/* + * 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.optional.differenttypes; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + OptionalDifferentTypesMapper.class, Source.class, Target.class +}) +class OptionalDifferentTypesTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalDifferentTypesMapper.class ); + } + + @ProcessorTest + void constructorOptionalToOptionalWhenPresent() { + Source source = new Source( Optional.of( new Source.SubType( "some value" ) ), Optional.empty(), null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void constructorOptionalToOptionalWhenEmpty() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void constructorOptionalToNonOptionalWhenPresent() { + Source source = new Source( Optional.empty(), Optional.of( new Source.SubType( "some value" ) ), null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.getConstructorOptionalToNonOptional(); + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void constructorOptionalToNonOptionalWhenEmpty() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + void constructorNonOptionalToOptionalWhenNotNull() { + Source source = new Source( Optional.empty(), Optional.empty(), new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void constructorNonOptionalToOptionalWhenNull() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void optionalToOptionalWhenPresent() { + Source source = new Source(); + source.setOptionalToOptional( Optional.of( new Source.SubType( "some value" ) ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void optionalToOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToOptional( Optional.empty() ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void optionalToNonOptionalWhenPresent() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.of( new Source.SubType( "some value" ) ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.getOptionalToNonOptional(); + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void optionalToNonOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.empty() ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + void nonOptionalToOptionalWhenNotNull() { + Source source = new Source(); + source.setNonOptionalToOptional( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void nonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.setNonOptionalToOptional( null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void publicOptionalToOptionalWhenPresent() { + Source source = new Source(); + source.publicOptionalToOptional = Optional.of( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void publicOptionalToOptionalWhenEmpty() { + Source source = new Source(); + source.publicOptionalToOptional = Optional.empty(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ).isEmpty(); + } + + @ProcessorTest + void publicOptionalToNonOptionalWhenPresent() { + Source source = new Source(); + source.publicOptionalToNonOptional = Optional.of( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.publicOptionalToNonOptional; + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void publicOptionalToNonOptionalWhenEmpty() { + Source source = new Source(); + source.publicOptionalToNonOptional = Optional.empty(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToNonOptional ).isNull(); + } + + @ProcessorTest + void publicNonOptionalToOptionalWhenNotNull() { + Source source = new Source(); + source.publicNonOptionalToOptional = new Source.SubType( "some value" ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void publicNonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.publicNonOptionalToOptional = null; + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ).isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java new file mode 100644 index 0000000000..0c0648000a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java @@ -0,0 +1,89 @@ +/* + * 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.optional.differenttypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional constructorOptionalToOptional; + private final Optional constructorOptionalToNonOptional; + private final SubType constructorNonOptionalToOptional; + + private Optional optionalToOptional = Optional.empty(); + private Optional optionalToNonOptional = Optional.empty(); + private SubType nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional = Optional.empty(); + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToNonOptional = Optional.empty(); + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicNonOptionalToOptional; + + public Source() { + this( Optional.empty(), Optional.empty(), null ); + } + + public Source(Optional constructorOptionalToOptional, Optional constructorOptionalToNonOptional, + SubType constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public Optional getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public SubType getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public Optional getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(Optional optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public SubType getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(SubType nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java new file mode 100644 index 0000000000..4f2b21c8a7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java @@ -0,0 +1,84 @@ +/* + * 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.optional.differenttypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional constructorOptionalToOptional; + private final SubType constructorOptionalToNonOptional; + private final Optional constructorNonOptionalToOptional; + + private Optional optionalToOptional = Optional.of( new SubType( "initial" ) ); + private SubType optionalToNonOptional; + private Optional nonOptionalToOptional = Optional.of( new SubType( "initial" ) ); + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional = Optional.of( new SubType( "initial" ) ); + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicOptionalToNonOptional; + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicNonOptionalToOptional = Optional.of( new SubType( "initial" ) ); + + public Target(Optional constructorOptionalToOptional, SubType constructorOptionalToNonOptional, + Optional constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public SubType getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public Optional getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public SubType getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(SubType optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public Optional getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(Optional nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java new file mode 100644 index 0000000000..3624efb00e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java @@ -0,0 +1,108 @@ +/* + * 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.optional.lifecycle; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; + +/** + * @author Filip Hrisafov + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class MappingContext { + + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + public void beforeWithoutParameters() { + invokedMethods.add( "beforeWithoutParameters" ); + } + + @BeforeMapping + public void beforeWithOptionalSource(Optional source) { + invokedMethods.add( "beforeWithOptionalSource" ); + } + + @BeforeMapping + public void beforeWithSource(Source source) { + invokedMethods.add( "beforeWithSource" ); + } + + @BeforeMapping + public void beforeWithTargetType(@TargetType Class targetClass) { + invokedMethods.add( "beforeWithTargetType" ); + } + + @BeforeMapping + public void beforeWithBuilderTargetType(@TargetType Class targetBuilderClass) { + invokedMethods.add( "beforeWithBuilderTargetType" ); + } + + @BeforeMapping + public void beforeWithOptionalTarget(@MappingTarget Optional target) { + invokedMethods.add( "beforeWithOptionalTarget" ); + } + + @BeforeMapping + public void beforeWithTarget(@MappingTarget Target target) { + invokedMethods.add( "beforeWithTarget" ); + } + + @BeforeMapping + public void beforeWithTargetBuilder(@MappingTarget Target.Builder target) { + invokedMethods.add( "beforeWithTargetBuilder" ); + } + + @AfterMapping + public void afterWithoutParameters() { + invokedMethods.add( "afterWithoutParameters" ); + } + + @AfterMapping + public void afterWithOptionalSource(Optional source) { + invokedMethods.add( "afterWithOptionalSource" ); + } + + @AfterMapping + public void afterWithSource(Source source) { + invokedMethods.add( "afterWithSource" ); + } + + @AfterMapping + public void afterWithTargetType(@TargetType Class targetClass) { + invokedMethods.add( "afterWithTargetType" ); + } + + @AfterMapping + public void afterWithBuilderTargetType(@TargetType Class targetClass) { + invokedMethods.add( "afterWithBuilderTargetType" ); + } + + @AfterMapping + public void afterWithTarget(@MappingTarget Target target) { + invokedMethods.add( "afterWithTarget" ); + } + + @AfterMapping + public void afterWithTargetBuilder(@MappingTarget Target.Builder target) { + invokedMethods.add( "afterWithTargetBuilder" ); + } + + @AfterMapping + public void afterWithOptionalTarget(@MappingTarget Optional target) { + invokedMethods.add( "afterWithOptionalTarget" ); + } + + public List getInvokedMethods() { + return invokedMethods; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java new file mode 100644 index 0000000000..c80dfcb6b6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java @@ -0,0 +1,165 @@ +/* + * 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.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + MappingContext.class, + Source.class, + Target.class, +}) +public class OptionalLifecycleTest { + + @ProcessorTest + @WithClasses(OptionalToOptionalMapper.class) + void optionalToOptional() { + MappingContext context = new MappingContext(); + + OptionalToOptionalMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToOptionalMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget", + "afterWithOptionalTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToOptionalWithBuilderMapper.class) + void optionalToOptionalWithBuilder() { + MappingContext context = new MappingContext(); + + OptionalToOptionalWithBuilderMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithBuilderTargetType" + ); + + context = new MappingContext(); + OptionalToOptionalWithBuilderMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithBuilderTargetType", + "beforeWithTargetBuilder", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithBuilderTargetType", + "afterWithTarget", + "afterWithTargetBuilder", + "afterWithOptionalTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToTypeMapper.class) + void optionalToType() { + MappingContext context = new MappingContext(); + + OptionalToTypeMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToTypeMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToTypeMultiSourceMapper.class) + void optionalToTypeMultiSource() { + MappingContext context = new MappingContext(); + + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.empty(), null, context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.of( new Source() ), null, context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + + context = new MappingContext(); + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.empty(), "Test", context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java new file mode 100644 index 0000000000..d7c5d08173 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java @@ -0,0 +1,27 @@ +/* + * 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.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToOptionalMapper { + + OptionalToOptionalMapper INSTANCE = Mappers.getMapper( OptionalToOptionalMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Optional map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java new file mode 100644 index 0000000000..8cfab7fd00 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java @@ -0,0 +1,24 @@ +/* + * 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.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToOptionalWithBuilderMapper { + + OptionalToOptionalWithBuilderMapper INSTANCE = Mappers.getMapper( OptionalToOptionalWithBuilderMapper.class ); + + Optional map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java new file mode 100644 index 0000000000..6a6f3e096e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java @@ -0,0 +1,27 @@ +/* + * 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.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToTypeMapper { + + OptionalToTypeMapper INSTANCE = Mappers.getMapper( OptionalToTypeMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Target map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java new file mode 100644 index 0000000000..d672f2ec05 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java @@ -0,0 +1,27 @@ +/* + * 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.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToTypeMultiSourceMapper { + + OptionalToTypeMultiSourceMapper INSTANCE = Mappers.getMapper( OptionalToTypeMultiSourceMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Target map(Optional source, String otherValue, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java new file mode 100644 index 0000000000..c7005c6ca6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java @@ -0,0 +1,21 @@ +/* + * 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.optional.lifecycle; + +/** + * @author Filip Hrisafov + */ +public class Source { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java new file mode 100644 index 0000000000..d5f68a1844 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java @@ -0,0 +1,39 @@ +/* + * 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.optional.lifecycle; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public static class Builder { + + private String value; + + public Builder value(String value) { + this.value = value; + return this; + } + + public Target build() { + Target target = new Target(); + target.setValue( value ); + return target; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java new file mode 100644 index 0000000000..e6ce6faa65 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java @@ -0,0 +1,81 @@ +/* + * 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.optional.nested; + +import java.util.Optional; + +/** + * @author Filip Hrisafov + */ +public class Artist { + private final String name; + private final Label label; + + public Artist(String name, Label label) { + this.name = name; + this.label = label; + } + + public boolean hasName() { + return name != null; + } + + public String getName() { + return name; + } + + public boolean hasLabel() { + return label != null; + } + + public Optional
      *

      @@ -446,13 +445,11 @@ * If not possible, MapStruct will try to apply a user defined mapping method. * * - *

      * *

    18. other *

      * MapStruct handles the constant as {@code String}. The value will be converted by applying a matching method, * type conversion method or built-in conversion. - *

      *

    19. * *

      diff --git a/core/src/main/java/org/mapstruct/SubclassMapping.java b/core/src/main/java/org/mapstruct/SubclassMapping.java index f5463b9026..4d635d8aa3 100644 --- a/core/src/main/java/org/mapstruct/SubclassMapping.java +++ b/core/src/main/java/org/mapstruct/SubclassMapping.java @@ -73,12 +73,17 @@ @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Experimental public @interface SubclassMapping { + /** + * The source subclass to check for before using the default mapping as fallback. + * * @return the source subclass to check for before using the default mapping as fallback. */ Class source(); /** + * The target subclass to map the source to. + * * @return the target subclass to map the source to. */ Class target(); diff --git a/core/src/main/java/org/mapstruct/SubclassMappings.java b/core/src/main/java/org/mapstruct/SubclassMappings.java index 2ddafaf516..d6aac264d4 100644 --- a/core/src/main/java/org/mapstruct/SubclassMappings.java +++ b/core/src/main/java/org/mapstruct/SubclassMappings.java @@ -51,6 +51,8 @@ public @interface SubclassMappings { /** + * The subclassMappings that should be applied. + * * @return the subclassMappings to apply. */ SubclassMapping[] value(); diff --git a/core/src/main/java/org/mapstruct/ValueMappings.java b/core/src/main/java/org/mapstruct/ValueMappings.java index bb593f53d1..95d4c7d5ce 100644 --- a/core/src/main/java/org/mapstruct/ValueMappings.java +++ b/core/src/main/java/org/mapstruct/ValueMappings.java @@ -44,6 +44,11 @@ @Retention(RetentionPolicy.CLASS) public @interface ValueMappings { + /** + * The value mappings that should be applied. + * + * @return the value mappings + */ ValueMapping[] value(); } diff --git a/core/src/main/java/org/mapstruct/control/MappingControl.java b/core/src/main/java/org/mapstruct/control/MappingControl.java index c44797602a..1cb5bf2bb1 100644 --- a/core/src/main/java/org/mapstruct/control/MappingControl.java +++ b/core/src/main/java/org/mapstruct/control/MappingControl.java @@ -99,8 +99,16 @@ @MappingControl( MappingControl.Use.COMPLEX_MAPPING ) public @interface MappingControl { + /** + * The type of mapping control that should be used. + * + * @return What should be used for the mapping control + */ Use value(); + /** + * Defines the options that can be used for the mapping control. + */ enum Use { /** diff --git a/core/src/main/java/org/mapstruct/control/MappingControls.java b/core/src/main/java/org/mapstruct/control/MappingControls.java index a3efef661f..c1663e551e 100644 --- a/core/src/main/java/org/mapstruct/control/MappingControls.java +++ b/core/src/main/java/org/mapstruct/control/MappingControls.java @@ -21,5 +21,10 @@ @Target(ElementType.ANNOTATION_TYPE) public @interface MappingControls { + /** + * The mapping controls that should be applied to the annotated class. + * + * @return The mapping controls that should be applied to the annotated class. + */ MappingControl[] value(); } diff --git a/core/src/main/java/org/mapstruct/util/Experimental.java b/core/src/main/java/org/mapstruct/util/Experimental.java index a945a87d7c..5fd5eb3b5c 100644 --- a/core/src/main/java/org/mapstruct/util/Experimental.java +++ b/core/src/main/java/org/mapstruct/util/Experimental.java @@ -17,5 +17,11 @@ @Documented @Retention(RetentionPolicy.SOURCE) public @interface Experimental { + + /** + * The reason why the feature is considered experimental. + * + * @return the reason why the feature is considered experimental. + */ String value() default ""; } diff --git a/processor/pom.xml b/processor/pom.xml index 01b8cbe263..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -172,6 +172,13 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + all,-missing + + org.apache.maven.plugins maven-surefire-plugin diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java index 31d4d27585..b392281bf4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java @@ -46,6 +46,8 @@ public interface ConversionProvider { Assignment from(ConversionContext conversionContext); /** + * Retrieves any helper methods required for creating the conversion. + * * @param conversionContext ConversionContext providing optional information required for creating the conversion. * * @return any helper methods when required. @@ -53,6 +55,8 @@ public interface ConversionProvider { List getRequiredHelperMethods(ConversionContext conversionContext); /** + * Retrieves any fields required for creating the conversion. + * * @param conversionContext ConversionContext providing optional information required for creating the conversion. * * @return any fields when required. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java index 99797f898c..3f32994734 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java @@ -15,6 +15,8 @@ import static org.mapstruct.ap.internal.conversion.ConversionUtils.currency; /** + * Conversion between {@link Currency} and {@link String}. + * * @author Darren Rambaud */ public class CurrencyToStringConversion extends SimpleConversion { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java index 2b3e1bd960..db9480662d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java @@ -17,6 +17,9 @@ import org.mapstruct.ap.internal.model.common.TypeFactory; /** + * A conversion provider that wraps / unwraps the underlying conversion in Optional. + * e.g., For conversion from {@code Optional} to {@code Integer}. + * * @author Filip Hrisafov */ public class OptionalWrapperConversionProvider implements ConversionProvider { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java index ad488d1a8f..dbab6bc4ca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java @@ -15,6 +15,8 @@ import static org.mapstruct.ap.internal.conversion.ReverseConversion.inverse; /** + * Conversion between {@link java.util.Optional Optional} and its base type. + * * @author Filip Hrisafov */ public class TypeToOptionalConversion extends SimpleConversion { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java index 3b6e3c1686..80b900bb57 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -19,6 +19,8 @@ /** * An abstract builder that can be reused for building {@link MappingMethod}(s). * + * @param the builder itself that needs to be used for chaining + * @param the method that the builder builds * @author Filip Hrisafov */ public abstract class AbstractMappingMethodBuilder, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java index 3f0b2123ae..cb9289e057 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java @@ -50,6 +50,8 @@ import static javax.lang.model.util.ElementFilter.methodsIn; /** + * Helper class which is responsible for collecting all additional annotations that should be added. + * * @author Ben Zegveld * @since 1.5 */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java index 5614376ea4..8f60b052b9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A method in a generated type that represents a setter with annotations. + * * @author Lucas Resch */ public class AnnotatedSetter extends GeneratedTypeMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java index 9b7912f752..9b0507f399 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java @@ -19,6 +19,8 @@ import static org.mapstruct.ap.internal.util.Collections.first; /** + * Factory for creating the appropriate builder finisher method. + * * @author Filip Hrisafov */ public class BuilderFinisherMethodResolver { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java index f8c596a368..e1c320e16b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * An inline conversion from an optional source to it's value. + * * @author Filip Hrisafov */ public class FromOptionalTypeConversion extends ModelElement implements Assignment { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java index f02cc16b2c..8d6a999665 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java @@ -8,6 +8,8 @@ import org.mapstruct.ap.internal.model.common.ModelElement; /** + * Base class for methods available in a generated type. + * * @author Filip Hrisafov */ public abstract class GeneratedTypeMethod extends ModelElement { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java index 879a76efbe..29904ff416 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java @@ -13,6 +13,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that is based on a {@link MethodReference}. + * * @author Filip Hrisafov */ public class MethodReferencePresenceCheck extends ModelElement implements PresenceCheck { 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 index 89f295981f..8b526b9177 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -26,6 +26,7 @@ import static org.mapstruct.ap.internal.util.Collections.first; /** + * Factory for creating the appropriate object factory method. * * @author Sjaak Derksen */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java index 784342b8d7..2de61089ca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -25,6 +25,8 @@ import org.mapstruct.ap.internal.util.Message; /** + * Factory for creating {@link PresenceCheck}s. + * * @author Filip Hrisafov */ public final class PresenceCheckMethodResolver { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java index 727518dad8..0d97c1a523 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * Represents a service entry for the service loader file. + * * @author Christophe Labouisse on 14/07/2015. */ public class ServicesEntry extends ModelElement { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java index 5925540e10..45bbc9d4b0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * An inline conversion from a source to an optional of the source. + * * @author Filip Hrisafov */ public class ToOptionalTypeConversion extends ModelElement implements Assignment { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java index 6ecde886bf..f242b51f47 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * Represents an annotation element. + * * @author Ben Zegveld */ public class AnnotationElement extends ModelElement { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java index a334312007..eaa2fe68ff 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java @@ -12,6 +12,7 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * Decorates the assignment as an {@link Enum} constant access. * * @author Sjaak Derksen */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java index 9064640bb4..6c405c871d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java @@ -10,6 +10,8 @@ import org.mapstruct.ap.internal.util.Strings; /** + * Decorates the assignment as an {@link java.util.Optional#get()} call. + * * @author Filip Hrisafov */ public class OptionalGetWrapper extends AssignmentWrapper { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java index b94bb82efd..9300f683ce 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java @@ -14,6 +14,9 @@ import org.mapstruct.ap.spi.BuilderInfo; /** + * Represents the information about a builder. + * How it can be constructed, the type it is building etc. + * * @author Filip Hrisafov */ public class BuilderType { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java index 48cee4bbe5..7ef818630a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java @@ -10,6 +10,7 @@ import javax.lang.model.element.Element; /** + * Represent information about the configured formatting. * * @author Sjaak Derksen */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java index f8844edcdd..78357affb8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java @@ -9,6 +9,8 @@ import java.util.Set; /** + * A {@link PresenceCheck} that negates the result of another presence check. + * * @author Filip Hrisafov */ public class NegatePresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java index 6bb191214f..d71f67a2a4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java @@ -16,6 +16,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that checks if all the given presence checks are present. + * * @author Filip Hrisafov */ public class AllPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java index 0b5770e42f..5f8fac9623 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java @@ -16,6 +16,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that checks if any of the given presence checks are present. + * * @author Filip Hrisafov */ public class AnyPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java index e100be7e79..0c7c132023 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that calls a Java expression. + * * @author Filip Hrisafov */ public class JavaExpressionPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java index 3496486e1d..2da4ae4023 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java @@ -14,6 +14,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A presence check that checks if the source reference is null. + * * @author Filip Hrisafov */ public class NullPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java index 0f75fbba69..d35e89fce2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java @@ -15,6 +15,8 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * A {@link PresenceCheck} that calls the suffix on the source reference. + * * @author Filip Hrisafov */ public class SuffixPresenceCheck extends ModelElement implements PresenceCheck { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java index 936d049af8..83e2bf2819 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java @@ -23,6 +23,8 @@ import org.mapstruct.ap.internal.util.Message; /** + * Represents a condition configuration as configured via {@code @Condition}. + * * @author Filip Hrisafov */ public class ConditionOptions { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java index 85734c3083..41b74aa13a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java @@ -21,6 +21,8 @@ import static org.mapstruct.ap.internal.util.Message.ENUMMAPPING_NO_ELEMENTS; /** + * Represents an enum mapping as configured via {@code @EnumMapping}. + * * @author Filip Hrisafov */ public class EnumMappingOptions extends DelegatingOptions { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java index 50ea5df6e0..7d0ab3a7ec 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java @@ -10,6 +10,8 @@ import static org.mapstruct.ap.internal.util.Collections.first; /** + * Utility class for mapping methods. + * * @author Filip Hrisafov */ public final class MappingMethodUtils { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java index e2ccc3f476..df9c8e0b0c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java @@ -17,6 +17,8 @@ import org.mapstruct.ap.internal.util.XmlConstants; /** + * Base class for built-in methods for converting from a particular type to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public abstract class AbstractToXmlGregorianCalendar extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java index c8cd89169b..3461538c41 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link Calendar} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class CalendarToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java index 036edd83a2..fb556e68a1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link Date} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class DateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java index d0b06300c4..ef57e03175 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java @@ -13,6 +13,8 @@ import org.mapstruct.ap.internal.util.JodaTimeConstants; /** + * A built-in method for converting from Joda {@code DateTime} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java index 80fa9ba326..285ebb29fe 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code LocalDateTime} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaLocalDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java index 37c89ef360..8163aa2fb2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code LocalDate} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaLocalDateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java index b29c7aec54..4fca4e0ed3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code LocalTime} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class JodaLocalTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java index 178a77b5fa..b52c7fb586 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java @@ -17,6 +17,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link LocalDateTime} to {@code XMLGregorianCalendar}. + * * @author Andrei Arlou */ public class LocalDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java index 2e2544ade0..e2ec7d7696 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link LocalDate} to {@code XMLGregorianCalendar}. + * * @author Gunnar Morling */ public class LocalDateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java index 9f0d2ad207..635853d0fe 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java @@ -19,6 +19,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link String} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public class StringToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java index 03927fd869..e52b306af0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link Calendar}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToCalendar extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java index 38f76a66ee..fd3d9a33a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link Date}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToDate extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java index ab6658bfc5..93ec3e8f96 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.internal.model.source.builtin; +import java.util.Calendar; import java.util.Set; import org.mapstruct.ap.internal.model.common.Parameter; @@ -16,6 +17,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from Joda {@code DateTime} to {@link Calendar}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaDateTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java index f45488755c..9a9c1561fc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to Joda {@code LocalDate}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaLocalDate extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java index af7fd9e9fd..a8d36a13cf 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to Joda {@code LocalDateTime}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaLocalDateTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java index 3725691134..b26450d560 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * Conversion from {@code XMLGregorianCalendar} to Joda {@code LocalTime}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToJodaLocalTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java index 2c9ced3413..7bcd5902a5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link LocalDate}. + * * @author Gunnar Morling */ public class XmlGregorianCalendarToLocalDate extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java index b676bd03a4..21b4c57854 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java @@ -17,6 +17,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link LocalDateTime}. + * * @author Andrei Arlou */ public class XmlGregorianCalendarToLocalDateTime extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java index c932da6675..e293a9c80b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java @@ -18,6 +18,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link String}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToString extends BuiltInMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java index 27a39cd2b7..d9e39b388e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java @@ -16,6 +16,8 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link ZonedDateTime} to {@code XMLGregorianCalendar}. + * * @author Christian Bandowski */ public class ZonedDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java index 23af0a55f9..87ec3b4dc1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java @@ -12,6 +12,8 @@ import org.mapstruct.ap.internal.model.source.Method; /** + * A {@link MethodSelector} that selects the most specific result type. + * * @author Filip Hrisafov */ public class MostSpecificResultTypeSelector implements MethodSelector { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java index 7acb1af210..1702a8d4d6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java @@ -14,6 +14,8 @@ /** * A selected method with additional metadata that might be required for further usage of the selected method. * + * @param the type of the method + * * @author Andreas Gudian */ public class SelectedMethod { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java index d35b2c0dc6..fedbe997a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.internal.option; /** + * The different compiler mapping options that are available in MapStruct. + * * @author Filip Hrisafov */ public enum MappingOption { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java index 72a44d106f..67ce6e10ff 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java @@ -34,6 +34,11 @@ import static javax.lang.model.util.ElementFilter.fieldsIn; import static javax.lang.model.util.ElementFilter.methodsIn; +/** + * MapStruct specific abstract implementation of {@link ElementUtils}. + * This allows us to provide different implementations for different compilers and / or + * to allow us for easier implementation of using the module system. + */ public abstract class AbstractElementUtilsDecorator implements ElementUtils { private final Elements delegate; @@ -312,6 +317,8 @@ private TypeElement asTypeElement(TypeMirror mirror) { } /** + * Checks whether the {@code executable} does not have a private modifier. + * * @param executable the executable to check * @return {@code true}, iff the executable does not have a private modifier */ diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java index f2328a91cc..96768daf56 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java @@ -16,6 +16,11 @@ import org.mapstruct.tools.gem.Gem; /** + * A base helper class that provides utility methods for working with meta annotations. + * + * @param The type of the processed annotation + * @param The type of the underlying holder for the processed annotation + * * @author Filip Hrisafov */ public abstract class MetaAnnotations { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java index accae77718..76126bf488 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java @@ -16,6 +16,12 @@ import org.mapstruct.tools.gem.Gem; /** + * A base helper class that provides utility methods for working with repeatable annotations. + * + * @param The singular annotation type + * @param The multiple annotation type + * @param The underlying holder for the processed annotations + * * @author Ben Zegveld */ public abstract class RepeatableAnnotations { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java index 9b2981d7c5..5ebc835ba1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java @@ -11,6 +11,8 @@ import javax.lang.model.type.TypeMirror; /** + * An {@link Accessor} which delegates all calls to another {@link Accessor}. + * * @author Filip Hrisafov */ public abstract class DelegateAccessor implements Accessor { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java index cc974b939f..d96788b7f7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java @@ -8,6 +8,8 @@ import javax.lang.model.element.ExecutableElement; /** + * Accessor for presence checks. + * * @author Filip Hrisafov */ public interface PresenceCheckAccessor { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java index 31edf3a6da..4be3c26ff9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java @@ -11,6 +11,8 @@ import javax.lang.model.type.TypeMirror; /** + * An {@link Accessor} that can be used for reading a property from a bean. + * * @author Filip Hrisafov */ public interface ReadAccessor extends Accessor { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java index 608c1b21cf..aa93936662 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java @@ -6,6 +6,9 @@ package org.mapstruct.ap.internal.util.accessor; /** + * {@link ReadAccessor} that delegates to another {@link Accessor} and requires an implementation of + * {@link #getSimpleName()} + * * @author Filip Hrisafov */ public abstract class ReadDelegateAccessor extends DelegateAccessor implements ReadAccessor { diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index 511504c937..e7529dcd08 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -188,7 +188,6 @@ private boolean isAdderWithUpperCase4thCharacter(ExecutableElement method) { * {@link #getElementName(ExecutableElement) }. *

      * The calling MapStruct code guarantees there's only one argument. - *

      * * @param method to be analyzed * diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index afcee7d247..333a11dbcc 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -317,7 +317,7 @@ protected boolean isPossibleBuilderCreationMethod(ExecutableElement method, Type *

      * The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the * {@code builderElement} should be ignored, i.e. not checked for build elements. - *

      + * * @param builderElement the element for the builder * @param typeElement the element for the type that is being built * @return the build method for the {@code typeElement} if it exists, or {@code null} if it does not diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java index 62d6c32809..11e1257be1 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java @@ -10,6 +10,8 @@ import javax.lang.model.util.Types; /** + * The default implementation of the {@link EnumMappingStrategy} service provider interface. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java index 9dae81cda2..2a1cacfb1f 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java @@ -15,7 +15,6 @@ *

      * When generating the implementation of a mapping method, MapStruct will apply the following routine for each * attribute pair in the source and target object: - *

      *

        *
      • If source and target attribute have the same type, the value will be simply copied from source to target. * If the attribute is a collection (e.g. a `List`) a copy of the collection will be set into the target diff --git a/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java index abb6e9cb4f..226251dfab 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that prepends a prefix to the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java index d3f0c515b3..dec7af6de4 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that strips a prefix from the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java index cf239600d4..3b76354fa7 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that strips a suffix from the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java index 4cd92b9af8..1f4eb89217 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java @@ -6,6 +6,8 @@ package org.mapstruct.ap.spi; /** + * An {@link EnumTransformationStrategy} that appends a suffix to the enum value. + * * @author Filip Hrisafov * * @since 1.4 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java b/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java index 7b8f374cf7..93bccf51d0 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java @@ -4,4 +4,7 @@ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ +/** + * Utility classes for the SPI package. + */ package org.mapstruct.ap.spi.util; From d59e6a04aa3fb4bb1ca313959d4eecc9891287c9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 Jan 2026 17:09:36 +0100 Subject: [PATCH 333/363] Cleanup checks for JRE prior 21 in processor test module The processor test module is compiled using Java 21. Therefore, all tests that were configured to run prior Java 21 are no longer executed and can be safely removed --- .../conversion/date/DateConversionTest.java | 75 ------------------- .../jodatime/JodaConversionTest.java | 32 -------- .../runner/JdkCompilingExtension.java | 33 -------- 3 files changed, 140 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java index bc90228206..346f34a054 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java @@ -13,9 +13,6 @@ import java.util.GregorianCalendar; import java.util.List; -import org.junit.jupiter.api.condition.EnabledForJreRange; -import org.junit.jupiter.api.condition.EnabledOnJre; -import org.junit.jupiter.api.condition.JRE; import org.junitpioneer.jupiter.DefaultLocale; import org.junitpioneer.jupiter.ReadsDefaultTimeZone; import org.mapstruct.ap.testutil.IssueKey; @@ -40,38 +37,6 @@ public class DateConversionTest { @ProcessorTest - @EnabledOnJre( JRE.JAVA_8 ) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversions() { - Source source = new Source(); - source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); - - Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); - - assertThat( target ).isNotNull(); - assertThat( target.getDate() ).isEqualTo( "06.07.2013" ); - assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13 00:00" ); - } - - @ProcessorTest - @EnabledOnJre( JRE.JAVA_8 ) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversionsWithCustomLocale() { - Source source = new Source(); - source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); - - Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); - - assertThat( target ).isNotNull(); - assertThat( target.getDate() ).isEqualTo( "juillet 06, 2013" ); - assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); - } - - @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionsJdk11() { Source source = new Source(); source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); @@ -85,8 +50,6 @@ public void shouldApplyDateFormatForConversionsJdk11() { } @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionsJdk11WithCustomLocale() { Source source = new Source(); source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); @@ -100,42 +63,6 @@ public void shouldApplyDateFormatForConversionsJdk11WithCustomLocale() { } @ProcessorTest - @EnabledOnJre(JRE.JAVA_8) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversionInReverseMapping() { - Target target = new Target(); - target.setDate( "06.07.2013" ); - target.setAnotherDate( "14.02.13 8:30" ); - - Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); - - assertThat( source ).isNotNull(); - assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - assertThat( source.getAnotherDate() ).isEqualTo( - new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() - ); - } - - @ProcessorTest - @EnabledOnJre(JRE.JAVA_8) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversionInReverseMappingWithCustomLocale() { - Target target = new Target(); - target.setDate( "juillet 06, 2013" ); - target.setAnotherDate( "14.02.13 8:30" ); - - Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); - - assertThat( source ).isNotNull(); - assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); - assertThat( source.getAnotherDate() ).isEqualTo( - new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() - ); - } - - @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionInReverseMappingJdk11() { Target target = new Target(); target.setDate( "06.07.2013" ); @@ -151,8 +78,6 @@ public void shouldApplyDateFormatForConversionInReverseMappingJdk11() { } @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void shouldApplyDateFormatForConversionInReverseMappingJdk11WithCustomLocale() { Target target = new Target(); target.setDate( "juillet 06, 2013" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java index 8574c7a6be..d447c455ff 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java @@ -13,8 +13,6 @@ import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; -import org.junit.jupiter.api.condition.EnabledForJreRange; -import org.junit.jupiter.api.condition.JRE; import org.junitpioneer.jupiter.DefaultLocale; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; @@ -71,8 +69,6 @@ public void testLocalTimeToString() { } @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_21) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ public void testSourceToTargetMappingForStrings() { Source src = new Source(); src.setLocalTime( new LocalTime( 0, 0 ) ); @@ -98,34 +94,6 @@ public void testSourceToTargetMappingForStrings() { assertThat( target.getLocalTime() ).isEqualTo( "00:00:00" ); } - @ProcessorTest - @EnabledForJreRange(min = JRE.JAVA_11, max = JRE.JAVA_17) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void testSourceToTargetMappingForStringsJdk11() { - Source src = new Source(); - src.setLocalTime( new LocalTime( 0, 0 ) ); - src.setLocalDate( new LocalDate( 2014, 1, 1 ) ); - src.setLocalDateTime( new LocalDateTime( 2014, 1, 1, 0, 0 ) ); - src.setDateTime( new DateTime( 2014, 1, 1, 0, 0, 0, DateTimeZone.UTC ) ); - - // with given format - Target target = SourceTargetMapper.INSTANCE.sourceToTarget( src ); - - assertThat( target ).isNotNull(); - assertThat( target.getDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); - assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); - assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); - assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); - - // and now with default mappings - target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( src ); - assertThat( target ).isNotNull(); - assertThat( target.getDateTime() ).isEqualTo( "1. Januar 2014 um 00:00:00 UTC" ); - assertThat( target.getLocalDateTime() ).isEqualTo( "1. Januar 2014 um 00:00:00" ); - assertThat( target.getLocalDate() ).isEqualTo( "1. Januar 2014" ); - assertThat( target.getLocalTime() ).isEqualTo( "00:00:00" ); - } - @ProcessorTest public void testStringToDateTime() { String dateTimeAsString = "01.01.2014 00:00 UTC"; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java index 115618382c..1e9a1bc22f 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java @@ -12,9 +12,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Objects; import javax.annotation.processing.Processor; -import javax.tools.Diagnostic.Kind; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; @@ -23,10 +21,8 @@ import javax.tools.StandardLocation; import javax.tools.ToolProvider; -import org.junit.jupiter.api.condition.JRE; import org.mapstruct.ap.MappingProcessor; import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor; -import org.mapstruct.ap.testutil.compilation.model.DiagnosticDescriptor; /** * Extension that uses the JDK compiler to compile. @@ -140,35 +136,6 @@ private static List asFiles(List paths) { return classpath; } - /** - * The JDK 8 compiler needs some special treatment for the diagnostics. - * See comment in the function. - */ - @Override - protected List filterExpectedDiagnostics(List expectedDiagnostics) { - if ( JRE.currentVersion() != JRE.JAVA_8 ) { - // The JDK 8+ compilers report all ERROR diagnostics properly. Also when there are multiple per line. - return expectedDiagnostics; - } - List filtered = new ArrayList<>(expectedDiagnostics.size()); - - // The JDK 8 compiler only reports the first message of kind ERROR that is reported for one source file line, - // so we filter out the surplus diagnostics. The input list is already sorted by file name and line number, - // with the order for the diagnostics in the same line being kept at the order as given in the test. - DiagnosticDescriptor previous = null; - for ( DiagnosticDescriptor diag : expectedDiagnostics ) { - if ( diag.getKind() != Kind.ERROR - || previous == null - || !previous.getSourceFileName().equals( diag.getSourceFileName() ) - || !Objects.equals( previous.getLine(), diag.getLine() ) ) { - filtered.add( diag ); - previous = diag; - } - } - - return filtered; - } - private static FilteringParentClassLoader newFilteringClassLoaderForJdk() { return new FilteringParentClassLoader( "kotlin.", From 73f042bdc5906a3d5a0774c6535dabce64a83799 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 Jan 2026 17:25:06 +0100 Subject: [PATCH 334/363] Use explicit version instead of JRE#OTHER to disable on Java 27-ea `@DisabledOnJre(JRE.OTHER)` no longer disables the test if the JRE is unknown. --- .../org/mapstruct/itest/tests/MavenIntegrationTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 4596efd630..60784d206a 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -89,7 +89,7 @@ void jsr330Test() { @ProcessorTest(baseDir = "lombokBuilderTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }) - @DisabledOnJre(JRE.OTHER) + @DisabledOnJre(versions = 27) void lombokBuilderTest() { } @@ -98,7 +98,7 @@ void lombokBuilderTest() { ProcessorTest.ProcessorType.JAVAC_WITH_PATHS }) @EnabledForJreRange(min = JRE.JAVA_11) - @DisabledOnJre(JRE.OTHER) + @DisabledOnJre(versions = 27) void lombokModuleTest() { } @@ -155,7 +155,7 @@ void expressionTextBlocksTest() { }, forkJvm = true) // We have to fork the jvm because there is an NPE in com.intellij.openapi.util.SystemInfo.getRtVersion // and the kotlin-maven-plugin uses that. See also https://youtrack.jetbrains.com/issue/IDEA-238907 - @DisabledOnJre(JRE.OTHER) + @DisabledOnJre(versions = 27) void kotlinDataTest() { } From 6708e3592539ec5b4f94c09643c039e359b78eae Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sat, 31 Jan 2026 19:49:02 +0100 Subject: [PATCH 335/363] Remove testing with no longer maintained org.bsc.maven:maven-processor-plugin The last release of the plugin was in April 2024. In addition to that, the maven-compiler-plugin is de-facto the standard for compiling Java. Therefore, we are removing testing with the maven-processor-plugin. --- .../ProcessorInvocationInterceptor.java | 23 +++++------- .../testutil/extension/ProcessorTest.java | 5 +-- integrationtest/src/test/resources/pom.xml | 37 ------------------- 3 files changed, 10 insertions(+), 55 deletions(-) diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java index 85ce64952f..39cd5fdae6 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorInvocationInterceptor.java @@ -136,21 +136,16 @@ private void addAdditionalCliArguments(Verifier verifier) private void configureProcessor(Verifier verifier) { ProcessorTest.ProcessorType processor = processorTestContext.getProcessor(); String compilerId = processor.getCompilerId(); - if ( compilerId != null ) { - String profile = processor.getProfile(); - if ( profile == null ) { - profile = "generate-via-compiler-plugin"; + String profile = processor.getProfile(); + if ( profile == null ) { + profile = "generate-via-compiler-plugin"; + } + verifier.addCliOption( "-P" + profile ); + verifier.addCliOption( "-Dcompiler-id=" + compilerId ); + if ( processor == ProcessorTest.ProcessorType.JAVAC ) { + if ( CURRENT_VERSION.ordinal() >= JRE.JAVA_21.ordinal() ) { + verifier.addCliOption( "-Dmaven.compiler.proc=full" ); } - verifier.addCliOption( "-P" + profile ); - verifier.addCliOption( "-Dcompiler-id=" + compilerId ); - if ( processor == ProcessorTest.ProcessorType.JAVAC ) { - if ( CURRENT_VERSION.ordinal() >= JRE.JAVA_21.ordinal() ) { - verifier.addCliOption( "-Dmaven.compiler.proc=full" ); - } - } - } - else { - verifier.addCliOption( "-Pgenerate-via-processor-plugin" ); } } diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java index 95480eee9d..d5b4860d5b 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/testutil/extension/ProcessorTest.java @@ -46,9 +46,7 @@ enum ProcessorType { JAVAC( "javac" ), JAVAC_WITH_PATHS( "javac", JRE.OTHER, "generate-via-compiler-plugin-with-annotation-processor-paths" ), - ECLIPSE_JDT( "jdt", JRE.JAVA_8 ), - - PROCESSOR_PLUGIN( null, JRE.JAVA_8 ); + ECLIPSE_JDT( "jdt", JRE.JAVA_8 ); private final String compilerId; private final JRE max; @@ -111,7 +109,6 @@ ProcessorType[] processorTypes() default { ProcessorType.JAVAC, ProcessorType.JAVAC_WITH_PATHS, ProcessorType.ECLIPSE_JDT, - ProcessorType.PROCESSOR_PLUGIN }; /** diff --git a/integrationtest/src/test/resources/pom.xml b/integrationtest/src/test/resources/pom.xml index c8a19228f0..c2ece3b38d 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -97,43 +97,6 @@ - - generate-via-processor-plugin - - false - - - - - org.bsc.maven - maven-processor-plugin - 3.3.3 - - - process - generate-sources - - process - - - - - \${project.build.directory}/generated-sources/mapstruct - - org.mapstruct.ap.MappingProcessor - - - - - ${project.groupId} - mapstruct-processor - ${mapstruct.version} - - - - - - debug-forked-javac From df97305df070ee5a33233a9072bd4c38d8d99e53 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 12:09:53 +0100 Subject: [PATCH 336/363] #3940 Use deterministic order for supporting fields and methods --- .../ap/internal/model/BeanMappingMethod.java | 4 ++-- .../ap/internal/model/SupportingField.java | 2 +- .../creation/MappingResolverImpl.java | 7 ++++--- .../numbers/SourceTargetMapperImpl.java | 8 ++++---- .../OptionalSourceTargetMapperImpl.java | 20 +++++++++---------- .../java8time/SourceTargetMapperImpl.java | 20 +++++++++---------- .../numbers/SourceTargetMapperImpl.java | 8 ++++---- 7 files changed, 35 insertions(+), 34 deletions(-) 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 c6fc7753f2..d67b550ad4 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 @@ -301,8 +301,8 @@ else if ( !method.isUpdateMethod() ) { } // get bean mapping (when specified as annotation ) - this.missingIgnoredSourceProperties = new HashSet<>(); - this.redundantIgnoredSourceProperties = new HashSet<>(); + this.missingIgnoredSourceProperties = new LinkedHashSet<>(); + this.redundantIgnoredSourceProperties = new LinkedHashSet<>(); if ( beanMapping != null && !beanMapping.getIgnoreUnmappedSourceProperties().isEmpty() ) { // Get source properties explicitly mapped using @Mapping annotations Set mappedSourceProperties = method.getOptions().getMappings().stream() diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java index caf6d6160e..2cac1ef351 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java @@ -47,7 +47,7 @@ public static void addAllFieldsIn(Set supportingMapping for ( SupportingMappingMethod supportingMappingMethod : supportingMappingMethods ) { Field field = supportingMappingMethod.getSupportingField(); if ( field != null ) { - targets.add( supportingMappingMethod.getSupportingField() ); + targets.add( field ); } } } 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 2a36f6b890..67cdc09fc9 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 @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -97,12 +98,12 @@ public class MappingResolverImpl implements MappingResolver { * Private methods which are not present in the original mapper interface and are added to map certain property * types. */ - private final Set usedSupportedMappings = new HashSet<>(); + private final Set usedSupportedMappings = new LinkedHashSet<>(); /** * Private fields which are added to map certain property types. */ - private final Set usedSupportedFields = new HashSet<>(); + private final Set usedSupportedFields = new LinkedHashSet<>(); public MappingResolverImpl(FormattingMessager messager, ElementUtils elementUtils, TypeUtils typeUtils, TypeFactory typeFactory, List sourceModel, @@ -199,7 +200,7 @@ private ResolvingAttempt(List sourceModel, Method mappingMethod, ForgedM this.formattingParameters = formattingParameters == null ? FormattingParameters.EMPTY : formattingParameters; this.sourceRHS = sourceRHS; - this.supportingMethodCandidates = new HashSet<>(); + this.supportingMethodCandidates = new LinkedHashSet<>(); this.selectionCriteria = criteria; this.positionHint = positionHint; this.forger = forger; diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java index ccf8042e6b..941bd929a7 100644 --- a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapperImpl.java @@ -509,16 +509,16 @@ public Map targetToSourceWithCustomLocale(Map targetToSourceWithCustomLocale(Map Date: Sun, 1 Feb 2026 12:41:20 +0100 Subject: [PATCH 337/363] #1830 Add support for NullValuePropertyMappingStrategy.CLEAR strategy Using this strategy will clear target collections / maps when the source property is null --- NEXT_RELEASE_CHANGELOG.md | 1 + .../NullValuePropertyMappingStrategy.java | 9 +- ...apter-10-advanced-mapping-options.asciidoc | 3 + .../NullValuePropertyMappingStrategyGem.java | 3 +- ...nceSetterWrapperForCollectionsAndMaps.java | 27 ++-- .../model/source/MapperConfigOptions.java | 2 +- .../internal/model/source/MapperOptions.java | 2 +- ...anceSetterWrapperForCollectionsAndMaps.ftl | 8 +- .../nullvaluepropertymapping/clear/Bean.java | 40 ++++++ .../clear/BeanDTO.java | 40 ++++++ .../clear/BeanDTOWithId.java | 19 +++ .../clear/BeanMapper.java | 34 +++++ .../clear/BeanMapperWithStrategyOnMapper.java | 25 ++++ .../clear/NestedBean.java | 18 +++ .../NullValuePropertyMappingClearTest.java | 116 ++++++++++++++++++ 15 files changed, 329 insertions(+), 18 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 59fed4fed3..476f3e025d 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -22,6 +22,7 @@ it will be treated as a potential builder. Builders through static methods on the type have a precedence. * Behaviour change: Warning when the target has no target properties (#1140) +* Add new `NullValuePropertyMappingStrategy#CLEAR` for clearing Collection and Map properties when updating a bean (#1830) diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java index a58eed8a2a..0ab30d5271 100644 --- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -56,5 +56,12 @@ public enum NullValuePropertyMappingStrategy { * If a source bean property equals {@code null} the target bean property will be ignored and retain its * existing value. */ - IGNORE; + IGNORE, + + /** + * If a source bean property equals {@code null} the target bean property will be cleared. + * This strategy is only applicable to target properties that are of type {@link java.util.Collection Collection} + * or {@link java.util.Map Map}. + */ + CLEAR; } diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 43f8fc38a0..34f1e19817 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -254,6 +254,9 @@ For all other objects an new instance is created. Please note that a default con 2. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the mapping result will be equal to the original value of the `@MappingTarget` annotated target. +3. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MapperConfig`, the target collection or map will be cleared when the source property is `null`. +This strategy only applies to `Collection` and `Map` target properties. + The strategy works in a hierarchical fashion. Setting `nullValuePropertyMappingStrategy` on mapping method level will override `@Mapper#nullValuePropertyMappingStrategy`, and `@Mapper#nullValuePropertyMappingStrategy` will override `@MapperConfig#nullValuePropertyMappingStrategy`. [NOTE] diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java index 77e4aa48d6..93c98b73ac 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java @@ -15,5 +15,6 @@ public enum NullValuePropertyMappingStrategyGem { SET_TO_NULL, SET_TO_DEFAULT, - IGNORE; + IGNORE, + CLEAR; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java index 2dd7f81a0b..875368b014 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java @@ -5,19 +5,18 @@ */ package org.mapstruct.ap.internal.model.assignment; -import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS; -import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; -import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT; - import java.util.HashSet; import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; -import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; + +import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; /** * This wrapper handles the situation where an assignment is done for an update method. @@ -34,8 +33,8 @@ public class ExistingInstanceSetterWrapperForCollectionsAndMaps extends SetterWrapperForCollectionsAndMapsWithNullCheck { - private final boolean includeElseBranch; - private final boolean mapNullToDefault; + private final NullValuePropertyMappingStrategyGem nvpms; + private final NullValueCheckStrategyGem nvcs; private final Type targetType; public ExistingInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, @@ -53,9 +52,9 @@ public ExistingInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAs typeFactory, fieldAssignment ); - this.mapNullToDefault = SET_TO_DEFAULT == nvpms; + this.nvcs = nvcs; + this.nvpms = nvpms; this.targetType = targetType; - this.includeElseBranch = ALWAYS != nvcs && IGNORE != nvpms; } @Override @@ -68,11 +67,15 @@ public Set getImportTypes() { } public boolean isIncludeElseBranch() { - return includeElseBranch; + return nvcs != ALWAYS && nvpms != IGNORE; } public boolean isMapNullToDefault() { - return mapNullToDefault; + return nvpms == NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT; + } + + public boolean isMapNullToClear() { + return nvpms == NullValuePropertyMappingStrategyGem.CLEAR; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java index d606658878..2766faa64c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -8,8 +8,8 @@ import java.util.Set; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; -import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java index 9c2203efd5..21cb7813c4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -12,8 +12,8 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl index 95662c4c79..8e771f0ddb 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl @@ -15,8 +15,12 @@ ${ext.targetBeanName}.${ext.targetReadAccessorName}.<#if ext.targetType.collectionType>addAll<#else>putAll( <@lib.handleWithAssignmentOrNullCheckVar/> ); <#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && includeElseBranch>else {<#-- the opposite (defaultValueAssignment) case is handeld inside lib.handleLocalVarNullCheck --> - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; - } + <#if mapNullToClear> + ${ext.targetBeanName}.${ext.targetReadAccessorName}.clear(); + <#else > + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; + + } } else { diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java new file mode 100644 index 0000000000..545ddffe8b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java @@ -0,0 +1,40 @@ +/* + * 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.nullvaluepropertymapping.clear; + +import java.util.Collection; +import java.util.Map; + +public class Bean { + + private String id; + private Collection list; + private Map map; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Collection getList() { + return list; + } + + public void setList(Collection list) { + this.list = list; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java new file mode 100644 index 0000000000..9529da8113 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java @@ -0,0 +1,40 @@ +/* + * 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.nullvaluepropertymapping.clear; + +import java.util.Collection; +import java.util.Map; + +public class BeanDTO { + + private String id; + private Collection list; + private Map map; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Collection getList() { + return list; + } + + public void setList(Collection list) { + this.list = list; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java new file mode 100644 index 0000000000..152a872d82 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java @@ -0,0 +1,19 @@ +/* + * 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.nullvaluepropertymapping.clear; + +public class BeanDTOWithId extends BeanDTO { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java new file mode 100644 index 0000000000..4d453579ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java @@ -0,0 +1,34 @@ +/* + * 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.nullvaluepropertymapping.clear; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BeanMapper { + + BeanMapper INSTANCE = Mappers.getMapper( BeanMapper.class ); + + @Mapping(target = "list", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + @Mapping(target = "map", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO map(Bean source, @MappingTarget BeanDTO target); + + @Mapping(target = "id", source = "bean.id") + @Mapping(target = "list", source = "bean.list", + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + @Mapping(target = "map", source = "bean.map", + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO map(NestedBean source, @MappingTarget BeanDTO target); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO mapWithBeanMapping(Bean source, @MappingTarget BeanDTO target); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java new file mode 100644 index 0000000000..0e26b91b5b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java @@ -0,0 +1,25 @@ +/* + * 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.nullvaluepropertymapping.clear; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) +public interface BeanMapperWithStrategyOnMapper { + + BeanMapperWithStrategyOnMapper INSTANCE = Mappers.getMapper( BeanMapperWithStrategyOnMapper.class ); + + BeanDTO map(Bean source, @MappingTarget BeanDTO target); + + @Mapping(target = "id", source = "bean.id") + @Mapping(target = "list", source = "bean.list") + @Mapping(target = "map", source = "bean.map") + BeanDTO map(NestedBean source, @MappingTarget BeanDTO target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java new file mode 100644 index 0000000000..448645c107 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java @@ -0,0 +1,18 @@ +/* + * 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.nullvaluepropertymapping.clear; + +public class NestedBean { + private Bean bean; + + public Bean getBean() { + return bean; + } + + public void setBean(Bean bean) { + this.bean = bean; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java new file mode 100644 index 0000000000..20cbcdc2b4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java @@ -0,0 +1,116 @@ +/* + * 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.nullvaluepropertymapping.clear; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Bean.class, + BeanDTO.class, + NestedBean.class, +}) +class NullValuePropertyMappingClearTest { + + @ProcessorTest + @WithClasses(BeanMapper.class) + void generatedMapperMethodsShouldCallClear() { + BeanDTO target = new BeanDTO(); + target.setId( "target" ); + List targetList = new ArrayList<>(); + targetList.add( "a" ); + targetList.add( "b" ); + target.setList( targetList ); + Map targetMap = new HashMap<>(); + targetMap.put( "a", "aValue" ); + target.setMap( targetMap ); + Bean source = new Bean(); + + BeanMapper.INSTANCE.map( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + NestedBean nestedBean = new NestedBean(); + nestedBean.setBean( source ); + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapper.INSTANCE.map( nestedBean, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapper.INSTANCE.mapWithBeanMapping( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + } + + @ProcessorTest + @WithClasses(BeanMapperWithStrategyOnMapper.class) + void generatedMapperWithMappingDefinedInConfigMethodsShouldCallClear() { + BeanDTO target = new BeanDTO(); + target.setId( "target" ); + List targetList = new ArrayList<>(); + targetList.add( "a" ); + targetList.add( "b" ); + target.setList( targetList ); + Map targetMap = new HashMap<>(); + targetMap.put( "a", "aValue" ); + target.setMap( targetMap ); + Bean source = new Bean(); + + BeanMapperWithStrategyOnMapper.INSTANCE.map( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + NestedBean nestedBean = new NestedBean(); + nestedBean.setBean( source ); + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapperWithStrategyOnMapper.INSTANCE.map( nestedBean, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + } + +} From a5614dd798dbb6c07ed41e21708eaa2ecd153aad Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 19:41:07 +0100 Subject: [PATCH 338/363] Prepare release notes and update copyright.txt --- NEXT_RELEASE_CHANGELOG.md | 47 ++++++++++++++++++++++++++++++++++----- copyright.txt | 14 +++++++++++- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 476f3e025d..c4ba49e24d 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -13,7 +13,7 @@ - Data classes with multiple constructors - Data classes with all default parameters - Sealed Classes (#3404) - Subclass exhaustiveness is now checked for Kotlin sealed classes - +* Add support for ignoring multiple target properties at once (#3838) - Using new annotation `@Ignored` ### Enhancements @@ -21,22 +21,59 @@ * Detect Builder without a factory method (#3729) - With this if there is an inner class that ends with `Builder` and has a constructor with parameters, it will be treated as a potential builder. Builders through static methods on the type have a precedence. -* Behaviour change: Warning when the target has no target properties (#1140) +* Add support for custom exception for subclass exhaustive strategy for `@SubclassMapping` mapping (#3821) - Available on `@BeanMapping`, `@Mapper` and `@MappingConfig`. * Add new `NullValuePropertyMappingStrategy#CLEAR` for clearing Collection and Map properties when updating a bean (#1830) - - +* Use deterministic order for supporting fields and methods (#3940) +* Support `@AnnotatedWith` on decorators (#3659) +* Behaviour change: Add warning/error for redundant `ignoreUnmappedSourceProperties` entries (#3906) +* Behaviour change: Warning when the target has no target properties (#1140) +* Behaviour change: Initialize `Optional` with `Optional.empty` instead of `null` (#3852) +* Behaviour change: Mark `String` to `Number` as lossy conversion (#3848) ### Bugs * Improve error message when mapping non-iterable to array (#3786) +* Fix conditional mapping with `@TargetPropertyName` failing for nested update mappings (#3809) +* Resolve duplicate invocation of overloaded lifecycle methods with inheritance (#3849) - It is possible to disable this by using the new compiler option `mapstruct.disableLifecycleOverloadDeduplicateSelector`. +* Support generic `@Context` (#3711) +* Properly apply `NullValuePropertyMappingStrategy.IGNORE` for collections / maps without setters (#3806) +* Properly recognize the type of public generic fields (#3807) +* Fix method in Record is treated as a fluent setter (#3886) +* Ensure `NullValuePropertyMappingStrategy.SET_TO_DEFAULT` initializes empty collection/map when target is null (#3884) +* Fix Compiler error when mapping an object named `Override` (#3905) ### Documentation +* General Improvements + * Javadoc + * Typos in comments + * Small code refactorings + ### Build +* Move Windows and MacOS builds outside of the main workflow +* Update release to release using the new Maven Central Portal +* Skip codecov coverage on forks +* Improve testing support for Kotlin + ### Behaviour Change -#### Warning when the target has no target properties +#### Warning when the target has no target properties (#1140) With this change, if the target bean does not have any target properties, a warning will be shown. This is like this to avoid potential mistakes by users, where they might think that the target bean has properties, but it does not. + +#### Warning for redundant `ignoreUnmappedSourceProperties` entries (#3906) + +With this change, if the `ignoreUnmappedSourceProperties` configuration contains properties that are actually mapped, a warning or compiler error will be shown. +The `unmappedSourcePolicy` is used to determine whether a warning, or an error is shown. + +#### Initialize `Optional` with `Optional.empty` instead of `null` (#3852) + +With this change, if the target `Optional` property is null, it will be initialized with `Optional.empty()` instead of `null`. + +#### Mark `String` to `Number` as lossy conversion (#3848) + +With this change, if the source `String` property is mapped to a `Number` property, a warning will be shown. +This is similar to what is happening when mapping `long` to `int`, etc. +The `typeConversionPolicy` `ReportingPolicy` is used to determine whether a warning, error or ignore is shown. diff --git a/copyright.txt b/copyright.txt index bd0034b076..356deb904d 100644 --- a/copyright.txt +++ b/copyright.txt @@ -1,12 +1,14 @@ Contributors ============ +Aleksey Ivashin - https://github.com/xumk Alexandr Shalugin - https://github.com/shalugin Amine Touzani - https://github.com/ttzn Andreas Gudian - https://github.com/agudian Andrei Arlou - https://github.com/Captain1653 Andres Jose Sebastian Rincon Gonzalez - https://github.com/stianrincon Arne Seime - https://github.com/seime +Cause Chung - https://github.com/cuzfrog Christian Bandowski - https://github.com/chris922 Chris DeLashmutt - https://github.com/cdelashmutt-pivotal Christian Kosmowski - https://github.com/ckosmowski @@ -15,6 +17,7 @@ Christophe Labouisse - https://github.com/ggtools Ciaran Liedeman - https://github.com/cliedeman Cindy Wang - https://github.com/birdfriend Cornelius Dirmeier - https://github.com/cornzy +Dennis Melzer - https://github.com/ David Feinblum - https://github.com/dvfeinblum Darren Rambaud - https://github.com/xyzst Dekel Pilli - https://github.com/dekelpilli @@ -53,7 +56,11 @@ Pavel Makhov - https://github.com/streetturtle Peter Larson - https://github.com/pjlarson Remko Plantenga - https://github.com/sonata82 Remo Meier - https://github.com/remmeier +Roel Mangelschots - https://github.com/rmschots +Ritesh Chopade - https://github.com/codeswithritesh Richard Lea - https://github.com/chigix +Roman Obolonskyi - https://github.com/Obolrom +Samil Can - https://github.com/SamilCan Saheb Preet Singh - https://github.com/sahebpreet Samuel Wright - https://github.com/samwright Sebastian Haberey - https://github.com/sebastianhaberey @@ -65,10 +72,15 @@ Taras Mychaskiw - https://github.com/twentylemon Thibault Duperron - https://github.com/Zomzog Tomáš Poledný - https://github.com/Saljack Tobias Meggendorfer - https://github.com/incaseoftrouble +Tran Ngoc Nhan - https://github.com/ Tillmann Gaida - https://github.com/Tillerino Timo Eckhardt - https://github.com/timoe Tomek Gubala - https://github.com/vgtworld Valentin Kulesh - https://github.com/unshare Vincent Alexander Beelte - https://github.com/grandmasterpixel Winter Andreas - https://github.dev/wandi34 -Xiu Hong Kooi - https://github.com/kooixh \ No newline at end of file +Xiu Hong Kooi - https://github.com/kooixh +Yang Tang - https://github.com/tangyang9464 +Zegveld - https://github.com/Zegveld +znight1020 - https://github.com/znight1020 +zral - https://github.com/zyberzebra From 14f9f6e2877e5ae1b28ce3b59e381628f72fa349 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:10:29 +0000 Subject: [PATCH 339/363] Releasing version 1.7.0.Beta1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..b1545ceb00 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..42ea5baec4 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..b3af781388 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index b480e24a06..fb4444e00d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..d36a6428d6 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 71a61fc2fe..a9037e613c 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index f2b85d4969..ae877ef10c 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2026-02-01T20:10:28Z 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index f55f0955d3..114085f46e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 622ba924ee..608175a8e2 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml From 561d5dad858a4ba621bcab1f50452ea3851c17eb Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 21:56:00 +0100 Subject: [PATCH 340/363] Adjust release to use Maven Central Publishing Portal --- .github/workflows/release.yml | 2 ++ parent/pom.xml | 31 ++++++++++++++----------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4dffe0586..a4feca94a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,6 +62,8 @@ jobs: JRELEASER_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }} JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME: ${{ secrets.SONATYPE_CENTRAL_USERNAME }} + JRELEASER_MAVENCENTRAL_SONATYPE_TOKEN: ${{ secrets.SONATYPE_CENTRAL_TOKEN }} JRELEASER_NEXUS2_MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }} JRELEASER_NEXUS2_MAVEN_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} run: | diff --git a/parent/pom.xml b/parent/pom.xml index ae877ef10c..0043cfb44a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -880,24 +880,21 @@ - - - ALWAYS - https://oss.sonatype.org/service/local - https://oss.sonatype.org/content/repositories/snapshots/ - true - true + + + RELEASE + https://central.sonatype.com/api/v1/publisher ${maven.multiModuleProjectDirectory}/target/staging-deploy - - - org.mapstruct - mapstruct-jdk8 - false - false - - - - + + + + org.mapstruct + mapstruct-jdk8 + false + false + + + From 6389f2a8056b01e792d02acee6dd1bf9ef3a3718 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 21:58:29 +0100 Subject: [PATCH 341/363] Revert "Releasing version 1.7.0.Beta1" This reverts commit 14f9f6e2877e5ae1b28ce3b59e381628f72fa349. --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index b1545ceb00..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 42ea5baec4..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index b3af781388..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index fb4444e00d..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d36a6428d6..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index a9037e613c..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 0043cfb44a..01b97c10bf 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2026-02-01T20:10:28Z + ${git.commit.author.time} 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index 114085f46e..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 608175a8e2..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml From ab72ced6fb7a71591d51343493bf736379c61beb Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 20:59:33 +0000 Subject: [PATCH 342/363] Releasing version 1.7.0.Beta1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..b1545ceb00 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..42ea5baec4 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..b3af781388 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index b480e24a06..fb4444e00d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..d36a6428d6 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 71a61fc2fe..a9037e613c 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 01b97c10bf..f81ea2c5d1 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2026-02-01T20:59:32Z 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index f55f0955d3..114085f46e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 622ba924ee..608175a8e2 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml From fc2d5e520c5bceff54c302641431465410bdf031 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 22:44:20 +0100 Subject: [PATCH 343/363] Move jReleaser artifactOverrides in the right place --- parent/pom.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index f81ea2c5d1..962839bf19 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -885,15 +885,15 @@ RELEASE https://central.sonatype.com/api/v1/publisher ${maven.multiModuleProjectDirectory}/target/staging-deploy + + + org.mapstruct + mapstruct-jdk8 + false + false + + - - - org.mapstruct - mapstruct-jdk8 - false - false - - From 744c398326e9fa281134330dafdbdfecf1653c02 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 1 Feb 2026 22:44:32 +0100 Subject: [PATCH 344/363] Revert "Releasing version 1.7.0.Beta1" This reverts commit ab72ced6fb7a71591d51343493bf736379c61beb. --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index b1545ceb00..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 42ea5baec4..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index b3af781388..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index fb4444e00d..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d36a6428d6..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index a9037e613c..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 962839bf19..8a46290cb8 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2026-02-01T20:59:32Z + ${git.commit.author.time} 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index 114085f46e..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 608175a8e2..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml From 84318113ae02adae62e1d92ee5b884d6b58337b1 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:45:33 +0000 Subject: [PATCH 345/363] Releasing version 1.7.0.Beta1 --- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 ++-- pom.xml | 2 +- processor/pom.xml | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build-config/pom.xml b/build-config/pom.xml index 0b3ca1c113..b1545ceb00 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index c73676192b..42ea5baec4 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index e62d5e4682..b3af781388 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index b480e24a06..fb4444e00d 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index 21a2b151bd..d36a6428d6 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 71a61fc2fe..a9037e613c 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 8a46290cb8..02fd68fcff 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - ${git.commit.author.time} + 2026-02-01T21:45:32Z 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index f55f0955d3..114085f46e 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 622ba924ee..608175a8e2 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0-SNAPSHOT + 1.7.0.Beta1 ../parent/pom.xml From c7a0385903bfadfb8ef4779be2707b469fc2ace1 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:02:16 +0000 Subject: [PATCH 346/363] Next version 1.7.0-SNAPSHOT --- NEXT_RELEASE_CHANGELOG.md | 69 --------------------------------------- build-config/pom.xml | 2 +- core-jdk8/pom.xml | 2 +- core/pom.xml | 2 +- distribution/pom.xml | 2 +- documentation/pom.xml | 2 +- integrationtest/pom.xml | 2 +- parent/pom.xml | 4 +-- pom.xml | 2 +- processor/pom.xml | 2 +- 10 files changed, 10 insertions(+), 79 deletions(-) diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index c4ba49e24d..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,79 +1,10 @@ ### Features -* Support for Java 21 Sequenced Collections (#3240) -* Native support for `java.util.Optional` mapping (#674) - MapStruct now fully supports Optional as both source and target types: - - `Optional` to `Optional` - Both source and target wrapped in `Optional` - - `Optional` to Non-`Optional` - Unwrapping `Optional` values - - Non-`Optional` to `Optional` - Wrapping values in `Optional` - - `Optional` properties in beans with automatic presence checks. Note, there is no null check done for `Optional` properties. -* Improved support for Kotlin. Requires use of `org.jetbrains.kotlin:kotlin-metadata-jvm`. - - Data Classes (#2281, #2577, #3031) - MapStruct now properly handles: - - Single field data classes - - Proper primary constructor detection - - Data classes with multiple constructors - - Data classes with all default parameters - - Sealed Classes (#3404) - Subclass exhaustiveness is now checked for Kotlin sealed classes -* Add support for ignoring multiple target properties at once (#3838) - Using new annotation `@Ignored` - ### Enhancements -* Add support for locale parameter for numberFormat and dateFormat (#3628) -* Detect Builder without a factory method (#3729) - With this if there is an inner class that ends with `Builder` and has a constructor with parameters, -it will be treated as a potential builder. -Builders through static methods on the type have a precedence. -* Add support for custom exception for subclass exhaustive strategy for `@SubclassMapping` mapping (#3821) - Available on `@BeanMapping`, `@Mapper` and `@MappingConfig`. -* Add new `NullValuePropertyMappingStrategy#CLEAR` for clearing Collection and Map properties when updating a bean (#1830) -* Use deterministic order for supporting fields and methods (#3940) -* Support `@AnnotatedWith` on decorators (#3659) -* Behaviour change: Add warning/error for redundant `ignoreUnmappedSourceProperties` entries (#3906) -* Behaviour change: Warning when the target has no target properties (#1140) -* Behaviour change: Initialize `Optional` with `Optional.empty` instead of `null` (#3852) -* Behaviour change: Mark `String` to `Number` as lossy conversion (#3848) - ### Bugs -* Improve error message when mapping non-iterable to array (#3786) -* Fix conditional mapping with `@TargetPropertyName` failing for nested update mappings (#3809) -* Resolve duplicate invocation of overloaded lifecycle methods with inheritance (#3849) - It is possible to disable this by using the new compiler option `mapstruct.disableLifecycleOverloadDeduplicateSelector`. -* Support generic `@Context` (#3711) -* Properly apply `NullValuePropertyMappingStrategy.IGNORE` for collections / maps without setters (#3806) -* Properly recognize the type of public generic fields (#3807) -* Fix method in Record is treated as a fluent setter (#3886) -* Ensure `NullValuePropertyMappingStrategy.SET_TO_DEFAULT` initializes empty collection/map when target is null (#3884) -* Fix Compiler error when mapping an object named `Override` (#3905) - ### Documentation -* General Improvements - * Javadoc - * Typos in comments - * Small code refactorings - ### Build -* Move Windows and MacOS builds outside of the main workflow -* Update release to release using the new Maven Central Portal -* Skip codecov coverage on forks -* Improve testing support for Kotlin - -### Behaviour Change - -#### Warning when the target has no target properties (#1140) - -With this change, if the target bean does not have any target properties, a warning will be shown. -This is like this to avoid potential mistakes by users, where they might think that the target bean has properties, but it does not. - -#### Warning for redundant `ignoreUnmappedSourceProperties` entries (#3906) - -With this change, if the `ignoreUnmappedSourceProperties` configuration contains properties that are actually mapped, a warning or compiler error will be shown. -The `unmappedSourcePolicy` is used to determine whether a warning, or an error is shown. - -#### Initialize `Optional` with `Optional.empty` instead of `null` (#3852) - -With this change, if the target `Optional` property is null, it will be initialized with `Optional.empty()` instead of `null`. - -#### Mark `String` to `Number` as lossy conversion (#3848) - -With this change, if the source `String` property is mapped to a `Number` property, a warning will be shown. -This is similar to what is happening when mapping `long` to `int`, etc. -The `typeConversionPolicy` `ReportingPolicy` is used to determine whether a warning, error or ignore is shown. diff --git a/build-config/pom.xml b/build-config/pom.xml index b1545ceb00..0b3ca1c113 100644 --- a/build-config/pom.xml +++ b/build-config/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 42ea5baec4..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index b3af781388..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/distribution/pom.xml b/distribution/pom.xml index fb4444e00d..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/documentation/pom.xml b/documentation/pom.xml index d36a6428d6..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index a9037e613c..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/parent/pom.xml b/parent/pom.xml index 02fd68fcff..8a46290cb8 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,7 +11,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT pom MapStruct Parent @@ -28,7 +28,7 @@ ${java.version} ${java.version} - 2026-02-01T21:45:32Z + ${git.commit.author.time} 1.0.0.Alpha3 3.6.2 diff --git a/pom.xml b/pom.xml index 114085f46e..f55f0955d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT parent/pom.xml diff --git a/processor/pom.xml b/processor/pom.xml index 608175a8e2..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.7.0.Beta1 + 1.7.0-SNAPSHOT ../parent/pom.xml From 5cf1a98d29975aa32fa4a6e2a78e2808bc8fdec9 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Feb 2026 11:48:24 +0100 Subject: [PATCH 347/363] Fix location for Javadoc when generating distribution zip --- distribution/src/main/assembly/dist.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/src/main/assembly/dist.xml b/distribution/src/main/assembly/dist.xml index f0c727f8da..e0bc00496b 100644 --- a/distribution/src/main/assembly/dist.xml +++ b/distribution/src/main/assembly/dist.xml @@ -76,7 +76,7 @@ - target/site/apidocs + target/reports/apidocs docs/api From 06e27d575365147ed17a991c5cc90a1f63436eda Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:56:16 +0100 Subject: [PATCH 348/363] Remove unnecessary `keySet()` invocation (#3989) --- .../ap/internal/model/NestedTargetPropertyMappingHolder.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 2b407e240c..f7b28b3f84 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -151,8 +151,7 @@ public NestedTargetPropertyMappingHolder build() { entryByTP.getValue(), groupedByTP.singleTargetReferences.get( targetProperty ) ); - boolean multipleSourceParametersForTP = - groupedBySourceParam.groupedBySourceParameter.keySet().size() > 1; + boolean multipleSourceParametersForTP = groupedBySourceParam.groupedBySourceParameter.size() > 1; // All not processed mappings that should have been applied to all are part of the unprocessed // defined targets From aff971d7b713bcc15eb750840b00369b61cb28f5 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Feb 2026 14:08:13 +0100 Subject: [PATCH 349/363] Let GitHub determine whether or not the released version is the latest or not --- parent/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/parent/pom.xml b/parent/pom.xml index 8a46290cb8..ffa9952de5 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -904,6 +904,11 @@ ${maven.multiModuleProjectDirectory}/NEXT_RELEASE_CHANGELOG.md + + legacy From e9ebec2307fed606fb58f60d2e2226caf9f47614 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 2 Feb 2026 14:08:56 +0100 Subject: [PATCH 350/363] Factory method for LinkedHashMap and LinkedHashSet is always there for SequencedSet and SequencedMap (#3990) --- .../mapstruct/ap/internal/model/common/TypeFactory.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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 c2646f63db..eb2c1dee07 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 @@ -152,15 +152,11 @@ public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMes ); implementationTypes.put( JavaCollectionConstants.SEQUENCED_SET_FQN, - sourceVersionAtLeast19 ? - withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) : - withLoadFactorAdjustment( getType( LinkedHashSet.class ) ) + withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) ); implementationTypes.put( JavaCollectionConstants.SEQUENCED_MAP_FQN, - sourceVersionAtLeast19 ? - withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) : - withLoadFactorAdjustment( getType( LinkedHashMap.class ) ) + withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) ); this.loggingVerbose = loggingVerbose; From 15312d6e46fb7744cf63e90ec70f20f518190457 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sat, 14 Feb 2026 21:14:53 +0100 Subject: [PATCH 351/363] Fix self check in equals of Type (#3995) --- .../java/org/mapstruct/ap/internal/model/common/Type.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 29bb3cd906..8a91c80f03 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -1303,8 +1303,8 @@ public boolean equals(Object obj) { Type other = (Type) obj; if ( this.isWildCardBoundByTypeVar() && other.isWildCardBoundByTypeVar() ) { - return ( this.hasExtendsBound() == this.hasExtendsBound() - || this.hasSuperBound() == this.hasSuperBound() ) + return ( this.hasExtendsBound() == other.hasExtendsBound() + || this.hasSuperBound() == other.hasSuperBound() ) && typeUtils.isSameType( getTypeBound().getTypeMirror(), other.getTypeBound().getTypeMirror() ); } else { From 588006727dea2beb8e0c6019e52bab95ec57993e Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Sat, 14 Feb 2026 21:53:45 +0100 Subject: [PATCH 352/363] Improve performance of `Type.describe()` by removing regex matching (#3991) --- .../org/mapstruct/ap/internal/model/common/Type.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 8a91c80f03..3e81cd622d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -1331,7 +1331,7 @@ public String describe() { } else { // name allows for inner classes - String name = getFullyQualifiedName().replaceFirst( "^" + getPackageName() + ".", "" ); + String name = getNameKeepingInnerClasses(); List typeParams = getTypeParameters(); if ( typeParams.isEmpty() ) { return name; @@ -1343,6 +1343,15 @@ public String describe() { } } + private String getNameKeepingInnerClasses() { + String packageNamePrefix = getPackageName() + "."; + String fullyQualifiedName = getFullyQualifiedName(); + if (fullyQualifiedName.startsWith( packageNamePrefix ) ) { + return fullyQualifiedName.substring( packageNamePrefix.length() ); + } + return fullyQualifiedName; + } + /** * * @return an identification that can be used as part in a forged method name. From 4a75490e6d8b622083720bf60596680b49ce5e31 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:56:19 +0100 Subject: [PATCH 353/363] Simplified boolean logic in ValueMappingMethod by removing inversion (#4007) --- .../ap/internal/model/ValueMappingMethod.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 66b6a75961..46af8f0efd 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 @@ -184,8 +184,8 @@ private List enumToEnumMapping(Method method, Type sourceType, Typ List mappings = new ArrayList<>(); List unmappedSourceConstants = new ArrayList<>( sourceType.getEnumConstants() ); - boolean sourceErrorOccurred = !reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); - boolean targetErrorOccurred = !reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); + boolean sourceErrorOccurred = reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); + boolean targetErrorOccurred = reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); if ( sourceErrorOccurred || targetErrorOccurred ) { return mappings; } @@ -268,8 +268,8 @@ private List enumToStringMapping(Method method, Type sourceType ) List mappings = new ArrayList<>(); List unmappedSourceConstants = new ArrayList<>( sourceType.getEnumConstants() ); - boolean sourceErrorOccurred = !reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); - boolean anyRemainingUsedError = !reportErrorIfSourceEnumConstantsContainsAnyRemaining( method ); + boolean sourceErrorOccurred = reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); + boolean anyRemainingUsedError = reportErrorIfSourceEnumConstantsContainsAnyRemaining( method ); if ( sourceErrorOccurred || anyRemainingUsedError ) { return mappings; } @@ -298,7 +298,7 @@ private List stringToEnumMapping(Method method, Type targetType ) List mappings = new ArrayList<>(); List unmappedSourceConstants = new ArrayList<>( targetType.getEnumConstants() ); - boolean sourceErrorOccurred = !reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); + boolean sourceErrorOccurred = reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); reportWarningIfAnyRemainingOrAnyUnMappedMissing( method ); if ( sourceErrorOccurred ) { return mappings; @@ -372,7 +372,7 @@ else if ( !sourceEnumConstants.contains( mappedConstant.getSource() ) ) { foundIncorrectMapping = true; } } - return !foundIncorrectMapping; + return foundIncorrectMapping; } private boolean reportErrorIfSourceEnumConstantsContainsAnyRemaining(Method method) { @@ -388,7 +388,7 @@ private boolean reportErrorIfSourceEnumConstantsContainsAnyRemaining(Method meth ); foundIncorrectMapping = true; } - return !foundIncorrectMapping; + return foundIncorrectMapping; } private void reportWarningIfAnyRemainingOrAnyUnMappedMissing(Method method) { @@ -462,7 +462,7 @@ else if ( valueMappings.nullTarget == null && valueMappings.nullValueTarget != n ); } - return !foundIncorrectMapping; + return foundIncorrectMapping; } private Type determineUnexpectedValueMappingException() { From 66625e61a4c4cbe72b58cb4187fde2cdf8e84fe4 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:01:38 +0100 Subject: [PATCH 354/363] Simplify fail in assertCheckstyleRules (#4003) --- .../org/mapstruct/ap/testutil/runner/CompilingExtension.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index a02ab2fe47..2599e62c2f 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -55,6 +55,7 @@ import org.xml.sax.InputSource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; @@ -237,8 +238,7 @@ private void assertCheckstyleRules() throws Exception { int errors = checker.process( findGeneratedFiles( new File( sourceOutputDir ) ) ); if ( errors > 0 ) { String errorLog = errorStream.toString( "UTF-8" ); - assertThat( true ).describedAs( "Expected checkstyle compliant output, but got errors:\n" + errorLog ) - .isEqualTo( false ); + fail( "Expected checkstyle compliant output, but got errors:\n" + errorLog ); } } } From 876a62d282af0ca0c5b6bf21070c5441db95c9b7 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:04:23 +0100 Subject: [PATCH 355/363] Update license plugin (#3999) --- .../build.gradle | 5 ++ .../settings.gradle | 5 ++ parent/pom.xml | 72 ++++++++++--------- pom.xml | 10 ++- 4 files changed, 55 insertions(+), 37 deletions(-) diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle index e62a8d087f..0df032f0f2 100644 --- a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/build.gradle @@ -1,3 +1,8 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ plugins { id 'java' } diff --git a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle index f62a77ab7b..b6ca918e91 100644 --- a/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/settings.gradle @@ -1 +1,6 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ rootProject.name = 'gradle-incremental-compilation-test' \ No newline at end of file diff --git a/parent/pom.xml b/parent/pom.xml index ffa9952de5..ad95ffacf3 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -509,9 +509,9 @@ ${kotlin.version} - com.mycila.maven-license-plugin - maven-license-plugin - 1.10.b1 + com.mycila + license-maven-plugin + 4.6 org.codehaus.mojo @@ -649,39 +649,43 @@ - com.mycila.maven-license-plugin - maven-license-plugin + com.mycila + license-maven-plugin -
        ${basedir}/../etc/license.txt
        true - - **/.idea/** - **/.mvn/** - **/build-config/checkstyle.xml - **/build-config/import-control.xml - copyright.txt - **/LICENSE.txt - **/mapstruct.xml - **/ci-settings.xml - **/eclipse-formatter-config.xml - **/forbidden-apis.txt - **/checkstyle-for-generated-sources.xml - **/nb-configuration.xml - **/junit-platform.properties - maven-settings.xml - readme.md - CONTRIBUTING.md - NEXT_RELEASE_CHANGELOG.md - .gitattributes - .gitignore - .factorypath - .checkstyle - *.yml - mvnw* - **/*.asciidoc - **/binding.xjb - **/*.flattened-pom.xml - + + +
        ${basedir}/../etc/license.txt
        + + **/.idea/** + **/.mvn/** + **/build-config/checkstyle.xml + **/build-config/import-control.xml + copyright.txt + **/LICENSE.txt + **/mapstruct.xml + **/ci-settings.xml + **/eclipse-formatter-config.xml + **/forbidden-apis.txt + **/checkstyle-for-generated-sources.xml + **/nb-configuration.xml + **/junit-platform.properties + maven-settings.xml + readme.md + CONTRIBUTING.md + NEXT_RELEASE_CHANGELOG.md + .gitattributes + .gitignore + .factorypath + .checkstyle + *.yml + mvnw* + **/*.asciidoc + **/binding.xjb + **/*.flattened-pom.xml + +
        +
        SLASHSTAR_STYLE SLASHSTAR_STYLE diff --git a/pom.xml b/pom.xml index f55f0955d3..c753e3c1e4 100644 --- a/pom.xml +++ b/pom.xml @@ -35,10 +35,14 @@ - com.mycila.maven-license-plugin - maven-license-plugin + com.mycila + license-maven-plugin -
        etc/license.txt
        + + +
        etc/license.txt
        +
        +
        XML_STYLE SLASHSTAR_STYLE From f0d488712f76e02a51218e5eeaefb65bd2680a32 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:43:48 +0100 Subject: [PATCH 356/363] Use StandardCharsets.UTF_8 in tests (#4002) --- .../mapstruct/ap/testutil/runner/CompilingExtension.java | 3 ++- .../org/mapstruct/ap/testutil/runner/GeneratedSource.java | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java index 2599e62c2f..cb12be725c 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -10,6 +10,7 @@ import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -237,7 +238,7 @@ private void assertCheckstyleRules() throws Exception { int errors = checker.process( findGeneratedFiles( new File( sourceOutputDir ) ) ); if ( errors > 0 ) { - String errorLog = errorStream.toString( "UTF-8" ); + String errorLog = errorStream.toString( StandardCharsets.UTF_8 ); fail( "Expected checkstyle compliant output, but got errors:\n" + errorLog ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java index 18a53b4971..6785ea5cf2 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java @@ -6,9 +6,9 @@ package org.mapstruct.ap.testutil.runner; import java.io.File; -import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -119,7 +119,7 @@ public JavaFileAssert forJavaFile(String path) { return new JavaFileAssert( new File( sourceOutputDir.get() + "/" + path ) ); } - private void handleFixtureComparison() throws UnsupportedEncodingException { + private void handleFixtureComparison() { for ( Class fixture : fixturesFor ) { String fixtureName = getMapperName( fixture ); URL expectedFile = getExpectedResource( fixtureName ); @@ -131,7 +131,7 @@ private void handleFixtureComparison() throws UnsupportedEncodingException { ) ); } else { - File expectedResource = new File( URLDecoder.decode( expectedFile.getFile(), "UTF-8" ) ); + File expectedResource = new File( URLDecoder.decode( expectedFile.getFile(), StandardCharsets.UTF_8 ) ); forMapper( fixture ).hasSameMapperContent( expectedResource ); } fixture.getPackage().getName(); From 1f3524570e362c34f4d0d7ce983c3596c83ad7c7 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:25:03 +0100 Subject: [PATCH 357/363] Add missing self reference in GeneratedTypeBuilder (#4009) --- .../java/org/mapstruct/ap/internal/model/GeneratedType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index 0bc3ec7bf0..4b39e92273 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -30,9 +30,9 @@ public abstract class GeneratedType extends ModelElement { private static final String JAVA_LANG_PACKAGE = "java.lang"; - protected abstract static class GeneratedTypeBuilder { + protected abstract static class GeneratedTypeBuilder> { - private T myself; + private final T myself; protected TypeFactory typeFactory; protected ElementUtils elementUtils; protected Options options; From bff3efd10640170a5ad10dea8a5fa0e4173a932a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 16 Mar 2026 00:25:54 +0100 Subject: [PATCH 358/363] Remove obsolete override of AssertJ version in integration tests (#4015) --- integrationtest/src/test/resources/pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/integrationtest/src/test/resources/pom.xml b/integrationtest/src/test/resources/pom.xml index c2ece3b38d..3053b211e1 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -26,8 +26,6 @@ ${mapstruct.version} - - 1.7.1 From 94da2c3f05af31ab9693056aab9785549212940b Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:30:40 +0100 Subject: [PATCH 359/363] Remove unsued methods in Fields leftover from c2e803403027f3fae92bd15b0ba50ab7df5063e6 (#4010) --- .../mapstruct/ap/internal/util/Fields.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java index 8e16a95274..b2f8ac1442 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java @@ -6,13 +6,7 @@ package org.mapstruct.ap.internal.util; import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.spi.TypeHierarchyErroneousException; /** * Provides functionality around {@link VariableElement}s. @@ -35,17 +29,4 @@ static boolean isPublic(VariableElement method) { private static boolean isNotStatic(VariableElement method) { return !method.getModifiers().contains( Modifier.STATIC ); } - - private static TypeElement asTypeElement(TypeMirror mirror) { - return (TypeElement) ( (DeclaredType) mirror ).asElement(); - } - - private static boolean hasNonObjectSuperclass(TypeElement element) { - if ( element.getSuperclass().getKind() == TypeKind.ERROR ) { - throw new TypeHierarchyErroneousException( element ); - } - - return element.getSuperclass().getKind() == TypeKind.DECLARED - && !asTypeElement( element.getSuperclass() ).getQualifiedName().toString().equals( "java.lang.Object" ); - } } From ded3daa5fcad390d60fe04d4d8e9479466722d63 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:31:23 +0100 Subject: [PATCH 360/363] Upgrade Visitor6 to Visitor8 (#4011) --- .../src/main/java/org/mapstruct/ap/MappingProcessor.java | 4 ++-- .../mapstruct/ap/internal/util/AccessorNamingUtils.java | 8 ++++---- .../mapstruct/ap/spi/DefaultAccessorNamingStrategy.java | 8 ++++---- .../java/org/mapstruct/ap/spi/DefaultBuilderProvider.java | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index a66762d739..32e65c7047 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -30,7 +30,7 @@ import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementKindVisitor6; +import javax.lang.model.util.ElementKindVisitor8; import javax.tools.Diagnostic.Kind; import org.mapstruct.ap.internal.gem.MapperGem; @@ -402,7 +402,7 @@ private R process(ProcessorContext context, ModelElementProcessor p private TypeElement asTypeElement(Element element) { return element.accept( - new ElementKindVisitor6() { + new ElementKindVisitor8() { @Override public TypeElement visitTypeAsInterface(TypeElement e, Void p) { return e; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java index 0f17946f72..8025acfeb1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java @@ -10,8 +10,8 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; @@ -84,7 +84,7 @@ public String getElementNameForAdder(Accessor adderMethod) { private static String getQualifiedName(TypeMirror type) { DeclaredType declaredType = type.accept( - new SimpleTypeVisitor6() { + new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; @@ -98,7 +98,7 @@ public DeclaredType visitDeclared(DeclaredType t, Void p) { } TypeElement typeElement = declaredType.asElement().accept( - new SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java index e7529dcd08..a726e9691f 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -14,8 +14,8 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; import kotlin.Metadata; @@ -273,7 +273,7 @@ public String getElementName(ExecutableElement adderMethod) { */ protected static String getQualifiedName(TypeMirror type) { DeclaredType declaredType = type.accept( - new SimpleTypeVisitor6() { + new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; @@ -287,7 +287,7 @@ public DeclaredType visitDeclared(DeclaredType t, Void p) { } TypeElement typeElement = declaredType.asElement().accept( - new SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index 333a11dbcc..c1126fa9fb 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -21,8 +21,8 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; /** @@ -126,7 +126,7 @@ private TypeElement getTypeElement(DeclaredType declaredType) { } return declaredType.asElement().accept( - new SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; @@ -148,7 +148,7 @@ private DeclaredType getDeclaredType(TypeMirror type) { throw new TypeHierarchyErroneousException( type ); } return type.accept( - new SimpleTypeVisitor6() { + new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; From 0780d98fc2045092adbfe4417e46d853ecd946b7 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:37:00 +0100 Subject: [PATCH 361/363] Upgrade freemarker to 2.3.34 (#4012) --- parent/pom.xml | 2 +- .../org/mapstruct/ap/internal/model/Annotation.ftl | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index ad95ffacf3..88bfd5053c 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -119,7 +119,7 @@ org.freemarker freemarker - 2.3.32 + 2.3.34 org.assertj diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl index 81f3d2ba8b..0b6737fbe4 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl @@ -7,12 +7,10 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Annotation" --> <#switch properties?size> - <#case 0> + <#on 0> @<@includeModel object=type/><#rt> - <#break> - <#case 1> + <#on 1> @<@includeModel object=type/>(<@includeModel object=properties[0]/>)<#rt> - <#break> <#default> @<@includeModel object=type/>( <#list properties as property> From e0fa28868d8e01ecdb9df23440464aa9f1f07832 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:37:41 +0100 Subject: [PATCH 362/363] Remove deprecated Number api usage from tests (#4013) --- .../ap/test/selection/twosteperror/ErroneousMapperMC.java | 2 +- .../mapstruct/ap/test/source/constants/SourceConstantsTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java b/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java index 4b7dcf90a3..b6381cf6da 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/twosteperror/ErroneousMapperMC.java @@ -22,7 +22,7 @@ default BigDecimal methodX1(SourceType s) { } default Double methodX2(SourceType s) { - return new Double( s.t1 ); + return Double.valueOf( s.t1 ); } // CHECKSTYLE:OFF diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java index b331a2efba..1b1764fe56 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java @@ -47,7 +47,7 @@ public void shouldMapSameSourcePropertyToSeveralTargetProperties() throws ParseE assertThat( target.getStringConstant() ).isEqualTo( "stringConstant" ); assertThat( target.getEmptyStringConstant() ).isEqualTo( "" ); assertThat( target.getIntegerConstant() ).isEqualTo( 14 ); - assertThat( target.getLongWrapperConstant() ).isEqualTo( new Long( 3001L ) ); + assertThat( target.getLongWrapperConstant() ).isEqualTo( Long.valueOf( 3001L ) ); assertThat( target.getDateConstant() ).isEqualTo( getDate( "dd-MM-yyyy", "09-01-2014" ) ); assertThat( target.getNameConstants() ).isEqualTo( Arrays.asList( "jack", "jill", "tom" ) ); assertThat( target.getCountry() ).isEqualTo( CountryEnum.THE_NETHERLANDS ); From dab3eaf237eb025a0d59f997263fea515cb6db64 Mon Sep 17 00:00:00 2001 From: hduelme <46139144+hduelme@users.noreply.github.com> Date: Mon, 16 Mar 2026 00:44:29 +0100 Subject: [PATCH 363/363] #3949: Support SET_TO_NULL for overloaded target methods, requiring a cast (#3988) --- .../model/CollectionAssignmentBuilder.java | 3 +- .../ap/internal/model/PropertyMapping.java | 34 ++++++- .../model/assignment/SetterWrapper.java | 24 ++++- .../model/assignment/UpdateWrapper.java | 9 +- .../ap/internal/model/common/Type.java | 2 +- .../model/assignment/UpdateWrapper.ftl | 2 +- .../ap/internal/model/macro/CommonMacros.ftl | 2 +- .../ap/test/bugs/_3949/DateSource.java | 14 +++ .../test/bugs/_3949/Issue3949ClassMapper.java | 29 ++++++ .../bugs/_3949/Issue3949InterfaceMapper.java | 29 ++++++ .../ap/test/bugs/_3949/Issue3949Test.java | 94 +++++++++++++++++++ .../ap/test/bugs/_3949/ParentSource.java | 12 +++ .../ap/test/bugs/_3949/ParentTarget.java | 25 +++++ .../bugs/_3949/ParentTargetInterface.java | 14 +++ .../ap/test/bugs/_3949/StringSource.java | 12 +++ .../ap/test/bugs/_3949/TargetDate.java | 33 +++++++ .../test/bugs/_3949/TargetDateInterface.java | 18 ++++ .../ap/test/bugs/_3949/TargetString.java | 34 +++++++ .../bugs/_3949/TargetStringInterface.java | 18 ++++ 19 files changed, 399 insertions(+), 9 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java 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 76e56cd976..db9e012d78 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 @@ -163,7 +163,8 @@ public Assignment build() { targetType, true, nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT + nvpms == SET_TO_DEFAULT, + false ); } else if ( method.isUpdateMethod() && !targetImmutable ) { 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 622ba9cef0..c120f035b2 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 @@ -13,6 +13,8 @@ import java.util.Set; import java.util.function.Supplier; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; @@ -454,7 +456,8 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) { targetType, !rhs.isSourceReferenceParameter(), nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName() ); } else { @@ -471,7 +474,10 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) { isFieldAssignment(), includeSourceNullCheck, includeSourceNullCheck && nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT ); + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName(), + targetType + ); } } @@ -547,12 +553,33 @@ else if ( result.getSourceType().isStreamType() ) { isFieldAssignment(), true, nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName(), + targetType ); } return result; } + private boolean hasTwoOrMoreSettersWithName() { + Element enclosingClass = this.targetWriteAccessor.getElement().getEnclosingElement(); + if ( enclosingClass == null || !( ElementKind.CLASS.equals( enclosingClass.getKind() ) + || ElementKind.INTERFACE.equals( enclosingClass.getKind() ) ) ) { + return false; + } + String simpleWriteAccessorName = this.targetWriteAccessor.getSimpleName(); + boolean firstMatchFound = false; + for ( Accessor setter : ctx.getTypeFactory().getType( enclosingClass.asType() ).getSetters() ) { + if ( setter.getSimpleName().equals( simpleWriteAccessorName ) ) { + if ( firstMatchFound ) { + return true; + } + firstMatchFound = true; + } + } + return false; + } + private Assignment assignToCollection(Type targetType, AccessorType targetAccessorType, Assignment rhs) { return new CollectionAssignmentBuilder() @@ -991,6 +1018,7 @@ public PropertyMapping build() { targetType, false, false, + false, false ); } else { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java index 7be948c857..94b031c622 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java @@ -6,7 +6,9 @@ package org.mapstruct.ap.internal.model.assignment; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; @@ -22,19 +24,25 @@ public class SetterWrapper extends AssignmentWrapper { private final boolean includeSourceNullCheck; private final boolean setExplicitlyToNull; private final boolean setExplicitlyToDefault; + private final boolean mustCastForNull; + private final Type nullCastType; public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fieldAssignment, boolean includeSourceNullCheck, boolean setExplicitlyToNull, - boolean setExplicitlyToDefault) { + boolean setExplicitlyToDefault, + boolean mustCastForNull, + Type nullCastType) { super( rhs, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; this.includeSourceNullCheck = includeSourceNullCheck; this.setExplicitlyToDefault = setExplicitlyToDefault; this.setExplicitlyToNull = setExplicitlyToNull; + this.mustCastForNull = mustCastForNull; + this.nullCastType = nullCastType; } public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fieldAssignment ) { @@ -43,6 +51,8 @@ public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fi this.includeSourceNullCheck = false; this.setExplicitlyToNull = false; this.setExplicitlyToDefault = false; + this.mustCastForNull = false; + this.nullCastType = null; } @Override @@ -59,6 +69,15 @@ public List getThrownTypes() { return result; } + @Override + public Set getImportTypes() { + Set imported = new HashSet<>( super.getImportTypes() ); + if ( isSetExplicitlyToNull() && isMustCastForNull() ) { + imported.add( nullCastType ); + } + return imported; + } + public boolean isSetExplicitlyToNull() { return setExplicitlyToNull; } @@ -71,4 +90,7 @@ public boolean isIncludeSourceNullCheck() { return includeSourceNullCheck; } + public boolean isMustCastForNull() { + return mustCastForNull; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java index ff5089d6c2..c3d3f9c446 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java @@ -26,6 +26,7 @@ public class UpdateWrapper extends AssignmentWrapper { private final boolean includeSourceNullCheck; private final boolean setExplicitlyToNull; private final boolean setExplicitlyToDefault; + private final boolean mustCastForNull; public UpdateWrapper( Assignment decoratedAssignment, List thrownTypesToExclude, @@ -34,7 +35,8 @@ public UpdateWrapper( Assignment decoratedAssignment, Type targetType, boolean includeSourceNullCheck, boolean setExplicitlyToNull, - boolean setExplicitlyToDefault ) { + boolean setExplicitlyToDefault, + boolean mustCastForNull) { super( decoratedAssignment, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; this.factoryMethod = factoryMethod; @@ -42,6 +44,7 @@ public UpdateWrapper( Assignment decoratedAssignment, this.includeSourceNullCheck = includeSourceNullCheck; this.setExplicitlyToDefault = setExplicitlyToDefault; this.setExplicitlyToNull = setExplicitlyToNull; + this.mustCastForNull = mustCastForNull; } private static Type determineImplType(Assignment factoryMethod, Type targetType) { @@ -100,4 +103,8 @@ public boolean isSetExplicitlyToNull() { public boolean isSetExplicitlyToDefault() { return setExplicitlyToDefault; } + + public boolean isMustCastForNull() { + return mustCastForNull; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 3e81cd622d..6287403ba3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -1061,7 +1061,7 @@ private TypeMirror boxed(TypeMirror possiblePrimitive) { * * @return an unmodifiable list of all setters */ - private List getSetters() { + public List getSetters() { if ( setters == null ) { setters = Collections.unmodifiableList( filters.setterMethodsIn( getAllMethods() ) ); } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index d8ed4b53f6..933612f579 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -18,7 +18,7 @@ } <#if setExplicitlyToDefault || setExplicitlyToNull> else { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else><#if mustCastForNull>(<@includeModel object=ext.targetType/>) ${ext.targetType.null}; } <#else> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index 9c4ad26e03..f409fda793 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -45,7 +45,7 @@ } <#elseif setExplicitlyToDefault || setExplicitlyToNull> else { - <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}; + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else><#if mustCastForNull!false>(<@includeModel object=ext.targetType/>) ${ext.targetType.null}; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java new file mode 100644 index 0000000000..e542c388d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java @@ -0,0 +1,14 @@ +/* + * 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._3949; + +import java.time.LocalDate; + +public class DateSource { + public LocalDate getDate() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java new file mode 100644 index 0000000000..135aa79056 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java @@ -0,0 +1,29 @@ +/* + * 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._3949; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL) +public interface Issue3949ClassMapper { + + Issue3949ClassMapper INSTANCE = Mappers.getMapper( Issue3949ClassMapper.class ); + + void overwriteDate(@MappingTarget TargetDate target, DateSource dateSource); + + void overwriteString(@MappingTarget TargetString target, StringSource stringSource); + + void overwriteDateWithConversion(@MappingTarget TargetDate target, StringSource dateSource); + + void overwriteStringWithConversion(@MappingTarget TargetString target, DateSource stringSource); + + void updateParent(@MappingTarget ParentTarget target, ParentSource source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java new file mode 100644 index 0000000000..c25041ad2f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java @@ -0,0 +1,29 @@ +/* + * 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._3949; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL) +public interface Issue3949InterfaceMapper { + + Issue3949InterfaceMapper INSTANCE = Mappers.getMapper( Issue3949InterfaceMapper.class ); + + void overwriteDate(@MappingTarget TargetDateInterface target, DateSource dateSource); + + void overwriteString(@MappingTarget TargetStringInterface target, StringSource stringSource); + + void overwriteDateWithConversion(@MappingTarget TargetDateInterface target, StringSource dateSource); + + void overwriteStringWithConversion(@MappingTarget TargetStringInterface target, DateSource stringSource); + + void updateParent(@MappingTarget ParentTargetInterface target, ParentSource source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java new file mode 100644 index 0000000000..b6abee819c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java @@ -0,0 +1,94 @@ +/* + * 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._3949; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests if overloaded targets are correctly cast when set to null + * + * @author hduelme + */ +@IssueKey("3949") +@WithClasses({ + ParentSource.class, + ParentTargetInterface.class, + ParentTarget.class, + StringSource.class, + TargetStringInterface.class, + TargetString.class, + DateSource.class, + TargetDateInterface.class, + TargetDate.class +}) +public class Issue3949Test { + + @ProcessorTest + @WithClasses({ + Issue3949ClassMapper.class + }) + void shouldCompileAndSetCorrectlyToNullForClass() { + TargetDate shouldSetDateToNull = new TargetDate(); + Issue3949ClassMapper.INSTANCE.overwriteDate( shouldSetDateToNull, new DateSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + shouldSetDateToNull = new TargetDate(); + Issue3949ClassMapper.INSTANCE.overwriteDateWithConversion( shouldSetDateToNull, new StringSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + TargetString shouldSetStringToNull = new TargetString(); + Issue3949ClassMapper.INSTANCE.overwriteString( shouldSetStringToNull, new StringSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + shouldSetStringToNull = new TargetString(); + Issue3949ClassMapper.INSTANCE.overwriteStringWithConversion( shouldSetStringToNull, new DateSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + ParentTarget parentTarget = new ParentTarget(); + parentTarget.setChild( new ParentTarget() ); + Issue3949ClassMapper.INSTANCE.updateParent( parentTarget, new ParentSource() ); + assertThat( parentTarget.getChild() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + Issue3949InterfaceMapper.class + }) + void shouldCompileAndSetCorrectlyToNullForInterface() { + TargetDateInterface shouldSetDateToNull = new TargetDate(); + Issue3949InterfaceMapper.INSTANCE.overwriteDate( shouldSetDateToNull, new DateSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + shouldSetDateToNull = new TargetDate(); + Issue3949InterfaceMapper.INSTANCE.overwriteDateWithConversion( shouldSetDateToNull, new StringSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + TargetStringInterface shouldSetStringToNull = new TargetString(); + Issue3949InterfaceMapper.INSTANCE.overwriteString( shouldSetStringToNull, new StringSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + shouldSetStringToNull = new TargetString(); + Issue3949InterfaceMapper.INSTANCE.overwriteStringWithConversion( shouldSetStringToNull, new DateSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + ParentTargetInterface parentTarget = new ParentTarget(); + parentTarget.setChild( new ParentTarget() ); + Issue3949InterfaceMapper.INSTANCE.updateParent( parentTarget, new ParentSource() ); + assertThat( parentTarget.getChild() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java new file mode 100644 index 0000000000..551501aa24 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java @@ -0,0 +1,12 @@ +/* + * 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._3949; + +public class ParentSource { + public ParentSource getChild() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java new file mode 100644 index 0000000000..3d8ca3b51b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java @@ -0,0 +1,25 @@ +/* + * 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._3949; + +public class ParentTarget implements ParentTargetInterface { + private ParentTarget child; + + @Override + public ParentTarget getChild() { + return child; + } + + @Override + public void setChild(String child) { + throw new IllegalArgumentException(); + } + + @Override + public void setChild(ParentTarget child) { + this.child = child; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java new file mode 100644 index 0000000000..5311fb4c9b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java @@ -0,0 +1,14 @@ +/* + * 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._3949; + +public interface ParentTargetInterface { + ParentTarget getChild(); + + void setChild(String child); + + void setChild(ParentTarget child); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java new file mode 100644 index 0000000000..11ba1419d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java @@ -0,0 +1,12 @@ +/* + * 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._3949; + +public class StringSource { + public String getDate() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java new file mode 100644 index 0000000000..85b12a618d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java @@ -0,0 +1,33 @@ +/* + * 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._3949; + +import java.time.LocalDate; + +public class TargetDate implements TargetDateInterface { + private LocalDate date = LocalDate.now(); + private String string = ""; + + @Override + public void setDate(LocalDate date) { + this.date = date; + } + + @Override + public void setDate(String date) { + this.string = date; + } + + @Override + public LocalDate getDate() { + return date; + } + + @Override + public String getString() { + return string; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java new file mode 100644 index 0000000000..df28936914 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java @@ -0,0 +1,18 @@ +/* + * 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._3949; + +import java.time.LocalDate; + +public interface TargetDateInterface { + void setDate(LocalDate date); + + void setDate(String date); + + LocalDate getDate(); + + String getString(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java new file mode 100644 index 0000000000..b8cbbb2bbb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java @@ -0,0 +1,34 @@ +/* + * 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._3949; + +import java.time.LocalDate; + +public class TargetString implements TargetStringInterface { + private LocalDate date = LocalDate.now(); + private String string = ""; + + @Override + public void setDate(LocalDate date) { + this.date = date; + } + + @Override + public void setDate(String date) { + this.string = date; + } + + @Override + public String getDate() { + return string; + } + + @Override + public LocalDate getDateValue() { + return date; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java new file mode 100644 index 0000000000..dd82231a51 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java @@ -0,0 +1,18 @@ +/* + * 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._3949; + +import java.time.LocalDate; + +public interface TargetStringInterface { + void setDate(LocalDate date); + + void setDate(String date); + + String getDate(); + + LocalDate getDateValue(); +}