From 35f5400e0038cf5eca276de5504ef63ccb38d8dd Mon Sep 17 00:00:00 2001 From: sjaakd Date: Sun, 1 Apr 2018 15:15:42 +0200 Subject: [PATCH] #1401 improvements by direct assigning constants --- .../mapstruct-reference-guide.asciidoc | 3 +- .../ap/internal/model/BeanMappingMethod.java | 3 +- .../ap/internal/model/PropertyMapping.java | 17 +- .../ap/internal/model/common/Type.java | 20 +- .../ap/internal/model/common/TypeFactory.java | 53 ++- .../creation/MappingResolverImpl.java | 12 + .../ap/internal/util/NativeTypes.java | 333 +++++++++++++++++ .../DateFormatValidatorFactoryTest.java | 2 + .../common/DefaultConversionContextTest.java | 2 + .../constants/ConstantOptimizingTest.java | 342 ++++++++++++++++++ .../source/constants/ErroneousMapper1.java | 2 +- .../source/constants/ErroneousMapper3.java | 2 +- .../source/constants/ErroneousMapper4.java | 2 +- .../source/constants/ErroneousMapper5.java | 2 +- .../source/constants/ErroneousMapper6.java | 46 +++ .../source/constants/SourceConstantsTest.java | 24 +- .../source/constants/SourceTargetMapper.java | 2 +- 17 files changed, 850 insertions(+), 17 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/source/constants/ConstantOptimizingTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper6.java diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index 122d59734..39b393f7c 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -1918,7 +1918,8 @@ This chapter describes several advanced options which allow to fine-tune the beh [[default-values-and-constants]] === Default values and constants -Default values can be specified to set a predefined value to a target property if the corresponding source property is `null`. Constants can be specified to set such a predefined value in any case. Default values and constants are specified as String values and are subject to type conversion either via built-in conversions or the invocation of other mapping methods in order to match the type required by the target property. +Default values can be specified to set a predefined value to a target property if the corresponding source property is `null`. Constants can be specified to set such a predefined value in any case. Default values and constants are specified as String values. When the target type is a primitive or a boxed type, the String value is taken literal. Bit / octal / decimal / hex patterns are allowed in such case as long as they are a valid literal. +In all other cases, constant or default values are subject to type conversion either via built-in conversions or the invocation of other mapping methods in order to match the type required by the target property. A mapping with a constant must not include a reference to a source property. The following example shows some mappings using default values and constants: diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 7eaf9f659..506b4bf4e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -496,13 +496,14 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { propertyMapping = new ConstantMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) - .constantExpression( "\"" + mapping.getConstant() + "\"" ) + .constantExpression( mapping.getConstant() ) .targetProperty( targetProperty ) .targetPropertyName( mapping.getTargetName() ) .formattingParameters( mapping.getFormattingParameters() ) .selectionParameters( mapping.getSelectionParameters() ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping.getDependsOn() ) + .mirror( mapping.getMirror() ) .build(); handledTargets.add( mapping.getTargetName() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 9f10ab2b4..3ed0abaf6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; @@ -116,6 +117,7 @@ public class PropertyMapping extends ModelElement { protected List dependsOn; protected Set existingVariableNames; + protected AnnotationMirror mirror; MappingBuilderBase(Class selfType) { super( selfType ); @@ -146,6 +148,11 @@ public class PropertyMapping extends ModelElement { return (T) this; } + T mirror(AnnotationMirror mirror) { + this.mirror = mirror; + return (T) this; + } + private Type determineTargetType() { // This is a bean mapping method, so we know the result is a declared type DeclaredType resultType = (DeclaredType) method.getResultType().getEffectiveType().getTypeMirror(); @@ -359,7 +366,7 @@ public class PropertyMapping extends ModelElement { && ( !rhs.getSourceType().isPrimitive() || rhs.getSourcePresenceCheckerReference() != null) ) { // cannot check on null source if source is primitive unless it has a presence checker PropertyMapping build = new ConstantMappingBuilder() - .constantExpression( '"' + defaultValue + '"' ) + .constantExpression( defaultValue ) .formattingParameters( formattingParameters ) .selectionParameters( selectionParameters ) .dependsOn( dependsOn ) @@ -749,7 +756,12 @@ public class PropertyMapping extends ModelElement { public PropertyMapping build() { // source String sourceErrorMessagePart = "constant '" + constantExpression + "'"; - Type sourceType = ctx.getTypeFactory().getType( String.class ); + + Type sourceType = ctx.getTypeFactory().getTypeForConstant( targetType, constantExpression ); + if ( String.class.getCanonicalName().equals( sourceType.getFullyQualifiedName() ) ) { + // convert to string + constantExpression = "\"" + constantExpression + "\""; + } Assignment assignment = null; if ( !targetType.isEnumType() ) { @@ -808,6 +820,7 @@ public class PropertyMapping extends ModelElement { else { ctx.getMessager().printMessage( method.getExecutable(), + mirror, Message.CONSTANTMAPPING_MAPPING_NOT_FOUND, sourceType, constantExpression, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 32f962b71..12f3f379f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -89,6 +89,8 @@ public class Type extends ModelElement implements Comparable { private final boolean isImported; private final boolean isVoid; private final boolean isStream; + private final boolean isBoxed; + private final boolean isOriginatedFromConstant; private final List enumConstants; @@ -111,7 +113,8 @@ public class Type extends ModelElement implements Comparable { BuilderInfo builderInfo, String packageName, String name, String qualifiedName, boolean isInterface, boolean isEnumType, boolean isIterableType, - boolean isCollectionType, boolean isMapType, boolean isStreamType, boolean isImported) { + boolean isCollectionType, boolean isMapType, boolean isStreamType, boolean isImported, + boolean isBoxed, boolean isOriginatedFromConstant ) { this.typeUtils = typeUtils; this.elementUtils = elementUtils; @@ -135,6 +138,8 @@ public class Type extends ModelElement implements Comparable { this.isStream = isStreamType; this.isImported = isImported; this.isVoid = typeMirror.getKind() == TypeKind.VOID; + this.isBoxed = isBoxed; + this.isOriginatedFromConstant = isOriginatedFromConstant; if ( isEnumType ) { enumConstants = new ArrayList(); @@ -384,7 +389,9 @@ public class Type extends ModelElement implements Comparable { isCollectionType, isMapType, isStream, - isImported + isImported, + isBoxed, + isOriginatedFromConstant ); } @@ -913,4 +920,13 @@ public class Type extends ModelElement implements Comparable { return null; } + + public boolean isBoxed() { + return isBoxed; + } + + public boolean hasOriginatedFromConstant() { + return isOriginatedFromConstant; + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index 878059682..dc06e1f12 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -64,6 +64,7 @@ import org.mapstruct.ap.spi.BuilderInfo; import org.mapstruct.ap.spi.BuilderProvider; import org.mapstruct.ap.spi.DefaultBuilderProvider; import org.mapstruct.ap.spi.TypeHierarchyErroneousException; +import org.mapstruct.ap.internal.util.NativeTypes; import static org.mapstruct.ap.internal.model.common.ImplementationType.withDefaultConstructor; import static org.mapstruct.ap.internal.model.common.ImplementationType.withInitialCapacity; @@ -131,6 +132,10 @@ public class TypeFactory { } public Type getType(String canonicalName) { + return getType( canonicalName, false ); + } + + private Type getType(String canonicalName, boolean isOriginatedFromConstant) { TypeElement typeElement = elementUtils.getTypeElement( canonicalName ); if ( typeElement == null ) { @@ -139,7 +144,31 @@ public class TypeFactory { ); } - return getType( typeElement ); + return getType( typeElement, isOriginatedFromConstant ); + } + + public Type getTypeForConstant(Type targetType, String literal) { + Type result = null; + if ( targetType.isPrimitive() ) { + TypeKind kind = targetType.getTypeMirror().getKind(); + boolean assignable = NativeTypes.isStringAssignable( kind, true, literal ); + if ( assignable ) { + result = getType( targetType.getTypeMirror(), true ); + } + } + else { + TypeKind boxedTypeKind = NativeTypes.getWrapperKind( targetType.getFullyQualifiedName() ); + if ( boxedTypeKind != null ) { + boolean assignable = NativeTypes.isStringAssignable( boxedTypeKind, false, literal ); + if ( assignable ) { + result = getType( targetType.getTypeMirror(), true ); + } + } + } + if ( result == null ) { + result = getType( String.class.getCanonicalName(), true ); + } + return result; } /** @@ -165,7 +194,15 @@ public class TypeFactory { return getType( typeElement.asType() ); } + private Type getType(TypeElement typeElement, boolean originatedFromConstant) { + return getType( typeElement.asType(), originatedFromConstant ); + } + public Type getType(TypeMirror mirror) { + return getType( mirror, false ); + } + + private Type getType(TypeMirror mirror, boolean originatedFromConstant) { if ( !canBeProcessed( mirror ) ) { throw new TypeHierarchyErroneousException( mirror ); } @@ -186,6 +223,7 @@ public class TypeFactory { TypeElement typeElement; Type componentType; boolean isImported; + boolean isBoxed = false; if ( mirror.getKind() == TypeKind.DECLARED ) { DeclaredType declaredType = (DeclaredType) mirror; @@ -207,6 +245,7 @@ public class TypeFactory { componentType = null; isImported = isImported( name, qualifiedName ); + isBoxed = NativeTypes.isWrapped( qualifiedName ); } else if ( mirror.getKind() == TypeKind.ARRAY ) { TypeMirror componentTypeMirror = getComponentType( mirror ); @@ -267,7 +306,9 @@ public class TypeFactory { isCollectionType, isMapType, isStreamType, - isImported + isImported, + isBoxed, + originatedFromConstant ); } @@ -315,8 +356,8 @@ public class TypeFactory { } /** - * Get the Type for given method as part of usedMapper. Possibly parameterized types in method declaration - * will be evaluated to concrete types then. + * Get the Type for given method as part of usedMapper. Possibly parameterized types in method declaration will be + * evaluated to concrete types then. * * @param includingType the type on which's scope the method type shall be evaluated * @param method the method @@ -479,7 +520,9 @@ public class TypeFactory { implementationType.isCollectionType(), implementationType.isMapType(), implementationType.isStreamType(), - isImported( implementationType.getName(), implementationType.getFullyQualifiedName() ) + isImported( implementationType.getName(), implementationType.getFullyQualifiedName() ), + implementationType.isBoxed(), + implementationType.hasOriginatedFromConstant() ); return implementation.createNew( replacement ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index d6f505032..81b346e0c 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -253,6 +253,18 @@ public class MappingResolverImpl implements MappingResolver { return simpleAssignment; } + // At this point the SourceType will either + // 1. be a String + // 2. or when its a primitive / wrapped type and analysis successful equal to its TargetType. But in that + // case it should have been direct assignable. + // In case of 1. and the target type is still a wrapped or primitive type we must assume that the check + // in NativeType is not successful. We don't want to go through type conversion, double mappings etc. + // with something that we already know to be wrong. + if ( sourceType.hasOriginatedFromConstant() && ( targetType.isPrimitive() || targetType.isBoxed() ) ) { + // TODO: convey some error message + return null; + } + // then type conversion Assignment conversion = resolveViaConversion( sourceType, targetType ); if ( conversion != null ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java index d73bacd73..db61bb7f3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java @@ -25,6 +25,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; +import javax.lang.model.type.TypeKind; /** * Provides functionality around the Java primitive data types and their wrapper @@ -37,6 +39,195 @@ public class NativeTypes { private static final Map, Class> WRAPPER_TO_PRIMITIVE_TYPES; private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPES; private static final Set> NUMBER_TYPES = new HashSet>(); + private static final Map WRAPPER_NAME_TO_PRIMITIVE_TYPES; + + private static final Pattern PTRN_HEX = Pattern.compile( "^0[x|X].*" ); + private static final Pattern PTRN_OCT = Pattern.compile( "^0_*[0-7].*" ); + private static final Pattern PTRN_BIN = Pattern.compile( "^0[b|B].*" ); + private static final Pattern PTRN_FLOAT_DEC_ZERO = Pattern.compile( "^[^eE]*[1-9].*[eE]?.*" ); + private static final Pattern PTRN_FLOAT_HEX_ZERO = Pattern.compile( "^[^pP]*[1-9a-fA-F].*[pP]?.*" ); + + private static final Pattern PTRN_SIGN = Pattern.compile( "^[\\+|-]" ); + + private static final Pattern PTRN_LONG = Pattern.compile( "[l|L]$" ); + private static final Pattern PTRN_FLOAT = Pattern.compile( "[f|F]$" ); + private static final Pattern PTRN_DOUBLE = Pattern.compile( "[d|D]$" ); + + private static final Pattern PTRN_FAULTY_UNDERSCORE_INT = Pattern.compile( "^_|_$|-_|_-|\\+_|_\\+" ); + private static final Pattern PTRN_FAULTY_UNDERSCORE_FLOAT = Pattern.compile( "^_|_$|-_|_-|\\+_|_\\+|\\._|_\\." ); + private static final Pattern PTRN_FAULTY_DEC_UNDERSCORE_FLOAT = Pattern.compile( "_e|_E|e_|E_" ); + private static final Pattern PTRN_FAULTY_HEX_UNDERSCORE_FLOAT = Pattern.compile( "_p|_P|p_|P_" ); + + private static final Map VALIDATORS = initValidators(); + + private interface NumberFormatValidator { + + boolean validate(boolean isPrimitive, String s); + } + + private abstract static class NumberRepresentation { + + int radix; + String val; + boolean isIntegralType; + boolean isLong; + boolean isFloat; + boolean isPrimitive; + + NumberRepresentation(String in, boolean isIntegralType, boolean isLong, boolean isFloat, boolean isPrimitive) { + this.isLong = isLong; + this.isFloat = isFloat; + this.isIntegralType = isIntegralType; + this.isPrimitive = isPrimitive; + + String valWithoutSign; + boolean isNegative = in.startsWith( "-" ); + boolean hasSign = PTRN_SIGN.matcher( in ).find(); + if ( hasSign ) { + valWithoutSign = in.substring( 1 ); + } + else { + valWithoutSign = in; + } + if ( PTRN_HEX.matcher( valWithoutSign ).matches() ) { + // hex + radix = 16; + val = (isNegative ? "-" : "") + valWithoutSign.substring( 2 ); + } + else if ( PTRN_BIN.matcher( valWithoutSign ).matches() ) { + // binary + radix = 2; + val = (isNegative ? "-" : "") + valWithoutSign.substring( 2 ); + } + else if ( PTRN_OCT.matcher( valWithoutSign ).matches() ) { + // octal + radix = 8; + val = (isNegative ? "-" : "") + valWithoutSign.substring( 1 ); + } + else { + // decimal + radix = 10; + val = (isNegative ? "-" : "") + valWithoutSign; + } + } + + abstract boolean parse(String val, int radix); + + boolean validate() { + try { + strip(); + return parse( val, radix ); + } + catch ( NumberFormatException ex ) { + return false; + } + } + + void strip() { + if ( isIntegralType ) { + removeAndValidateIntegerLiteralSuffix(); + removeAndValidateIntegerLiteralUnderscore(); + } + else { + removeAndValidateFloatingPointLiteralSuffix(); + removeAndValidateFloatingPointLiteralUnderscore(); + } + } + + /** + * remove java7+ underscores from the input + */ + void removeAndValidateIntegerLiteralUnderscore() { + if ( PTRN_FAULTY_UNDERSCORE_INT.matcher( val ).find() ) { + throw new NumberFormatException("Improperly placed underscores"); + } + else { + val = val.replace( "_", "" ); + } + } + + /** + * remove java7+ underscores from the input + */ + void removeAndValidateFloatingPointLiteralUnderscore() { + boolean isHex = radix == 16; + if ( PTRN_FAULTY_UNDERSCORE_FLOAT.matcher( val ).find() + || !isHex && PTRN_FAULTY_DEC_UNDERSCORE_FLOAT.matcher( val ).find() + || isHex && PTRN_FAULTY_HEX_UNDERSCORE_FLOAT.matcher( val ).find() ) { + throw new NumberFormatException("Improperly placed underscores"); + } + else { + val = val.replace( "_", "" ); + } + } + + /** + * + */ + void removeAndValidateIntegerLiteralSuffix() { + boolean endsWithLSuffix = PTRN_LONG.matcher( val ).find(); + // error handling + if (endsWithLSuffix && !isLong) { + throw new NumberFormatException("L/l not allowed for non-long types"); + } + if (!isPrimitive && !endsWithLSuffix && isLong) { + throw new NumberFormatException("L/l mandatory for boxed long"); + } + // remove suffix + if ( endsWithLSuffix ) { + val = val.substring( 0, val.length() - 1 ); + } + + } + + /** + * Double suffix forbidden for float. + * + * @param isFloat + */ + void removeAndValidateFloatingPointLiteralSuffix() { + boolean endsWithLSuffix = PTRN_LONG.matcher( val ).find(); + boolean endsWithFSuffix = PTRN_FLOAT.matcher( val ).find(); + boolean endsWithDSuffix = PTRN_DOUBLE.matcher( val ).find(); + // error handling + if ( isFloat && endsWithDSuffix ) { + throw new NumberFormatException("Assiging double to a float"); + } + // remove suffix + if ( endsWithLSuffix || endsWithFSuffix || endsWithDSuffix ) { + val = val.substring( 0, val.length() - 1 ); + } + } + + boolean floatHasBecomeZero(float parsed) { + if ( parsed == 0f ) { + return floatHasBecomeZero(); + } + else { + return false; + } + } + + boolean doubleHasBecomeZero(double parsed) { + if ( parsed == 0d ) { + return floatHasBecomeZero(); + } + else { + return false; + } + } + + private boolean floatHasBecomeZero() { + if ( radix == 10 ) { + // decimal, should be at least some number before exponent (eE) unequal to 0. + return PTRN_FLOAT_DEC_ZERO.matcher( val ).matches(); + } + else { + // hex, should be at least some number before exponent (pP) unequal to 0. + return PTRN_FLOAT_HEX_ZERO.matcher( val ).matches(); + } + } + } private NativeTypes() { } @@ -80,6 +271,19 @@ public class NativeTypes { NUMBER_TYPES.add( Double.class ); NUMBER_TYPES.add( BigInteger.class ); NUMBER_TYPES.add( BigDecimal.class ); + + Map tmp2 = new HashMap(); + tmp2.put( Boolean.class.getName(), TypeKind.BOOLEAN ); + tmp2.put( Byte.class.getName(), TypeKind.BYTE ); + tmp2.put( Character.class.getName(), TypeKind.CHAR ); + tmp2.put( Double.class.getName(), TypeKind.DOUBLE ); + tmp2.put( Float.class.getName(), TypeKind.FLOAT ); + tmp2.put( Integer.class.getName(), TypeKind.INT ); + tmp2.put( Long.class.getName(), TypeKind.LONG ); + tmp2.put( Short.class.getName(), TypeKind.SHORT ); + + WRAPPER_NAME_TO_PRIMITIVE_TYPES = Collections.unmodifiableMap( tmp2 ); + } public static Class getWrapperType(Class clazz) { @@ -98,6 +302,14 @@ public class NativeTypes { return WRAPPER_TO_PRIMITIVE_TYPES.get( clazz ); } + public static boolean isWrapped(String fullyQualifiedName) { + return WRAPPER_NAME_TO_PRIMITIVE_TYPES.containsKey( fullyQualifiedName ); + } + + public static TypeKind getWrapperKind(String fullyQualifiedName) { + return WRAPPER_NAME_TO_PRIMITIVE_TYPES.get( fullyQualifiedName ); + } + public static boolean isNumber(Class clazz) { if ( clazz == null ) { return false; @@ -106,4 +318,125 @@ public class NativeTypes { return NUMBER_TYPES.contains( clazz ); } } + + public static boolean isStringAssignable(TypeKind kind, boolean isPrimitive, String in) { + NumberFormatValidator validator = VALIDATORS.get( kind ); + return validator != null && validator.validate( isPrimitive, in ); + } + + private static Map initValidators() { + Map result = new HashMap(); + result.put( TypeKind.BOOLEAN, new NumberFormatValidator() { + @Override + public boolean validate(boolean isPrimitive, String s) { + return "true".equals( s ) || "false".equals( s ); + } + } ); + result.put( TypeKind.CHAR, new NumberFormatValidator() { + @Override + public boolean validate(boolean isPrimitive, String s) { + return s.length() == 3 && s.startsWith( "'" ) && s.endsWith( "'" ); + } + } ); + result.put( TypeKind.BYTE, new NumberFormatValidator() { + @Override + public boolean validate(boolean isPrimitive, String s) { + NumberRepresentation br = new NumberRepresentation( s, true, false, false, isPrimitive ) { + + @Override + boolean parse(String val, int radix) { + Byte.parseByte( val, radix ); + return true; + } + }; + return br.validate(); + } + } ); + result.put( TypeKind.DOUBLE, new NumberFormatValidator() { + @Override + public boolean validate(boolean isPrimitive, String s) { + NumberRepresentation br = new NumberRepresentation( s, false, false, false, isPrimitive ) { + + @Override + boolean parse(String val, int radix) { + Double d = Double.parseDouble( radix == 16 ? "0x" + val : val ); + return !d.isInfinite() && !doubleHasBecomeZero( d ); + } + }; + return br.validate(); + } + } ); + result.put( TypeKind.FLOAT, new NumberFormatValidator() { + @Override + public boolean validate(boolean isPrimitive, String s) { + + NumberRepresentation br = new NumberRepresentation( s, false, false, true, isPrimitive ) { + @Override + boolean parse(String val, int radix) { + Float f = Float.parseFloat( radix == 16 ? "0x" + val : val ); + return !f.isInfinite() && !floatHasBecomeZero( f ); + } + }; + return br.validate(); + } + } ); + result.put( TypeKind.INT, new NumberFormatValidator() { + @Override + public boolean validate(boolean isPrimitive, String s) { + NumberRepresentation br = new NumberRepresentation( s, true, false, false, isPrimitive ) { + + @Override + boolean parse(String val, int radix) { + if ( radix == 10 ) { + // when decimal: treat like signed + Integer.parseInt( val, radix ); + return true; + } + else { + // when binary, octal or hex: treat like unsigned + return new BigInteger( val, radix ).bitLength() <= 32; + } + } + }; + return br.validate(); + } + } ); + result.put( TypeKind.LONG, new NumberFormatValidator() { + @Override + public boolean validate(boolean isPrimitive, String s) { + NumberRepresentation br = new NumberRepresentation( s, true, true, false, isPrimitive ) { + + @Override + boolean parse(String val, int radix) { + if ( radix == 10 ) { + // when decimal: treat like signed + Long.parseLong( val, radix ); + return true; + } + else { + // when binary, octal or hex: treat like unsigned + return new BigInteger( val, radix ).bitLength() <= 64; + } + } + }; + return br.validate(); + } + } ); + result.put( TypeKind.SHORT, new NumberFormatValidator() { + @Override + public boolean validate(boolean isPrimitive, String s) { + NumberRepresentation br = new NumberRepresentation( s, true, false, false, isPrimitive ) { + + @Override + boolean parse(String val, int radix) { + Short.parseShort( val, radix ); + return true; + } + }; + return br.validate(); + } + } ); + return result; + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java index e74b9574e..7afafc0d6 100755 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java @@ -183,6 +183,8 @@ public class DateFormatValidatorFactoryTest { false, false, false, + false, + false, false ); } diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java index 577086819..73796af0f 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 @@ -134,6 +134,8 @@ public class DefaultConversionContextTest { false, false, false, + false, + false, false ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/ConstantOptimizingTest.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ConstantOptimizingTest.java new file mode 100644 index 000000000..d703d6096 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ConstantOptimizingTest.java @@ -0,0 +1,342 @@ +/** + * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.source.constants; + +import javax.lang.model.type.TypeKind; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mapstruct.ap.internal.util.NativeTypes.isStringAssignable; + +/** + * + * @author Sjaak Derksen + */ +public class ConstantOptimizingTest { + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * The following example shows other ways you can use the underscore in numeric literals: + */ + @Test + public void testUnderscorePlacement1() { + assertThat( isStringAssignable( TypeKind.LONG, true, "1234_5678_9012_3456L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "999_99_9999L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.FLOAT, true, "3.14_15F" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0xFF_EC_DE_5EL" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0xCAFE_BABEL" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0x7fff_ffff_ffff_ffffL" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.BYTE, true, "0b0010_0101" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0b11010010_01101001_10010100_10010010L" ) ).isTrue(); + } + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * You can place underscores only between digits; you cannot place underscores in the following places: + *
    + *
  1. At the beginning or end of a number
  2. + *
  3. Adjacent to a decimal point in a floating point literal
  4. + *
  5. Prior to an F or L suffix
  6. + *
  7. In positions where a string of digits is expected
  8. + *
