This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.22.x by this push:
new 65cd5d3f4c4e CAMEL-24407: simple predicate fails for long digital
strings
65cd5d3f4c4e is described below
commit 65cd5d3f4c4e1a92b7e4c3a2ce0753d47886e100
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Aug 31 22:56:28 2026 +0200
CAMEL-24407: simple predicate fails for long digital strings
Backport of #25945 to camel-4.22.x. The simple language threw
NumberFormatException when comparing numbers with more digits than a
long can hold, such as bank account numbers. Numbers that don't fit
in a long are now compared as BigInteger instead of being force-cast,
so equality/ordering against oversized numeric literals works
correctly instead of throwing.
Co-authored-by: Karol <[email protected]>
Co-authored-by: Claude <[email protected]>
Closes #25978
---
.../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() {