This is an automated email from the ASF dual-hosted git repository.
gnodet 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 8089af56cf44 CAMEL-22873: Add ternary operator support to Simple
language (#20936)
8089af56cf44 is described below
commit 8089af56cf4451edcd96ca46ab0a46c96647271a
Author: Guillaume Nodet <[email protected]>
AuthorDate: Fri Jan 23 10:56:12 2026 +0100
CAMEL-22873: Add ternary operator support to Simple language (#20936)
Add support for the ternary operator (condition ? trueValue : falseValue)
in Apache Camel's Simple language, providing a more familiar syntax alongside
the existing iif function.
---
.../language/csimple/joor/OriginalSimpleTest.java | 85 ++++
.../modules/languages/pages/csimple-language.adoc | 4 +-
.../modules/languages/pages/simple-language.adoc | 22 +
.../camel/language/csimple/CSimpleHelper.java | 12 +
.../camel/language/simple/BaseSimpleParser.java | 77 ++++
.../language/simple/SimpleExpressionParser.java | 41 +-
.../language/simple/SimplePredicateParser.java | 33 ++
.../camel/language/simple/SimpleTokenizer.java | 22 +-
.../language/simple/ast/SimpleFunctionStart.java | 501 ++++++++++++++++++++-
.../language/simple/ast/TernaryExpression.java | 156 +++++++
.../language/simple/types/SimpleTokenType.java | 7 +
.../{TokenType.java => TernaryOperatorType.java} | 45 +-
.../camel/language/simple/types/TokenType.java | 1 +
.../apache/camel/language/simple/SimpleTest.java | 85 ++++
14 files changed, 1060 insertions(+), 31 deletions(-)
diff --git
a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
index 1a76affa12bb..a223b8f9d1a8 100644
---
a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
+++
b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
@@ -2845,6 +2845,91 @@ public class OriginalSimpleTest extends
LanguageTestSupport {
assertEquals(1, exchange.getVariables().size());
}
+ @Test
+ public void testTernaryOperator() {
+ // Test that the same expression object evaluates correctly with
different header values
+ exchange.getIn().setHeader("foo", 44);
+ Expression exp =
context.resolveLanguage("csimple").createExpression("${header.foo > 0 ?
'positive' : 'negative'}");
+ assertEquals("positive", exp.evaluate(exchange, String.class), "First
evaluation with foo=44");
+
+ exchange.getIn().setHeader("foo", -123);
+ assertEquals("negative", exp.evaluate(exchange, String.class), "Second
evaluation with foo=-123");
+
+ // Test a simple ternary with a constant condition
+ Expression expTrue =
context.resolveLanguage("csimple").createExpression("${true ? 'yes' : 'no'}");
+ assertEquals("yes", expTrue.evaluate(exchange, String.class),
"Constant true ternary");
+
+ Expression expFalse =
context.resolveLanguage("csimple").createExpression("${false ? 'yes' : 'no'}");
+ assertEquals("no", expFalse.evaluate(exchange, String.class),
"Constant false ternary");
+
+ // Test with body
+ exchange.getIn().setBody("Hello World");
+ exchange.getIn().setHeader("foo", 44);
+ assertExpression("${header.foo > 0 ? ${body} : 'Bye World'}", "Hello
World");
+ exchange.getIn().setHeader("foo", -123);
+ assertExpression("${header.foo > 0 ? ${body} : 'Bye World'}", "Bye
World");
+ assertExpression("${header.foo > 0 ? ${body} : ${null}}", null);
+
+ // Test with file name
+ exchange.getIn().setHeader("CamelFileName", "testfile.txt");
+ assertExpression("${file:name startsWith 'test' ? 'foo' : 'bar'}",
"foo");
+ exchange.getIn().setHeader("CamelFileName", "dummy.txt");
+ assertExpression("${file:name startsWith 'test' ? 'foo' : 'bar'}",
"bar");
+ }
+
+ @Test
+ public void testTernaryOperatorWithNumbers() {
+ exchange.getIn().setHeader("score", 85);
+ assertExpression("${header.score >= 90 ? 'A' : 'B'}", "B");
+ exchange.getIn().setHeader("score", 95);
+ assertExpression("${header.score >= 90 ? 'A' : 'B'}", "A");
+
+ exchange.getIn().setHeader("age", 25);
+ assertExpression("${header.age >= 18 ? 'adult' : 'minor'}", "adult");
+ exchange.getIn().setHeader("age", 15);
+ assertExpression("${header.age >= 18 ? 'adult' : 'minor'}", "minor");
+ }
+
+ @Test
+ public void testTernaryOperatorWithBooleans() {
+ exchange.getIn().setHeader("enabled", true);
+ assertExpression("${header.enabled == true ? 'yes' : 'no'}", "yes");
+ exchange.getIn().setHeader("enabled", false);
+ assertExpression("${header.enabled == true ? 'yes' : 'no'}", "no");
+ }
+
+ @Test
+ public void testTernaryOperatorWithNull() {
+ exchange.getIn().setHeader("value", null);
+ assertExpression("${header.value == null ? 'empty' : 'full'}",
"empty");
+ exchange.getIn().setHeader("value", "something");
+ assertExpression("${header.value == null ? 'empty' : 'full'}", "full");
+ }
+
+ @Test
+ public void testTernaryOperatorNested() {
+ // Nested ternary operators
+ exchange.getIn().setHeader("score", 95);
+ assertExpression("${header.score >= 90 ? 'A' : ${header.score} >= 80 ?
'B' : 'C'}", "A");
+ exchange.getIn().setHeader("score", 85);
+ assertExpression("${header.score >= 90 ? 'A' : ${header.score} >= 80 ?
'B' : 'C'}", "B");
+ exchange.getIn().setHeader("score", 75);
+ assertExpression("${header.score >= 90 ? 'A' : ${header.score} >= 80 ?
'B' : 'C'}", "C");
+ }
+
+ @Test
+ public void testTernaryOperatorWithStrings() {
+ exchange.getIn().setBody("Hello");
+ assertExpression("${body == 'Hello' ? 'greeting' : 'other'}",
"greeting");
+ exchange.getIn().setBody("Goodbye");
+ assertExpression("${body == 'Hello' ? 'greeting' : 'other'}", "other");
+
+ exchange.getIn().setHeader("name", "John");
+ assertExpression("${header.name contains 'John' ? 'found' : 'not
found'}", "found");
+ exchange.getIn().setHeader("name", "Jane");
+ assertExpression("${header.name contains 'John' ? 'found' : 'not
found'}", "not found");
+ }
+
@Test
public void testAbs() {
exchange.getMessage().setBody("-987");
diff --git
a/core/camel-core-languages/src/main/docs/modules/languages/pages/csimple-language.adoc
b/core/camel-core-languages/src/main/docs/modules/languages/pages/csimple-language.adoc
index 1a1326360161..4cbc0df85c42 100644
---
a/core/camel-core-languages/src/main/docs/modules/languages/pages/csimple-language.adoc
+++
b/core/camel-core-languages/src/main/docs/modules/languages/pages/csimple-language.adoc
@@ -187,7 +187,9 @@ include::partial$language-options.adoc[]
Currently, the csimple language does **not** support:
- nested functions (aka functions inside functions)
-- the _null safe_ operator (`?`).
+- the _null safe_ operator (`?.`).
+
+NOTE: The ternary operator (`? :`) IS supported in csimple. The limitation
above refers only to the null-safe operator used in OGNL expressions (e.g.,
`${body?.address}`).
For example the following scripts cannot compile:
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 6df8b7ea9ded..bcb8c13cb006 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
@@ -413,9 +413,31 @@ And the following other operators can be used:
[width="100%",cols="50%,50%",options="header",]
|====
|Operator |Description
+|`? :` | The ternary operator evaluates a condition and returns a value based
on the result. If the condition is true, the first value (after `?`) is
returned; otherwise, the second value (after `:`) is returned. There must be
spaces around both `?` and `:` operators. This is similar to the ternary
operator in Java.
|`?:` | 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).
|====
+The syntax for the ternary operator is:
+
+[source,text]
+----
+${leftValue} OP rightValue ? trueValue : falseValue
+----
+
+For example the following ternary operator will return `positive` if header
`foo` is greater than 0, otherwise `negative`:
+
+[source,java]
+----
+simple("${header.foo > 0 ? 'positive' : 'negative'}");
+----
+
+Ternary operators can also be nested to handle multiple conditions:
+
+[source,java]
+----
+simple("${header.score >= 90 ? 'A' : ${header.score >= 80 ? 'B' : 'C'}}");
+----
+
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.
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 d4b7caaea8d4..249addbb81a6 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
@@ -1160,6 +1160,18 @@ public final class CSimpleHelper {
}
}
+ public static Object ternary(Exchange exchange, Object condition, Object
trueValue, Object falseValue) {
+ boolean result;
+ if (condition instanceof Boolean b) {
+ result = b;
+ } else {
+ // Try to convert to boolean - treat null, empty, and "false" as
false
+ result = condition != null && !ObjectHelper.isEmpty(condition)
+ && !Boolean.FALSE.equals(condition) &&
!"false".equalsIgnoreCase(String.valueOf(condition));
+ }
+ return result ? trueValue : falseValue;
+ }
+
public static Object setHeader(Exchange exchange, String name, Class<?>
type, Object value) {
if (type != null && value != null) {
value = convertTo(exchange, type, value);
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 359f7a979ccb..1cb610ea7974 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
@@ -29,6 +29,7 @@ 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.TernaryExpression;
import org.apache.camel.language.simple.ast.UnaryExpression;
import org.apache.camel.language.simple.types.SimpleParserException;
import org.apache.camel.language.simple.types.SimpleToken;
@@ -270,6 +271,82 @@ public abstract class BaseSimpleParser {
Collections.reverse(nodes);
}
+ /**
+ * Prepares ternary expressions.
+ * <p/>
+ * This process prepares the ternary expressions in the AST. This is done
by linking the ternary operator (condition
+ * ? trueValue : falseValue) with all three parts: condition, trueValue,
and falseValue.
+ * <p/>
+ * The ternary operator consists of two tokens: ? and : We need to find
the pattern: condition ? trueValue :
+ * falseValue
+ */
+ protected void prepareTernaryExpressions() {
+ List<SimpleNode> answer = new ArrayList<>();
+
+ for (int i = 0; i < nodes.size(); i++) {
+ SimpleNode token = nodes.get(i);
+
+ if (token instanceof TernaryExpression ternary &&
"?".equals(token.getToken().getText())) {
+ // We found the ? operator
+ // Get the condition (left side)
+ if (answer.isEmpty()) {
+ throw new SimpleParserException(
+ "Ternary operator ? has no condition",
token.getToken().getIndex());
+ }
+ SimpleNode condition = answer.remove(answer.size() - 1);
+
+ // Get the true value (right side of ?)
+ if (i >= nodes.size() - 1) {
+ throw new SimpleParserException(
+ "Ternary operator ? has no true value",
token.getToken().getIndex());
+ }
+ SimpleNode trueValue = nodes.get(++i);
+
+ // Find the : operator
+ if (i >= nodes.size() - 1) {
+ throw new SimpleParserException(
+ "Ternary operator ? has no : operator",
token.getToken().getIndex());
+ }
+ SimpleNode colonToken = nodes.get(++i);
+ if (!(colonToken instanceof TernaryExpression) ||
!":".equals(colonToken.getToken().getText())) {
+ throw new SimpleParserException(
+ "Ternary operator ? must be followed by :",
token.getToken().getIndex());
+ }
+
+ // Get the false value (right side of :)
+ if (i >= nodes.size() - 1) {
+ throw new SimpleParserException(
+ "Ternary operator : has no false value",
token.getToken().getIndex());
+ }
+ SimpleNode falseValue = nodes.get(++i);
+
+ // Link all parts to the ternary expression
+ if (!ternary.acceptCondition(condition)) {
+ throw new SimpleParserException(
+ "Ternary operator does not support condition token
" + condition.getToken(),
+ token.getToken().getIndex());
+ }
+ if (!ternary.acceptTrueValue(trueValue)) {
+ throw new SimpleParserException(
+ "Ternary operator does not support true value
token " + trueValue.getToken(),
+ token.getToken().getIndex());
+ }
+ if (!ternary.acceptFalseValue(falseValue)) {
+ throw new SimpleParserException(
+ "Ternary operator does not support false value
token " + falseValue.getToken(),
+ token.getToken().getIndex());
+ }
+
+ answer.add(ternary);
+ } else {
+ answer.add(token);
+ }
+ }
+
+ nodes.clear();
+ nodes.addAll(answer);
+ }
+
// --------------------------------------------------------------
// 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 7ef8b8271dc7..f8042976310a 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
@@ -29,6 +29,7 @@ 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.TernaryExpression;
import org.apache.camel.language.simple.ast.UnaryExpression;
import org.apache.camel.language.simple.types.OtherOperatorType;
import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
@@ -99,10 +100,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,
unary, or other operator
+ // an expression supports just template (eg text), functions,
unary, ternary, or other operator
templateText();
functionText();
unaryOperator();
+ ternaryOperator();
otherOperator();
nextToken();
}
@@ -119,6 +121,8 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
prepareBlocks();
// compact and stack unary operators
prepareUnaryExpressions();
+ // compact and stack ternary expressions
+ prepareTernaryExpressions();
// compact and stack other expressions
prepareOtherExpressions();
@@ -212,7 +216,7 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
}
private SimpleNode createNode(SimpleToken token, AtomicInteger functions) {
- // expression only support functions and unary operators
+ // expression only support functions, unary operators, ternary
operators, and other operators
if (token.getType().isFunctionStart()) {
// starting a new function
functions.incrementAndGet();
@@ -226,6 +230,8 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
if (!nodes.isEmpty() && nodes.get(nodes.size() - 1) instanceof
SimpleFunctionEnd) {
return new UnaryExpression(token);
}
+ } else if (token.getType().isTernary()) {
+ return new TernaryExpression(token);
} else if (token.getType().isOther()) {
return new OtherExpression(token);
}
@@ -308,9 +314,9 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
// - other operator = operator attached to both the left and right hand
side nodes
protected void templateText() {
- // for template, we accept anything but functions / other operator
+ // for template, we accept anything but functions / ternary operator /
other operator
while (!token.getType().isFunctionStart() &&
!token.getType().isFunctionEnd() && !token.getType().isEol()
- && !token.getType().isOther()) {
+ && !token.getType().isTernary() && !token.getType().isOther())
{
nextToken();
}
}
@@ -365,6 +371,33 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
return false;
}
+ protected boolean ternaryOperator() {
+ if (accept(TokenType.ternaryOperator)) {
+ 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(
+ "Ternary operator does not support token " + token,
token.getIndex());
+ }
+ return true;
+ }
+ return false;
+ }
+
protected boolean unaryOperator() {
if (accept(TokenType.unaryOperator)) {
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 a0f674adaa26..a7ad6f8e0d08 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
@@ -43,6 +43,7 @@ import
org.apache.camel.language.simple.ast.SimpleFunctionStart;
import org.apache.camel.language.simple.ast.SimpleNode;
import org.apache.camel.language.simple.ast.SingleQuoteEnd;
import org.apache.camel.language.simple.ast.SingleQuoteStart;
+import org.apache.camel.language.simple.ast.TernaryExpression;
import org.apache.camel.language.simple.ast.UnaryExpression;
import org.apache.camel.language.simple.types.BinaryOperatorType;
import org.apache.camel.language.simple.types.LogicalOperatorType;
@@ -122,6 +123,7 @@ public class SimplePredicateParser extends BaseSimpleParser
{
&& !functionText()
&& !unaryOperator()
&& !binaryOperator()
+ && !ternaryOperator()
&& !otherOperator()
&& !logicalOperator()
&& !isBooleanValue()
@@ -149,6 +151,8 @@ public class SimplePredicateParser extends BaseSimpleParser
{
prepareUnaryExpressions();
// compact and stack binary expressions
prepareBinaryExpressions();
+ // compact and stack ternary expressions
+ prepareTernaryExpressions();
// compact and stack other expressions
prepareOtherExpressions();
// compact and stack logical expressions
@@ -340,6 +344,8 @@ public class SimplePredicateParser extends BaseSimpleParser
{
return new UnaryExpression(token);
} else if (token.getType().isBinary()) {
return new BinaryExpression(token);
+ } else if (token.getType().isTernary()) {
+ return new TernaryExpression(token);
} else if (token.getType().isOther()) {
return new OtherExpression(token);
} else if (token.getType().isLogical()) {
@@ -732,6 +738,33 @@ public class SimplePredicateParser extends
BaseSimpleParser {
return false;
}
+ protected boolean ternaryOperator() {
+ if (accept(TokenType.ternaryOperator)) {
+ 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(
+ "Ternary operator does not support token " + token,
token.getIndex());
+ }
+ return true;
+ }
+ return false;
+ }
+
protected boolean otherOperator() {
if (accept(TokenType.otherOperator)) {
// remember the other 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 0258ce18b8ff..8beb7b343522 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 = 50;
+ private static final int NUMBER_OF_TOKENS = 52;
private static final SimpleTokenType[] KNOWN_TOKENS = new
SimpleTokenType[NUMBER_OF_TOKENS];
@@ -96,10 +96,14 @@ public final class SimpleTokenizer {
KNOWN_TOKENS[47] = new SimpleTokenType(TokenType.logicalOperator,
"&&");
KNOWN_TOKENS[48] = new SimpleTokenType(TokenType.logicalOperator,
"||");
+ // ternary operators
+ KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.ternaryOperator, "?");
+ KNOWN_TOKENS[50] = new SimpleTokenType(TokenType.ternaryOperator, ":");
+
//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[49] = new SimpleTokenType(TokenType.minusValue, "-");
+ KNOWN_TOKENS[51] = new SimpleTokenType(TokenType.minusValue, "-");
}
private SimpleTokenizer() {
@@ -267,6 +271,9 @@ public final class SimpleTokenizer {
if (token.isOther()) {
return evalOther(token, text, expression, index);
}
+ if (token.isTernary()) {
+ return evalTernary(token, text, expression, index);
+ }
return text.startsWith(token.getValue());
}
@@ -293,6 +300,17 @@ public final class SimpleTokenizer {
return " ".equals(previousOne) && " ".equals(afterOne) &&
text.substring(0, len).equals(token.getValue());
}
+ private static boolean evalTernary(SimpleTokenType token, String text,
String expression, int index) {
+ int len = token.getValue().length();
+ // The ternary operator must be used in the format of "exp1 ? exp2 :
exp3"
+ 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/SimpleFunctionStart.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java
index 1953dbb8ea1a..0442c33288c8 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java
@@ -16,11 +16,16 @@
*/
package org.apache.camel.language.simple.ast;
+import java.util.List;
import java.util.Map;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
+import org.apache.camel.Predicate;
+import org.apache.camel.language.simple.BaseSimpleParser;
+import org.apache.camel.language.simple.SimpleExpressionParser;
+import org.apache.camel.language.simple.SimplePredicateParser;
import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
import org.apache.camel.language.simple.types.SimpleParserException;
import org.apache.camel.language.simple.types.SimpleToken;
@@ -61,6 +66,11 @@ public class SimpleFunctionStart extends BaseSimpleNode
implements BlockStart {
@Override
public Expression createExpression(CamelContext camelContext, String
expression) {
+ // Check if the block contains ternary expression nodes - if so,
process them first
+ if (containsTernaryExpressionNodes()) {
+ return doCreateTernaryExpression(camelContext, expression);
+ }
+
// a function can either be a simple literal function, or contain
nested functions
if (block.getChildren().size() == 1 && block.getChildren().get(0)
instanceof LiteralNode) {
return doCreateLiteralExpression(camelContext, expression);
@@ -69,13 +79,329 @@ public class SimpleFunctionStart extends BaseSimpleNode
implements BlockStart {
}
}
+ /**
+ * Check if the block contains TernaryExpression nodes (? or : operators)
+ */
+ private boolean containsTernaryExpressionNodes() {
+ for (SimpleNode child : block.getChildren()) {
+ if (child instanceof TernaryExpression) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Create an expression from a block that contains ternary expression
nodes. This handles the pattern: condition ?
+ * trueValue : falseValue
+ */
+ private Expression doCreateTernaryExpression(CamelContext camelContext,
String expression) {
+ List<SimpleNode> children = block.getChildren();
+
+ // Find the ? operator
+ int questionIdx = -1;
+ for (int i = 0; i < children.size(); i++) {
+ SimpleNode child = children.get(i);
+ if (child instanceof TernaryExpression &&
"?".equals(child.getToken().getText())) {
+ questionIdx = i;
+ break;
+ }
+ }
+
+ if (questionIdx < 0) {
+ // No ? found, fall back to composite expression
+ return doCreateCompositeExpression(camelContext, expression);
+ }
+
+ // Find the : operator after the ?
+ int colonIdx = -1;
+ for (int i = questionIdx + 1; i < children.size(); i++) {
+ SimpleNode child = children.get(i);
+ if (child instanceof TernaryExpression &&
":".equals(child.getToken().getText())) {
+ colonIdx = i;
+ break;
+ }
+ }
+
+ if (colonIdx < 0) {
+ throw new SimpleParserException(
+ "Ternary operator ? must be followed by :",
children.get(questionIdx).getToken().getIndex());
+ }
+
+ // Extract condition, true value, and false value
+ List<SimpleNode> conditionNodes = children.subList(0, questionIdx);
+ List<SimpleNode> trueNodes = children.subList(questionIdx + 1,
colonIdx);
+ List<SimpleNode> falseNodes = children.subList(colonIdx + 1,
children.size());
+
+ // Build the condition text
+ String conditionText = buildTextFromNodes(conditionNodes,
camelContext);
+ String trueText = buildTextFromNodes(trueNodes, camelContext);
+ String falseText = buildTextFromNodes(falseNodes, camelContext);
+
+ // Wrap the condition for predicate parsing
+ String predicateText = wrapFunctionsInCondition(conditionText.trim());
+
+ // Parse the condition as a predicate
+ SimplePredicateParser predicateParser
+ = new SimplePredicateParser(camelContext, predicateText, true,
skipFileFunctions, null);
+ final Predicate conditionPredicate = predicateParser.parsePredicate();
+
+ // Parse the true and false values as expressions
+ final Expression trueExp = parseValueExpression(camelContext,
trueText.trim());
+ final Expression falseExp = parseValueExpression(camelContext,
falseText.trim());
+
+ return new Expression() {
+ @Override
+ public <T> T evaluate(Exchange exchange, Class<T> type) {
+ if (conditionPredicate.matches(exchange)) {
+ return trueExp.evaluate(exchange, type);
+ } else {
+ return falseExp.evaluate(exchange, type);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return conditionText + " ? " + trueText + " : " + falseText;
+ }
+ };
+ }
+
+ /**
+ * Build a text string from a list of nodes
+ */
+ private String buildTextFromNodes(List<SimpleNode> nodes, CamelContext
camelContext) {
+ StringBuilder sb = new StringBuilder();
+ for (SimpleNode node : nodes) {
+ if (node instanceof LiteralNode literal) {
+ sb.append(literal.getText());
+ } else if (node instanceof SingleQuoteStart || node instanceof
DoubleQuoteStart) {
+ sb.append(node.toString());
+ } else if (node instanceof SimpleFunctionStart) {
+ sb.append(node.toString());
+ } else if (node instanceof TernaryExpression) {
+ // Include the ternary operator (? or :) in the text
+ sb.append(node.getToken().getText());
+ }
+ }
+ return sb.toString();
+ }
+
private Expression doCreateLiteralExpression(CamelContext camelContext,
String expression) {
- SimpleFunctionExpression function = new
SimpleFunctionExpression(this.getToken(), cacheExpression, skipFileFunctions);
LiteralNode literal = (LiteralNode) block.getChildren().get(0);
- function.addText(literal.getText());
+ String text = literal.getText();
+
+ // Check if this is a ternary expression
+ Expression ternaryExp = tryParseTernaryExpression(camelContext, text);
+ if (ternaryExp != null) {
+ return ternaryExp;
+ }
+
+ SimpleFunctionExpression function = new
SimpleFunctionExpression(this.getToken(), cacheExpression, skipFileFunctions);
+ function.addText(text);
return function.createExpression(camelContext, expression);
}
+ /**
+ * Try to parse the text as a ternary expression. Returns null if the text
is not a ternary expression.
+ */
+ private Expression tryParseTernaryExpression(CamelContext camelContext,
String text) {
+ // Find the ? operator (not inside quotes or nested ${})
+ int questionIdx = findTernaryOperator(text, '?');
+ if (questionIdx < 0) {
+ return null;
+ }
+
+ // Find the : operator after the ?
+ int colonIdx = findTernaryOperator(text.substring(questionIdx + 1),
':');
+ if (colonIdx < 0) {
+ return null;
+ }
+ colonIdx = questionIdx + 1 + colonIdx;
+
+ // Extract the three parts
+ String conditionText = text.substring(0, questionIdx).trim();
+ String trueText = text.substring(questionIdx + 1, colonIdx).trim();
+ String falseText = text.substring(colonIdx + 1).trim();
+
+ if (conditionText.isEmpty() || trueText.isEmpty() ||
falseText.isEmpty()) {
+ return null;
+ }
+
+ // The condition text is like "header.foo > 0" but the predicate
parser expects
+ // "${header.foo} > 0". We need to transform the condition to wrap
function references
+ // with ${}. A simple approach: if there's no ${} in the condition,
wrap the left side.
+ String predicateText = wrapFunctionsInCondition(conditionText);
+
+ // Parse the condition as a predicate - use null for cache to avoid
caching issues
+ SimplePredicateParser predicateParser
+ = new SimplePredicateParser(camelContext, predicateText, true,
skipFileFunctions, null);
+ final Predicate conditionPredicate = predicateParser.parsePredicate();
+
+ // Parse the true and false values as expressions
+ final Expression trueExp = parseValueExpression(camelContext,
trueText);
+ final Expression falseExp = parseValueExpression(camelContext,
falseText);
+
+ return new Expression() {
+ @Override
+ public <T> T evaluate(Exchange exchange, Class<T> type) {
+ if (conditionPredicate.matches(exchange)) {
+ return trueExp.evaluate(exchange, type);
+ } else {
+ return falseExp.evaluate(exchange, type);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return conditionText + " ? " + trueText + " : " + falseText;
+ }
+ };
+ }
+
+ /**
+ * Parse a value as an expression. Handles quoted literals, functions,
ternary expressions, and null.
+ */
+ private Expression parseValueExpression(CamelContext camelContext, String
text) {
+ // Handle quoted strings
+ if ((text.startsWith("'") && text.endsWith("'")) ||
(text.startsWith("\"") && text.endsWith("\""))) {
+ final String value = text.substring(1, text.length() - 1);
+ return new Expression() {
+ @Override
+ public <T> T evaluate(Exchange exchange, Class<T> type) {
+ return
exchange.getContext().getTypeConverter().convertTo(type, value);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+ };
+ }
+
+ // Handle null
+ if ("null".equals(text) || "${null}".equals(text)) {
+ return new Expression() {
+ @Override
+ public <T> T evaluate(Exchange exchange, Class<T> type) {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "null";
+ }
+ };
+ }
+
+ // Check if this is a nested ternary expression (contains ? and :)
+ Expression ternaryExp = tryParseTernaryExpression(camelContext, text);
+ if (ternaryExp != null) {
+ return ternaryExp;
+ }
+
+ // Handle function expressions (may or may not have ${})
+ String expText = text;
+ if (!text.startsWith("${")) {
+ expText = "${" + text + "}";
+ }
+ // use null for cache to avoid caching issues with ternary expressions
+ SimpleExpressionParser parser
+ = new SimpleExpressionParser(camelContext, expText, true,
skipFileFunctions, null);
+ return parser.parseExpression();
+ }
+
+ /**
+ * Wrap function references in the condition text with ${}. For example:
"header.foo > 0" becomes "${header.foo} >
+ * 0"
+ */
+ private String wrapFunctionsInCondition(String conditionText) {
+ // If the condition already has ${}, assume it's properly formatted
+ if (conditionText.contains("${")) {
+ return conditionText;
+ }
+
+ // Find the operator in the condition
+ String[] operators = {
+ " >= ", " <= ", " > ", " < ", " == ", " != ", " =~ ", " !=~ ",
+ " contains ", " !contains ", " ~~ ", " !~~ ", " regex ", "
!regex ",
+ " in ", " !in ", " is ", " !is ", " range ", " !range ",
+ " startsWith ", " !startsWith ", " endsWith ", " !endsWith " };
+
+ for (String op : operators) {
+ int opIdx = conditionText.indexOf(op);
+ if (opIdx > 0) {
+ String leftSide = conditionText.substring(0, opIdx).trim();
+ String rightSide = conditionText.substring(opIdx +
op.length()).trim();
+
+ // Wrap the left side with ${} if it looks like a function
reference
+ if (!leftSide.startsWith("${") && !leftSide.startsWith("'") &&
!leftSide.startsWith("\"")
+ && !isNumeric(leftSide) &&
!"true".equalsIgnoreCase(leftSide)
+ && !"false".equalsIgnoreCase(leftSide) &&
!"null".equalsIgnoreCase(leftSide)) {
+ leftSide = "${" + leftSide + "}";
+ }
+
+ return leftSide + op + rightSide;
+ }
+ }
+
+ // No operator found, return as-is
+ return conditionText;
+ }
+
+ private boolean isNumeric(String str) {
+ if (str == null || str.isEmpty()) {
+ return false;
+ }
+ try {
+ Double.parseDouble(str);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Find the index of the ternary operator character, skipping nested ${},
quotes, etc.
+ */
+ private int findTernaryOperator(String text, char operator) {
+ int depth = 0;
+ boolean inSingleQuote = false;
+ boolean inDoubleQuote = false;
+
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+
+ if (!inSingleQuote && !inDoubleQuote) {
+ if (c == '$' && i + 1 < text.length() && text.charAt(i + 1) ==
'{') {
+ depth++;
+ i++; // skip the {
+ continue;
+ }
+ if (c == '}' && depth > 0) {
+ depth--;
+ continue;
+ }
+ if (c == '\'' && depth == 0) {
+ inSingleQuote = true;
+ continue;
+ }
+ if (c == '"' && depth == 0) {
+ inDoubleQuote = true;
+ continue;
+ }
+ if (c == operator && depth == 0) {
+ return i;
+ }
+ } else if (inSingleQuote && c == '\'') {
+ inSingleQuote = false;
+ } else if (inDoubleQuote && c == '"') {
+ inDoubleQuote = false;
+ }
+ }
+ return -1;
+ }
+
private Expression doCreateCompositeExpression(CamelContext camelContext,
String expression) {
final SimpleToken token = getToken();
return new Expression() {
@@ -121,6 +447,13 @@ public class SimpleFunctionStart extends BaseSimpleNode
implements BlockStart {
// we have now concat the block as a String which contains the
function expression
// which we then need to evaluate as a function
String exp = sb.toString();
+
+ // Check if this is a ternary expression
+ Expression ternaryExp =
tryParseTernaryExpression(camelContext, exp);
+ if (ternaryExp != null) {
+ return ternaryExp.evaluate(exchange, type);
+ }
+
SimpleFunctionExpression function = new
SimpleFunctionExpression(token, cacheExpression, skipFileFunctions);
function.addText(exp);
try {
@@ -140,9 +473,10 @@ public class SimpleFunctionStart extends BaseSimpleNode
implements BlockStart {
@Override
public boolean acceptAndAddNode(SimpleNode node) {
- // only accept literals, quotes or embedded functions
+ // only accept literals, quotes, ternary expressions, or embedded
functions
if (node instanceof LiteralNode || node instanceof SimpleFunctionStart
- || node instanceof SingleQuoteStart || node instanceof
DoubleQuoteStart) {
+ || node instanceof SingleQuoteStart || node instanceof
DoubleQuoteStart
+ || node instanceof TernaryExpression) {
block.addChild(node);
return true;
} else {
@@ -152,6 +486,11 @@ public class SimpleFunctionStart extends BaseSimpleNode
implements BlockStart {
@Override
public String createCode(CamelContext camelContext, String expression)
throws SimpleParserException {
+ // Check if the block contains ternary expression nodes - if so,
process them first
+ if (containsTernaryExpressionNodes()) {
+ return doCreateTernaryCode(camelContext, expression);
+ }
+
String answer;
// a function can either be a simple literal function or contain
nested functions
if (block.getChildren().size() == 1 && block.getChildren().get(0)
instanceof LiteralNode) {
@@ -162,13 +501,163 @@ public class SimpleFunctionStart extends BaseSimpleNode
implements BlockStart {
return answer;
}
+ /**
+ * Create code from a block that contains ternary expression nodes. This
handles the pattern: condition ? trueValue
+ * : falseValue
+ */
+ private String doCreateTernaryCode(CamelContext camelContext, String
expression) {
+ List<SimpleNode> children = block.getChildren();
+
+ // Find the ? operator
+ int questionIdx = -1;
+ for (int i = 0; i < children.size(); i++) {
+ SimpleNode child = children.get(i);
+ if (child instanceof TernaryExpression &&
"?".equals(child.getToken().getText())) {
+ questionIdx = i;
+ break;
+ }
+ }
+
+ if (questionIdx < 0) {
+ // No ? found, fall back to composite code
+ return doCreateCompositeCode(camelContext, expression);
+ }
+
+ // Find the : operator after the ?
+ int colonIdx = -1;
+ for (int i = questionIdx + 1; i < children.size(); i++) {
+ SimpleNode child = children.get(i);
+ if (child instanceof TernaryExpression &&
":".equals(child.getToken().getText())) {
+ colonIdx = i;
+ break;
+ }
+ }
+
+ if (colonIdx < 0) {
+ throw new SimpleParserException(
+ "Ternary operator ? must be followed by :",
children.get(questionIdx).getToken().getIndex());
+ }
+
+ // Extract condition, true value, and false value nodes
+ List<SimpleNode> conditionNodes = children.subList(0, questionIdx);
+ List<SimpleNode> trueNodes = children.subList(questionIdx + 1,
colonIdx);
+ List<SimpleNode> falseNodes = children.subList(colonIdx + 1,
children.size());
+
+ // Build the text from nodes
+ String conditionText = buildTextFromNodes(conditionNodes,
camelContext);
+ String trueText = buildTextFromNodes(trueNodes, camelContext);
+ String falseText = buildTextFromNodes(falseNodes, camelContext);
+
+ // Wrap the condition for predicate parsing
+ String predicateText = wrapFunctionsInCondition(conditionText.trim());
+
+ // Parse the condition as a predicate and generate code
+ SimplePredicateParser predicateParser
+ = new SimplePredicateParser(camelContext, predicateText, true,
skipFileFunctions, null);
+ String conditionCode = predicateParser.parseCode();
+
+ // Parse the true and false values as expressions and generate code
+ String trueCode = parseValueCode(camelContext, trueText.trim());
+ String falseCode = parseValueCode(camelContext, falseText.trim());
+
+ return BaseSimpleParser.CODE_START + "ternary(exchange, " +
conditionCode + ", " + trueCode + ", " + falseCode
+ + ")" + BaseSimpleParser.CODE_END;
+ }
+
private String doCreateLiteralCode(CamelContext camelContext, String
expression) {
- SimpleFunctionExpression function = new
SimpleFunctionExpression(this.getToken(), cacheExpression, skipFileFunctions);
LiteralNode literal = (LiteralNode) block.getChildren().get(0);
- function.addText(literal.getText());
+ String text = literal.getText();
+
+ // Check if this is a ternary expression
+ String ternaryCode = tryParseTernaryCode(camelContext, text);
+ if (ternaryCode != null) {
+ return ternaryCode;
+ }
+
+ SimpleFunctionExpression function = new
SimpleFunctionExpression(this.getToken(), cacheExpression, skipFileFunctions);
+ function.addText(text);
return function.createCode(camelContext, expression);
}
+ /**
+ * Try to parse the text as a ternary expression and generate code.
Returns null if the text is not a ternary
+ * expression.
+ */
+ private String tryParseTernaryCode(CamelContext camelContext, String text)
{
+ // Find the ? operator (not inside quotes or nested ${})
+ int questionIdx = findTernaryOperator(text, '?');
+ if (questionIdx < 0) {
+ return null;
+ }
+
+ // Find the : operator after the ?
+ int colonIdx = findTernaryOperator(text.substring(questionIdx + 1),
':');
+ if (colonIdx < 0) {
+ return null;
+ }
+ colonIdx = questionIdx + 1 + colonIdx;
+
+ // Extract the three parts
+ String conditionText = text.substring(0, questionIdx).trim();
+ String trueText = text.substring(questionIdx + 1, colonIdx).trim();
+ String falseText = text.substring(colonIdx + 1).trim();
+
+ if (conditionText.isEmpty() || trueText.isEmpty() ||
falseText.isEmpty()) {
+ return null;
+ }
+
+ // The condition text needs to be wrapped with ${} for parsing
+ String predicateText = wrapFunctionsInCondition(conditionText);
+
+ // Parse the condition as a predicate and generate code
+ SimplePredicateParser predicateParser
+ = new SimplePredicateParser(camelContext, predicateText, true,
skipFileFunctions, null);
+ String conditionCode = predicateParser.parseCode();
+
+ // Parse the true and false values as expressions and generate code
+ String trueCode = parseValueCode(camelContext, trueText);
+ String falseCode = parseValueCode(camelContext, falseText);
+
+ return BaseSimpleParser.CODE_START + "ternary(exchange, " +
conditionCode + ", " + trueCode + ", " + falseCode
+ + ")" + BaseSimpleParser.CODE_END;
+ }
+
+ /**
+ * Parse a value as code. Handles quoted literals, functions, and null.
+ */
+ private String parseValueCode(CamelContext camelContext, String text) {
+ // Handle quoted strings - return as string literal
+ if ((text.startsWith("'") && text.endsWith("'")) ||
(text.startsWith("\"") && text.endsWith("\""))) {
+ String value = text.substring(1, text.length() - 1);
+ return "\"" + value + "\"";
+ }
+
+ // Handle null
+ if ("null".equals(text) || "${null}".equals(text)) {
+ return "null";
+ }
+
+ // Check if this is a nested ternary expression
+ String nestedTernary = tryParseTernaryCode(camelContext, text);
+ if (nestedTernary != null) {
+ // Remove the CODE_START and CODE_END markers for nested
expressions
+ String code = nestedTernary.replace(BaseSimpleParser.CODE_START,
"").replace(BaseSimpleParser.CODE_END, "");
+ return code;
+ }
+
+ // Handle function expressions (may or may not have ${})
+ String expText = text;
+ if (!text.startsWith("${")) {
+ expText = "${" + text + "}";
+ }
+ SimpleExpressionParser parser
+ = new SimpleExpressionParser(camelContext, expText, true,
skipFileFunctions, null);
+ String code = parser.parseCode();
+ // Remove the CODE_START and CODE_END markers
+ code = code.replace(BaseSimpleParser.CODE_START,
"").replace(BaseSimpleParser.CODE_END, "");
+ return code;
+ }
+
private String doCreateCompositeCode(CamelContext camelContext, String
expression) {
StringBuilder sb = new StringBuilder(256);
boolean quoteEmbeddedFunctions = false;
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/TernaryExpression.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/TernaryExpression.java
new file mode 100644
index 000000000000..708a8ca5dd19
--- /dev/null
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/TernaryExpression.java
@@ -0,0 +1,156 @@
+/*
+ * 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.Predicate;
+import org.apache.camel.language.simple.BaseSimpleParser;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.language.simple.types.SimpleToken;
+import org.apache.camel.support.ExpressionToPredicateAdapter;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+
+/**
+ * Represents a ternary expression in the AST.
+ * <p>
+ * Syntax: condition ? trueValue : falseValue
+ */
+public class TernaryExpression extends BaseSimpleNode {
+
+ private SimpleNode condition;
+ private SimpleNode trueValue;
+ private SimpleNode falseValue;
+
+ public TernaryExpression(SimpleToken token) {
+ super(token);
+ }
+
+ @Override
+ public String toString() {
+ return condition + " ? " + trueValue + " : " + falseValue;
+ }
+
+ public boolean acceptCondition(SimpleNode condition) {
+ this.condition = condition;
+ return true;
+ }
+
+ public boolean acceptTrueValue(SimpleNode trueValue) {
+ this.trueValue = trueValue;
+ return true;
+ }
+
+ public boolean acceptFalseValue(SimpleNode falseValue) {
+ this.falseValue = falseValue;
+ return true;
+ }
+
+ public SimpleNode getCondition() {
+ return condition;
+ }
+
+ public SimpleNode getTrueValue() {
+ return trueValue;
+ }
+
+ public SimpleNode getFalseValue() {
+ return falseValue;
+ }
+
+ @Override
+ public Expression createExpression(CamelContext camelContext, String
expression) {
+ if (condition == null) {
+ throw new SimpleParserException(
+ "Ternary operator ? has no condition at index " +
token.getIndex(), token.getIndex());
+ }
+ if (trueValue == null) {
+ throw new SimpleParserException(
+ "Ternary operator ? has no true value at index " +
token.getIndex(), token.getIndex());
+ }
+ if (falseValue == null) {
+ throw new SimpleParserException(
+ "Ternary operator : has no false value at index " +
token.getIndex(), token.getIndex());
+ }
+
+ // 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
+ if (trueValue instanceof LiteralExpression le) {
+ String text = le.getText();
+ String changed = StringHelper.removeLeadingAndEndingQuotes(text);
+ if (!changed.equals(text)) {
+ le.replaceText(changed);
+ }
+ }
+ if (falseValue instanceof LiteralExpression le) {
+ String text = le.getText();
+ String changed = StringHelper.removeLeadingAndEndingQuotes(text);
+ if (!changed.equals(text)) {
+ le.replaceText(changed);
+ }
+ }
+
+ final Expression conditionExp =
condition.createExpression(camelContext, expression);
+ final Expression trueExp = trueValue.createExpression(camelContext,
expression);
+ final Expression falseExp = falseValue.createExpression(camelContext,
expression);
+
+ return createTernaryExpression(camelContext, conditionExp, trueExp,
falseExp);
+ }
+
+ private Expression createTernaryExpression(
+ final CamelContext camelContext, final Expression conditionExp,
+ final Expression trueExp, final Expression falseExp) {
+ return new Expression() {
+ @Override
+ public <T> T evaluate(Exchange exchange, Class<T> type) {
+ // Convert condition to predicate
+ Predicate predicate =
ExpressionToPredicateAdapter.toPredicate(conditionExp);
+
+ if (predicate.matches(exchange)) {
+ return trueExp.evaluate(exchange, type);
+ } else {
+ return falseExp.evaluate(exchange, type);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return condition + " ? " + trueValue + " : " + falseValue;
+ }
+ };
+ }
+
+ @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 {
+ ObjectHelper.notNull(condition, "condition node", this);
+ ObjectHelper.notNull(trueValue, "trueValue node", this);
+ ObjectHelper.notNull(falseValue, "falseValue node", this);
+
+ final String conditionCode = condition.createCode(camelContext,
expression);
+ final String trueCode = trueValue.createCode(camelContext, expression);
+ final String falseCode = falseValue.createCode(camelContext,
expression);
+
+ return "ternary(exchange, " + conditionCode + ", " + trueCode + ", " +
falseCode + ")";
+ }
+
+}
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 7663e1d21cd3..eb78ea50c991 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
@@ -152,6 +152,13 @@ public final class SimpleTokenType {
return type == TokenType.numericValue;
}
+ /**
+ * Whether the type is ternary operator
+ */
+ public boolean isTernary() {
+ return type == TokenType.ternaryOperator;
+ }
+
@Override
public String toString() {
return value;
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/TernaryOperatorType.java
similarity index 54%
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/TernaryOperatorType.java
index e737ab809b09..75d919c36ab2 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/TernaryOperatorType.java
@@ -17,25 +17,34 @@
package org.apache.camel.language.simple.types;
/**
- * Classifications of known token types.
+ * Types of ternary operators supported
*/
-public enum TokenType {
+public enum TernaryOperatorType {
- whiteSpace,
- character,
- booleanValue,
- numericValue,
- nullValue,
- singleQuote,
- doubleQuote,
- minusValue,
- escape,
- functionStart,
- functionEnd,
- binaryOperator,
- otherOperator,
- unaryOperator,
- logicalOperator,
- eol
+ QUESTION,
+ COLON;
+
+ public static TernaryOperatorType asOperator(String text) {
+ if ("?".equals(text)) {
+ return QUESTION;
+ } else if (":".equals(text)) {
+ return COLON;
+ }
+ throw new IllegalArgumentException("Operator not supported: " + text);
+ }
+
+ public static String getOperatorText(TernaryOperatorType operator) {
+ if (operator == QUESTION) {
+ return "?";
+ } else if (operator == COLON) {
+ 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/TokenType.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
index e737ab809b09..ebd3f73e5818 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
@@ -36,6 +36,7 @@ public enum TokenType {
otherOperator,
unaryOperator,
logicalOperator,
+ ternaryOperator,
eol
}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
index eb3eb01d45ef..1e3981e971b6 100644
---
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
@@ -2209,6 +2209,91 @@ public class SimpleTest extends LanguageTestSupport {
assertExpression("${iif(${file:name} startsWith 'test',foo,bar)}",
"bar");
}
+ @Test
+ public void testTernaryOperator() {
+ // Test that the same expression object evaluates correctly with
different header values
+ exchange.getIn().setHeader("foo", 44);
+ Expression exp =
context.resolveLanguage("simple").createExpression("${header.foo > 0 ?
'positive' : 'negative'}");
+ assertEquals("positive", exp.evaluate(exchange, String.class), "First
evaluation with foo=44");
+
+ exchange.getIn().setHeader("foo", -123);
+ assertEquals("negative", exp.evaluate(exchange, String.class), "Second
evaluation with foo=-123");
+
+ // Test a simple ternary with a constant condition
+ Expression expTrue =
context.resolveLanguage("simple").createExpression("${true ? 'yes' : 'no'}");
+ assertEquals("yes", expTrue.evaluate(exchange, String.class),
"Constant true ternary");
+
+ Expression expFalse =
context.resolveLanguage("simple").createExpression("${false ? 'yes' : 'no'}");
+ assertEquals("no", expFalse.evaluate(exchange, String.class),
"Constant false ternary");
+
+ // Test with body
+ exchange.getIn().setBody("Hello World");
+ exchange.getIn().setHeader("foo", 44);
+ assertExpression("${header.foo > 0 ? ${body} : 'Bye World'}", "Hello
World");
+ exchange.getIn().setHeader("foo", -123);
+ assertExpression("${header.foo > 0 ? ${body} : 'Bye World'}", "Bye
World");
+ assertExpression("${header.foo > 0 ? ${body} : ${null}}", null);
+
+ // Test with file name
+ exchange.getIn().setHeader("CamelFileName", "testfile.txt");
+ assertExpression("${file:name startsWith 'test' ? 'foo' : 'bar'}",
"foo");
+ exchange.getIn().setHeader("CamelFileName", "dummy.txt");
+ assertExpression("${file:name startsWith 'test' ? 'foo' : 'bar'}",
"bar");
+ }
+
+ @Test
+ public void testTernaryOperatorWithNumbers() {
+ exchange.getIn().setHeader("score", 85);
+ assertExpression("${header.score >= 90 ? 'A' : 'B'}", "B");
+ exchange.getIn().setHeader("score", 95);
+ assertExpression("${header.score >= 90 ? 'A' : 'B'}", "A");
+
+ exchange.getIn().setHeader("age", 25);
+ assertExpression("${header.age >= 18 ? 'adult' : 'minor'}", "adult");
+ exchange.getIn().setHeader("age", 15);
+ assertExpression("${header.age >= 18 ? 'adult' : 'minor'}", "minor");
+ }
+
+ @Test
+ public void testTernaryOperatorWithBooleans() {
+ exchange.getIn().setHeader("enabled", true);
+ assertExpression("${header.enabled == true ? 'yes' : 'no'}", "yes");
+ exchange.getIn().setHeader("enabled", false);
+ assertExpression("${header.enabled == true ? 'yes' : 'no'}", "no");
+ }
+
+ @Test
+ public void testTernaryOperatorWithNull() {
+ exchange.getIn().setHeader("value", null);
+ assertExpression("${header.value == null ? 'empty' : 'full'}",
"empty");
+ exchange.getIn().setHeader("value", "something");
+ assertExpression("${header.value == null ? 'empty' : 'full'}", "full");
+ }
+
+ @Test
+ public void testTernaryOperatorNested() {
+ // Nested ternary operators
+ exchange.getIn().setHeader("score", 95);
+ assertExpression("${header.score >= 90 ? 'A' : ${header.score} >= 80 ?
'B' : 'C'}", "A");
+ exchange.getIn().setHeader("score", 85);
+ assertExpression("${header.score >= 90 ? 'A' : ${header.score} >= 80 ?
'B' : 'C'}", "B");
+ exchange.getIn().setHeader("score", 75);
+ assertExpression("${header.score >= 90 ? 'A' : ${header.score} >= 80 ?
'B' : 'C'}", "C");
+ }
+
+ @Test
+ public void testTernaryOperatorWithStrings() {
+ exchange.getIn().setBody("Hello");
+ assertExpression("${body == 'Hello' ? 'greeting' : 'other'}",
"greeting");
+ exchange.getIn().setBody("Goodbye");
+ assertExpression("${body == 'Hello' ? 'greeting' : 'other'}", "other");
+
+ exchange.getIn().setHeader("name", "John");
+ assertExpression("${header.name contains 'John' ? 'found' : 'not
found'}", "found");
+ exchange.getIn().setHeader("name", "Jane");
+ assertExpression("${header.name contains 'John' ? 'found' : 'not
found'}", "not found");
+ }
+
@Test
public void testListRemoveByInstance() {
List<Object> data = new ArrayList<>();