diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index 44c757b11..d371e310b 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -944,6 +944,8 @@ public interface CarMapper { * Between `java.time.ZonedDateTime`, `java.time.LocalDateTime`, `java.time.LocalDate`, `java.time.LocalTime` from Java 8 Date-Time package and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option (see above). +* Between `java.time.Instant`, `java.time.Duration`, `java.time.Period` from Java 8 Date-Time package and `String` using the `parse` method in each class to map from `String` and using `toString` to map into `String`. + * Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Date` where, when mapping a `ZonedDateTime` from a given `Date`, the system default timezone is used. * Between `java.time.LocalDateTime` from Java 8 Date-Time package and `java.util.Date` where timezone UTC is used as the timezone. 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 6f4db9316..9f0ad9748 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 @@ -9,10 +9,12 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Time; import java.sql.Timestamp; +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.Calendar; import java.util.Currency; @@ -171,8 +173,8 @@ public class Conversions { registerToStringConversion( Boolean.class ); register( char.class, String.class, new CharToStringConversion() ); register( Character.class, String.class, new CharWrapperToStringConversion() ); - register( BigInteger.class, String.class, new BigIntegerToStringConversion( ) ); - register( BigDecimal.class, String.class, new BigDecimalToStringConversion( ) ); + register( BigInteger.class, String.class, new BigIntegerToStringConversion() ); + register( BigDecimal.class, String.class, new BigDecimalToStringConversion() ); registerJodaConversions(); @@ -217,6 +219,9 @@ public class Conversions { register( LocalDate.class, String.class, new JavaLocalDateToStringConversion() ); register( LocalDateTime.class, String.class, new JavaLocalDateTimeToStringConversion() ); register( LocalTime.class, String.class, new JavaLocalTimeToStringConversion() ); + register( Instant.class, String.class, new StaticParseToStringConversion() ); + register( Period.class, String.class, new StaticParseToStringConversion() ); + register( Duration.class, String.class, new StaticParseToStringConversion() ); // Java 8 to Date register( ZonedDateTime.class, Date.class, new JavaZonedDateTimeToDateConversion() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/StaticParseToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/StaticParseToStringConversion.java new file mode 100644 index 000000000..15d7b4bcc --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/StaticParseToStringConversion.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.conversion; + +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; + +/** + * Handles conversion between a target type T and {@link String}, + * where {@code T#parse(String)} and {@code T#toString} are inverse operations. + * The {@link ConversionContext#getTargetType()} is used as the from target type T. + */ +public class StaticParseToStringConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toString()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".parse( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( conversionContext.getTargetType() ); + } + +} 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 271a8d958..573e300bf 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 @@ -5,12 +5,12 @@ */ package org.mapstruct.ap.test.conversion.java8time; -import static org.assertj.core.api.Assertions.assertThat; - +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; @@ -23,6 +23,8 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import static org.assertj.core.api.Assertions.assertThat; + /** * Tests for conversions to/from Java 8 date and time types. */ @@ -99,7 +101,7 @@ public class Java8TimeConversionTest { Target target = new Target(); target.setZonedDateTime( dateTimeAsString ); ZonedDateTime sourceDateTime = - ZonedDateTime.of( java.time.LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + ZonedDateTime.of( java.time.LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); Source src = SourceTargetMapper.INSTANCE.targetToSourceDateTimeMapped( target ); assertThat( src ).isNotNull(); @@ -112,7 +114,7 @@ public class Java8TimeConversionTest { Target target = new Target(); target.setLocalDateTime( dateTimeAsString ); LocalDateTime sourceDateTime = - LocalDateTime.of( 2014, 1, 1, 0, 0, 0 ); + LocalDateTime.of( 2014, 1, 1, 0, 0, 0 ); Source src = SourceTargetMapper.INSTANCE.targetToSourceLocalDateTimeMapped( target ); assertThat( src ).isNotNull(); @@ -125,7 +127,7 @@ public class Java8TimeConversionTest { Target target = new Target(); target.setLocalDate( dateTimeAsString ); LocalDate sourceDate = - LocalDate.of( 2014, 1, 1 ); + LocalDate.of( 2014, 1, 1 ); Source src = SourceTargetMapper.INSTANCE.targetToSourceLocalDateMapped( target ); assertThat( src ).isNotNull(); @@ -138,7 +140,7 @@ public class Java8TimeConversionTest { Target target = new Target(); target.setLocalTime( dateTimeAsString ); LocalTime sourceTime = - LocalTime.of( 0, 0 ); + LocalTime.of( 0, 0 ); Source src = SourceTargetMapper.INSTANCE.targetToSourceLocalTimeMapped( target ); assertThat( src ).isNotNull(); @@ -169,13 +171,14 @@ public class Java8TimeConversionTest { Source src = SourceTargetMapper.INSTANCE.targetToSource( target ); assertThat( src.getZonedDateTime() ).isEqualTo( - ZonedDateTime.of( - java.time.LocalDateTime.of( - 2014, - 1, - 1, - 0, - 0 ), ZoneId.of( "UTC" ) ) ); + ZonedDateTime.of( + java.time.LocalDateTime.of( + 2014, + 1, + 1, + 0, + 0 + ), ZoneId.of( "UTC" ) ) ); assertThat( src.getLocalDateTime() ).isEqualTo( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); assertThat( src.getLocalDate() ).isEqualTo( LocalDate.of( 2014, 1, 1 ) ); assertThat( src.getLocalTime() ).isEqualTo( LocalTime.of( 0, 0 ) ); @@ -186,17 +189,17 @@ public class Java8TimeConversionTest { Source source = new Source(); ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); source.setForCalendarConversion( - dateTime ); + dateTime ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target.getForCalendarConversion() ).isNotNull(); assertThat( target.getForCalendarConversion().getTimeZone() ).isEqualTo( - TimeZone.getTimeZone( - "UTC" ) ); + TimeZone.getTimeZone( + "UTC" ) ); assertThat( target.getForCalendarConversion().get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); assertThat( target.getForCalendarConversion().get( Calendar.MONTH ) ).isEqualTo( - dateTime.getMonthValue() - 1 ); + 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() ); @@ -212,7 +215,7 @@ public class Java8TimeConversionTest { Source source = new Source(); ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); source.setForDateConversionWithZonedDateTime( - dateTime ); + dateTime ); Target target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); assertThat( target.getForDateConversionWithZonedDateTime() ).isNotNull(); @@ -319,4 +322,124 @@ public class Java8TimeConversionTest { assertThat( source.getForSqlDateConversionWithLocalDate() ).isEqualTo( localDate ); } + + @Test + public void testInstantToStringMapping() { + Source source = new Source(); + source.setForInstantConversionWithString( Instant.ofEpochSecond( 42L ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isEqualTo( "1970-01-01T00:00:42Z" ); + } + + @Test + public void testInstantToStringNullMapping() { + Source source = new Source(); + source.setForInstantConversionWithString( null ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isNull(); + } + + @Test + public void testStringToInstantMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( "1970-01-01T00:00:00.000Z" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Instant instant = source.getForInstantConversionWithString(); + assertThat( instant ).isEqualTo( Instant.EPOCH ); + } + + @Test + public void testStringToInstantNullMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( null ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Instant instant = source.getForInstantConversionWithString(); + assertThat( instant ).isNull(); + } + + @Test + public void testPeriodToStringMapping() { + Source source = new Source(); + source.setForPeriodConversionWithString( Period.ofDays( 42 ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isEqualTo( "P42D" ); + } + + @Test + public void testPeriodToStringNullMapping() { + Source source = new Source(); + source.setForPeriodConversionWithString( null ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isNull(); + } + + @Test + public void testStringToPeriodMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( "P1Y2M3D" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Period period = source.getForPeriodConversionWithString(); + assertThat( period ).isEqualTo( Period.of( 1, 2, 3 ) ); + } + + @Test + public void testStringToPeriodNullMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( null ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Period period = source.getForPeriodConversionWithString(); + assertThat( period ).isNull(); + } + + @Test + public void testDurationToStringMapping() { + Source source = new Source(); + source.setForDurationConversionWithString( Duration.ofMinutes( 42L ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isEqualTo( "PT42M" ); + } + + @Test + public void testDurationToStringNullMapping() { + Source source = new Source(); + source.setForDurationConversionWithString( null ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isNull(); + } + + @Test + public void testStringToDurationMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( "PT20.345S" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Duration duration = source.getForDurationConversionWithString(); + assertThat( duration ).isEqualTo( Duration.ofSeconds( 20L, 345000000L ) ); + } + + @Test + public void testStringToDurationNullMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( null ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Duration duration = source.getForDurationConversionWithString(); + assertThat( duration ).isNull(); + } } 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 886297170..91298617c 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 @@ -5,10 +5,12 @@ */ 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; /** @@ -36,6 +38,12 @@ public class Source { private Instant forDateConversionWithInstant; + private Instant forInstantConversionWithString; + + private Period forPeriodConversionWithString; + + private Duration forDurationConversionWithString; + public ZonedDateTime getZonedDateTime() { return zonedDateTime; } @@ -115,4 +123,28 @@ public class Source { public void setForDateConversionWithInstant(Instant forDateConversionWithInstant) { this.forDateConversionWithInstant = forDateConversionWithInstant; } + + public Instant getForInstantConversionWithString() { + return forInstantConversionWithString; + } + + public void setForInstantConversionWithString(Instant forInstantConversionWithString) { + this.forInstantConversionWithString = forInstantConversionWithString; + } + + public Period getForPeriodConversionWithString() { + return forPeriodConversionWithString; + } + + public void setForPeriodConversionWithString(Period forPeriodConversionWithString) { + this.forPeriodConversionWithString = forPeriodConversionWithString; + } + + public Duration getForDurationConversionWithString() { + return forDurationConversionWithString; + } + + public void setForDurationConversionWithString(Duration forDurationConversionWithString) { + this.forDurationConversionWithString = forDurationConversionWithString; + } } 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 eac1603d4..188a6d0e2 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 @@ -33,6 +33,12 @@ public class Target { private Date forDateConversionWithInstant; + private String forInstantConversionWithString; + + private String forPeriodConversionWithString; + + private String forDurationConversionWithString; + public String getZonedDateTime() { return zonedDateTime; } @@ -112,4 +118,28 @@ public class Target { public void setForDateConversionWithInstant(Date forDateConversionWithInstant) { this.forDateConversionWithInstant = forDateConversionWithInstant; } + + public String getForInstantConversionWithString() { + return forInstantConversionWithString; + } + + public void setForInstantConversionWithString(String forInstantConversionWithString) { + this.forInstantConversionWithString = forInstantConversionWithString; + } + + public String getForPeriodConversionWithString() { + return forPeriodConversionWithString; + } + + public void setForPeriodConversionWithString(String forPeriodConversionWithString) { + this.forPeriodConversionWithString = forPeriodConversionWithString; + } + + public String getForDurationConversionWithString() { + return forDurationConversionWithString; + } + + public void setForDurationConversionWithString(String forDurationConversionWithString) { + this.forDurationConversionWithString = forDurationConversionWithString; + } }