+ * The following examples demonstrate valid and invalid underscore placements (which are highlighted) in numeric + * literals: + */ + @Test + public void testUnderscorePlacement2() { + + // Invalid: cannot put underscores + // adjacent to a decimal point + assertThat( isStringAssignable( TypeKind.FLOAT, true, "3_.1415F" ) ).isFalse(); + + // Invalid: cannot put underscores + // adjacent to a decimal point + assertThat( isStringAssignable( TypeKind.FLOAT, true, "3._1415F" ) ).isFalse(); + + // Invalid: cannot put underscores + // prior to an L suffix + assertThat( isStringAssignable( TypeKind.LONG, true, "999_99_9999_L" ) ).isFalse(); + + // OK (decimal literal) + assertThat( isStringAssignable( TypeKind.INT, true, "5_2" ) ).isTrue(); + + // Invalid: cannot put underscores + // At the end of a literal + assertThat( isStringAssignable( TypeKind.INT, true, "52_" ) ).isFalse(); + + // OK (decimal literal) + assertThat( isStringAssignable( TypeKind.INT, true, "5_______2" ) ).isTrue(); + + // Invalid: cannot put underscores + // in the 0x radix prefix + assertThat( isStringAssignable( TypeKind.INT, true, "0_x52" ) ).isFalse(); + + // Invalid: cannot put underscores + // at the beginning of a number + assertThat( isStringAssignable( TypeKind.INT, true, "0x_52" ) ).isFalse(); + + // OK (hexadecimal literal) + assertThat( isStringAssignable( TypeKind.INT, true, "0x5_2" ) ).isTrue(); + + // Invalid: cannot put underscores + // at the end of a number + assertThat( isStringAssignable( TypeKind.INT, true, "0x52_" ) ).isFalse(); + } + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * The following example shows other ways you can use the underscore in numeric literals: + */ + @Test + public void testIntegerLiteralFromJLS() { + + // largest positive int: dec / octal / int / binary + assertThat( isStringAssignable( TypeKind.INT, true, "2147483647" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "0x7fff_ffff" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "0177_7777_7777" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "0b0111_1111_1111_1111_1111_1111_1111_1111" ) ).isTrue(); + + // most negative int: dec / octal / int / binary + // NOTE parseInt should be changed to parseUnsignedInt in Java, than the - sign can disssapear (java8) + // and the function will be true to what the compiler shows. + assertThat( isStringAssignable( TypeKind.INT, true, "-2147483648" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "0x8000_0000" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "0200_0000_0000" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "0b1000_0000_0000_0000_0000_0000_0000_0000" ) ).isTrue(); + + // -1 representation int: dec / octal / int / binary + assertThat( isStringAssignable( TypeKind.INT, true, "-1" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "0xffff_ffff" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "0377_7777_7777" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "0b1111_1111_1111_1111_1111_1111_1111_1111" ) ).isTrue(); + + // largest positive long: dec / octal / int / binary + assertThat( isStringAssignable( TypeKind.LONG, true, "9223372036854775807L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0x7fff_ffff_ffff_ffffL" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "07_7777_7777_7777_7777_7777L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0b0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_" + + "1111_1111_1111_1111_1111_1111L" ) ).isTrue(); + // most negative long: dec / octal / int / binary + assertThat( isStringAssignable( TypeKind.LONG, true, "-9223372036854775808L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0x8000_0000_0000_0000L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "010_0000_0000_0000_0000_0000L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_" + + "0000_0000_0000_0000_0000L" ) ).isTrue(); + // -1 representation long: dec / octal / int / binary + assertThat( isStringAssignable( TypeKind.LONG, true, "-1L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0xffff_ffff_ffff_ffffL" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "017_7777_7777_7777_7777_7777L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_" + + "1111_1111_1111_1111_1111L" ) ).isTrue(); + + // some examples of ints + assertThat( isStringAssignable( TypeKind.INT, true, "0" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "2" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "0372" ) ).isTrue(); + //assertThat( isStringAssignable( TypeKind.INT, true, "0xDada_Cafe" ) ).isTrue(); java8 + assertThat( isStringAssignable( TypeKind.INT, true, "1996" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "0x00_FF__00_FF" ) ).isTrue(); + + // some examples of longs + assertThat( isStringAssignable( TypeKind.LONG, true, "0777l" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0x100000000L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "2_147_483_648L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0xC0B0L" ) ).isTrue(); + } + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * The following example shows other ways you can use the underscore in numeric literals: + */ + @Test + public void testFloatingPoingLiteralFromJLS() { + + // The largest positive finite literal of type float is 3.4028235e38f. + assertThat( isStringAssignable( TypeKind.FLOAT, true, "3.4028235e38f" ) ).isTrue(); + // The smallest positive finite non-zero literal of type float is 1.40e-45f. + assertThat( isStringAssignable( TypeKind.FLOAT, true, "1.40e-45f" ) ).isTrue(); + // The largest positive finite literal of type double is 1.7976931348623157e308. + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "1.7976931348623157e308" ) ).isTrue(); + // The smallest positive finite non-zero literal of type double is 4.9e-324 + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4.9e-324" ) ).isTrue(); + + // some floats + assertThat( isStringAssignable( TypeKind.FLOAT, true, "3.1e1F" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.FLOAT, true, "2.f" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.FLOAT, true, ".3f" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.FLOAT, true, "0f" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.FLOAT, true, "3.14f" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.FLOAT, true, "6.022137e+23f" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.FLOAT, true, "-3.14f" ) ).isTrue(); + + // some doubles + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "1e1" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "1e+1" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "2." ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, ".3" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "0.0" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "3.14" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "-3.14" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "1e-9D" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "1e137" ) ).isTrue(); + + // too large (infinitve) + assertThat( isStringAssignable( TypeKind.FLOAT, true, "3.4028235e38f" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "1.7976931348623157e308" ) ).isTrue(); + + // too large (infinitve) + assertThat( isStringAssignable( TypeKind.FLOAT, true, "3.4028235e39f" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "1.7976931348623159e308" ) ).isFalse(); + + // small + assertThat( isStringAssignable( TypeKind.FLOAT, true, "1.40e-45f" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.FLOAT, true, "0x1.0p-149" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4.9e-324" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "0x0.001P-1062d" ) ).isTrue(); + + // too small + assertThat( isStringAssignable( TypeKind.FLOAT, true, "1.40e-46f" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.FLOAT, true, "0x1.0p-150" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4.9e-325" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "0x0.001p-1063d" ) ).isFalse(); + } + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * The following example shows other ways you can use the underscore in numeric literals: + */ + @Test + public void testBooleanLiteralFromJLS() { + assertThat( isStringAssignable( TypeKind.BOOLEAN, true, "true" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.BOOLEAN, true, "false" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.BOOLEAN, true, "FALSE" ) ).isFalse(); + } + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * The following example shows other ways you can use the underscore in numeric literals: + */ + @Test + public void testCharLiteralFromJLS() { + + assertThat( isStringAssignable( TypeKind.CHAR, true, "'a'" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "'%'" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "'\t'" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "'\\'" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "'\''" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "'\u03a9'" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "'\uFFFF'" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "'\177'" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "'Ω'" ) ).isTrue(); + + } + + @Test + public void testShortAndByte() { + assertThat( isStringAssignable( TypeKind.SHORT, true, "0xFE" ) ).isTrue(); + + // some examples of ints + assertThat( isStringAssignable( TypeKind.BYTE, true, "0" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.BYTE, true, "2" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.BYTE, true, "127" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.BYTE, true, "-128" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.SHORT, true, "1996" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.SHORT, true, "-1996" ) ).isTrue(); + } + + @Test + public void testMiscellaneousErroneousPatterns() { + assertThat( isStringAssignable( TypeKind.INT, true, "1F" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.FLOAT, true, "1D" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.INT, true, "_1" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.INT, true, "1_" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.INT, true, "0x_1" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.INT, true, "0_x1" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4.9e_-3" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4.9_e-3" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4._9e-3" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4_.9e-3" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "_4.9e-3" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4.9E-3_" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4.9E_-3" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4.9E-_3" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4.9E+-3" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4.9E+_3" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "4.9_E-3" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "0x0.001_P-10d" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "0x0.001P_-10d" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "0x0.001_p-10d" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.DOUBLE, true, "0x0.001p_-10d" ) ).isFalse(); + } + + @Test + public void testNegatives() { + assertThat( isStringAssignable( TypeKind.INT, true, "-0xffaa" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "-0377_7777" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.INT, true, "-0b1111_1111" ) ).isTrue(); + } + + @Test + public void testFaultyChar() { + assertThat( isStringAssignable( TypeKind.CHAR, true, "''" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "'a" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "'aa" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "a'" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "aa'" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "'" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.CHAR, true, "a" ) ).isFalse(); + } + + @Test + public void testFloatWithLongLiteral() { + assertThat( isStringAssignable( TypeKind.FLOAT, true, "156L" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.FLOAT, true, "156l" ) ).isTrue(); + } + + @Test + public void testLongPrimitivesAndNonRequiredLongSuffix() { + assertThat( isStringAssignable( TypeKind.LONG, true, "156" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "156l" ) ).isTrue(); + assertThat( isStringAssignable( TypeKind.LONG, true, "156L" ) ).isTrue(); + } + + @Test + public void testIntPrimitiveWithLongSuffix() { + assertThat( isStringAssignable( TypeKind.INT, true, "156l" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.INT, true, "156L" ) ).isFalse(); + } + + @Test + public void testTooBigIntegersAndBigLongs() { + assertThat( isStringAssignable( TypeKind.INT, true, "0xFFFF_FFFF_FFFF" ) ).isFalse(); + assertThat( isStringAssignable( TypeKind.LONG, true, "0xFFFF_FFFF_FFFF_FFFF_FFFF" ) ).isFalse(); + } + + @Test + public void testNonSupportedPrimitiveType() { + assertThat( isStringAssignable( TypeKind.VOID, true, "0xFFFF_FFFF_FFFF" ) ).isFalse(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper1.java index 482af9666..87d9c07de 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper1.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper1.java @@ -35,7 +35,7 @@ public interface ErroneousMapper1 { @Mapping(target = "stringConstant", constant = "stringConstant"), @Mapping(target = "emptyStringConstant", constant = ""), @Mapping(source = "test", target = "integerConstant", constant = "14"), - @Mapping(target = "longWrapperConstant", constant = "3001"), + @Mapping(target = "longWrapperConstant", constant = "3001L"), @Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014"), @Mapping(target = "nameConstants", constant = "jack-jill-tom"), @Mapping(target = "country", constant = "THE_NETHERLANDS") diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper3.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper3.java index b7eb7c0bb..71049fba4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper3.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper3.java @@ -35,7 +35,7 @@ public interface ErroneousMapper3 { @Mapping(target = "stringConstant", constant = "stringConstant"), @Mapping(target = "emptyStringConstant", constant = ""), @Mapping(target = "integerConstant", expression = "java('test')", constant = "14"), - @Mapping(target = "longWrapperConstant", constant = "3001"), + @Mapping(target = "longWrapperConstant", constant = "3001L"), @Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014"), @Mapping(target = "nameConstants", constant = "jack-jill-tom"), @Mapping(target = "country", constant = "THE_NETHERLANDS") diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper4.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper4.java index 458b58e5c..533f02def 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper4.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper4.java @@ -35,7 +35,7 @@ public interface ErroneousMapper4 { @Mapping(target = "stringConstant", constant = "stringConstant"), @Mapping(target = "emptyStringConstant", constant = ""), @Mapping(source = "test", target = "integerConstant", expression = "java('test')"), - @Mapping(target = "longWrapperConstant", constant = "3001"), + @Mapping(target = "longWrapperConstant", constant = "3001L"), @Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014"), @Mapping(target = "nameConstants", constant = "jack-jill-tom"), @Mapping(target = "country", constant = "THE_NETHERLANDS") diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper5.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper5.java index d2a947218..e65c9b58d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper5.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper5.java @@ -35,7 +35,7 @@ public interface ErroneousMapper5 { @Mapping(target = "stringConstant", constant = "stringConstant"), @Mapping(target = "emptyStringConstant", constant = ""), @Mapping(target = "integerConstant", constant = "14"), - @Mapping(target = "longWrapperConstant", constant = "3001"), + @Mapping(target = "longWrapperConstant", constant = "3001L"), @Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014"), @Mapping(target = "nameConstants", constant = "jack-jill-tom"), @Mapping(target = "country", constant = "DENMARK") diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper6.java new file mode 100644 index 000000000..0cd149ee1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/ErroneousMapper6.java @@ -0,0 +1,46 @@ +/** + * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.source.constants; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper(uses = StringListMapper.class) +public interface ErroneousMapper6 { + + ErroneousMapper6 INSTANCE = Mappers.getMapper( ErroneousMapper6.class ); + + @Mappings({ + @Mapping(target = "stringConstant", constant = "stringConstant"), + @Mapping(target = "emptyStringConstant", constant = ""), + @Mapping(target = "integerConstant", constant = "14"), + @Mapping(target = "longWrapperConstant", constant = "3001"), + @Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014"), + @Mapping(target = "nameConstants", constant = "jack-jill-tom"), + @Mapping(target = "country", constant = "THE_NETHERLANDS") + }) + Target sourceToTarget(Source s); + + Source targetToSource(Target t); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java index 5105fe289..be4a046d9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceConstantsTest.java @@ -209,7 +209,7 @@ public class SourceConstantsTest { + "constants.CountryEnum for property \"country\".$"), @Diagnostic(type = ErroneousMapper5.class, kind = Kind.ERROR, - line = 43, + line = 41, messageRegExp = "^Can't map \"java.lang.String \"DENMARK\"\" to \"org.mapstruct.ap.test.source." + "constants.CountryEnum country\".$") } @@ -217,6 +217,28 @@ public class SourceConstantsTest { public void errorOnNonExistingEnumConstant() throws ParseException { } + @Test + @IssueKey("1401") + @WithClasses({ + Source.class, + Target.class, + CountryEnum.class, + ErroneousMapper6.class, + StringListMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMapper6.class, + kind = Kind.ERROR, + line = 38, + messageRegExp = "^.*Can't map \"java.lang.String \"3001\"\" to \"java.lang.Long " + + "longWrapperConstant\".*$") + } + ) + public void cannotMapIntConstantToLong() throws ParseException { + } + private Date getDate(String format, String date) throws ParseException { SimpleDateFormat dateFormat = new SimpleDateFormat( format ); Date result = dateFormat.parse( date ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceTargetMapper.java index 1005b01dd..5721b1735 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/constants/SourceTargetMapper.java @@ -35,7 +35,7 @@ public interface SourceTargetMapper { @Mapping(target = "stringConstant", constant = "stringConstant"), @Mapping(target = "emptyStringConstant", constant = ""), @Mapping(target = "integerConstant", constant = "14"), - @Mapping(target = "longWrapperConstant", constant = "3001"), + @Mapping(target = "longWrapperConstant", constant = "3001L"), @Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014"), @Mapping(target = "nameConstants", constant = "jack-jill-tom"), @Mapping(target = "country", constant = "THE_NETHERLANDS")