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] #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 76153127b..d644dfe03 100644 --- a/core/src/main/java/org/mapstruct/IterableMapping.java +++ b/core/src/main/java/org/mapstruct/IterableMapping.java @@ -66,19 +66,38 @@ public @interface IterableMapping { /** * 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 271272bb4..093099cf5 100644 --- a/core/src/main/java/org/mapstruct/MapMapping.java +++ b/core/src/main/java/org/mapstruct/MapMapping.java @@ -56,8 +56,12 @@ public @interface MapMapping { /** * 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 @@ public @interface MapMapping { * 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 8b0c4adb0..6c95b0db2 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -175,19 +175,38 @@ public @interface Mapping { /** * 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 92a93e389..384013a7b 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.HelperMethod; 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 @@ public class BigDecimalToStringConversion extends AbstractNumberToStringConversi 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 df0a48a67..540d89db5 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.HelperMethod; 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 @@ public class BigIntegerToStringConversion extends AbstractNumberToStringConversi 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 aa01a7327..96960c4a1 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.net.URL; 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 final class ConversionUtils { 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 77c59445a..d1b49cff6 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.Type; 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 @@ import org.mapstruct.ap.internal.model.source.MappingMethodOptions; 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 class CreateDecimalFormat extends HelperMethod { 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 361ce9d9a..35c7f88d7 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.text.SimpleDateFormat; 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 class DateToStringConversion implements ConversionProvider { @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 class DateToStringConversion implements ConversionProvider { 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 @@ public class DateToStringConversion implements ConversionProvider { 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 fcc724129..909ce8c0f 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.NativeTypes; 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 class PrimitiveToStringConversion extends AbstractNumberToStringConversio @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 class PrimitiveToStringConversion extends AbstractNumberToStringConversio @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 @@ public class PrimitiveToStringConversion extends AbstractNumberToStringConversio 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 300c90121..dd7b6bac8 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.NativeTypes; 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 class WrapperToStringConversion extends AbstractNumberToStringConversion @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 class WrapperToStringConversion extends AbstractNumberToStringConversion @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 @@ public class WrapperToStringConversion extends AbstractNumberToStringConversion 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 c2aa73f30..96d3d6fe7 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 f5a9fcc76..159f1663e 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 class DefaultConversionContext implements ConversionContext { this.formattingParameters = formattingParameters; this.dateFormat = this.formattingParameters.getDate(); this.numberFormat = this.formattingParameters.getNumber(); + this.locale = this.formattingParameters.getLocale(); validateDateFormat(); } @@ -64,6 +66,11 @@ public class DefaultConversionContext implements ConversionContext { 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 e21f5d74f..48cee4bbe 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 @@ import javax.lang.model.element.Element; */ 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 class FormattingParameters { 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 c4770efde..50fca3e4d 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 class IterableMappingOptions extends DelegatingOptions { 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 fd1758d8d..9f3d12faf 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 @@ package org.mapstruct.ap.internal.model.source; 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 class MapMappingOptions extends DelegatingOptions { return options; } + String locale = mapMapping.locale().getValue(); + SelectionParameters keySelection = new SelectionParameters( mapMapping.keyQualifiedBy().get(), mapMapping.keyQualifiedByName().get(), @@ -66,7 +68,8 @@ public class MapMappingOptions extends DelegatingOptions { mapMapping.keyNumberFormat().get(), mapMapping.mirror(), mapMapping.keyDateFormat().getAnnotationValue(), - method + method, + locale ); FormattingParameters valueFormatting = new FormattingParameters( @@ -74,7 +77,8 @@ public class MapMappingOptions extends DelegatingOptions { 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 22f9ccdc2..746aca5a3 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.AnnotationMirror; 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 class MappingOptions extends DelegatingOptions { 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 class MappingOptions extends DelegatingOptions { 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 ce0f605f1..6753a73d6 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 ce2f895e0..401a6b224 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 class DefaultConversionContextTest { 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 class DefaultConversionContextTest { 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 class DefaultConversionContextTest { 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 9ccf8e7e1..bc9022820 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 class DateConversionTest { 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 class DateConversionTest { 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 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 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 class DateConversionTest { ); } + @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 class DateConversionTest { 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 class DateConversionTest { 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 class DateConversionTest { ); } + @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 class DateConversionTest { ); } + @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 2ef6a7bd9..1971a8c06 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 6dfc1b3ca..39b4e9831 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 @@ package org.mapstruct.ap.test.conversion.numbers; 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 @@ import static org.assertj.core.api.Assertions.entry; @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 class NumberFormatConversionTest { 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 class NumberFormatConversionTest { 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 class NumberFormatConversionTest { 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 537452fca..b82a7a619 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 000000000..ccf8042e6 --- /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 000000000..0b536b832 --- /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; + } +}