Rework thrown exceptions for DecimalType, PercentType and QuantityType (#2374)

Throw NumberFormatException when numbers are invalid and cannot be parsed to BigDecimals.
Add JavaDocs to document when which exception is thrown.
Add more unit tests to cover the thrown exceptions.

Signed-off-by: Wouter Born <github@maindrain.net>
pull/2376/head
Wouter Born 2021-05-20 20:37:46 +02:00 committed by GitHub
parent 3c30177269
commit 696ebacdc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 5 deletions

View File

@ -56,17 +56,33 @@ public class DecimalType extends Number implements PrimitiveType, State, Command
this.value = BigDecimal.valueOf(value);
}
/**
* Creates a new {@link DecimalType} with the given value.
* The English locale is used to determine (decimal/grouping) separator characters.
*
* @param value the non null value representing a number
*
* @throws NumberFormatException when the number could not be parsed to a {@link BigDecimal}
*/
public DecimalType(String value) {
this(value, Locale.ENGLISH);
}
/**
* Creates a new {@link DecimalType} with the given value.
*
* @param value the non null value representing a number
* @param locale the locale used to determine (decimal/grouping) separator characters
*
* @throws NumberFormatException when the number could not be parsed to a {@link BigDecimal}
*/
public DecimalType(String value, Locale locale) {
DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(locale);
df.setParseBigDecimal(true);
ParsePosition position = new ParsePosition(0);
BigDecimal parsedValue = (BigDecimal) df.parseObject(value, position);
if (parsedValue == null || position.getErrorIndex() != -1 || position.getIndex() < value.length()) {
throw new IllegalArgumentException("Invalid BigDecimal value: " + value);
throw new NumberFormatException("Invalid BigDecimal value: " + value);
}
this.value = parsedValue;
}
@ -81,6 +97,14 @@ public class DecimalType extends Number implements PrimitiveType, State, Command
return value.toPlainString();
}
/**
* Static access to {@link DecimalType#DecimalType(String)}.
*
* @param value the non null value representing a number
* @return a new {@link DecimalType}
*
* @throws NumberFormatException when the number could not be parsed to a {@link BigDecimal}
*/
public static DecimalType valueOf(String value) {
return new DecimalType(value);
}

View File

