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 4f4182d5c..7d63ccf77 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 @@ public class MavenIntegrationTest { 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 000000000..b70c48f9d --- /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 000000000..28d4fd3cd --- /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 000000000..23d665c6f --- /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 000000000..f4baa4d04 --- /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 000000000..6effd485a --- /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 000000000..42ed70b18 --- /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 e788b51f6..5046ce8b2 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 @@ import org.mapstruct.tools.gem.GemValue; */ 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 3df90421b..8f3a745e8 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 class JavaDefaultExpressionTest { 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 000000000..ba2772637 --- /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 be8064460..9a20ddcdb 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 class JavaExpressionTest { 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 000000000..50d0b4b2c --- /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); +}