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

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

commit d082d6aa08ae0578fb1464819dc29bbafe837116
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Jan 18 13:13:05 2026 +0100

    CAMEL-22870: camel-core - Add elvis ?: operator to simple language
---
 .../csimple/joor/OriginalSimpleOperatorTest.java   |  33 +++++
 .../modules/languages/pages/simple-language.adoc   |  16 +++
 .../camel/language/csimple/CSimpleHelper.java      |   8 ++
 .../camel/language/simple/BaseSimpleParser.java    |  65 +++++++++
 .../language/simple/SimpleExpressionParser.java    | 119 ++++++++++++++++-
 .../language/simple/SimplePredicateParser.java     |  38 ++++++
 .../camel/language/simple/SimpleTokenizer.java     |  29 ++++-
 .../language/simple/ast/LiteralExpression.java     |   5 +
 .../camel/language/simple/ast/OtherExpression.java | 145 +++++++++++++++++++++
 .../{TokenType.java => OtherOperatorType.java}     |  39 +++---
 .../language/simple/types/SimpleTokenType.java     |   7 +
 .../camel/language/simple/types/TokenType.java     |   1 +
 .../camel/language/simple/SimpleOperatorTest.java  |  33 +++++
 .../simple/SimplePredicateParserNodesTest.java     |  22 ++++
 14 files changed, 533 insertions(+), 27 deletions(-)

diff --git 
a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleOperatorTest.java
 
