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

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

commit defc344dc6fec7987b20e5752634d273bcdc5045
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Sep 24 08:17:17 2026 +0200

    CAMEL-24983: simple - say what to write when ! is used to negate a function
    
    Simple has no boolean ! prefix, and a negated function failed with a
    message that pointed at the operator instead of at the !:
    
      ${body != null && !${body.isEmpty()}}
      -> Logical operator && needs a predicate on the right hand side,
         e.g. ${header.foo} == 'bar'; was: !${body.isEmpty()}
    
    The nesting is not the problem - ${body != null && ${body.isEmpty()}}
    evaluates fine - and the plain ${body != null && !body.isEmpty()} fails
    the same way. The message now names the ! and the comparison to write:
    
      ! does not negate a function: compare it instead, so !${body.isEmpty()}
      is written as ${body.isEmpty()} == false, or negate the operator
      (!=, !contains)
    
    A negated operator is left alone, so ${body} !contains 'x' is unaffected.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
---
 .../camel/language/simple/SimpleSyntaxHints.java   | 22 +++++++++++++++++
 .../language/simple/SimpleSyntaxHintsTest.java     | 28 ++++++++++++++++++++++
 2 files changed, 50 insertions(+)

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 9b80f36a0e8b..ffd5f4710811 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
@@ -103,6 +103,23 @@ public final class SimpleSyntaxHints {
         return expression.substring(start, end);
     }
 
+    /**
+     * The comparison that a negated function is written as, or null when the 
text is not one: simple has no boolean
+     * {@code !} prefix, so {@code !${body.isEmpty()}} is {@code 
${body.isEmpty()} == false}. Models and people write
+     * the Java form, and the parser otherwise reports it as a missing 
predicate next to the operator.
+     */
+    private static String negatedFunction(String word) {
+        if (word.length() < 2 || word.charAt(0) != '!') {
+            return null;
+        }
+        String rest = word.substring(1);
+        // !=, !contains and the other negated operators are words of their 
own, not a negated function
+        if (rest.isEmpty() || rest.charAt(0) == '=' || 
Character.isLetter(rest.charAt(0)) && !rest.contains("(")) {
+            return null;
+        }
+        return rest + " == false";
+    }
+
     /** The message for a token the grammar does not know at the given index. 
*/
     public static String unexpectedToken(String expression, int index) {
         String word = wordAt(expression, index);
@@ -145,6 +162,11 @@ public final class SimpleSyntaxHints {
     /** The message when an operator has no usable value next to it. */
     public static String unsupportedOperand(String kind, Object operator, 
String expression, int index) {
         String word = wordAt(expression, index);
+        String compared = negatedFunction(word);
+        if (compared != null) {
+            return "! does not negate a function: compare it instead, so " + 
word + " is written as " + compared
+                   + ", or negate the operator (!=, !contains)";
+        }
         if ("Logical".equals(kind)) {
             return kind + " operator " + operator + " needs a predicate on the 
right hand side, e.g. ${header.foo} == 'bar'"
                    + (word.isEmpty() ? "" : "; was: " + word);
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 55de40810c70..ea81f6c87e83 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
@@ -17,6 +17,7 @@
 package org.apache.camel.language.simple;
 
 import org.apache.camel.ExchangeTestSupport;
+import org.apache.camel.Expression;
 import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
 import org.junit.jupiter.api.Test;
 
@@ -35,6 +36,18 @@ public class SimpleSyntaxHintsTest extends 
ExchangeTestSupport {
         return assertThrows(SimpleIllegalSyntaxException.class, 
parser::parsePredicate).getMessage();
     }
 
+    /**
+     * The message of a function that only fails when the node builds its 
expression, such as one with operators inside
+     * the {@code ${ }} (CAMEL-24921), which the parsers above accept as a 
single function token.
+     */
+    private String functionError(String text) {
+        return assertThrows(SimpleIllegalSyntaxException.class, () -> {
+            Expression exp = 
context.resolveLanguage("simple").createExpression(text);
+            exp.init(context);
+            exp.evaluate(exchange, Object.class);
+        }).getMessage();
+    }
+
     private String expressionError(String text) {
         SimpleExpressionParser parser = new SimpleExpressionParser(context, 
text, true, null);
         return assertThrows(SimpleIllegalSyntaxException.class, 
parser::parseExpression).getMessage();
@@ -77,6 +90,21 @@ public class SimpleSyntaxHintsTest extends 
ExchangeTestSupport {
         assertThat(predicateError("${body} == 'x' || ")).contains("needs a 
predicate on the right hand side");
     }
 
+    @Test
+    public void testNegatedFunction() {
+        // simple has no boolean ! prefix; both the nested and the plain form 
fail the same way, and the message
+        // says the comparison to write instead (the shapes a local model 
wrote in the benchmark)
+        assertThat(functionError("${body != null && !${body.isEmpty()}}"))
+                .contains("! does not negate a function")
+                .contains("${body.isEmpty()} == false")
+                .doesNotContain("needs a predicate on the right hand side");
+        assertThat(functionError("${body != null && !body.isEmpty()}"))
+                .contains("! does not negate a function")
+                .contains("body.isEmpty() == false");
+        // a negated operator is not a negated function
+        assertThat(predicateError("${body} !contains")).doesNotContain("does 
not negate a function");
+    }
+
     @Test
     public void testQuotes() {
         assertThat(predicateError("${body} == 'it''s'")).contains("double 
quotes");

Reply via email to