This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch backport/CAMEL-24407-4.22.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit bf13b4d66c2dfa284b600a4b139ef7e9af384095 Author: Karol <[email protected]> AuthorDate: Mon Aug 31 10:05:32 2026 +0200 CAMEL-24407: simple predicate fails for long digital strings Numbers with more digits than a long can hold, such as bank account numbers, made comparisons throw NumberFormatException. isNumber only checks that the text is all digits, and the callers then parsed it with Long.parseLong or Integer.valueOf. Compare those as BigInteger instead. A number that does not fit in a long can never equal an int or long, so equality with one is false. A numeric literal in a simple predicate that does not fit in a long is now kept as literal text, so it takes the same comparison path. Co-authored-by: Claude <[email protected]> --- .../language/simple/SimplePredicateParser.java | 5 +- .../language/simple/ast/NumericExpression.java | 12 +++ .../camel/converter/TypeCoerceCompareTest.java | 21 +++++ .../camel/language/simple/SimpleOperatorTest.java | 17 ++++ .../org/apache/camel/util/ObjectHelperTest.java | 22 +++++ .../org/apache/camel/support/ObjectHelper.java | 98 +++++++++++++++------- .../org/apache/camel/support/ObjectHelperTest.java | 29 +++++++ 7 files changed, 168 insertions(+), 36 deletions(-) diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java index c52cc491050d..05ea56bfc31d 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java @@ -59,9 +59,6 @@ import org.apache.camel.support.ExpressionToPredicateAdapter; import org.apache.camel.support.builder.PredicateBuilder; import org.apache.camel.util.StringHelper; -import static org.apache.camel.support.ObjectHelper.isFloatingNumber; -import static org.apache.camel.support.ObjectHelper.isNumber; - /** * A parser to parse simple language as a Camel {@link Predicate} */ @@ -326,7 +323,7 @@ public class SimplePredicateParser extends BaseSimpleParser { if (!quoted) { // if the text is not in a quoted block (literal text), then lets see if // its numeric then we can optimize this - numeric = isNumber(text) || isFloatingNumber(text); + numeric = NumericExpression.isNumericValue(text); } if (numeric) { nodes.add(new NumericExpression(imageToken.getToken(), text)); diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java index 9f39a63e4f4f..8f38ab8155ed 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java @@ -21,6 +21,7 @@ import org.apache.camel.Exchange; import org.apache.camel.Expression; import org.apache.camel.language.simple.types.SimpleParserException; import org.apache.camel.language.simple.types.SimpleToken; +import org.apache.camel.support.ObjectHelper; /** * Represents a numeric value. @@ -47,6 +48,17 @@ public class NumericExpression extends BaseSimpleNode { } } + /** + * Whether the text can be represented as a numeric value. Numbers with more digits than a long can hold, such as + * bank account numbers, are kept as literal text instead, so they can be compared as big integers. + */ + public static boolean isNumericValue(String text) { + if (text.indexOf('.') != -1) { + return ObjectHelper.isFloatingNumber(text); + } + return ObjectHelper.isLongNumber(text); + } + public Object getNumber() { return number; } diff --git a/core/camel-core/src/test/java/org/apache/camel/converter/TypeCoerceCompareTest.java b/core/camel-core/src/test/java/org/apache/camel/converter/TypeCoerceCompareTest.java index 67d35dd47481..68ef8d464f97 100644 --- a/core/camel-core/src/test/java/org/apache/camel/converter/TypeCoerceCompareTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/converter/TypeCoerceCompareTest.java @@ -41,6 +41,27 @@ public class TypeCoerceCompareTest extends ContextTestSupport { assertEquals(0, ObjectHelper.typeCoerceCompare(tc, "7.0", "7")); } + @Test + public void testCompareStringStringTooBigForLong() { + TypeConverter tc = context.getTypeConverter(); + // numbers such as bank account numbers have more digits than a long can hold + assertEquals(0, ObjectHelper.typeCoerceCompare(tc, "12345678901234567890", "12345678901234567890")); + assertTrue(ObjectHelper.typeCoerceCompare(tc, "12345678901234567891", "12345678901234567890") > 0); + assertTrue(ObjectHelper.typeCoerceCompare(tc, "12345678901234567890", "12345678901234567891") < 0); + assertTrue(ObjectHelper.typeCoerceCompare(tc, "12345678901234567890", "7") > 0); + assertTrue(ObjectHelper.typeCoerceCompare(tc, "7", "12345678901234567890") < 0); + } + + @Test + public void testCompareStringNumberTooBigForLong() { + TypeConverter tc = context.getTypeConverter(); + assertTrue(ObjectHelper.typeCoerceCompare(tc, "12345678901234567890", 7L) > 0); + assertTrue(ObjectHelper.typeCoerceCompare(tc, 7L, "12345678901234567890") < 0); + // does not fit in an int, but still fits in a long + assertTrue(ObjectHelper.typeCoerceCompare(tc, "99999999999", 7) > 0); + assertTrue(ObjectHelper.typeCoerceCompare(tc, 7, "99999999999") < 0); + } + @Test public void testCompareStringInteger() { TypeConverter tc = context.getTypeConverter(); diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java index 6d123c342f72..dd1539b0beb3 100644 --- a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java @@ -973,6 +973,23 @@ public class SimpleOperatorTest extends LanguageTestSupport { assertExpression("${trim()} ~> ${replace('Hello','Hi',$param)} ~> ${split($param,' ')} ~> ${size($param)}", 5); } + @Test + public void testDigitalStringTooBigForLong() { + // CAMEL-24407: numbers such as bank account numbers have more digits than a long can hold + exchange.getIn().setHeader("Account1", "12345678901234567890"); + exchange.getIn().setHeader("Account2", "12345678901234567890"); + exchange.getIn().setHeader("Account3", "12345678901234567891"); + + assertPredicate("${header.Account1} == ${header.Account2}", true); + assertPredicate("${header.Account1} == ${header.Account3}", false); + assertPredicate("${header.Account1} != ${header.Account3}", true); + assertPredicate("${header.Account1} < ${header.Account3}", true); + assertPredicate("${header.Account3} > ${header.Account1}", true); + assertPredicate("${header.Account1} == 12345678901234567890", true); + assertPredicate("${header.Account1} == '12345678901234567890'", true); + assertPredicate("${header.Account1} > 7", true); + } + @Override protected String getLanguageName() { return "simple"; diff --git a/core/camel-core/src/test/java/org/apache/camel/util/ObjectHelperTest.java b/core/camel-core/src/test/java/org/apache/camel/util/ObjectHelperTest.java index 981627e9d617..3b4e1e09b865 100644 --- a/core/camel-core/src/test/java/org/apache/camel/util/ObjectHelperTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/util/ObjectHelperTest.java @@ -175,6 +175,28 @@ public class ObjectHelperTest { } } + @Test + void testEqualsNumberTooBigForLong() throws Exception { + try (CamelContext context = new DefaultCamelContext()) { + context.start(); + TypeConverter tc = context.getTypeConverter(); + + // numbers such as bank account numbers have more digits than a long can hold + assertTrue(ObjectHelper.typeCoerceEquals(tc, "12345678901234567890", "12345678901234567890")); + assertFalse(ObjectHelper.typeCoerceEquals(tc, "12345678901234567890", "12345678901234567891")); + assertFalse(ObjectHelper.typeCoerceEquals(tc, "12345678901234567890", "7")); + + // such a number cannot be equal to an int or long + assertFalse(ObjectHelper.typeCoerceEquals(tc, "12345678901234567890", 7L)); + assertFalse(ObjectHelper.typeCoerceEquals(tc, 7L, "12345678901234567890")); + + // does not fit in an int, but still fits in a long + assertFalse(ObjectHelper.typeCoerceEquals(tc, "99999999999", 7)); + assertFalse(ObjectHelper.typeCoerceEquals(tc, 7, "99999999999")); + assertTrue(ObjectHelper.typeCoerceEquals(tc, "99999999999", 99999999999L)); + } + } + @Test void testContainsStringBuilder() throws Exception { try (CamelContext context = new DefaultCamelContext()) { diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java index df24b780a78b..f91a7e6e4e88 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/ObjectHelper.java @@ -22,6 +22,7 @@ import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.math.BigInteger; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -217,19 +218,29 @@ public final class ObjectHelper { } private static boolean typeCoerceIntLong(Object leftValue, String rightValue) { + Long rightNum = toLong(rightValue); + if (rightNum == null) { + // too big for a long so it cannot be equal to an int or long + return false; + } if (leftValue instanceof Integer intValue) { - return integerPairComparison(intValue, Integer.valueOf(rightValue)); + return longPairComparison(intValue.longValue(), rightNum); } else if (leftValue instanceof Long longValue) { - return longPairComparison(longValue, Long.valueOf(rightValue)); + return longPairComparison(longValue, rightNum); } return false; } private static boolean typeCoerceILString(String leftValue, Object rightValue) { + Long leftNum = toLong(leftValue); + if (leftNum == null) { + // too big for a long so it cannot be equal to an int or long + return false; + } if (rightValue instanceof Integer intValue) { - return integerPairComparison(Integer.valueOf(leftValue), intValue); + return longPairComparison(leftNum, intValue.longValue()); } else if (rightValue instanceof Long longValue) { - return longPairComparison(Long.valueOf(leftValue), longValue); + return longPairComparison(leftNum, longValue); } return false; } @@ -237,7 +248,13 @@ public final class ObjectHelper { private static boolean typeCoerceStringPair(String leftNum, String rightNum, boolean ignoreCase) { if (isNumber(leftNum) && isNumber(rightNum)) { // favour to use numeric comparison - return longPairComparison(Long.parseLong(leftNum), Long.parseLong(rightNum)); + Long left = toLong(leftNum); + Long right = toLong(rightNum); + if (left != null && right != null) { + return longPairComparison(left, right); + } + // too big for a long so compare as big integers + return new BigInteger(leftNum).equals(new BigInteger(rightNum)); } if (ignoreCase) { return leftNum.compareToIgnoreCase(rightNum) == 0; @@ -276,23 +293,22 @@ public final class ObjectHelper { return leftNum.compareTo(rightNum); } else if ((rightValue instanceof Integer || rightValue instanceof Long) && leftValue instanceof String leftStr && isNumber(leftStr)) { - if (rightValue instanceof Integer rightNum) { - Integer leftNum = Integer.valueOf(leftStr); - return leftNum.compareTo(rightNum); - } else { - Long leftNum = Long.valueOf(leftStr); - Long rightNum = (Long) rightValue; - return leftNum.compareTo(rightNum); + long rightNum = ((Number) rightValue).longValue(); + Long leftNum = toLong(leftStr); + if (leftNum == null) { + // too big for a long so compare as big integers + return new BigInteger(leftStr).compareTo(BigInteger.valueOf(rightNum)); } + return Long.compare(leftNum, rightNum); } else if (rightValue instanceof String rightStr && (leftValue instanceof Integer || leftValue instanceof Long) && isNumber(rightStr)) { - if (leftValue instanceof Integer leftNum) { - Integer rightNum = Integer.valueOf(rightStr); - return leftNum.compareTo(rightNum); - } else if (leftValue instanceof Long leftNum) { - Long rightNum = Long.valueOf(rightStr); - return leftNum.compareTo(rightNum); + long leftNum = ((Number) leftValue).longValue(); + Long rightNum = toLong(rightStr); + if (rightNum == null) { + // too big for a long so compare as big integers + return BigInteger.valueOf(leftNum).compareTo(new BigInteger(rightStr)); } + return Long.compare(leftNum, rightNum); } else if (rightValue instanceof Double rightNum && leftValue instanceof String leftStr && isFloatingNumber(leftStr)) { Double leftNum = Double.valueOf(leftStr); @@ -355,27 +371,45 @@ public final class ObjectHelper { private static int typeCoerceCompareStringString(String leftNum, String rightNum) { // prioritize non-floating numbers first - Long num1 = isNumber(leftNum) ? Long.parseLong(leftNum) : null; - Long num2 = isNumber(rightNum) ? Long.parseLong(rightNum) : null; - Double dec1 = num1 == null && isFloatingNumber(leftNum) ? Double.parseDouble(leftNum) : null; - Double dec2 = num2 == null && isFloatingNumber(rightNum) ? Double.parseDouble(rightNum) : null; - if (num1 != null && num2 != null) { - return num1.compareTo(num2); - } else if (dec1 != null && dec2 != null) { - return dec1.compareTo(dec2); + if (isNumber(leftNum) && isNumber(rightNum)) { + Long num1 = toLong(leftNum); + Long num2 = toLong(rightNum); + if (num1 != null && num2 != null) { + return num1.compareTo(num2); + } + // too big for a long so compare as big integers + return new BigInteger(leftNum).compareTo(new BigInteger(rightNum)); } - // okay mixed but we need to convert to floating - if (num1 != null && dec2 != null) { - dec1 = Double.parseDouble(leftNum); - return dec1.compareTo(dec2); - } else if (num2 != null && dec1 != null) { - dec2 = Double.parseDouble(rightNum); + // mixed or floating numbers are compared as floating + Double dec1 = isFloatingNumber(leftNum) ? Double.parseDouble(leftNum) : null; + Double dec2 = isFloatingNumber(rightNum) ? Double.parseDouble(rightNum) : null; + if (dec1 != null && dec2 != null) { return dec1.compareTo(dec2); } // fallback to string comparison return leftNum.compareTo(rightNum); } + /** + * Checks whether the text is an integer number that fits in a {@link Long}. Numbers such as bank account numbers + * can have more digits than a long can hold, and must be compared as {@link BigInteger} instead. + */ + public static boolean isLongNumber(String text) { + return isNumber(text) && toLong(text) != null; + } + + /** + * Parses the text as a long, or <tt>null</tt> if the number has too many digits to fit in a {@link Long}. The text + * is expected to be checked with {@link #isNumber(String)} first, so overflow is the only way this fails. + */ + private static Long toLong(String text) { + try { + return Long.parseLong(text); + } catch (NumberFormatException e) { + return null; + } + } + /** * Checks whether the text is an integer number */ diff --git a/core/camel-support/src/test/java/org/apache/camel/support/ObjectHelperTest.java b/core/camel-support/src/test/java/org/apache/camel/support/ObjectHelperTest.java index 23f42f12191d..1e7a24ace881 100644 --- a/core/camel-support/src/test/java/org/apache/camel/support/ObjectHelperTest.java +++ b/core/camel-support/src/test/java/org/apache/camel/support/ObjectHelperTest.java @@ -62,6 +62,35 @@ class ObjectHelperTest { assertFalse(ObjectHelper.isNumber("0.0")); } + @Test + @DisplayName("Tests that isLongNumber returns true for integers that fit in a long") + void isLongNumberIntegers() { + assertTrue(ObjectHelper.isLongNumber("1234")); + assertTrue(ObjectHelper.isLongNumber("-1234")); + assertTrue(ObjectHelper.isLongNumber("0")); + assertTrue(ObjectHelper.isLongNumber("9223372036854775807")); + assertTrue(ObjectHelper.isLongNumber("-9223372036854775808")); + assertTrue(ObjectHelper.isLongNumber("00000000000000000000001")); + } + + @Test + @DisplayName("Tests that isLongNumber returns false for integers with too many digits") + void isLongNumberTooBig() { + assertFalse(ObjectHelper.isLongNumber("9223372036854775808")); + assertFalse(ObjectHelper.isLongNumber("-9223372036854775809")); + assertFalse(ObjectHelper.isLongNumber("12345678901234567890")); + } + + @Test + @DisplayName("Tests that isLongNumber returns false for non-integers") + void isLongNumberNonIntegers() { + assertFalse(ObjectHelper.isLongNumber("")); + assertFalse(ObjectHelper.isLongNumber(" ")); + assertFalse(ObjectHelper.isLongNumber(null)); + assertFalse(ObjectHelper.isLongNumber("ABC")); + assertFalse(ObjectHelper.isLongNumber("12.34")); + } + @Test @DisplayName("Tests that isFloatingNumber returns true for empty, space or null") void isFloatingNumberEmpty() {
