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 2624a155fabd CAMEL-22870: camel-core - Add elvis ?: operator to simple
language (#20863)
2624a155fabd is described below
commit 2624a155fabd9ad109e664120efa73264ba94920
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Jan 18 14:05:54 2026 +0100
CAMEL-22870: camel-core - Add elvis ?: operator to simple language (#20863)
---
.../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());
+ }
+
}