diff --git a/src/main/java/com/cedarsoftware/util/convert/Converter.java b/src/main/java/com/cedarsoftware/util/convert/Converter.java index 23132750d..6eb90fec1 100644 --- a/src/main/java/com/cedarsoftware/util/convert/Converter.java +++ b/src/main/java/com/cedarsoftware/util/convert/Converter.java @@ -4,7 +4,6 @@ import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; -import java.math.RoundingMode; import java.sql.Timestamp; import java.text.DecimalFormat; import java.text.SimpleDateFormat; @@ -84,16 +83,7 @@ public final class Converter { private static final Map, Set> cacheParentTypes = new ConcurrentHashMap<>(); private static final Map, Class> primitiveToWrapper = new HashMap<>(20, .8f); private static final Map, Class>, Convert> DEFAULT_FACTORY = new ConcurrentHashMap<>(500, .8f); - - private static final BigDecimal bigDecimalMinByte = BigDecimal.valueOf(Byte.MIN_VALUE); - private static final BigDecimal bigDecimalMaxByte = BigDecimal.valueOf(Byte.MAX_VALUE); - private static final BigDecimal bigDecimalMinShort = BigDecimal.valueOf(Short.MIN_VALUE); - private static final BigDecimal bigDecimalMaxShort = BigDecimal.valueOf(Short.MAX_VALUE); - private static final BigDecimal bigDecimalMinInteger = BigDecimal.valueOf(Integer.MIN_VALUE); - private static final BigDecimal bigDecimalMaxInteger = BigDecimal.valueOf(Integer.MAX_VALUE); - private static final BigDecimal bigDecimalMaxLong = BigDecimal.valueOf(Long.MAX_VALUE); - private static final BigDecimal bigDecimalMinLong = BigDecimal.valueOf(Long.MIN_VALUE); - + // Create a Map.Entry (pair) of source class to target class. private static Map.Entry, Class> pair(Class source, Class target) { return new AbstractMap.SimpleImmutableEntry<>(source, target); @@ -136,21 +126,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(BigDecimal.class, Byte.class), NumberConversion::toByte); DEFAULT_FACTORY.put(pair(Number.class, Byte.class), NumberConversion::toByte); DEFAULT_FACTORY.put(pair(Map.class, Byte.class), (fromInstance, converter, options) -> converter.fromValueMap((Map) fromInstance, byte.class, null, options)); - DEFAULT_FACTORY.put(pair(String.class, Byte.class), (fromInstance, converter, options) -> { - String str = ((String) fromInstance).trim(); - if (str.isEmpty()) { - return CommonValues.BYTE_ZERO; - } - try { - return Byte.valueOf(str); - } catch (NumberFormatException e) { - Byte value = strToByte(str); - if (value == null) { - throw new IllegalArgumentException("Value: " + fromInstance + " not parseable as a byte value or outside " + Byte.MIN_VALUE + " to " + Byte.MAX_VALUE); - } - return value; - } - }); + DEFAULT_FACTORY.put(pair(String.class, Byte.class), StringConversion::stringToByte); // Short/short conversions supported DEFAULT_FACTORY.put(pair(Void.class, short.class), (fromInstance, converter, options) -> (short) 0); @@ -171,21 +147,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(LocalDate.class, Short.class), (fromInstance, converter, options) -> ((LocalDate) fromInstance).toEpochDay()); DEFAULT_FACTORY.put(pair(Number.class, Short.class), NumberConversion::toShort); DEFAULT_FACTORY.put(pair(Map.class, Short.class), (fromInstance, converter, options) -> converter.fromValueMap((Map) fromInstance, short.class, null, options)); - DEFAULT_FACTORY.put(pair(String.class, Short.class), (fromInstance, converter, options) -> { - String str = ((String) fromInstance).trim(); - if (str.isEmpty()) { - return CommonValues.SHORT_ZERO; - } - try { - return Short.valueOf(str); - } catch (NumberFormatException e) { - Short value = strToShort(str); - if (value == null) { - throw new IllegalArgumentException("Value: " + fromInstance + " not parseable as a short value or outside " + Short.MIN_VALUE + " to " + Short.MAX_VALUE); - } - return value; - } - }); + DEFAULT_FACTORY.put(pair(String.class, Short.class), StringConversion::stringToShort); // Integer/int conversions supported DEFAULT_FACTORY.put(pair(Void.class, int.class), (fromInstance, converter, options) -> 0); @@ -206,21 +168,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(LocalDate.class, Integer.class), (fromInstance, converter, options) -> (int) ((LocalDate) fromInstance).toEpochDay()); DEFAULT_FACTORY.put(pair(Number.class, Integer.class), NumberConversion::toInt); DEFAULT_FACTORY.put(pair(Map.class, Integer.class), (fromInstance, converter, options) -> converter.fromValueMap((Map) fromInstance, int.class, null, options)); - DEFAULT_FACTORY.put(pair(String.class, Integer.class), (fromInstance, converter, options) -> { - String str = ((String) fromInstance).trim(); - if (str.isEmpty()) { - return CommonValues.INTEGER_ZERO; - } - try { - return Integer.valueOf(str); - } catch (NumberFormatException e) { - Integer value = strToInteger(str); - if (value == null) { - throw new IllegalArgumentException("Value: " + fromInstance + " not parseable as an integer value or outside " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE); - } - return value; - } - }); + DEFAULT_FACTORY.put(pair(String.class, Integer.class), StringConversion::stringToInteger); // Long/long conversions supported DEFAULT_FACTORY.put(pair(Void.class, long.class), (fromInstance, converter, options) -> 0L); @@ -247,21 +195,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(Calendar.class, Long.class), (fromInstance, converter, options) -> ((Calendar) fromInstance).getTime().getTime()); DEFAULT_FACTORY.put(pair(Number.class, Long.class), NumberConversion::toLong); DEFAULT_FACTORY.put(pair(Map.class, Long.class), (fromInstance, converter, options) -> converter.fromValueMap((Map) fromInstance, long.class, null, options)); - DEFAULT_FACTORY.put(pair(String.class, Long.class), (fromInstance, converter, options) -> { - String str = ((String) fromInstance).trim(); - if (str.isEmpty()) { - return CommonValues.LONG_ZERO; - } - try { - return Long.valueOf(str); - } catch (NumberFormatException e) { - Long value = strToLong(str, bigDecimalMinLong, bigDecimalMaxLong); - if (value == null) { - throw new IllegalArgumentException("Value: " + fromInstance + " not parseable as a long value or outside " + Long.MIN_VALUE + " to " + Long.MAX_VALUE); - } - return value; - } - }); + DEFAULT_FACTORY.put(pair(String.class, Long.class), StringConversion::stringToLong); // Float/float conversions supported DEFAULT_FACTORY.put(pair(Void.class, float.class), (fromInstance, converter, options) -> 0.0f); @@ -282,17 +216,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(BigDecimal.class, Float.class), NumberConversion::toFloat); DEFAULT_FACTORY.put(pair(Number.class, Float.class), NumberConversion::toFloat); DEFAULT_FACTORY.put(pair(Map.class, Float.class), (fromInstance, converter, options) -> converter.fromValueMap((Map) fromInstance, float.class, null, options)); - DEFAULT_FACTORY.put(pair(String.class, Float.class), (fromInstance, converter, options) -> { - String str = ((String) fromInstance).trim(); - if (str.isEmpty()) { - return CommonValues.FLOAT_ZERO; - } - try { - return Float.valueOf(str); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Value: " + fromInstance + " not parseable as a float value"); - } - }); + DEFAULT_FACTORY.put(pair(String.class, Float.class), StringConversion::stringToFloat); // Double/double conversions supported DEFAULT_FACTORY.put(pair(Void.class, double.class), (fromInstance, converter, options) -> 0.0d); @@ -319,17 +243,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(Calendar.class, Double.class), (fromInstance, converter, options) -> (double) ((Calendar) fromInstance).getTime().getTime()); DEFAULT_FACTORY.put(pair(Number.class, Double.class), NumberConversion::toDouble); DEFAULT_FACTORY.put(pair(Map.class, Double.class), (fromInstance, converter, options) -> converter.fromValueMap((Map) fromInstance, double.class, null, options)); - DEFAULT_FACTORY.put(pair(String.class, Double.class), (fromInstance, converter, options) -> { - String str = ((String) fromInstance).trim(); - if (str.isEmpty()) { - return CommonValues.DOUBLE_ZERO; - } - try { - return Double.valueOf(str); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Value: " + fromInstance + " not parseable as a double value"); - } - }); + DEFAULT_FACTORY.put(pair(String.class, Double.class), StringConversion::stringToDouble); // Boolean/boolean conversions supported DEFAULT_FACTORY.put(pair(Void.class, boolean.class), (fromInstance, converter, options) -> false); @@ -345,7 +259,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(AtomicBoolean.class, Boolean.class), (fromInstance, converter, options) -> ((AtomicBoolean) fromInstance).get()); DEFAULT_FACTORY.put(pair(AtomicInteger.class, Boolean.class), NumberConversion::isIntTypeNotZero); DEFAULT_FACTORY.put(pair(AtomicLong.class, Boolean.class), NumberConversion::isIntTypeNotZero); - DEFAULT_FACTORY.put(pair(BigInteger.class, Boolean.class), NumberConversion::isIntTypeNotZero); + DEFAULT_FACTORY.put(pair(BigInteger.class, Boolean.class), (fromInstance, converter, options) -> ((BigInteger)fromInstance).compareTo(BigInteger.ZERO) != 0); DEFAULT_FACTORY.put(pair(BigDecimal.class, Boolean.class), (fromInstance, converter, options) -> ((BigDecimal)fromInstance).compareTo(BigDecimal.ZERO) != 0); DEFAULT_FACTORY.put(pair(Number.class, Boolean.class), NumberConversion::isIntTypeNotZero); DEFAULT_FACTORY.put(pair(Map.class, Boolean.class), (fromInstance, converter, options) -> converter.fromValueMap((Map) fromInstance, boolean.class, null, options)); @@ -523,18 +437,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(LocalDate.class, AtomicInteger.class), (fromInstance, converter, options) -> new AtomicInteger((int) ((LocalDate) fromInstance).toEpochDay())); DEFAULT_FACTORY.put(pair(Number.class, AtomicBoolean.class), NumberConversion::numberToAtomicInteger); DEFAULT_FACTORY.put(pair(Map.class, AtomicInteger.class), (fromInstance, converter, options) -> converter.fromValueMap((Map) fromInstance, AtomicInteger.class, null, options)); - DEFAULT_FACTORY.put(pair(String.class, AtomicInteger.class), (fromInstance, converter, options) -> { - String str = ((String) fromInstance).trim(); - if (str.isEmpty()) { - return new AtomicInteger(0); - } - - Integer integer = strToInteger(str); - if (integer == null) { - throw new IllegalArgumentException("Value: " + fromInstance + " not parseable as an AtomicInteger value or outside " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE); - } - return new AtomicInteger(integer); - }); + DEFAULT_FACTORY.put(pair(String.class, AtomicInteger.class), StringConversion::stringToAtomicInteger); // AtomicLong conversions supported DEFAULT_FACTORY.put(pair(Void.class, AtomicLong.class), VoidConversion::toNull); @@ -560,17 +463,7 @@ private static void buildFactoryConversions() { DEFAULT_FACTORY.put(pair(Calendar.class, AtomicLong.class), (fromInstance, converter, options) -> new AtomicLong(((Calendar) fromInstance).getTime().getTime())); DEFAULT_FACTORY.put(pair(Number.class, AtomicLong.class), NumberConversion::numberToAtomicLong); DEFAULT_FACTORY.put(pair(Map.class, AtomicLong.class), (fromInstance, converter, options) -> converter.fromValueMap((Map) fromInstance, AtomicLong.class, null, options)); - DEFAULT_FACTORY.put(pair(String.class, AtomicLong.class), (fromInstance, converter, options) -> { - String str = ((String) fromInstance).trim(); - if (str.isEmpty()) { - return new AtomicLong(0L); - } - Long value = strToLong(str, bigDecimalMinLong, bigDecimalMaxLong); - if (value == null) { - throw new IllegalArgumentException("Value: " + fromInstance + " not parseable as an AtomicLong value or outside " + Long.MIN_VALUE + " to " + Long.MAX_VALUE); - } - return new AtomicLong(value); - }); + DEFAULT_FACTORY.put(pair(String.class, AtomicLong.class), StringConversion::stringToAtomicLong); // Date conversions supported DEFAULT_FACTORY.put(pair(Void.class, Date.class), VoidConversion::toNull); @@ -1386,48 +1279,7 @@ public static long localDateTimeToMillis(LocalDateTime localDateTime, ZoneId zon public static long zonedDateTimeToMillis(ZonedDateTime zonedDateTime) { return zonedDateTime.toInstant().toEpochMilli(); } - - private static Byte strToByte(String s) - { - Long value = strToLong(s, bigDecimalMinByte, bigDecimalMaxByte); - if (value == null) { - return null; - } - return value.byteValue(); - } - - private static Short strToShort(String s) - { - Long value = strToLong(s, bigDecimalMinShort, bigDecimalMaxShort); - if (value == null) { - return null; - } - return value.shortValue(); - } - - private static Integer strToInteger(String s) - { - Long value = strToLong(s, bigDecimalMinInteger, bigDecimalMaxInteger); - if (value == null) { - return null; - } - return value.intValue(); - } - - private static Long strToLong(String s, BigDecimal low, BigDecimal high) - { - try { - BigDecimal big = new BigDecimal(s); - big = big.setScale(0, RoundingMode.DOWN); - if (big.compareTo(low) == -1 || big.compareTo(high) == 1) { - return null; - } - return big.longValue(); - } catch (Exception e) { - return null; - } - } - + /** * Given a primitive class, return the Wrapper class equivalent. */ diff --git a/src/main/java/com/cedarsoftware/util/convert/StringConversion.java b/src/main/java/com/cedarsoftware/util/convert/StringConversion.java new file mode 100644 index 000000000..af0d334a2 --- /dev/null +++ b/src/main/java/com/cedarsoftware/util/convert/StringConversion.java @@ -0,0 +1,184 @@ +package com.cedarsoftware.util.convert; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author John DeRegnaucourt (jdereg@gmail.com) + *
+ * Copyright (c) Cedar Software LLC + *

+ * 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 + *

+ * License + *

+ * 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. + */ +public class StringConversion { + private static final BigDecimal bigDecimalMinByte = BigDecimal.valueOf(Byte.MIN_VALUE); + private static final BigDecimal bigDecimalMaxByte = BigDecimal.valueOf(Byte.MAX_VALUE); + private static final BigDecimal bigDecimalMinShort = BigDecimal.valueOf(Short.MIN_VALUE); + private static final BigDecimal bigDecimalMaxShort = BigDecimal.valueOf(Short.MAX_VALUE); + private static final BigDecimal bigDecimalMinInteger = BigDecimal.valueOf(Integer.MIN_VALUE); + private static final BigDecimal bigDecimalMaxInteger = BigDecimal.valueOf(Integer.MAX_VALUE); + private static final BigDecimal bigDecimalMaxLong = BigDecimal.valueOf(Long.MAX_VALUE); + private static final BigDecimal bigDecimalMinLong = BigDecimal.valueOf(Long.MIN_VALUE); + + static Byte stringToByte(Object from, Converter converter, ConverterOptions options) { + String str = ((String) from).trim(); + if (str.isEmpty()) { + return CommonValues.BYTE_ZERO; + } + try { + return Byte.valueOf(str); + } catch (NumberFormatException e) { + Byte value = stringToByte(str); + if (value == null) { + throw new IllegalArgumentException("Value: " + from + " not parseable as a byte value or outside " + Byte.MIN_VALUE + " to " + Byte.MAX_VALUE); + } + return value; + } + } + + private static Byte stringToByte(String s) { + Long value = stringToLong(s, bigDecimalMinByte, bigDecimalMaxByte); + if (value == null) { + return null; + } + return value.byteValue(); + } + + static Short stringToShort(Object from, Converter converter, ConverterOptions options) { + String str = ((String) from).trim(); + if (str.isEmpty()) { + return CommonValues.SHORT_ZERO; + } + try { + return Short.valueOf(str); + } catch (NumberFormatException e) { + Short value = stringToShort(str); + if (value == null) { + throw new IllegalArgumentException("Value: " + from + " not parseable as a short value or outside " + Short.MIN_VALUE + " to " + Short.MAX_VALUE); + } + return value; + } + } + + private static Short stringToShort(String s) { + Long value = stringToLong(s, bigDecimalMinShort, bigDecimalMaxShort); + if (value == null) { + return null; + } + return value.shortValue(); + } + + static Integer stringToInteger(Object from, Converter converter, ConverterOptions options) { + String str = ((String) from).trim(); + if (str.isEmpty()) { + return CommonValues.INTEGER_ZERO; + } + try { + return Integer.valueOf(str); + } catch (NumberFormatException e) { + Integer value = stringToInteger(str); + if (value == null) { + throw new IllegalArgumentException("Value: " + from + " not parseable as an int value or outside " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE); + } + return value; + } + } + + private static Integer stringToInteger(String s) { + Long value = stringToLong(s, bigDecimalMinInteger, bigDecimalMaxInteger); + if (value == null) { + return null; + } + return value.intValue(); + } + + static Long stringToLong(Object from, Converter converter, ConverterOptions options) { + String str = ((String) from).trim(); + if (str.isEmpty()) { + return CommonValues.LONG_ZERO; + } + try { + return Long.valueOf(str); + } catch (NumberFormatException e) { + Long value = stringToLong(str, bigDecimalMinLong, bigDecimalMaxLong); + if (value == null) { + throw new IllegalArgumentException("Value: " + from + " not parseable as a long value or outside " + Long.MIN_VALUE + " to " + Long.MAX_VALUE); + } + return value; + } + } + + private static Long stringToLong(String s, BigDecimal low, BigDecimal high) { + try { + BigDecimal big = new BigDecimal(s); + big = big.setScale(0, RoundingMode.DOWN); + if (big.compareTo(low) == -1 || big.compareTo(high) == 1) { + return null; + } + return big.longValue(); + } catch (Exception e) { + return null; + } + } + + static Float stringToFloat(Object from, Converter converter, ConverterOptions options) { + String str = ((String) from).trim(); + if (str.isEmpty()) { + return CommonValues.FLOAT_ZERO; + } + try { + return Float.valueOf(str); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Value: " + from + " not parseable as a float value"); + } + } + + static Double stringToDouble(Object from, Converter converter, ConverterOptions options) { + String str = ((String) from).trim(); + if (str.isEmpty()) { + return CommonValues.DOUBLE_ZERO; + } + try { + return Double.valueOf(str); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Value: " + from + " not parseable as a double value"); + } + } + + static AtomicInteger stringToAtomicInteger(Object from, Converter converter, ConverterOptions options) { + String str = ((String) from).trim(); + if (str.isEmpty()) { + return new AtomicInteger(0); + } + + Integer integer = stringToInteger(str); + if (integer == null) { + throw new IllegalArgumentException("Value: " + from + " not parseable as an AtomicInteger value or outside " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE); + } + return new AtomicInteger(integer); + } + + static AtomicLong stringToAtomicLong(Object from, Converter converter, ConverterOptions options) { + String str = ((String) from).trim(); + if (str.isEmpty()) { + return new AtomicLong(0L); + } + Long value = stringToLong(str, bigDecimalMinLong, bigDecimalMaxLong); + if (value == null) { + throw new IllegalArgumentException("Value: " + from + " not parseable as an AtomicLong value or outside " + Long.MIN_VALUE + " to " + Long.MAX_VALUE); + } + return new AtomicLong(value); + } +} diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java index e4360ad77..541dad80d 100644 --- a/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java +++ b/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java @@ -380,9 +380,9 @@ void testInt_fromBoolean(Object value, Integer expectedResult) private static Stream testIntegerParams_withIllegalArguments() { return Stream.of( - Arguments.of("45badNumber", "not parseable as an integer"), - Arguments.of( "12147483648", "not parseable as an integer"), - Arguments.of("2147483649", "not parseable as an integer"), + Arguments.of("45badNumber", "Value: 45badNumber not parseable as an int value or outside -2147483648 to 2147483647"), + Arguments.of( "12147483648", "Value: 12147483648 not parseable as an int value or outside -2147483648 to 2147483647"), + Arguments.of("2147483649", "Value: 2147483649 not parseable as an int value or outside -2147483648 to 2147483647"), Arguments.of( TimeZone.getDefault(), "Unsupported conversion")); } @@ -741,7 +741,6 @@ void testString_fromLocalDate() assertThat(converted).isEqualTo("2015-09-03"); } - @Test void testBigDecimal() { @@ -809,6 +808,7 @@ void testBigInteger() assertSame(BigInteger.ONE, this.converter.convert(true, BigInteger.class)); assertEquals(BigInteger.ZERO, this.converter.convert(false, BigInteger.class)); assertSame(BigInteger.ZERO, this.converter.convert(false, BigInteger.class)); + assertEquals(converter.convert(new BigInteger("314159"), Boolean.class), true); assertEquals(new BigInteger("11"), converter.convert("11.5", BigInteger.class)); Date now = new Date();