@ -34,24 +34,59 @@ public class PercentType extends DecimalType {
public static final PercentType ZERO = new PercentType(0);
public static final PercentType HUNDRED = new PercentType(100);
/**
* Creates a new {@link PercentType} with 0 as value.
*/
public PercentType() {
this(0);
}
/**
* Creates a new {@link PercentType} with the given value.
*
* @param value the value representing a percentage
*
* @throws IllegalArgumentException when the value is not between 0 and 100
*/
public PercentType(int value) {
super(value);
validateValue(this.value);
}
/**
* Creates a new {@link PercentType} with the given value.
* The English locale is used to determine (decimal/grouping) separator characters.
*
* @param value the non null value representing a percentage
*
* @throws NumberFormatException when the number could not be parsed to a {@link BigDecimal}
* @throws IllegalArgumentException when the value is not between 0 and 100
*/
public PercentType(String value) {
this(value, Locale.ENGLISH);
}
/**
* Creates a new {@link PercentType} with the given value.
*
* @param value the non null value representing a percentage
* @param locale the locale used to determine (decimal/grouping) separator characters
*
* @throws NumberFormatException when the number could not be parsed to a {@link BigDecimal}
* @throws IllegalArgumentException when the value is not between 0 and 100
*/
public PercentType(String value, Locale locale) {
super(value, locale);
validateValue(this.value);
}
/**
* Creates a new {@link PercentType} with the given value.
*
* @param value the value representing a percentage.
*
* @throws IllegalArgumentException when the value is not between 0 and 100
*/
public PercentType(BigDecimal value) {
super(value);
validateValue(this.value);
@ -63,6 +98,15 @@ public class PercentType extends DecimalType {
}
}
/**
* Static access to {@link PercentType#PercentType(String)}.
*
* @param value the non null value representing a percentage
* @return new {@link PercentType}
*
* @throws NumberFormatException when the number could not be parsed to a {@link BigDecimal}
* @throws IllegalArgumentException when the value is not between 0 and 100
*/
public static PercentType valueOf(String value) {
return new PercentType(value);
}

View File

@ -99,6 +99,9 @@ public class QuantityType<T extends Quantity<T>> extends Number
* The English locale is used to determine (decimal/grouping) separator characters.
*
* @param value the non null value representing a quantity with an optional unit.
*
* @throws NumberFormatException when a quantity without a unit could not be parsed
* @throws IllegalArgumentException when a quantity with a unit could not be parsed
*/
public QuantityType(String value) {
this(value, Locale.ENGLISH);
@ -110,6 +113,9 @@ public class QuantityType<T extends Quantity<T>> extends Number
*
* @param value the non null value representing a quantity with an optional unit.
* @param locale the locale used to determine (decimal/grouping) separator characters.
*
* @throws NumberFormatException when a quantity without a unit could not be parsed
* @throws IllegalArgumentException when a quantity with a unit could not be parsed
*/
@SuppressWarnings("unchecked")
public QuantityType(String value, Locale locale) {
@ -123,7 +129,7 @@ public class QuantityType<T extends Quantity<T>> extends Number
ParsePosition position = new ParsePosition(0);
BigDecimal parsedValue = (BigDecimal) df.parseObject(value, position);
if (parsedValue == null || position.getErrorIndex() != -1 || position.getIndex() < value.length()) {
throw new IllegalArgumentException("Invalid BigDecimal value: " + value);
throw new NumberFormatException("Invalid BigDecimal value: " + value);
}
quantity = (Quantity<T>) Quantities.getQuantity(parsedValue, AbstractUnit.ONE, Scale.RELATIVE);
} else {
@ -190,6 +196,15 @@ public class QuantityType<T extends Quantity<T>> extends Number
return toFullString();
}
/**
* Static access to {@link QuantityType#QuantityType(String)}.
*
* @param value the non null value representing a quantity with an optional unit
* @return a new {@link QuantityType}
*
* @throws NumberFormatException when a quantity without a unit could not be parsed
* @throws IllegalArgumentException when a quantity with a unit could not be parsed
*/
public static QuantityType<? extends Quantity<?>> valueOf(String value) {
return new QuantityType<>(value);
}

View File

@ -12,6 +12,7 @@
*/
package org.openhab.core.library.types;
import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.*;
import java.text.DecimalFormatSymbols;
@ -45,6 +46,38 @@ public class DecimalTypeTest {
Locale.forLanguageTag("en-US"));
}
@ParameterizedTest
@MethodSource("locales")
public void testKnownInvalidConstructors(Locale locale) {
Locale.setDefault(locale);
assertThrows(NumberFormatException.class, () -> new DecimalType("123 Hello World"));
assertThrows(NumberFormatException.class, () -> new DecimalType(""));
assertThrows(NumberFormatException.class, () -> new DecimalType("."));
assertThrows(NumberFormatException.class, () -> new DecimalType("1 2"));
assertThrows(NumberFormatException.class, () -> new DecimalType("123..56"));
assertThrows(NumberFormatException.class, () -> new DecimalType("123abc56"));
assertThrows(NumberFormatException.class, () -> new DecimalType("123.123,56"));
assertThrows(NumberFormatException.class, () -> new DecimalType("123٬123٫56"));
assertThrows(NumberFormatException.class, () -> new DecimalType("", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new DecimalType(".", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new DecimalType("1 2", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new DecimalType("123..56", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new DecimalType("123abc56", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new DecimalType("123.123,56", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new DecimalType("123٬123٫56", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new DecimalType("", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new DecimalType(",", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new DecimalType("1 2", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new DecimalType("123,,56", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new DecimalType("123abc56", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new DecimalType("123,123.56", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new DecimalType("123٬123٫56", Locale.GERMAN));
}
@ParameterizedTest
@ValueSource(strings = { "-2,000.5", "-2.5", "-2", "-0", "0", "2", "2.5", "2,000.5", "-10E3", "-10E-3", "-0E-22",
"-0E0", "-0E-0", "0E0", "0E-22", "10E-3", "10E3" })

View File

@ -44,6 +44,32 @@ public class PercentTypeTest {
Locale.forLanguageTag("en-US"));
}
@ParameterizedTest
@MethodSource("locales")
public void testKnownInvalidConstructors(Locale locale) {
Locale.setDefault(locale);
assertThrows(NumberFormatException.class, () -> new PercentType("123 Hello World"));
assertThrows(NumberFormatException.class, () -> new PercentType(""));
assertThrows(NumberFormatException.class, () -> new PercentType("."));
assertThrows(NumberFormatException.class, () -> new PercentType("1 2"));
assertThrows(NumberFormatException.class, () -> new PercentType("1..56"));
assertThrows(NumberFormatException.class, () -> new PercentType("1abc"));
assertThrows(NumberFormatException.class, () -> new PercentType("", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new PercentType(".", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new PercentType("1 2", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new PercentType("1..56", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new PercentType("1abc", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new PercentType("", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new PercentType(",", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new PercentType("1 2", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new PercentType("1,,56", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new PercentType("1abc", Locale.GERMAN));
}
@ParameterizedTest
@ValueSource(strings = { "0", "0.000", "0.001", "2", "2.5", "0E0", "0E-22", "10E-3", "1E2" })
public void testValidConstructors(String value) {

View File

@ -84,7 +84,8 @@ public class QuantityTypeTest {
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("123 Hello World"));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("°C"));
assertThrows(NumberFormatException.class, () -> new QuantityType<>("abc"));
assertThrows(NumberFormatException.class, () -> new QuantityType<>("°C"));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>(". °C"));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("1 2 °C"));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("123..56 °C"));
@ -92,7 +93,8 @@ public class QuantityTypeTest {
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("123.123,56 °C"));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("123٬123٫56 °C"));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("°C", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new QuantityType<>("abc", Locale.ENGLISH));
assertThrows(NumberFormatException.class, () -> new QuantityType<>("°C", Locale.ENGLISH));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>(". °C", Locale.ENGLISH));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("1 2 °C", Locale.ENGLISH));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("123..56 °C", Locale.ENGLISH));
@ -100,7 +102,8 @@ public class QuantityTypeTest {
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("123.123,56 °C", Locale.ENGLISH));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("123٬123٫56 °C", Locale.ENGLISH));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("°C", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new QuantityType<>("abc", Locale.GERMAN));
assertThrows(NumberFormatException.class, () -> new QuantityType<>("°C", Locale.GERMAN));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>(", °C", Locale.GERMAN));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("1 2 °C", Locale.GERMAN));
assertThrows(IllegalArgumentException.class, () -> new QuantityType<>("123,,56 °C", Locale.GERMAN));