b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleOperatorTest.java
index 9e11a8ed20fd..bc0e81d3efd0 100644
--- 
a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleOperatorTest.java
+++ 
b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleOperatorTest.java
@@ -34,6 +34,7 @@ public class OriginalSimpleOperatorTest extends 
LanguageTestSupport {
     public void testValueWithSpace() {
         exchange.getIn().setBody("Hello Big World");
         assertPredicate("${in.body} == 'Hello Big World'", true);
+        assertPredicate("${in.body} == ${body}", true);
     }
 
     @Test
@@ -805,6 +806,38 @@ public class OriginalSimpleOperatorTest extends 
LanguageTestSupport {
         assertPredicate("${in.body} !endsWith 'Hi'", true);
     }
 
+    @Test
+    public void testElvis() {
+        exchange.getIn().setBody(false);
+        assertPredicate("${body} ?: 'true'", true);
+        assertPredicate("${body} ?: 'false'", false);
+        exchange.getIn().setBody("Hello");
+        assertPredicate("${body} ?: 'false'", true);
+        exchange.getIn().setBody(0);
+        assertPredicate("${body} ?: 'true'", true);
+        assertPredicate("${body} ?: 'false'", false);
+        exchange.getIn().setBody(1);
+        assertPredicate("${body} ?: 'true'", true);
+        assertPredicate("${body} ?: 'false'", true);
+
+        exchange.getIn().setBody(null);
+        assertExpression("${body} ?: 'World'", "World");
+        exchange.getIn().setBody("");
+        assertExpression("${body} ?: 'World'", "World");
+        exchange.getIn().setBody("Hello");
+        assertExpression("${body} ?: 'World'", "Hello");
+        exchange.getIn().setBody(false);
+        assertExpression("${body} ?: 'World'", "World");
+        exchange.getIn().setBody(true);
+        assertExpression("${body} ?: 'World'", true);
+        exchange.getIn().setHeader("myHeader", "Camel");
+        assertExpression("${header.myHeader} ?: 'World'", "Camel");
+        exchange.getIn().setBody(0);
+        assertExpression("${body} ?: 'World'", "World");
+        exchange.getIn().setBody(1);
+        assertExpression("${body} ?: 'World'", 1);
+    }
+
     @Override
     protected String getLanguageName() {
         return "csimple";
diff --git 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
index 85767faa8282..c50f5303b6a9 100644
--- 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
+++ 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
@@ -336,6 +336,22 @@ And the following unary operators can be used:
 |`--` | To decrement a number by one. The left-hand side must be a function, 
otherwise parsed as literal.
 |====
 
+And the following other operators can be used:
+
+[width="100%",cols="50%,50%",options="header",]
+|====
+|Operator |Description
+|`?:` | The elvis operator returns the left-hand side if it has an effective 
Boolean value of true, otherwise it returns the right-hand side. This is useful 
for providing fallback values when an expression may evaluate to a value with 
an effective Boolean value of false (such as `null`, `false`, `0`, empty/blank 
string).
+|====
+
+For example the following elvis operator will return the username header 
unless its null or empty, which
+then the default value of `Guest` is returned.
+
+[source,java]
+----
+simple("${header.username} ?: 'Guest'");
+----
+
 And the following special symbols:
 
 [width="100%",cols="50%,50%",options="header",]
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java
index efb47b7c3cd1..2d5fe8a2178d 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java
@@ -979,4 +979,12 @@ public final class CSimpleHelper {
         return 0;
     }
 
+    public static Object elvis(Exchange exchange, Object left, Object right) {
+        if (left == null || Boolean.FALSE == left || 
ObjectHelper.isEmpty(left) || ObjectHelper.equal(0, left)) {
+            return right;
+        } else {
+            return left;
+        }
+    }
+
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
index 863714d5eb45..359f7a979ccb 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
@@ -23,9 +23,11 @@ import java.util.Deque;
 import java.util.List;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.Predicate;
 import org.apache.camel.language.simple.ast.Block;
 import org.apache.camel.language.simple.ast.BlockEnd;
 import org.apache.camel.language.simple.ast.BlockStart;
+import org.apache.camel.language.simple.ast.OtherExpression;
 import org.apache.camel.language.simple.ast.SimpleNode;
 import org.apache.camel.language.simple.ast.UnaryExpression;
 import org.apache.camel.language.simple.types.SimpleParserException;
@@ -205,6 +207,69 @@ public abstract class BaseSimpleParser {
         Collections.reverse(nodes);
     }
 
+    /**
+     * Prepares other expressions.
+     * <p/>
+     * This process prepares the other expressions in the AST. This is done by 
linking the other operator with both the
+     * right and left hand side nodes, to have the AST graph updated and 
prepared properly.
+     * <p/>
+     * So when the AST node is later used to create the {@link Predicate}s to 
be used by Camel then the AST graph has a
+     * linked and prepared graph of nodes which represent the input expression.
+     */
+    protected void prepareOtherExpressions() {
+        Deque<SimpleNode> stack = new ArrayDeque<>();
+
+        SimpleNode left = null;
+        for (int i = 0; i < nodes.size(); i++) {
+            if (left == null) {
+                left = i > 0 ? nodes.get(i - 1) : null;
+            }
+            SimpleNode token = nodes.get(i);
+            SimpleNode right = i < nodes.size() - 1 ? nodes.get(i + 1) : null;
+
+            if (token instanceof OtherExpression other) {
+                // remember the other operator
+                String operator = other.getOperator().toString();
+
+                if (left == null) {
+                    throw new SimpleParserException(
+                            "Other operator " + operator + " has no left hand 
side token", token.getToken().getIndex());
+                }
+                if (!other.acceptLeftNode(left)) {
+                    throw new SimpleParserException(
+                            "Other operator " + operator + " does not support 
left hand side token " + left.getToken(),
+                            token.getToken().getIndex());
+                }
+                if (right == null) {
+                    throw new SimpleParserException(
+                            "Other operator " + operator + " has no right hand 
side token", token.getToken().getIndex());
+                }
+                if (!other.acceptRightNode(right)) {
+                    throw new SimpleParserException(
+                            "Other operator " + operator + " does not support 
right hand side token " + right.getToken(),
+                            token.getToken().getIndex());
+                }
+
+                // pop previous as we need to replace it with this other 
operator
+                stack.pop();
+                stack.push(token);
+                // advantage after the right hand side
+                i++;
+                // this token is now the left for the next loop
+                left = token;
+            } else {
+                // clear left
+                left = null;
+                stack.push(token);
+            }
+        }
+
+        nodes.clear();
+        nodes.addAll(stack);
+        // must reverse as it was added from a stack that is reverse
+        Collections.reverse(nodes);
+    }
+
     // --------------------------------------------------------------
     // grammar
     // --------------------------------------------------------------
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
index 33efc4c65c99..7ef8b8271dc7 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
@@ -25,10 +25,12 @@ import org.apache.camel.CamelContext;
 import org.apache.camel.Expression;
 import org.apache.camel.language.simple.ast.LiteralExpression;
 import org.apache.camel.language.simple.ast.LiteralNode;
+import org.apache.camel.language.simple.ast.OtherExpression;
 import org.apache.camel.language.simple.ast.SimpleFunctionEnd;
 import org.apache.camel.language.simple.ast.SimpleFunctionStart;
 import org.apache.camel.language.simple.ast.SimpleNode;
 import org.apache.camel.language.simple.ast.UnaryExpression;
+import org.apache.camel.language.simple.types.OtherOperatorType;
 import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
 import org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.language.simple.types.SimpleToken;
@@ -97,10 +99,11 @@ public class SimpleExpressionParser extends 
BaseSimpleParser {
         // parse the expression using the following grammar
         nextToken();
         while (!token.getType().isEol()) {
-            // an expression supports just template (eg text), functions, or 
unary operator
+            // an expression supports just template (eg text), functions, 
unary, or other operator
             templateText();
             functionText();
             unaryOperator();
+            otherOperator();
             nextToken();
         }
 
@@ -108,12 +111,16 @@ public class SimpleExpressionParser extends 
BaseSimpleParser {
         // into an ast, and then from the ast, to Camel expression(s).
         // hence why there are a number of tasks going on below to accomplish 
this
 
+        // remove any ignorable white space tokens
+        removeIgnorableWhiteSpaceTokens();
         // turn the tokens into the ast model
         parseAndCreateAstModel();
         // compact and stack blocks (eg function start/end)
         prepareBlocks();
         // compact and stack unary operators
         prepareUnaryExpressions();
+        // compact and stack other expressions
+        prepareOtherExpressions();
 
         return nodes;
     }
@@ -135,6 +142,34 @@ public class SimpleExpressionParser extends 
BaseSimpleParser {
         }
     }
 
+    /**
+     * Removes any ignorable whitespace tokens before and after other 
operators.
+     * <p/>
+     * During the initial parsing (input -> tokens), then there may be 
excessive whitespace tokens, which can safely be
+     * removed, which makes the succeeding parsing easier.
+     */
+    private void removeIgnorableWhiteSpaceTokens() {
+        // white space should be removed before and after the other operator
+        List<SimpleToken> toRemove = new ArrayList<>();
+        for (int i = 1; i < tokens.size() - 1; i++) {
+            SimpleToken prev = tokens.get(i - 1);
+            SimpleToken cur = tokens.get(i);
+            SimpleToken next = tokens.get(i + 1);
+            if (cur.getType().isOther()) {
+                if (prev.getType().isWhitespace()) {
+                    toRemove.add(prev);
+                }
+                if (next.getType().isWhitespace()) {
+                    toRemove.add(next);
+                }
+            }
+        }
+
+        if (!toRemove.isEmpty()) {
+            tokens.removeAll(toRemove);
+        }
+    }
+
     protected void parseAndCreateAstModel() {
         // we loop the tokens and create a sequence of ast nodes
 
@@ -187,10 +222,12 @@ public class SimpleExpressionParser extends 
BaseSimpleParser {
             functions.decrementAndGet();
             return new SimpleFunctionEnd(token);
         } else if (token.getType().isUnary()) {
-            // there must be a end function as previous, to let this be a 
unary function
+            // there must be an end function as previous, to let this be a 
unary function
             if (!nodes.isEmpty() && nodes.get(nodes.size() - 1) instanceof 
SimpleFunctionEnd) {
                 return new UnaryExpression(token);
             }
+        } else if (token.getType().isOther()) {
+            return new OtherExpression(token);
         }
 
         // by returning null, we will let the parser determine what to do
@@ -268,10 +305,12 @@ public class SimpleExpressionParser extends 
BaseSimpleParser {
     // - template = literal texts with can contain embedded functions
     // - function = simple functions such as ${body} etc.
     // - unary operator = operator attached to the left-hand side node
+    // - other operator = operator attached to both the left and right hand 
side nodes
 
     protected void templateText() {
-        // for template, we accept anything but functions
-        while (!token.getType().isFunctionStart() && 
!token.getType().isFunctionEnd() && !token.getType().isEol()) {
+        // for template, we accept anything but functions / other operator
+        while (!token.getType().isFunctionStart() && 
!token.getType().isFunctionEnd() && !token.getType().isEol()
+                && !token.getType().isOther()) {
             nextToken();
         }
     }
@@ -296,6 +335,36 @@ public class SimpleExpressionParser extends 
BaseSimpleParser {
         return false;
     }
 
+    protected boolean otherOperator() {
+        if (accept(TokenType.otherOperator)) {
+            // remember the other operator
+            OtherOperatorType operatorType = 
OtherOperatorType.asOperator(token.getText());
+
+            nextToken();
+            // there should be at least one whitespace after the operator
+            expectAndAcceptMore(TokenType.whiteSpace);
+
+            // then we expect either some quoted text, another function, or a 
numeric, boolean or null value
+            if (singleQuotedLiteralWithFunctionsText()
+                    || doubleQuotedLiteralWithFunctionsText()
+                    || functionText()
+                    || numericValue()
+                    || booleanValue()
+                    || nullValue()) {
+                // then after the right hand side value, there should be a 
whitespace if there is more tokens
+                nextToken();
+                if (!token.getType().isEol()) {
+                    expect(TokenType.whiteSpace);
+                }
+            } else {
+                throw new SimpleParserException(
+                        "Other operator " + operatorType + " does not support 
token " + token, token.getIndex());
+            }
+            return true;
+        }
+        return false;
+    }
+
     protected boolean unaryOperator() {
         if (accept(TokenType.unaryOperator)) {
             nextToken();
@@ -305,4 +374,46 @@ public class SimpleExpressionParser extends 
BaseSimpleParser {
         }
         return false;
     }
+
+    protected boolean singleQuotedLiteralWithFunctionsText() {
+        if (accept(TokenType.singleQuote)) {
+            nextToken(TokenType.singleQuote, TokenType.eol, 
TokenType.functionStart, TokenType.functionEnd);
+            while (!token.getType().isSingleQuote() && 
!token.getType().isEol()) {
+                // we need to loop until we find the ending single quote, or 
the eol
+                nextToken(TokenType.singleQuote, TokenType.eol, 
TokenType.functionStart, TokenType.functionEnd);
+            }
+            expect(TokenType.singleQuote);
+            return true;
+        }
+        return false;
+    }
+
+    protected boolean doubleQuotedLiteralWithFunctionsText() {
+        if (accept(TokenType.doubleQuote)) {
+            nextToken(TokenType.doubleQuote, TokenType.eol, 
TokenType.functionStart, TokenType.functionEnd);
+            while (!token.getType().isDoubleQuote() && 
!token.getType().isEol()) {
+                // we need to loop until we find the ending double quote, or 
the eol
+                nextToken(TokenType.doubleQuote, TokenType.eol, 
TokenType.functionStart, TokenType.functionEnd);
+            }
+            expect(TokenType.doubleQuote);
+            return true;
+        }
+        return false;
+    }
+
+    protected boolean numericValue() {
+        return accept(TokenType.numericValue);
+        // no other tokens to check so do not use nextToken
+    }
+
+    protected boolean booleanValue() {
+        return accept(TokenType.booleanValue);
+        // no other tokens to check so do not use nextToken
+    }
+
+    protected boolean nullValue() {
+        return accept(TokenType.nullValue);
+        // no other tokens to check so do not use nextToken
+    }
+
 }
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 c4204c01cf94..a0f674adaa26 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
@@ -37,6 +37,7 @@ import org.apache.camel.language.simple.ast.LiteralNode;
 import org.apache.camel.language.simple.ast.LogicalExpression;
 import org.apache.camel.language.simple.ast.NullExpression;
 import org.apache.camel.language.simple.ast.NumericExpression;
+import org.apache.camel.language.simple.ast.OtherExpression;
 import org.apache.camel.language.simple.ast.SimpleFunctionEnd;
 import org.apache.camel.language.simple.ast.SimpleFunctionStart;
 import org.apache.camel.language.simple.ast.SimpleNode;
@@ -45,6 +46,7 @@ import org.apache.camel.language.simple.ast.SingleQuoteStart;
 import org.apache.camel.language.simple.ast.UnaryExpression;
 import org.apache.camel.language.simple.types.BinaryOperatorType;
 import org.apache.camel.language.simple.types.LogicalOperatorType;
+import org.apache.camel.language.simple.types.OtherOperatorType;
 import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
 import org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.language.simple.types.SimpleToken;
@@ -120,6 +122,7 @@ public class SimplePredicateParser extends BaseSimpleParser 
{
                     && !functionText()
                     && !unaryOperator()
                     && !binaryOperator()
+                    && !otherOperator()
                     && !logicalOperator()
                     && !isBooleanValue()
                     && !token.getType().isWhitespace()
@@ -146,6 +149,8 @@ public class SimplePredicateParser extends BaseSimpleParser 
{
         prepareUnaryExpressions();
         // compact and stack binary expressions
         prepareBinaryExpressions();
+        // compact and stack other expressions
+        prepareOtherExpressions();
         // compact and stack logical expressions
         prepareLogicalExpressions();
 
@@ -335,6 +340,8 @@ public class SimplePredicateParser extends BaseSimpleParser 
{
             return new UnaryExpression(token);
         } else if (token.getType().isBinary()) {
             return new BinaryExpression(token);
+        } else if (token.getType().isOther()) {
+            return new OtherExpression(token);
         } else if (token.getType().isLogical()) {
             return new LogicalExpression(token);
         } else if (token.getType().isNullValue()) {
@@ -560,6 +567,7 @@ public class SimplePredicateParser extends BaseSimpleParser 
{
     // - null = null value
     // - unary operator = operator attached to the left hand side node
     // - binary operator = operator attached to both the left and right hand 
side nodes
+    // - other operator = operator attached to both the left and right hand 
side nodes
     // - logical operator = operator attached to both the left and right hand 
side nodes
 
     protected boolean isBooleanValue() {
@@ -724,6 +732,36 @@ public class SimplePredicateParser extends 
BaseSimpleParser {
         return false;
     }
 
+    protected boolean otherOperator() {
+        if (accept(TokenType.otherOperator)) {
+            // remember the other operator
+            OtherOperatorType operatorType = 
OtherOperatorType.asOperator(token.getText());
+
+            nextToken();
+            // there should be at least one whitespace after the operator
+            expectAndAcceptMore(TokenType.whiteSpace);
+
+            // then we expect either some quoted text, another function, or a 
numeric, boolean or null value
+            if (singleQuotedLiteralWithFunctionsText()
+                    || doubleQuotedLiteralWithFunctionsText()
+                    || functionText()
+                    || numericValue()
+                    || booleanValue()
+                    || nullValue()) {
+                // then after the right hand side value, there should be a 
whitespace if there is more tokens
+                nextToken();
+                if (!token.getType().isEol()) {
+                    expect(TokenType.whiteSpace);
+                }
+            } else {
+                throw new SimpleParserException(
+                        "Other operator " + operatorType + " does not support 
token " + token, token.getIndex());
+            }
+            return true;
+        }
+        return false;
+    }
+
     protected boolean logicalOperator() {
         if (accept(TokenType.logicalOperator)) {
             // remember the logical operator
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
index bbaf198ab832..0258ce18b8ff 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
@@ -27,7 +27,7 @@ import org.apache.camel.util.ObjectHelper;
 public final class SimpleTokenizer {
 
     // keep this number in sync with tokens list
-    private static final int NUMBER_OF_TOKENS = 49;
+    private static final int NUMBER_OF_TOKENS = 50;
 
     private static final SimpleTokenType[] KNOWN_TOKENS = new 
SimpleTokenType[NUMBER_OF_TOKENS];
 
@@ -85,18 +85,21 @@ public final class SimpleTokenizer {
         KNOWN_TOKENS[42] = new SimpleTokenType(TokenType.binaryOperator, "ends 
with");
         KNOWN_TOKENS[43] = new SimpleTokenType(TokenType.binaryOperator, 
"!endsWith");
 
+        // other operators
+        KNOWN_TOKENS[44] = new SimpleTokenType(TokenType.otherOperator, "?:");
+
         // unary operators
-        KNOWN_TOKENS[44] = new SimpleTokenType(TokenType.unaryOperator, "++");
-        KNOWN_TOKENS[45] = new SimpleTokenType(TokenType.unaryOperator, "--");
+        KNOWN_TOKENS[45] = new SimpleTokenType(TokenType.unaryOperator, "++");
+        KNOWN_TOKENS[46] = new SimpleTokenType(TokenType.unaryOperator, "--");
 
         // logical operators
-        KNOWN_TOKENS[46] = new SimpleTokenType(TokenType.logicalOperator, 
"&&");
-        KNOWN_TOKENS[47] = new SimpleTokenType(TokenType.logicalOperator, 
"||");
+        KNOWN_TOKENS[47] = new SimpleTokenType(TokenType.logicalOperator, 
"&&");
+        KNOWN_TOKENS[48] = new SimpleTokenType(TokenType.logicalOperator, 
"||");
 
         //binary operator
         // it is added as the last item because unary -- has the priority
         // if unary not found it is highly possible - operator is run into.
-        KNOWN_TOKENS[48] = new SimpleTokenType(TokenType.minusValue, "-");
+        KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.minusValue, "-");
     }
 
     private SimpleTokenizer() {
@@ -261,6 +264,9 @@ public final class SimpleTokenizer {
         if (token.isBinary()) {
             return evalBinary(token, text, expression, index);
         }
+        if (token.isOther()) {
+            return evalOther(token, text, expression, index);
+        }
 
         return text.startsWith(token.getValue());
     }
@@ -276,6 +282,17 @@ public final class SimpleTokenizer {
         return " ".equals(previousOne) && " ".equals(afterOne) && 
text.substring(0, len).equals(token.getValue());
     }
 
+    private static boolean evalOther(SimpleTokenType token, String text, 
String expression, int index) {
+        int len = token.getValue().length();
+        // The other operator must be used in the format of "exp1 op exp2"
+        if (index < 2 || len >= text.length() - 1) {
+            return false;
+        }
+        String previousOne = expression.substring(index - 1, index);
+        String afterOne = text.substring(len, len + 1);
+        return " ".equals(previousOne) && " ".equals(afterOne) && 
text.substring(0, len).equals(token.getValue());
+    }
+
     private static boolean evalUnary(SimpleTokenType token, String text, 
String expression, int index) {
         int endLen = 1;
 
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java
index 5c59c101e1ad..626ab79c5626 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java
@@ -43,6 +43,11 @@ public class LiteralExpression extends BaseSimpleNode 
implements LiteralNode {
         this.text.append(text);
     }
 
+    void replaceText(String text) {
+        this.text.setLength(0);
+        addText(text);
+    }
+
     @Override
     public String getText() {
         return text.toString();
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
new file mode 100644
index 000000000000..09902ffd6fe0
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.simple.ast;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.BaseSimpleParser;
+import org.apache.camel.language.simple.types.OtherOperatorType;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.language.simple.types.SimpleToken;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.StringQuoteHelper;
+
+/**
+ * Represents other operator expression in the AST.
+ */
+public class OtherExpression extends BaseSimpleNode {
+
+    private final OtherOperatorType operator;
+    private SimpleNode left;
+    private SimpleNode right;
+
+    public OtherExpression(SimpleToken token) {
+        super(token);
+        operator = OtherOperatorType.asOperator(token.getText());
+    }
+
+    @Override
+    public String toString() {
+        return left + " " + token.getText() + " " + right;
+    }
+
+    public boolean acceptLeftNode(SimpleNode lef) {
+        this.left = lef;
+        return true;
+    }
+
+    public boolean acceptRightNode(SimpleNode right) {
+        this.right = right;
+        return true;
+    }
+
+    public OtherOperatorType getOperator() {
+        return operator;
+    }
+
+    public SimpleNode getLeft() {
+        return left;
+    }
+
+    public SimpleNode getRight() {
+        return right;
+    }
+
+    @Override
+    public Expression createExpression(CamelContext camelContext, String 
expression) {
+        org.apache.camel.util.ObjectHelper.notNull(left, "left node", this);
+        org.apache.camel.util.ObjectHelper.notNull(right, "right node", this);
+
+        // the expression parser does not parse literal text into 
single/double quote tokens
+        // so we need to manually remove leading quotes from the literal text 
when using the other operators
+        final Expression leftExp = left.createExpression(camelContext, 
expression);
+        if (right instanceof LiteralExpression le) {
+            String text = le.getText();
+            String changed = StringHelper.removeLeadingAndEndingQuotes(text);
+            if (!changed.equals(text)) {
+                le.replaceText(changed);
+            }
+        }
+        final Expression rightExp = right.createExpression(camelContext, 
expression);
+
+        if (operator == OtherOperatorType.ELVIS) {
+            return createElvisExpression(camelContext, leftExp, rightExp);
+        }
+
+        throw new SimpleParserException("Unknown other operator " + operator, 
token.getIndex());
+    }
+
+    private Expression createElvisExpression(
+            final CamelContext camelContext, final Expression leftExp, final 
Expression rightExp) {
+        return new Expression() {
+            @Override
+            public <T> T evaluate(Exchange exchange, Class<T> type) {
+                Object value = leftExp.evaluate(exchange, Object.class);
+                if (value == null || Boolean.FALSE == value || 
ObjectHelper.isEmpty(value) || ObjectHelper.equal(0, value)) {
+                    return rightExp.evaluate(exchange, type);
+                } else {
+                    return leftExp.evaluate(exchange, type);
+                }
+            }
+
+            @Override
+            public String toString() {
+                return left + " " + token.getText() + " " + right;
+            }
+        };
+    }
+
+    @Override
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
+        return BaseSimpleParser.CODE_START + doCreateCode(camelContext, 
expression) + BaseSimpleParser.CODE_END;
+    }
+
+    private String doCreateCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
+        org.apache.camel.util.ObjectHelper.notNull(left, "left node", this);
+        org.apache.camel.util.ObjectHelper.notNull(right, "right node", this);
+
+        // the expression parser does not parse literal text into 
single/double quote tokens
+        // so we need to manually remove leading quotes from the literal text 
when using the other operators
+        final String leftExp = left.createCode(camelContext, expression);
+        if (right instanceof LiteralExpression le) {
+            String text = le.getText();
+            // must be in double quotes to be a String type
+            String changed = StringHelper.removeLeadingAndEndingQuotes(text);
+            changed = StringQuoteHelper.doubleQuote(changed);
+            if (!changed.equals(text)) {
+                le.replaceText(changed);
+            }
+        }
+        final String rightExp = right.createCode(camelContext, expression);
+
+        if (operator == OtherOperatorType.ELVIS) {
+            return "elvis(exchange, " + leftExp + ", " + rightExp + ")";
+        }
+
+        throw new SimpleParserException("Unknown other operator " + operator, 
token.getIndex());
+    }
+
+}
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java
similarity index 60%
copy from 
core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
copy to 
core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java
index a7a6e6923d68..7ae09a14339d 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java
@@ -17,24 +17,29 @@
 package org.apache.camel.language.simple.types;
 
 /**
- * Classifications of known token types.
+ * Types of other operators supported
  */
-public enum TokenType {
+public enum OtherOperatorType {
 
-    whiteSpace,
-    character,
-    booleanValue,
-    numericValue,
-    nullValue,
-    singleQuote,
-    doubleQuote,
-    minusValue,
-    escape,
-    functionStart,
-    functionEnd,
-    binaryOperator,
-    unaryOperator,
-    logicalOperator,
-    eol
+    ELVIS;
+
+    public static OtherOperatorType asOperator(String text) {
+        if ("?:".equals(text)) {
+            return ELVIS;
+        }
+        throw new IllegalArgumentException("Operator not supported: " + text);
+    }
+
+    public static String getOperatorText(OtherOperatorType operator) {
+        if (operator == ELVIS) {
+            return "?:";
+        }
+        return "";
+    }
+
+    @Override
+    public String toString() {
+        return getOperatorText(this);
+    }
 
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
index 558db07ed7da..7663e1d21cd3 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
@@ -103,6 +103,13 @@ public final class SimpleTokenType {
         return type == TokenType.binaryOperator;
     }
 
+    /**
+     * Whether the type is other operator
+     */
+    public boolean isOther() {
+        return type == TokenType.otherOperator;
+    }
+
     /**
      * Whether the type is unary operator
      */
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
index a7a6e6923d68..e737ab809b09 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
@@ -33,6 +33,7 @@ public enum TokenType {
     functionStart,
     functionEnd,
     binaryOperator,
+    otherOperator,
     unaryOperator,
     logicalOperator,
     eol
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 8360f656447e..36a893be856e 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
@@ -38,6 +38,7 @@ public class SimpleOperatorTest extends LanguageTestSupport {
     public void testValueWithSpace() {
         exchange.getIn().setBody("Hello Big World");
         assertPredicate("${in.body} == 'Hello Big World'", true);
+        assertPredicate("${in.body} == ${body}", true);
     }
 
     @Test
@@ -827,6 +828,38 @@ public class SimpleOperatorTest extends 
LanguageTestSupport {
         assertPredicate("${in.body} !endsWith 'Hi'", true);
     }
 
+    @Test
+    public void testElvis() {
+        exchange.getIn().setBody(false);
+        assertPredicate("${body} ?: 'true'", true);
+        assertPredicate("${body} ?: 'false'", false);
+        exchange.getIn().setBody("Hello");
+        assertPredicate("${body} ?: 'false'", true);
+        exchange.getIn().setBody(0);
+        assertPredicate("${body} ?: 'true'", true);
+        assertPredicate("${body} ?: 'false'", false);
+        exchange.getIn().setBody(1);
+        assertPredicate("${body} ?: 'true'", true);
+        assertPredicate("${body} ?: 'false'", true);
+
+        exchange.getIn().setBody(null);
+        assertExpression("${body} ?: 'World'", "World");
+        exchange.getIn().setBody("");
+        assertExpression("${body} ?: 'World'", "World");
+        exchange.getIn().setBody("Hello");
+        assertExpression("${body} ?: 'World'", "Hello");
+        exchange.getIn().setBody(false);
+        assertExpression("${body} ?: 'World'", "World");
+        exchange.getIn().setBody(true);
+        assertExpression("${body} ?: 'World'", true);
+        exchange.getIn().setHeader("myHeader", "Camel");
+        assertExpression("${header.myHeader} ?: 'World'", "Camel");
+        exchange.getIn().setBody(0);
+        assertExpression("${body} ?: 'World'", "World");
+        exchange.getIn().setBody(1);
+        assertExpression("${body} ?: 'World'", 1);
+    }
+
     @Override
     protected String getLanguageName() {
         return "simple";
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateParserNodesTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateParserNodesTest.java
index 51c2b87b5908..4b4c879d4250 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateParserNodesTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateParserNodesTest.java
@@ -21,10 +21,12 @@ import java.util.List;
 import org.apache.camel.ExchangeTestSupport;
 import org.apache.camel.language.simple.ast.BinaryExpression;
 import org.apache.camel.language.simple.ast.LiteralNode;
+import org.apache.camel.language.simple.ast.OtherExpression;
 import org.apache.camel.language.simple.ast.SimpleFunctionStart;
 import org.apache.camel.language.simple.ast.SimpleNode;
 import org.apache.camel.language.simple.ast.SingleQuoteStart;
 import org.apache.camel.language.simple.types.BinaryOperatorType;
+import org.apache.camel.language.simple.types.OtherOperatorType;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -70,4 +72,24 @@ public class SimplePredicateParserNodesTest extends 
ExchangeTestSupport {
         Assertions.assertEquals("header.bar", ln.toString());
     }
 
+    @Test
+    public void testParserNodesElvis() {
+        exchange.getIn().setBody("Hello");
+
+        SimplePredicateParser parser = new SimplePredicateParser(null, 
"${body} ?: 'Bye'", true, null);
+        List<SimpleNode> nodes = parser.parseTokens();
+        Assertions.assertEquals(1, nodes.size());
+        OtherExpression be = (OtherExpression) nodes.get(0);
+
+        Assertions.assertEquals(OtherOperatorType.ELVIS, be.getOperator());
+
+        SingleQuoteStart qe = (SingleQuoteStart) be.getRight();
+        LiteralNode ln = (LiteralNode) qe.getBlock().getChildren().get(0);
+        Assertions.assertEquals("Bye", ln.getText());
+
+        SimpleFunctionStart fe = (SimpleFunctionStart) be.getLeft();
+        ln = (LiteralNode) fe.getBlock().getChildren().get(0);
+        Assertions.assertEquals("body", ln.toString());
+    }
+
 }


Reply via email to