This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch semicolon in repository https://gitbox.apache.org/repos/asf/camel.git
commit 6d2a1aa2078751f0f4ec52ab0c1769eaea1ee3a7 Author: Claus Ibsen <[email protected]> AuthorDate: Sun Feb 22 16:27:26 2026 +0100 CAMEL-23048: camel-core - Simple init block to require using semi colon to end each variable --- .../JsonPathSimpleInitBlockFunctionTest.java | 10 +- .../jsonpath/JsonPathSimpleInitBlockTest.java | 16 +-- .../modules/languages/pages/simple-language.adoc | 48 ++++---- .../camel/language/simple/BaseSimpleParser.java | 44 +++++++- .../language/simple/SimpleExpressionParser.java | 18 ++- .../language/simple/SimpleInitBlockParser.java | 122 ++++++++++++++++----- .../language/simple/SimpleInitBlockTokenizer.java | 3 +- .../language/simple/SimplePredicateParser.java | 18 +-- .../language/simple/ast/LiteralExpression.java | 2 +- .../language/simple/types/SimpleTokenType.java | 7 ++ .../camel/language/simple/types/TokenType.java | 1 + .../simple/SimpleInitBlockFunctionTest.java | 22 ++-- .../camel/language/simple/SimpleInitBlockTest.java | 120 +++++++++++++++++--- .../ROOT/pages/camel-4x-upgrade-guide-4_18.adoc | 37 +++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_19.adoc | 37 +++++++ .../camel/dsl/yaml/SimpleInitBlockTest.groovy | 4 +- 16 files changed, 398 insertions(+), 111 deletions(-) diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockFunctionTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockFunctionTest.java index 195a40ce2fd8..546c0d3c7a3f 100644 --- a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockFunctionTest.java +++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockFunctionTest.java @@ -25,11 +25,11 @@ public class JsonPathSimpleInitBlockFunctionTest extends CamelTestSupport { private final String MAPPING = """ $init{ - $id := ${jsonpath($.id)} - $type := ${header.type} - $price := ${jsonpath($.amount)} - $level ~:= ${body > 100 ? 'HIGH' : 'LOW'} - $newStatus ~:= ${sum(${body},50)} + $id := ${jsonpath($.id)}; + $type := ${header.type}; + $price := ${jsonpath($.amount)}; + $level ~:= ${body > 100 ? 'HIGH' : 'LOW'}; + $newStatus ~:= ${sum(${body},50)}; }init$ { "id": "$id", diff --git a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockTest.java b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockTest.java index ff85426c7e44..556ca138462b 100644 --- a/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockTest.java +++ b/components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockTest.java @@ -25,10 +25,10 @@ public class JsonPathSimpleInitBlockTest extends CamelTestSupport { private final String MAPPING = """ $init{ - $id := ${jsonpath($.id)} - $type := ${header.type} - $price := ${jsonpath($.amount)} - $level := ${iif(${jsonpath($.amount)} > 100,HIGH,LOW)} + $id := ${jsonpath($.id)}; + $type := ${header.type}; + $price := ${jsonpath($.amount)}; + $level := ${iif(${jsonpath($.amount)} > 100,HIGH,LOW)}; }init$ { "id": "$id", @@ -40,10 +40,10 @@ public class JsonPathSimpleInitBlockTest extends CamelTestSupport { private final String MAPPING2 = """ $init{ - $id := ${jsonpath($.id)} - $type := ${header.type} - $price := ${jsonpath($.amount)} - $level := ${iif(${jsonpath($.amount)} > 100,HIGH,LOW)} + $id := ${jsonpath($.id)}; + $type := ${header.type}; + $price := ${jsonpath($.amount)}; + $level := ${iif(${jsonpath($.amount)} > 100,HIGH,LOW)}; }init$ { "id": "$id", 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 2fb5b93ba8d0..5ea6e5cc412b 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 @@ -1179,29 +1179,37 @@ $init{ Notice how the block uses the `$init{` ... `}init$` markers to indicate the start and end of the block. -Inside the init block, then you can assign local variables in the syntax `$key := function` where you can then use simple language function(s) to compute +Inside the init block, then you can assign local variables in the syntax `$key := <statement>;` where you can then use simple language to compute the value of the variable. +IMPORTANT: Each statement must end with semicolon and new-line (`;\n`). This makes the init block more similar to Java programming language, +and it was also necessary to make this work for the internal simple parser used by Camel. + +Here are a couple of examples: + [source,text] ---- $init{ - $foo := ${upper('Hello $body}'} - $bar := ${header.code > 999 ? 'Gold' : 'Silver'} + $cheese := 'Hello ${body}'; + $minAge := 18; + $foo := ${upper('Hello $body}'}; + $bar := ${header.code > 999 ? 'Gold' : 'Silver'}; }init$ ---- -IMPORTANT: The left hand side must be a function - you cannot assign a hardcoded literal value. Use the `val` function for this, such as `${val(123)}` to use `123` as the value. - You can have Java style code comments in the init block using `// comment here` as follows: [source,text] ---- $init{ + // minimum age to drive + $minAge := 18; + // say hello to my friend - $foo := ${upper('Hello $body}'} + $foo := ${upper('Hello $body}'}; // either gold or silver - $bar := ${header.code > 999 ? 'Gold' : 'Silver'} + $bar := ${header.code > 999 ? 'Gold' : 'Silver'}; }init$ ---- @@ -1237,8 +1245,8 @@ from("direct:welcome") .transform().simple( """ $init{ - $greeting := ${upper('Hello $body}'} - $level := ${header.code > 999 ? 'Gold' : 'Silver'} + $greeting := ${upper('Hello $body}'}; + $level := ${header.code > 999 ? 'Gold' : 'Silver'}; }init$ { "message": "$greeting", @@ -1258,8 +1266,8 @@ XML:: <transform> <simple> $init{ - $greeting := ${upper('Hello $body}'} - $level := ${header.code > 999 ? 'Gold' : 'Silver'} + $greeting := ${upper('Hello $body}'}; + $level := ${header.code > 999 ? 'Gold' : 'Silver'}; }init$ { "message": "$greeting", @@ -1283,8 +1291,8 @@ YAML:: simple: expression: |- $init{ - $greeting := ${upper('Hello $body}'} - $level := ${header.code > 999 ? 'Gold' : 'Silver'} + $greeting := ${upper('Hello $body}'}; + $level := ${header.code > 999 ? 'Gold' : 'Silver'}; }init$ { "message": "$greeting", @@ -1306,7 +1314,7 @@ and then refer to the file such as `resource:classpath:mymapping.txt` where the You can also declare custom functions using -Inside the init block, it is a lso possible to define custom functions in the syntax `$nane ~:= ...` where you can then use simple language to declare +Inside the init block, it is a lso possible to define custom functions in the syntax `$nane ~:= <statement>;` where you can then use simple language to declare the structure of the function. Then you can later use these custom functions in your simple language expressions. For example to create a function that can cleanup a `String` value: @@ -1314,7 +1322,7 @@ For example to create a function that can cleanup a `String` value: [source,text] ---- $init{ - $cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()} + $cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}; }init$ ---- @@ -1343,20 +1351,20 @@ It is also possible to reuse custom functions from other functions using `${func [source,text] ---- $init{ - $cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()} - $count ~:= ${function(cleanUp)} ~> ${split(' ')} ~> ${size()} + $cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}; + $count ~:= ${function(cleanUp)} ~> ${split(' ')} ~> ${size()}; }init$ ---- Here the `$count` is declared as a function which calls the cleanUp function and then counts the words via split and size functions. -Instead of using `${function(name)}` you can also same syntax as the built-in functions, as follows: +Instead of using `${function(name)}` you can also same syntax as the built-in Simple functions, as follows: [source,text] ---- $init{ - $cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()} - $count ~:= ${cleanUp()} ~> ${split(' ')} ~> ${size()} + $cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}; + $count ~:= ${cleanUp()} ~> ${split(' ')} ~> ${size()}; }init$ ---- 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 e821b04ec370..e181c12d5dd7 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 @@ -18,6 +18,7 @@ package org.apache.camel.language.simple; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Deque; import java.util.List; @@ -96,6 +97,19 @@ public abstract class BaseSimpleParser { } } + protected void skipToken() { + if (index < expression.length()) { + SimpleToken next = tokenizer.nextToken(expression, index, allowEscape); + token = next; + // position index after the token + previousIndex = index; + index += next.getLength(); + } else { + // end of tokens + token = new SimpleToken(new SimpleTokenType(TokenType.eol, null), index); + } + } + /** * Advances the parser position to the next known {@link SimpleToken} in the input. * @@ -138,7 +152,7 @@ public abstract class BaseSimpleParser { * {@link org.apache.camel.Expression}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 prepareBlocks() { + protected void prepareBlocks(List<SimpleNode> nodes) { List<SimpleNode> answer = new ArrayList<>(); Deque<Block> stack = new ArrayDeque<>(); @@ -191,7 +205,7 @@ public abstract class BaseSimpleParser { * {@link org.apache.camel.Expression}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 prepareUnaryExpressions() { + protected void prepareUnaryExpressions(List<SimpleNode> nodes) { Deque<SimpleNode> stack = new ArrayDeque<>(); for (SimpleNode node : nodes) { @@ -226,7 +240,7 @@ public abstract class BaseSimpleParser { * 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 prepareChainExpression() { + protected void prepareChainExpression(List<SimpleNode> nodes) { Deque<SimpleNode> stack = new ArrayDeque<>(); SimpleNode left = null; @@ -287,7 +301,7 @@ public abstract class BaseSimpleParser { Collections.reverse(nodes); } - protected void prepareOtherExpressions() { + protected void prepareOtherExpressions(List<SimpleNode> nodes) { Deque<SimpleNode> stack = new ArrayDeque<>(); SimpleNode left = null; @@ -350,7 +364,7 @@ public abstract class BaseSimpleParser { * The ternary operator consists of two tokens: ? and : We need to find the pattern: condition ? trueValue : * falseValue */ - protected void prepareTernaryExpressions() { + protected void prepareTernaryExpressions(List<SimpleNode> nodes) { List<SimpleNode> answer = new ArrayList<>(); for (int i = 0; i < nodes.size(); i++) { @@ -434,6 +448,26 @@ public abstract class BaseSimpleParser { return token == null || token.getType().getType() == accept; } + /** + * Expect any of the given token(s) + * + * @param expect the token(s) to expect + * @throws SimpleParserException is thrown if the token is not as expected + */ + protected void expect(TokenType... expect) throws SimpleParserException { + if (token == null) { + throw new SimpleParserException("expected any symbol " + Arrays.asList(expect) + " but reached eol", previousIndex); + } + for (TokenType target : expect) { + if (token.getType().getType() == target) { + return; + } + } + // use the previous index as that is where the problem is + throw new SimpleParserException( + "expected any symbol " + Arrays.asList(expect) + " but was " + token.getType().getType(), previousIndex); + } + /** * Expect a given token * 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 5ff3b4c4ec0c..018f2a1df742 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 @@ -160,13 +160,13 @@ public class SimpleExpressionParser extends BaseSimpleParser { // turn the tokens into the ast model parseAndCreateAstModel(); // compact and stack blocks (eg function start/end) - prepareBlocks(); + prepareBlocks(nodes); // compact and stack unary operators - prepareUnaryExpressions(); + prepareUnaryExpressions(nodes); // compact and stack chain expressions - prepareChainExpression(); + prepareChainExpression(nodes); // compact and stack other expressions - prepareOtherExpressions(); + prepareOtherExpressions(nodes); return nodes; } @@ -228,7 +228,7 @@ public class SimpleExpressionParser extends BaseSimpleParser { } } if (cur.getType().isInitVariable()) { - if (prev.getType().isWhitespace()) { + if (prev.getType().isWhitespace() || " ".equals(prev.getText())) { toRemove.add(prev); } } @@ -266,6 +266,14 @@ public class SimpleExpressionParser extends BaseSimpleParser { continue; } + if (token.getType().isInitVariable()) { + // we start a new init variable so the current image token need to be added first + if (imageToken != null) { + nodes.add(imageToken); + imageToken = null; + } + } + // if no token was created, then it's a character/whitespace/escaped symbol // which we need to add together in the same image if (imageToken == null) { diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java index a627321d29d0..b56ab155219a 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java @@ -24,10 +24,13 @@ import java.util.Set; import org.apache.camel.CamelContext; import org.apache.camel.Expression; +import org.apache.camel.language.simple.ast.CompositeNodes; import org.apache.camel.language.simple.ast.InitBlockExpression; +import org.apache.camel.language.simple.ast.LiteralExpression; import org.apache.camel.language.simple.ast.LiteralNode; import org.apache.camel.language.simple.ast.SimpleNode; import org.apache.camel.language.simple.types.InitOperatorType; +import org.apache.camel.language.simple.types.SimpleToken; import org.apache.camel.language.simple.types.TokenType; import org.apache.camel.util.StringHelper; @@ -96,11 +99,6 @@ class SimpleInitBlockParser extends SimpleExpressionParser { nextToken(); while (!token.getType().isEol()) { initText(); - functionText(); - unaryOperator(); - otherOperator(); - chainOperator(); - nextToken(); } // now after parsing, we need a bit of work to do, to make it easier to turn the tokens @@ -109,20 +107,13 @@ class SimpleInitBlockParser extends SimpleExpressionParser { // remove any ignore and ignorable white space tokens removeIgnorableWhiteSpaceTokens(); - // prepare for any local variables to use $$ syntax in simple expression // turn the tokens into the ast model parseAndCreateAstModel(); // compact and stack blocks (eg function start/end) - prepareBlocks(); - // compact and stack chain expressions - prepareChainExpression(); - // compact and stack unary operators - prepareUnaryExpressions(); - // compact and stack other expressions - prepareOtherExpressions(); + prepareBlocks(nodes); // compact and stack init blocks - prepareInitBlocks(); + prePrepareInitBlocks(nodes); return nodes; } @@ -131,20 +122,12 @@ class SimpleInitBlockParser extends SimpleExpressionParser { // $$name := <function> // $$name2 := <function> protected boolean initText() { - // has there been a new line since last (which reset and allow to look for new init variable) - if (!getTokenizer().hasNewLine()) { - return false; - } - - // turn on init mode so the parser can find the beginning of the init variable - getTokenizer().setAcceptInitTokens(true, index); + // skip until we find a new init variable while (!token.getType().isInitVariable() && !token.getType().isEol()) { - // skip until we find init variable/function (this skips code comments) - nextToken(TokenType.functionStart, TokenType.unaryOperator, TokenType.chainOperator, TokenType.otherOperator, - TokenType.initVariable, - TokenType.eol); + skipToken(); } if (accept(TokenType.initVariable)) { + tokens.add(token); while (!token.getType().isWhitespace() && !token.getType().isEol()) { nextToken(); } @@ -153,18 +136,99 @@ class SimpleInitBlockParser extends SimpleExpressionParser { expect(TokenType.initOperator); nextToken(); expectAndAcceptMore(TokenType.whiteSpace); - // turn off init mode so the parser does not detect init variables inside functions or literal text - // because they may also use := or $$ symbols - getTokenizer().setAcceptInitTokens(false, index); + // must either be a function, string literal, a number, or boolean + expect(TokenType.functionStart, TokenType.singleQuote, TokenType.doubleQuote, TokenType.numericValue, + TokenType.booleanValue); + + // accept until we find init function end + SimpleToken prev = token; + while (!token.getType().isEol() && !prev.getType().isInitFunctionEnd()) { + // an init variable supports functions, chain, unary and also whitespace as chain uses that between functions + nextToken(TokenType.functionStart, TokenType.functionEnd, TokenType.unaryOperator, TokenType.chainOperator, + TokenType.otherOperator, TokenType.whiteSpace, + TokenType.numericValue, TokenType.booleanValue, + TokenType.singleQuote, TokenType.doubleQuote, + TokenType.initFunctionEnd, TokenType.eol); + prev = token; + } + if (prev.getType().isInitFunctionEnd()) { + tokens.remove(prev); + } return true; } return false; } + protected void prePrepareInitBlocks(List<SimpleNode> nodes) { + List<SimpleNode> answer = new ArrayList<>(); + + for (int i = 1; i < nodes.size() - 1; i++) { + SimpleNode token = nodes.get(i); + if (token instanceof InitBlockExpression ie) { + answer.add(ie); + + SimpleNode prev = nodes.get(i - 1); + ie.acceptLeftNode(prev); + // remember which init variables we have created + if (prev instanceof LiteralNode ln) { + String key = StringHelper.after(ln.getText(), "$"); + if (key != null) { + key = key.trim(); + if (ie.getOperator().equals(InitOperatorType.CHAIN_ASSIGNMENT)) { + initFunctions.add(key); + } else { + initKeys.add(key); + } + } + } + + CompositeNodes cn = new CompositeNodes(ie.getToken()); + ie.acceptRightNode(cn); + int j = i + 1; + while (j < nodes.size()) { + SimpleNode next = nodes.get(j); + if (next instanceof InitBlockExpression || next.getToken().getType().isInitVariable()) { + break; + } else if (!next.getToken().getType().isInitFunctionEnd()) { + if (next instanceof LiteralExpression le) { + String text = unquote(le.getText()); + le.replaceText(text); + } + cn.addChild(next); + } + j++; + } + + // prepare the children + prepareChainExpression(cn.getChildren()); + prepareUnaryExpressions(cn.getChildren()); + prepareOtherExpressions(cn.getChildren()); + // if there are only 1 child then flatten and add directly to right hand side + if (cn.getChildren().size() == 1) { + ie.acceptRightNode(cn.getChildren().get(0)); + } + i = j; + } + } + nodes.clear(); + nodes.addAll(answer); + } + + private static String unquote(String text) { + if (text.startsWith("\"") || text.startsWith("'")) { + text = text.substring(1); + } + if (text.endsWith("\"") || text.endsWith("'")) { + text = text.substring(0, text.length() - 1); + } + return text; + } + + @Deprecated protected void prepareInitBlocks() { List<SimpleNode> answer = new ArrayList<>(); - for (int i = 1; i < nodes.size() - 2; i++) { + for (int i = 1; i < nodes.size() - 1; i++) { SimpleNode token = nodes.get(i); if (token instanceof InitBlockExpression ie) { SimpleNode prev = nodes.get(i - 1); diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockTokenizer.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockTokenizer.java index 285c10688924..628eb7868b49 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockTokenizer.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockTokenizer.java @@ -28,7 +28,7 @@ import org.apache.camel.language.simple.types.TokenType; public class SimpleInitBlockTokenizer extends SimpleTokenizer { // keep this number in sync with tokens list - private static final int NUMBER_OF_TOKENS = 3; + private static final int NUMBER_OF_TOKENS = 4; private static final SimpleTokenType[] INIT_TOKENS = new SimpleTokenType[NUMBER_OF_TOKENS]; @@ -42,6 +42,7 @@ public class SimpleInitBlockTokenizer extends SimpleTokenizer { INIT_TOKENS[0] = new SimpleTokenType(TokenType.initVariable, "$"); INIT_TOKENS[1] = new SimpleTokenType(TokenType.initOperator, ":="); INIT_TOKENS[2] = new SimpleTokenType(TokenType.initOperator, "~:="); + INIT_TOKENS[3] = new SimpleTokenType(TokenType.initFunctionEnd, ";\n"); } private boolean acceptInitTokens = true; // flag to turn on|off 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 0766704ed241..016e819faa3d 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 @@ -175,19 +175,19 @@ public class SimplePredicateParser extends BaseSimpleParser { // turn the tokens into the ast model parseTokensAndCreateNodes(); // compact and stack blocks (eg function start/end, quotes start/end, etc.) - prepareBlocks(); + prepareBlocks(nodes); // compact and stack unary expressions - prepareUnaryExpressions(); + prepareUnaryExpressions(nodes); // compact and stack chain expressions - prepareChainExpression(); + prepareChainExpression(nodes); // compact and stack binary expressions - prepareBinaryExpressions(); + prepareBinaryExpressions(nodes); // compact and stack ternary expressions - prepareTernaryExpressions(); + prepareTernaryExpressions(nodes); // compact and stack other expressions - prepareOtherExpressions(); + prepareOtherExpressions(nodes); // compact and stack logical expressions - prepareLogicalExpressions(); + prepareLogicalExpressions(nodes); return nodes; } @@ -459,7 +459,7 @@ public class SimplePredicateParser extends BaseSimpleParser { * 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. */ - private void prepareBinaryExpressions() { + private void prepareBinaryExpressions(List<SimpleNode> nodes) { Deque<SimpleNode> stack = new ArrayDeque<>(); SimpleNode left = null; @@ -522,7 +522,7 @@ public class SimplePredicateParser extends BaseSimpleParser { * 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. */ - private void prepareLogicalExpressions() { + private void prepareLogicalExpressions(List<SimpleNode> nodes) { Deque<SimpleNode> stack = new ArrayDeque<>(); SimpleNode left = null; 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 626ab79c5626..2207ed343afe 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,7 +43,7 @@ public class LiteralExpression extends BaseSimpleNode implements LiteralNode { this.text.append(text); } - void replaceText(String text) { + public void replaceText(String text) { this.text.setLength(0); addText(text); } 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 9a2d1dfa4772..63e14b5171de 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.initVariable; } + /** + * Whether the type is init function end + */ + public boolean isInitFunctionEnd() { + return type == TokenType.initFunctionEnd; + } + /** * Whether the type is a null 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/TokenType.java index 99bc5dbba43b..9b742f488f1b 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 @@ -39,6 +39,7 @@ public enum TokenType { logicalOperator, initOperator, initVariable, + initFunctionEnd, ternaryOperator, chainOperator, eol diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockFunctionTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockFunctionTest.java index 2194182e2354..993395b1e653 100644 --- a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockFunctionTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockFunctionTest.java @@ -23,53 +23,53 @@ public class SimpleInitBlockFunctionTest extends LanguageTestSupport { private static final String INIT1 = """ $init{ - $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()} + $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}; }init$ You said: $clean() """; private static final String INIT2 = """ $init{ - $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()} - $count ~:= ${function(clean)} ~> ${split(' ')} ~> ${size()} + $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}; + $count ~:= ${function(clean)} ~> ${split(' ')} ~> ${size()}; }init$ You said: $clean() in $count() words """; private static final String INIT3 = """ $init{ - $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()} + $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}; }init$ You said: ${clean(' Clean this text please ... ')} and then do something else """; private static final String INIT4 = """ $init{ - $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()} - $count ~:= ${quote()} ~> ${function(clean)} ~> ${unquote()} ~> ${trim()} ~> ${split(' ')} ~> ${size()} + $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}; + $count ~:= ${quote()} ~> ${function(clean)} ~> ${unquote()} ~> ${trim()} ~> ${split(' ')} ~> ${size()}; }init$ You said: $clean() in $count() words """; private static final String INIT5 = """ $init{ - $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()} - $count ~:= $clean() ~> ${split(' ')} ~> ${size()} + $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}; + $count ~:= ${clean()} ~> ${split(' ')} ~> ${size()}; }init$ You said: $clean() in $count() words """; private static final String INIT6 = """ $init{ - $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()} - $count ~:= ${quote()} ~> ${clean()} ~> ${unquote()} ~> ${trim()} ~> ${split(' ')} ~> ${size()} + $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}; + $count ~:= ${quote()} ~> ${clean()} ~> ${unquote()} ~> ${trim()} ~> ${split(' ')} ~> ${size()}; }init$ You said: $clean() in $count() words """; private static final String INIT7 = """ $init{ - $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()} + $clean ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}; }init$ You said: ${clean()} """; diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java index 65b0d2d06cb3..bc3e1d7301c8 100644 --- a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java @@ -28,9 +28,9 @@ public class SimpleInitBlockTest extends LanguageTestSupport { private static final String INIT = """ $init{ // this is a java like comment - $sum := ${sum(${header.lines},100)} + $sum := ${sum(${header.lines},100)}; - $sku := ${body contains 'Camel' ? '123' : '999'} + $sku := ${body contains 'Camel' ? '123' : '999'}; }init$ orderId=$sku,total=$sum """; @@ -38,9 +38,9 @@ public class SimpleInitBlockTest extends LanguageTestSupport { private static final String INIT2 = """ $init{ // this is a java like comment - $sum := ${sum(${header.lines},100)} + $sum := ${sum(${header.lines},100)}; - $sku := ${body contains 'Camel' ? '123' : '999'} + $sku := ${body contains 'Camel' ? '123' : '999'}; }init$ $sum > 200 && $sku != 999 """; @@ -48,18 +48,33 @@ public class SimpleInitBlockTest extends LanguageTestSupport { private static final String INIT3 = """ $init{ // this is a java like comment - $sum := ${sum(${header.lines},100)} + $sum := ${sum(${header.lines},100)}; - $sku := ${body contains 'Camel' ? '123' : '999'} + $sku := ${body contains 'Camel' ? '123' : '999'}; }init$ """; private static final String INIT4 = """ $init{ // this is a java like comment - $sum := ${sum(${header.lines},100)} + $sum := ${sum(${header.lines},100)}; - $sku := ${body contains 'Hi := Me $sku' ? '123' : '999'} + $sku := ${body contains 'Hi := Me $sku' ? '123' : '999'}; + }init$ + orderId=$sku,total=$sum + """; + + private static final String INIT5 = """ + $init{ + // this is a java like comment + $sum := ${sum(${header.lines},100)}; + + $sku := ${body contains 'Hi := Me $sku' + ? + '123' + : + '999' + }; }init$ orderId=$sku,total=$sum """; @@ -109,13 +124,21 @@ public class SimpleInitBlockTest extends LanguageTestSupport { assertExpression(exchange, INIT4, "orderId=123,total=208\n"); } + @Test + public void testInitBlockSpanLines() throws Exception { + exchange.getMessage().setBody("Hello Hi := Me $sku"); + exchange.getMessage().setHeader("lines", "76,34"); + + assertExpression(exchange, INIT5, "orderId=123,total=210\n"); + } + @Test public void testInitBlockAverageFunction() { String exp = """ $init{ - $a := ${body} - $b := ${header.foo} - $c := ${header.bar} + $a := ${body}; + $b := ${header.foo}; + $c := ${header.bar}; }init$ average: ${average($a,$b,$c)} """; @@ -133,9 +156,9 @@ public class SimpleInitBlockTest extends LanguageTestSupport { public void testInitBlockAverageVal() { String exp = """ $init{ - $a := ${val(4)} - $b := ${val(5)} - $c := ${val(6)} + $a := ${val(4)}; + $b := ${val(5)}; + $c := ${val(6)}; }init$ average: ${average($a,$b,$c)} """; @@ -145,11 +168,78 @@ public class SimpleInitBlockTest extends LanguageTestSupport { assertEquals("average: 5\n", s); } + @Test + public void testInitBlockAverageLiteral() { + String exp = """ + $init{ + $a := '5'; + $b := '6'; + $c := '7'; + }init$ + average: ${average($a,$b,$c)} + """; + + Expression expression = context.resolveLanguage("simple").createExpression(exp); + String s = expression.evaluate(exchange, String.class); + assertEquals("average: 6\n", s); + } + + @Test + public void testInitBlockAverageNumeric() { + String exp = """ + $init{ + $a := 6; + $b := 7; + $c := 8; + }init$ + ${average($a,$b,$c)} + """; + + Expression expression = context.resolveLanguage("simple").createExpression(exp); + String s = expression.evaluate(exchange, String.class); + assertEquals("7\n", s); + } + + @Test + public void testInitBlockBoolean() { + String exp = """ + $init{ + $a := true; + $b := false; + }init$ + ${body != null ? $a : $b} + """; + + Expression expression = context.resolveLanguage("simple").createExpression(exp); + String s = expression.evaluate(exchange, String.class); + assertEquals("true\n", s); + + exchange.getMessage().setBody(null); + s = expression.evaluate(exchange, String.class); + assertEquals("false\n", s); + } + + @Test + public void testInitBlockVal() { + String exp = """ + $init{ + $bar := ${val(Hi from ${body})}; + }init$ + $bar + """; + + exchange.getMessage().setBody("Camel"); + + Expression expression = context.resolveLanguage("simple").createExpression(exp); + String s = expression.evaluate(exchange, String.class); + assertEquals("Hi from Camel\n", s); + } + @Test public void testInitBlockConstant() { String exp = """ $init{ - $bar := ${val(Hi from ${body})} + $bar := 'Hi from ${body}'; }init$ $bar """; diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc index faf9f5c1cf37..68509821c942 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc @@ -13,9 +13,46 @@ See the xref:camel-upgrade-recipes-tool.adoc[documentation] page for details. == Upgrading from 4.18.0 to 4.18.1 +=== camel-yaml-io / camel-xml-io + In the YAML DSL we have renamed `routePolicy` to `routePolicyRef` on the `route` node, as that is the correct name. +=== camel-simple + +In the simple language then init blocks syntax has changed to require that each variable ends with a semicolon and new line (no trailing comments etc is allowed) + +For example + +[source,yaml] +---- + - setBody: + simple: + expression: |- + $init{ + // this is a java like comment + $sum := ${sum(${header.lines},100)} + + $sku := ${iif(${body} contains 'Camel',123,999)} + }init$ + orderId=$sku,total=$sum +---- + +Should be changed to have semicolons as shown below: + +[source,yaml] +---- + - setBody: + simple: + expression: |- + $init{ + // this is a java like comment + $sum := ${sum(${header.lines},100)}; + + $sku := ${iif(${body} contains 'Camel',123,999)}; + }init$ + orderId=$sku,total=$sum +---- == Upgrading Camel 4.17 to 4.18 diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_19.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_19.adoc index 05948c3420a5..1db893eae666 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_19.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_19.adoc @@ -20,6 +20,43 @@ uses `InOnly` pattern. Removed 2 deprecated methods in Java DSL for `throttler` EIP. +=== camel-simple + +In the simple language then init blocks syntax has changed to require that each variable ends with a semicolon and new line (no trailing comments etc is allowed) + +For example + +[source,yaml] +---- + - setBody: + simple: + expression: |- + $init{ + // this is a java like comment + $sum := ${sum(${header.lines},100)} + + $sku := ${iif(${body} contains 'Camel',123,999)} + }init$ + orderId=$sku,total=$sum +---- + +Should be changed to have semicolons as shown below: + +[source,yaml] +---- + - setBody: + simple: + expression: |- + $init{ + // this is a java like comment + $sum := ${sum(${header.lines},100)}; + + $sku := ${iif(${body} contains 'Camel',123,999)}; + }init$ + orderId=$sku,total=$sum +---- + + === camel-yaml-io / camel-xml-io In the YAML DSL we have renamed `routePolicy` to `routePolicyRef` on the `route` node, diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/SimpleInitBlockTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/SimpleInitBlockTest.groovy index cbc63a01d037..b24a58b38049 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/SimpleInitBlockTest.groovy +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/SimpleInitBlockTest.groovy @@ -33,9 +33,9 @@ class SimpleInitBlockTest extends YamlTestSupport { expression: |- $init{ // this is a java like comment - $sum := ${sum(${header.lines},100)} + $sum := ${sum(${header.lines},100)}; - $sku := ${iif(${body} contains 'Camel',123,999)} + $sku := ${iif(${body} contains 'Camel',123,999)}; }init$ orderId=$sku,total=$sum - to:
