This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch fix/CAMEL-24962
in repository https://gitbox.apache.org/repos/asf/camel.git

commit a80d45acd87e6b880598d9dc2736e84d068971de
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Sep 23 18:43:11 2026 +0200

    CAMEL-24966: simple - fix numeric literals, elvis, ++/-- and null 
range/regex
    
    - a numeric literal evaluated as Long/int/Double is converted instead of 
cast (ClassCastException)
    - negative literals below the int range parse as long
    - elvis ?: treats any numeric zero as falsy and evaluates the left side once
    - ++ and -- keep decimals (1.5++ is 2.5) and evaluate the left side once
    - range and regex with a null right hand side do not match instead of a 
NullPointerException
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../language/simple/ast/BinaryExpression.java      | 12 +++-
 .../language/simple/ast/NumericExpression.java     |  8 +--
 .../camel/language/simple/ast/OtherExpression.java | 10 ++-
 .../camel/language/simple/ast/UnaryExpression.java | 63 +++++++------------
 .../camel/language/simple/SimpleOperatorTest.java  | 72 ++++++++++++++++++++++
 5 files changed, 115 insertions(+), 50 deletions(-)

diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
index 46ce040e1c1f..39f4c8886e52 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
@@ -192,7 +192,10 @@ public class BinaryExpression extends BaseSimpleNode {
             @Override
             public <T> T evaluate(Exchange exchange, Class<T> type) {
                 // reg ex should use String pattern, so we evaluate the right 
hand side as a String
-                Predicate predicate = PredicateBuilder.regex(leftExp, 
rightExp.evaluate(exchange, String.class));
+                String pattern = rightExp.evaluate(exchange, String.class);
+                // no pattern (such as a missing header) matches nothing
+                Predicate predicate = pattern != null
+                        ? PredicateBuilder.regex(leftExp, pattern) : 
PredicateBuilder.constant(false);
                 if (operator == BinaryOperatorType.NOT_REGEX) {
                     predicate = PredicateBuilder.not(predicate);
                 }
@@ -245,8 +248,11 @@ public class BinaryExpression extends BaseSimpleNode {
                 Predicate predicate;
 
                 String range = rightExp.evaluate(exchange, String.class);
-                Matcher matcher = RANGE_PATTERN.matcher(range);
-                if (matcher.matches()) {
+                Matcher matcher = range != null ? RANGE_PATTERN.matcher(range) 
: null;
+                if (range == null) {
+                    // no range (such as a missing header) contains nothing
+                    predicate = PredicateBuilder.constant(false);
+                } else if (matcher.matches()) {
                     // wrap as constant expression for the from and to values
                     Expression from = 
ExpressionBuilder.constantExpression(matcher.group(1));
                     Expression to = 
ExpressionBuilder.constantExpression(matcher.group(3));
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 ac5cb062a88e..445abf97a0bb 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
@@ -40,8 +40,8 @@ public class NumericExpression extends BaseSimpleNode {
         } else {
             // its either a long or integer value (lets just avoid bytes)
             long lon = Long.parseLong(text);
-            if (lon < Integer.MAX_VALUE) {
-                number = Integer.valueOf(text);
+            if (lon >= Integer.MIN_VALUE && lon <= Integer.MAX_VALUE) {
+                number = (int) lon;
             } else {
                 number = lon;
             }
@@ -68,9 +68,7 @@ public class NumericExpression extends BaseSimpleNode {
         return new Expression() {
             @Override
             public <T> T evaluate(Exchange exchange, Class<T> type) {
-                if (type == Object.class || type == int.class || type == 
Integer.class
-                        || type == long.class || type == Long.class
-                        || type == double.class || type == Double.class) {
+                if (type == Object.class || type.isInstance(number)) {
                     return type.cast(number);
                 }
                 return 
exchange.getContext().getTypeConverter().tryConvertTo(type, exchange, number);
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java
index ca3d97449937..f5dd6a376787 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java
@@ -95,11 +95,12 @@ public class OtherExpression extends BaseSimpleNode {
         return new Expression() {
             @Override
             public <T> T evaluate(Exchange exchange, Class<T> type) {
+                // evaluate only once as the left hand side may have side 
effects
                 Object value = leftExp.evaluate(exchange, Object.class);
-                if (value == null || Boolean.FALSE == value || 
ObjectHelper.isEmpty(value) || ObjectHelper.equal(0, value)) {
+                if (value == null || Boolean.FALSE == value || 
ObjectHelper.isEmpty(value) || isZero(value)) {
                     return rightExp.evaluate(exchange, type);
                 } else {
-                    return leftExp.evaluate(exchange, type);
+                    return camelContext.getTypeConverter().convertTo(type, 
exchange, value);
                 }
             }
 
@@ -109,4 +110,9 @@ public class OtherExpression extends BaseSimpleNode {
             }
         };
     }
+
+    private static boolean isZero(Object value) {
+        // any kind of number such as 0, 0L, 0.0 or BigDecimal.ZERO
+        return value instanceof Number n && n.doubleValue() == 0;
+    }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java
index 6b50e5b97b7d..07fcd0fb1c60 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java
@@ -16,6 +16,9 @@
  */
 package org.apache.camel.language.simple.ast;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
 import org.apache.camel.CamelContext;
 import org.apache.camel.CamelExchangeException;
 import org.apache.camel.Exchange;
@@ -73,65 +76,45 @@ public class UnaryExpression extends BaseSimpleNode {
         final Expression leftExp = left.createExpression(camelContext, 
expression);
 
         if (operator == UnaryOperatorType.INC) {
-            return createIncExpression(camelContext, leftExp);
+            return createIncDecExpression(camelContext, leftExp, 1);
         } else if (operator == UnaryOperatorType.DEC) {
-            return createDecExpression(camelContext, leftExp);
+            return createIncDecExpression(camelContext, leftExp, -1);
         }
 
         throw new SimpleParserException("Unknown unary operator " + operator, 
token.getIndex());
     }
 
-    private Expression createIncExpression(CamelContext camelContext, final 
Expression leftExp) {
+    private Expression createIncDecExpression(CamelContext camelContext, final 
Expression leftExp, final int delta) {
         return new Expression() {
             @Override
             public <T> T evaluate(Exchange exchange, Class<T> type) {
-                Number num = leftExp.evaluate(exchange, Number.class);
+                // evaluate only once as the left hand side may have side 
effects
+                Object value = leftExp.evaluate(exchange, Object.class);
+                Number num = value instanceof Number n
+                        ? n : 
camelContext.getTypeConverter().convertTo(Number.class, exchange, value);
                 if (num != null) {
-                    long val = num.longValue();
-                    val++;
-
-                    // convert value back to same type as input as we want to 
preserve type
-                    Object left = leftExp.evaluate(exchange, Object.class);
-                    try {
-                        left = 
camelContext.getTypeConverter().mandatoryConvertTo(left.getClass(), exchange, 
val);
-                    } catch (NoTypeConversionAvailableException e) {
-                        throw 
RuntimeCamelException.wrapRuntimeCamelException(e);
+                    // keep decimals such as 1.5++ is 2.5
+                    Number result;
+                    if (num instanceof BigDecimal bd) {
+                        result = bd.add(BigDecimal.valueOf(delta));
+                    } else if (num instanceof BigInteger bi) {
+                        result = bi.add(BigInteger.valueOf(delta));
+                    } else if (num instanceof Double || num instanceof Float) {
+                        result = num.doubleValue() + delta;
+                    } else {
+                        result = num.longValue() + delta;
                     }
 
-                    // and return the result
-                    return camelContext.getTypeConverter().convertTo(type, 
left);
-                }
-                // cannot convert the expression as a number
-                Exception cause = new CamelExchangeException("Cannot evaluate 
" + leftExp + " as a number", exchange);
-                throw RuntimeCamelException.wrapRuntimeCamelException(cause);
-            }
-
-            @Override
-            public String toString() {
-                return left + operator.toString();
-            }
-        };
-    }
-
-    private Expression createDecExpression(CamelContext camelContext, final 
Expression leftExp) {
-        return new Expression() {
-            @Override
-            public <T> T evaluate(Exchange exchange, Class<T> type) {
-                Number num = leftExp.evaluate(exchange, Number.class);
-                if (num != null) {
-                    long val = num.longValue();
-                    val--;
-
                     // convert value back to same type as input as we want to 
preserve type
-                    Object left = leftExp.evaluate(exchange, Object.class);
+                    Object answer;
                     try {
-                        left = 
camelContext.getTypeConverter().mandatoryConvertTo(left.getClass(), exchange, 
val);
+                        answer = 
camelContext.getTypeConverter().mandatoryConvertTo(value.getClass(), exchange, 
result);
                     } catch (NoTypeConversionAvailableException e) {
                         throw 
RuntimeCamelException.wrapRuntimeCamelException(e);
                     }
 
                     // and return the result
-                    return camelContext.getTypeConverter().convertTo(type, 
left);
+                    return camelContext.getTypeConverter().convertTo(type, 
answer);
                 }
                 // cannot convert the expression as a number
                 Exception cause = new CamelExchangeException("Cannot evaluate 
" + leftExp + " as a number", exchange);
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 a20eb732c9a2..4e1d666c33d8 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
@@ -16,7 +16,10 @@
  */
 package org.apache.camel.language.simple;
 
+import java.math.BigDecimal;
+
 import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
 import org.apache.camel.LanguageTestSupport;
 import org.apache.camel.Predicate;
 import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
@@ -1092,11 +1095,80 @@ public class SimpleOperatorTest extends 
LanguageTestSupport {
         assertPredicate("${header.Account1} > 7", true);
     }
 
+    @Test
+    public void testNumericLiteralAsOtherNumberType() {
+        // CAMEL-24966: a numeric literal evaluated as another number type is 
converted, not cast
+        exchange.getIn().setBody(5);
+        Expression exp = 
context.resolveLanguage("simple").createExpression("${body > 0 ? 1 : 0}");
+        assertEquals(1L, exp.evaluate(exchange, Long.class));
+        assertEquals(1, exp.evaluate(exchange, int.class));
+        assertEquals(1.0d, exp.evaluate(exchange, Double.class));
+        assertEquals("1", exp.evaluate(exchange, String.class));
+    }
+
+    @Test
+    public void testNegativeLongLiteral() {
+        exchange.getIn().setHeader("x", 5);
+        assertPredicate("${header.x} > -3000000000", true);
+        assertPredicate("${header.x} < -3000000000", false);
+        assertPredicate("${header.x} > -2147483648", true);
+    }
+
+    @Test
+    public void testElvisAnyZero() {
+        exchange.getIn().setHeader("count", 0L);
+        assertExpression("${header.count} ?: 'none'", "none");
+        exchange.getIn().setHeader("count", 0.0d);
+        assertExpression("${header.count} ?: 'none'", "none");
+        exchange.getIn().setHeader("count", BigDecimal.ZERO);
+        assertExpression("${header.count} ?: 'none'", "none");
+        exchange.getIn().setHeader("count", 3L);
+        assertExpression("${header.count} ?: 'none'", 3L);
+    }
+
+    @Test
+    public void testElvisEvaluatesLeftOnce() {
+        MyCounter counter = new MyCounter();
+        context.getRegistry().bind("counter", counter);
+        assertExpression("${bean:counter?method=next} ?: 'none'", 1);
+        assertEquals(1, counter.count);
+    }
+
+    @Test
+    public void testUnaryIncDecKeepsDecimals() {
+        exchange.getIn().setHeader("price", 1.5d);
+        assertExpression("${header.price}++", 2.5d);
+        assertExpression("${header.price}--", 0.5d);
+        exchange.getIn().setHeader("price", new BigDecimal("1.25"));
+        assertExpression("${header.price}++", new BigDecimal("2.25"));
+        exchange.getIn().setHeader("price", "1.5");
+        assertExpression("${header.price}++", "2.5");
+        exchange.getIn().setHeader("price", 7);
+        assertExpression("${header.price}--", 6);
+    }
+
+    @Test
+    public void testRegexAndRangeWithNullRightHandSide() {
+        exchange.getIn().setBody(5);
+        assertPredicate("${body} range ${header.none}", false);
+        assertPredicate("${body} !range ${header.none}", true);
+        assertPredicate("${body} regex ${header.none}", false);
+        assertPredicate("${body} !regex ${header.none}", true);
+    }
+
     @Override
     protected String getLanguageName() {
         return "simple";
     }
 
+    public static class MyCounter {
+        private int count;
+
+        public int next() {
+            return ++count;
+        }
+    }
+
     public static class MyFileNameGenerator {
         public String generateFilename(Exchange exchange) {
             return "abc";

Reply via email to