You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/09/24 06:22:50 UTC

(camel) 01/01: CAMEL-24983: simple - say what to write when ! is used to negate a function

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 <cl...@gmail.com>
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) <no...@anthropic.com>
    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");