diff --git a/src/main/java/org/apache/commons/validator/routines/AbstractNumberValidator.java b/src/main/java/org/apache/commons/validator/routines/AbstractNumberValidator.java index 4052ca8bc..94a041e75 100644 --- a/src/main/java/org/apache/commons/validator/routines/AbstractNumberValidator.java +++ b/src/main/java/org/apache/commons/validator/routines/AbstractNumberValidator.java @@ -272,6 +272,12 @@ public boolean isValid(final String value, final String pattern, final Locale lo /** * Tests if the value is less than or equal to a maximum. * + *
+ * For an integer validator the bound is compared exactly rather than narrowed to a {@code long}, so a {@code BigInteger} or {@code BigDecimal} bound outside + * the long range keeps its magnitude and a fractional bound keeps its fractional part. A non-finite {@link Double} or {@link Float} operand keeps the + * {@code doubleValue()} comparison, since {@code BigDecimal} cannot represent {@code NaN} or an infinity. + *
+ * * @param value The value validation is being performed on. * @param max The maximum value. * @return {@code true} if the value is less than or equal to the maximum. @@ -280,12 +286,18 @@ public boolean maxValue(final Number value, final Number max) { if (isAllowFractions()) { return value.doubleValue() <= max.doubleValue(); } - return value.longValue() <= max.longValue(); + return isFinite(value) && isFinite(max) ? compareTo(value, max) <= 0 : value.doubleValue() <= max.doubleValue(); } /** * Tests if the value is greater than or equal to a minimum. * + *+ * For an integer validator the bound is compared exactly rather than narrowed to a {@code long}, so a {@code BigInteger} or {@code BigDecimal} bound outside + * the long range keeps its magnitude and a fractional bound keeps its fractional part. A non-finite {@link Double} or {@link Float} operand keeps the + * {@code doubleValue()} comparison, since {@code BigDecimal} cannot represent {@code NaN} or an infinity. + *
+ * * @param value The value validation is being performed on. * @param min The minimum value. * @return {@code true} if the value is greater than or equal to the minimum. @@ -294,7 +306,7 @@ public boolean minValue(final Number value, final Number min) { if (isAllowFractions()) { return value.doubleValue() >= min.doubleValue(); } - return value.longValue() >= min.longValue(); + return isFinite(value) && isFinite(min) ? compareTo(value, min) >= 0 : value.doubleValue() >= min.doubleValue(); } /** diff --git a/src/test/java/org/apache/commons/validator/routines/ByteValidatorTest.java b/src/test/java/org/apache/commons/validator/routines/ByteValidatorTest.java index 2869c0aed..b08b231c8 100644 --- a/src/test/java/org/apache/commons/validator/routines/ByteValidatorTest.java +++ b/src/test/java/org/apache/commons/validator/routines/ByteValidatorTest.java @@ -21,6 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.Locale; import org.junit.jupiter.api.BeforeEach; @@ -111,6 +113,21 @@ void testByteRangeMinMax() { assertFalse(validator.maxValue(number21, max), "maxValue() > max"); } + /** + * The inherited {@link Number}-typed range overloads must compare the exact bound rather than one narrowed to a {@code long}. + */ + @Test + void testNumberRangeExactBound() { + final AbstractNumberValidator instance = ByteValidator.getInstance(); + final Number value = Byte.valueOf((byte) 100); + // A bound above the long range narrows to a negative long, wrongly reporting 100 as above the maximum. + final Number aboveLongMax = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE); + assertTrue(instance.maxValue(value, aboveLongMax)); + assertTrue(instance.isInRange(value, BigInteger.ZERO, aboveLongMax)); + // A fractional bound is not floored: 5 >= 5.5 is false. + assertFalse(instance.minValue(Byte.valueOf((byte) 5), new BigDecimal("5.5"))); + } + /** * Test ByteValidator validate Methods */ diff --git a/src/test/java/org/apache/commons/validator/routines/CurrencyValidatorTest.java b/src/test/java/org/apache/commons/validator/routines/CurrencyValidatorTest.java index 0acb701c1..6191da6aa 100644 --- a/src/test/java/org/apache/commons/validator/routines/CurrencyValidatorTest.java +++ b/src/test/java/org/apache/commons/validator/routines/CurrencyValidatorTest.java @@ -19,8 +19,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; +import java.math.BigInteger; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.util.Locale; @@ -45,6 +47,22 @@ protected void setUp() { ukPound = new DecimalFormatSymbols(Locale.UK).getCurrencySymbol(); } + /** + * The {@link Number}-typed range overloads inherited through {@link BigDecimalValidator} from {@link AbstractNumberValidator} must compare the exact bound, + * so a {@code BigInteger} or {@code BigDecimal} bound outside the long range or a fractional bound is not silently truncated. + */ + @Test + void testNumberRangeExactBound() { + final AbstractNumberValidator instance = CurrencyValidator.getInstance(); + final Number value = new BigDecimal("100"); + // A bound above the long range must not narrow to a negative long and wrongly report 100 as above the maximum. + final Number aboveLongMax = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE); + assertTrue(instance.maxValue(value, aboveLongMax)); + assertTrue(instance.isInRange(value, BigInteger.ZERO, aboveLongMax)); + // A fractional bound is not floored: 5 >= 5.5 is false. + assertFalse(instance.minValue(new BigDecimal("5"), new BigDecimal("5.5"))); + } + /** * Test Format Type */ diff --git a/src/test/java/org/apache/commons/validator/routines/FloatValidatorTest.java b/src/test/java/org/apache/commons/validator/routines/FloatValidatorTest.java index 29ba5e11a..f87f17fd9 100644 --- a/src/test/java/org/apache/commons/validator/routines/FloatValidatorTest.java +++ b/src/test/java/org/apache/commons/validator/routines/FloatValidatorTest.java @@ -23,6 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.math.BigDecimal; +import java.math.BigInteger; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; @@ -136,6 +138,22 @@ void testFloatRangeMinMaxNaN() { assertFalse(validator.maxValue(Float.NaN, 20), "maxValue() NaN"); } + /** + * The {@link Number}-typed range overloads inherited from {@link AbstractNumberValidator} must compare the exact bound, so a {@code BigInteger} or + * {@code BigDecimal} bound outside the long range or a fractional bound is not silently truncated. + */ + @Test + void testNumberRangeExactBound() { + final AbstractNumberValidator instance = FloatValidator.getInstance(); + final Number value = Float.valueOf(100); + // A bound above the long range must not narrow to a negative long and wrongly report 100 as above the maximum. + final Number aboveLongMax = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE); + assertTrue(instance.maxValue(value, aboveLongMax)); + assertTrue(instance.isInRange(value, BigInteger.ZERO, aboveLongMax)); + // A fractional bound is not floored: 5 >= 5.5 is false. + assertFalse(instance.minValue(Float.valueOf(5), new BigDecimal("5.5"))); + } + /** * Test Float validation for values too small to handle. (slightly different from max/min which are the largest +ve/-ve */ diff --git a/src/test/java/org/apache/commons/validator/routines/IntegerValidatorTest.java b/src/test/java/org/apache/commons/validator/routines/IntegerValidatorTest.java index 9602d60d6..76a907606 100644 --- a/src/test/java/org/apache/commons/validator/routines/IntegerValidatorTest.java +++ b/src/test/java/org/apache/commons/validator/routines/IntegerValidatorTest.java @@ -21,6 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.Locale; import org.junit.jupiter.api.BeforeEach; @@ -149,4 +151,19 @@ void testMinMaxValues() { assertTrue(validator.isValid("-2147483648"), "-2147483648 is min integer"); assertFalse(validator.isValid("-2147483649"), "-2147483649 < min integer"); } + + /** + * The inherited {@link Number}-typed range overloads must compare the exact bound rather than one narrowed to a {@code long}. + */ + @Test + void testNumberRangeExactBound() { + final AbstractNumberValidator instance = IntegerValidator.getInstance(); + final Number value = Integer.valueOf(100); + // A bound above the long range narrows to a negative long, wrongly reporting 100 as above the maximum. + final Number aboveLongMax = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE); + assertTrue(instance.maxValue(value, aboveLongMax)); + assertTrue(instance.isInRange(value, BigInteger.ZERO, aboveLongMax)); + // A fractional bound is not floored: 5 >= 5.5 is false. + assertFalse(instance.minValue(Integer.valueOf(5), new BigDecimal("5.5"))); + } } diff --git a/src/test/java/org/apache/commons/validator/routines/LongValidatorTest.java b/src/test/java/org/apache/commons/validator/routines/LongValidatorTest.java index 425f488a3..0c36da945 100644 --- a/src/test/java/org/apache/commons/validator/routines/LongValidatorTest.java +++ b/src/test/java/org/apache/commons/validator/routines/LongValidatorTest.java @@ -21,6 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.Locale; import org.junit.jupiter.api.BeforeEach; @@ -111,6 +113,28 @@ void testLongRangeMinMax() { assertFalse(validator.maxValue(number21, 20), "maxValue() > max"); } + /** + * The {@link Number}-typed range overloads inherited from {@link AbstractNumberValidator} must compare the exact bound rather than one narrowed to a + * {@code long}, so a {@code BigInteger}/{@code BigDecimal} bound outside the long range or a fractional bound is not silently truncated. + */ + @Test + void testNumberRangeExactBound() { + final AbstractNumberValidator instance = LongValidator.getInstance(); + final Number value = Long.valueOf(Long.MAX_VALUE); + // One above Long.MAX_VALUE narrows to Long.MIN_VALUE, wrongly reporting Long.MAX_VALUE as above the maximum. + final Number aboveLongMax = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE); + assertTrue(instance.maxValue(value, aboveLongMax)); + assertTrue(instance.isInRange(value, BigInteger.ZERO, aboveLongMax)); + // A fractional bound is not floored: 5 >= 5.5 is false, 5 <= 5.5 is true. + assertFalse(instance.minValue(Long.valueOf(5), Double.valueOf(5.5))); + assertFalse(instance.minValue(Long.valueOf(5), new BigDecimal("5.5"))); + assertTrue(instance.maxValue(Long.valueOf(5), new BigDecimal("5.5"))); + // A non-finite bound falls back to the double comparison, matching the Big* validators. + assertFalse(instance.minValue(value, Double.NaN)); + assertTrue(instance.maxValue(value, Double.POSITIVE_INFINITY)); + assertTrue(instance.minValue(value, Double.NEGATIVE_INFINITY)); + } + /** * Test LongValidator validate Methods */ diff --git a/src/test/java/org/apache/commons/validator/routines/PercentValidatorTest.java b/src/test/java/org/apache/commons/validator/routines/PercentValidatorTest.java index 5d765b6c1..9afe81b25 100644 --- a/src/test/java/org/apache/commons/validator/routines/PercentValidatorTest.java +++ b/src/test/java/org/apache/commons/validator/routines/PercentValidatorTest.java @@ -19,8 +19,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; +import java.math.BigInteger; import java.util.Locale; import org.junit.jupiter.api.AfterEach; @@ -51,6 +53,22 @@ protected void tearDown() { validator = null; } + /** + * The {@link Number}-typed range overloads inherited through {@link BigDecimalValidator} from {@link AbstractNumberValidator} must compare the exact bound, + * so a {@code BigInteger} or {@code BigDecimal} bound outside the long range or a fractional bound is not silently truncated. + */ + @Test + void testNumberRangeExactBound() { + final AbstractNumberValidator instance = PercentValidator.getInstance(); + final Number value = new BigDecimal("100"); + // A bound above the long range must not narrow to a negative long and wrongly report 100 as above the maximum. + final Number aboveLongMax = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE); + assertTrue(instance.maxValue(value, aboveLongMax)); + assertTrue(instance.isInRange(value, BigInteger.ZERO, aboveLongMax)); + // A fractional bound is not floored: 5 >= 5.5 is false. + assertFalse(instance.minValue(new BigDecimal("5"), new BigDecimal("5.5"))); + } + /** * Test Format Type */ diff --git a/src/test/java/org/apache/commons/validator/routines/ShortValidatorTest.java b/src/test/java/org/apache/commons/validator/routines/ShortValidatorTest.java index e69adb9ea..66a863830 100644 --- a/src/test/java/org/apache/commons/validator/routines/ShortValidatorTest.java +++ b/src/test/java/org/apache/commons/validator/routines/ShortValidatorTest.java @@ -21,6 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.Locale; import org.junit.jupiter.api.BeforeEach; @@ -102,6 +104,21 @@ void testShortRangeMinMax() { assertFalse(validator.maxValue(number21, max), "maxValue() > max"); } + /** + * The inherited {@link Number}-typed range overloads must compare the exact bound rather than one narrowed to a {@code long}. + */ + @Test + void testNumberRangeExactBound() { + final AbstractNumberValidator instance = ShortValidator.getInstance(); + final Number value = Short.valueOf((short) 100); + // A bound above the long range narrows to a negative long, wrongly reporting 100 as above the maximum. + final Number aboveLongMax = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE); + assertTrue(instance.maxValue(value, aboveLongMax)); + assertTrue(instance.isInRange(value, BigInteger.ZERO, aboveLongMax)); + // A fractional bound is not floored: 5 >= 5.5 is false. + assertFalse(instance.minValue(Short.valueOf((short) 5), new BigDecimal("5.5"))); + } + /** * Test ShortValidator validate Methods */