Skip to content

Commit

Permalink
mapstruct#1401 unit test coverage and handling long primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
sjaakd committed Apr 10, 2018
1 parent 8032645 commit 9433d1d
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 179 deletions.
Expand Up @@ -160,15 +160,16 @@ private Type getType(String canonicalName, boolean isOriginatedFromConstant) {
public Type getTypeForConstant(Type targetType, String literal) {
Type result = null;
if ( targetType.isPrimitive() ) {
boolean assignable = PrimitiveUtils.isStringAssignable( targetType.getTypeMirror().getKind(), literal );
TypeKind kind = targetType.getTypeMirror().getKind();
boolean assignable = PrimitiveUtils.isStringAssignable( kind, true, literal );
if ( assignable ) {
result = getType( targetType.getTypeMirror(), true );
}
}
else {
TypeKind boxedTypeKind = boxedTypeKinds.get( targetType.getFullyQualifiedName() );
if ( boxedTypeKind != null ) {
boolean assignable = PrimitiveUtils.isStringAssignable( boxedTypeKind, literal );
boolean assignable = PrimitiveUtils.isStringAssignable( boxedTypeKind, false, literal );
if ( assignable ) {
result = getType( targetType.getTypeMirror(), true );
}
Expand Down
Expand Up @@ -21,18 +21,36 @@
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import javax.lang.model.type.TypeKind;

/**
* Utilities for working with primitives.
*
* @author Sjaak Derksen
*/
public class PrimitiveUtils {
public final class PrimitiveUtils {

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 interface NumberFormatValidator {

boolean validate(String s);
boolean validate(boolean isPrimitive, String s);
}

private abstract static class NumberRepresentation {
Expand All @@ -42,32 +60,34 @@ private abstract static class NumberRepresentation {
boolean isIntegralType;
boolean isLong;
boolean isFloat;
boolean isPrimitive;

NumberRepresentation(String in, boolean isIntegralType, boolean isLong, boolean isFloat) {
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 = isNegative || in.startsWith( "+" );
boolean hasSign = PTRN_SIGN.matcher( in ).find();
if ( hasSign ) {
valWithoutSign = in.substring( 1 );
}
else {
valWithoutSign = in;
}
if ( valWithoutSign.startsWith( "0x" ) || valWithoutSign.startsWith( "0X" ) ) {
if ( PTRN_HEX.matcher( valWithoutSign ).matches() ) {
// hex
radix = 16;
val = (isNegative ? "-" : "") + valWithoutSign.substring( 2 );
}
else if ( valWithoutSign.startsWith( "0b" ) || valWithoutSign.startsWith( "0B" ) ) {
else if ( PTRN_BIN.matcher( valWithoutSign ).matches() ) {
// binary
radix = 2;
val = (isNegative ? "-" : "") + valWithoutSign.substring( 2 );
}
else if ( valWithoutSign.matches( "^0_*[0-7].*$" ) ) {
else if ( PTRN_OCT.matcher( valWithoutSign ).matches() ) {
// octal
radix = 8;
val = (isNegative ? "-" : "") + valWithoutSign.substring( 1 );
Expand Down Expand Up @@ -106,7 +126,7 @@ void strip() {
* remove java7+ underscores from the input
*/
void removeAndValidateIntegerLiteralUnderscore() {
if ( val.startsWith( "_" ) || (val.endsWith( "_" ) || val.startsWith( "-_" )) ) {
if ( PTRN_FAULTY_UNDERSCORE_INT.matcher( val ).find() ) {
throw new NumberFormatException();
}
else {
Expand All @@ -119,14 +139,9 @@ void removeAndValidateIntegerLiteralUnderscore() {
*/
void removeAndValidateFloatingPointLiteralUnderscore() {
boolean isHex = radix == 16;
if ( val.startsWith( "_" ) || val.endsWith( "_" )
|| val.contains( "-_" ) || val.contains( "+_" )
|| val.contains( "_-" ) || val.contains( "_+" )
|| val.contains( "._" ) || val.contains( "_." )
|| !isHex && (val.contains( "E_" ) || val.contains( "_E" )
|| val.contains( "_e" ) || val.contains( "e_" ))
|| isHex && (val.contains( "P_" ) || val.contains( "_P" )
|| val.contains( "_p" ) || val.contains( "p_" )) ) {
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();
}
else {
Expand All @@ -141,13 +156,17 @@ void removeAndValidateFloatingPointLiteralUnderscore() {
* @param isLong
*/
void removeAndValidateIntegerLiteralSuffix() {
boolean endsWithLSuffix = val.endsWith( "L" ) || val.endsWith( "l" );
if ( endsWithLSuffix && isLong ) {
val = val.substring( 0, val.length() - 1 );
}
else if ( (endsWithLSuffix && !isLong) || (!endsWithLSuffix && isLong) ) {
boolean endsWithLSuffix = PTRN_LONG.matcher( val ).find();
// error handling
if ( (endsWithLSuffix && !isLong) || (!isPrimitive && !endsWithLSuffix && isLong) ) {
// L/l forbidden for non-long types, mandatory for boxed long, optional for primitive long
throw new NumberFormatException();
}
// remove suffix
if ( endsWithLSuffix ) {
val = val.substring( 0, val.length() - 1 );
}

}

/**
Expand All @@ -156,15 +175,17 @@ else if ( (endsWithLSuffix && !isLong) || (!endsWithLSuffix && isLong) ) {
* @param isFloat
*/
void removeAndValidateFloatingPointLiteralSuffix() {
boolean endsWithLSuffix = val.endsWith( "L" ) || val.endsWith( "l" );
boolean endsWithFSuffix = val.endsWith( "F" ) || val.endsWith( "f" );
boolean endsWithDSuffix = val.endsWith( "D" ) || val.endsWith( "d" );
if ( (endsWithLSuffix || endsWithFSuffix) || (!isFloat && endsWithDSuffix) ) {
val = val.substring( 0, val.length() - 1 );
}
else if ( isFloat && endsWithDSuffix ) {
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();
}
// remove suffix
if ( endsWithLSuffix || endsWithFSuffix || endsWithDSuffix ) {
val = val.substring( 0, val.length() - 1 );
}
}

boolean floatHasBecomeZero(float parsed) {
Expand All @@ -188,11 +209,11 @@ boolean doubleHasBecomeZero(double parsed) {
private boolean floatHasBecomeZero() {
if ( radix == 10 ) {
// decimal, should be at least some number before exponent (eE) unequal to 0.
return val.matches( "^[^eE]*[1-9].*[eE]?.*$" );
return PTRN_FLOAT_DEC_ZERO.matcher( val ).matches();
}
else {
// hex, should be at least some number before exponent (pP) unequal to 0.
return val.matches( "^[^pP]*[1-9a-fA-F].*[pP]?.*$" );
return PTRN_FLOAT_HEX_ZERO.matcher( val ).matches();
}
}
}
Expand All @@ -202,29 +223,29 @@ private boolean floatHasBecomeZero() {
private PrimitiveUtils() {
}

public static boolean isStringAssignable(TypeKind kind, String in) {
public static boolean isStringAssignable(TypeKind kind, boolean isPrimitive, String in) {
NumberFormatValidator validator = VALIDATORS.get( kind );
return validator != null ? validator.validate( in ) : false;
return validator != null ? validator.validate( isPrimitive, in ) : false;
}

private static Map<TypeKind, NumberFormatValidator> initValidators() {
Map<TypeKind, NumberFormatValidator> result = new HashMap<TypeKind, NumberFormatValidator>();
result.put( TypeKind.BOOLEAN, new NumberFormatValidator() {
@Override
public boolean validate(String s) {
public boolean validate(boolean isPrimitive, String s) {
return "true".equals( s ) || "false".equals( s );
}
} );
result.put( TypeKind.CHAR, new NumberFormatValidator() {
@Override
public boolean validate(String s) {
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(String s) {
NumberRepresentation br = new NumberRepresentation( s, true, false, false ) {
public boolean validate(boolean isPrimitive, String s) {
NumberRepresentation br = new NumberRepresentation( s, true, false, false, isPrimitive ) {

@Override
boolean parse(String val, int radix) {
Expand All @@ -237,36 +258,36 @@ boolean parse(String val, int radix) {
} );
result.put( TypeKind.DOUBLE, new NumberFormatValidator() {
@Override
public boolean validate(String s) {
NumberRepresentation br = new NumberRepresentation( s, false, false, false ) {
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() && !d.isNaN() && !doubleHasBecomeZero( d );
return !d.isInfinite() && !doubleHasBecomeZero( d );
}
};
return br.validate();
}
} );
result.put( TypeKind.FLOAT, new NumberFormatValidator() {
@Override
public boolean validate(String s) {
public boolean validate(boolean isPrimitive, String s) {

NumberRepresentation br = new NumberRepresentation( s, false, false, true ) {
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() && !f.isNaN() && !floatHasBecomeZero( f );
return !f.isInfinite() && !floatHasBecomeZero( f );
}
};
return br.validate();
}
} );
result.put( TypeKind.INT, new NumberFormatValidator() {
@Override
public boolean validate(String s) {
NumberRepresentation br = new NumberRepresentation( s, true, false, false ) {
public boolean validate(boolean isPrimitive, String s) {
NumberRepresentation br = new NumberRepresentation( s, true, false, false, isPrimitive ) {

@Override
boolean parse(String val, int radix) {
Expand All @@ -286,8 +307,8 @@ boolean parse(String val, int radix) {
} );
result.put( TypeKind.LONG, new NumberFormatValidator() {
@Override
public boolean validate(String s) {
NumberRepresentation br = new NumberRepresentation( s, true, true, false ) {
public boolean validate(boolean isPrimitive, String s) {
NumberRepresentation br = new NumberRepresentation( s, true, true, false, isPrimitive ) {

@Override
boolean parse(String val, int radix) {
Expand All @@ -307,8 +328,8 @@ boolean parse(String val, int radix) {
} );
result.put( TypeKind.SHORT, new NumberFormatValidator() {
@Override
public boolean validate(String s) {
NumberRepresentation br = new NumberRepresentation( s, true, false, false ) {
public boolean validate(boolean isPrimitive, String s) {
NumberRepresentation br = new NumberRepresentation( s, true, false, false, isPrimitive ) {

@Override
boolean parse(String val, int radix) {
Expand Down

0 comments on commit 9433d1d

Please sign in to comment.