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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 1c94c6ef20aa CAMEL-25954: camel-core-languages - Simple: a predicate 
inside ${ } is split at an operator outside brackets and parentheses only 
(#28271)
1c94c6ef20aa is described below

commit 1c94c6ef20aa100fef0eb017de5d37e6e521e8d4
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 25 12:58:06 2026 +0100

    CAMEL-25954: camel-core-languages - Simple: a predicate inside ${ } is 
split at an operator outside brackets and parentheses only (#28271)
    
    SimpleSyntaxHints.wrapFunctions split a predicate written inside ${ }, and 
the condition of a ternary there, at its first operator outside quotes, also 
when the operator was in an index or an argument, such as ${body.?[qty < 
1].size() > 0 ? 'a' : 'b'} or ${iif(header.a > 0 && header.b > 0, 'x', 'y')}. 
The error then quoted text that was never written. The comparison operators and 
&& and || now count only outside quotes, brackets and parentheses.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../camel/language/simple/SimpleSyntaxHints.java   | 25 ++++++++++++++++----
 .../language/simple/SimpleSyntaxHintsTest.java     | 27 ++++++++++++++++++++++
 2 files changed, 47 insertions(+), 5 deletions(-)

diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java
index 1e8a344a38fc..cfed1a9a57ef 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java
@@ -194,8 +194,9 @@ public final class SimpleSyntaxHints {
      * {@code body != null && body.size() > 0} becomes {@code ${body} != null 
&& ${body.size()} > 0}.
      * <p/>
      * Each comparison is wrapped, not only the first, so that a compound 
condition reads the way it looks (CAMEL-24920,
-     * CAMEL-24921). An operator counts only when whitespace surrounds it 
outside quotes, which is what keeps
-     * {@code ${header.Content-Length}} and {@code ${date:now:yyyy-MM-dd}} a 
plain function.
+     * CAMEL-24921). An operator counts only when whitespace surrounds it 
outside quotes, brackets and parentheses,
+     * which is what keeps {@code ${header.Content-Length}} and {@code 
${date:now:yyyy-MM-dd}} a plain function, and an
+     * operator in an index or an argument, as in {@code ${iif(header.a > 0, 
'x', 'y')}}, where it is.
      */
     public static String wrapFunctions(String text) {
         StringBuilder answer = new StringBuilder();
@@ -218,13 +219,18 @@ public final class SimpleSyntaxHints {
     private static int logicalOperator(String text, int from) {
         boolean single = false;
         boolean dubble = false;
+        int depth = 0;
         for (int i = from; i < text.length(); i++) {
             char c = text.charAt(i);
             if (c == '\'' && !dubble) {
                 single = !single;
             } else if (c == '"' && !single) {
                 dubble = !dubble;
-            } else if (!single && !dubble && c == ' ') {
+            } else if (!single && !dubble && (c == '(' || c == '[')) {
+                depth++;
+            } else if (!single && !dubble && (c == ')' || c == ']')) {
+                depth--;
+            } else if (!single && !dubble && depth == 0 && c == ' ') {
                 for (String op : new String[] { "&& ", "|| " }) {
                     if (text.startsWith(op, i + 1)) {
                         return i + 1;
@@ -278,17 +284,26 @@ public final class SimpleSyntaxHints {
         return left + op + right;
     }
 
-    /** The index of the text outside single and double quotes, or -1. */
+    /**
+     * The index of the text outside single and double quotes, brackets and 
parentheses, or -1: an operator inside them
+     * belongs to an index or an argument, as in {@code body.?[qty < 1].size() 
> 0}, which was split at the {@code <}
+     * and reported as {@code ${body.?[qty} < 1].size() > 0}, text that was 
never written.
+     */
     private static int indexOutsideQuotes(String text, String find) {
         boolean single = false;
         boolean dubble = false;
+        int depth = 0;
         for (int i = 0; i < text.length(); i++) {
             char c = text.charAt(i);
             if (c == '\'' && !dubble) {
                 single = !single;
             } else if (c == '"' && !single) {
                 dubble = !dubble;
-            } else if (!single && !dubble && text.startsWith(find, i)) {
+            } else if (!single && !dubble && (c == '(' || c == '[')) {
+                depth++;
+            } else if (!single && !dubble && (c == ')' || c == ']')) {
+                depth--;
+            } else if (!single && !dubble && depth == 0 && 
text.startsWith(find, i)) {
                 return i;
             }
         }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
index 9e9946b61c4e..c0f5ffa9d3a6 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
@@ -20,9 +20,11 @@ import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.ExchangeTestSupport;
+import org.apache.camel.Expression;
 import org.apache.camel.component.file.GenericFile;
 import org.apache.camel.converter.stream.ByteArrayInputStreamCache;
 import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
@@ -166,6 +168,31 @@ public class SimpleSyntaxHintsTest extends 
ExchangeTestSupport {
         assertEquals(false, 
context.resolveLanguage("simple").createPredicate("${body.length() > 
3}").matches(exchange));
     }
 
+    /**
+     * An operator inside brackets or parentheses belongs to an index or an 
argument: a predicate inside ${ } is split
+     * at an operator outside them only, so the error quotes the text as 
written (a local model wrote ${body.?[qty <
+     * 1].size() > 0 ? ...} and was told about ${body.?[qty} < 1], text it 
never wrote).
+     */
+    @Test
+    public void testAnOperatorInsideBracketsOrParenthesesIsNotSplit() {
+        assertEquals("${body.?[qty < 1].size()} > 0", 
SimpleSyntaxHints.wrapFunctions("body.?[qty < 1].size() > 0"));
+        assertEquals("iif(header.a > 0 && header.b > 0, 'x', 'y')",
+                SimpleSyntaxHints.wrapFunctions("iif(header.a > 0 && header.b 
> 0, 'x', 'y')"));
+        assertEquals("${header.x} > 0 && ${body.size()} > 1",
+                SimpleSyntaxHints.wrapFunctions("header.x > 0 && body.size() > 
1"));
+        assertEquals("${header.list[0]} == 'a'", 
SimpleSyntaxHints.wrapFunctions("header.list[0] == 'a'"));
+
+        // the expression parses as written now; what the index means is the 
runtime's to say, about the text as written
+        Expression expression = context.resolveLanguage("simple")
+                .createExpression("${body.?[qty < 1].size() > 0 ? 'not enough' 
: 'reserved'}");
+        exchange.getIn().setBody(List.of(Map.of("qty", 0)));
+        try {
+            expression.evaluate(exchange, String.class);
+        } catch (Exception e) {
+            assertThat(e.getMessage()).doesNotContain("[qty}");
+        }
+    }
+
     @Test
     public void testOgnlRuntimeMessages() {
         exchange.getIn().setBody("hello");

Reply via email to