Skip to content

Commit 696a7ec

Browse files
authored
CAMEL-23048: camel-core - Simple init block to require using semi colon to end each variable (#21550)
1 parent 0694101 commit 696a7ec

16 files changed

Lines changed: 398 additions & 111 deletions

File tree

components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockFunctionTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ public class JsonPathSimpleInitBlockFunctionTest extends CamelTestSupport {
2525

2626
private final String MAPPING = """
2727
$init{
28-
$id := ${jsonpath($.id)}
29-
$type := ${header.type}
30-
$price := ${jsonpath($.amount)}
31-
$level ~:= ${body > 100 ? 'HIGH' : 'LOW'}
32-
$newStatus ~:= ${sum(${body},50)}
28+
$id := ${jsonpath($.id)};
29+
$type := ${header.type};
30+
$price := ${jsonpath($.amount)};
31+
$level ~:= ${body > 100 ? 'HIGH' : 'LOW'};
32+
$newStatus ~:= ${sum(${body},50)};
3333
}init$
3434
{
3535
"id": "$id",

components/camel-jsonpath/src/test/java/org/apache/camel/jsonpath/JsonPathSimpleInitBlockTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ public class JsonPathSimpleInitBlockTest extends CamelTestSupport {
2525

2626
private final String MAPPING = """
2727
$init{
28-
$id := ${jsonpath($.id)}
29-
$type := ${header.type}
30-
$price := ${jsonpath($.amount)}
31-
$level := ${iif(${jsonpath($.amount)} > 100,HIGH,LOW)}
28+
$id := ${jsonpath($.id)};
29+
$type := ${header.type};
30+
$price := ${jsonpath($.amount)};
31+
$level := ${iif(${jsonpath($.amount)} > 100,HIGH,LOW)};
3232
}init$
3333
{
3434
"id": "$id",
@@ -40,10 +40,10 @@ public class JsonPathSimpleInitBlockTest extends CamelTestSupport {
4040

4141
private final String MAPPING2 = """
4242
$init{
43-
$id := ${jsonpath($.id)}
44-
$type := ${header.type}
45-
$price := ${jsonpath($.amount)}
46-
$level := ${iif(${jsonpath($.amount)} > 100,HIGH,LOW)}
43+
$id := ${jsonpath($.id)};
44+
$type := ${header.type};
45+
$price := ${jsonpath($.amount)};
46+
$level := ${iif(${jsonpath($.amount)} > 100,HIGH,LOW)};
4747
}init$
4848
{
4949
"id": "$id",

core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,29 +1179,37 @@ $init{
11791179

11801180
Notice how the block uses the `$init{` ... `}init$` markers to indicate the start and end of the block.
11811181

1182-
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
1182+
Inside the init block, then you can assign local variables in the syntax `$key := <statement>;` where you can then use simple language to compute
11831183
the value of the variable.
11841184

1185+
IMPORTANT: Each statement must end with semicolon and new-line (`;\n`). This makes the init block more similar to Java programming language,
1186+
and it was also necessary to make this work for the internal simple parser used by Camel.
1187+
1188+
Here are a couple of examples:
1189+
11851190
[source,text]
11861191
----
11871192
$init{
1188-
$foo := ${upper('Hello $body}'}
1189-
$bar := ${header.code > 999 ? 'Gold' : 'Silver'}
1193+
$cheese := 'Hello ${body}';
1194+
$minAge := 18;
1195+
$foo := ${upper('Hello $body}'};
1196+
$bar := ${header.code > 999 ? 'Gold' : 'Silver'};
11901197
}init$
11911198
----
11921199

1193-
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.
1194-
11951200
You can have Java style code comments in the init block using `// comment here` as follows:
11961201

11971202
[source,text]
11981203
----
11991204
$init{
1205+
// minimum age to drive
1206+
$minAge := 18;
1207+
12001208
// say hello to my friend
1201-
$foo := ${upper('Hello $body}'}
1209+
$foo := ${upper('Hello $body}'};
12021210
12031211
// either gold or silver
1204-
$bar := ${header.code > 999 ? 'Gold' : 'Silver'}
1212+
$bar := ${header.code > 999 ? 'Gold' : 'Silver'};
12051213
}init$
12061214
----
12071215

@@ -1237,8 +1245,8 @@ from("direct:welcome")
12371245
.transform().simple(
12381246
"""
12391247
$init{
1240-
$greeting := ${upper('Hello $body}'}
1241-
$level := ${header.code > 999 ? 'Gold' : 'Silver'}
1248+
$greeting := ${upper('Hello $body}'};
1249+
$level := ${header.code > 999 ? 'Gold' : 'Silver'};
12421250
}init$
12431251
{
12441252
"message": "$greeting",
@@ -1258,8 +1266,8 @@ XML::
12581266
<transform>
12591267
<simple>
12601268
$init{
1261-
$greeting := ${upper('Hello $body}'}
1262-
$level := ${header.code > 999 ? 'Gold' : 'Silver'}
1269+
$greeting := ${upper('Hello $body}'};
1270+
$level := ${header.code > 999 ? 'Gold' : 'Silver'};
12631271
}init$
12641272
{
12651273
"message": "$greeting",
@@ -1283,8 +1291,8 @@ YAML::
12831291
simple:
12841292
expression: |-
12851293
$init{
1286-
$greeting := ${upper('Hello $body}'}
1287-
$level := ${header.code > 999 ? 'Gold' : 'Silver'}
1294+
$greeting := ${upper('Hello $body}'};
1295+
$level := ${header.code > 999 ? 'Gold' : 'Silver'};
12881296
}init$
12891297
{
12901298
"message": "$greeting",
@@ -1306,15 +1314,15 @@ and then refer to the file such as `resource:classpath:mymapping.txt` where the
13061314

13071315
You can also declare custom functions using
13081316

1309-
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
1317+
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
13101318
the structure of the function. Then you can later use these custom functions in your simple language expressions.
13111319

13121320
For example to create a function that can cleanup a `String` value:
13131321

13141322
[source,text]
13151323
----
13161324
$init{
1317-
$cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}
1325+
$cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()};
13181326
}init$
13191327
----
13201328

@@ -1343,20 +1351,20 @@ It is also possible to reuse custom functions from other functions using `${func
13431351
[source,text]
13441352
----
13451353
$init{
1346-
$cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}
1347-
$count ~:= ${function(cleanUp)} ~> ${split(' ')} ~> ${size()}
1354+
$cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()};
1355+
$count ~:= ${function(cleanUp)} ~> ${split(' ')} ~> ${size()};
13481356
}init$
13491357
----
13501358

13511359
Here the `$count` is declared as a function which calls the cleanUp function and then counts the words via split and size functions.
13521360

1353-
Instead of using `${function(name)}` you can also same syntax as the built-in functions, as follows:
1361+
Instead of using `${function(name)}` you can also same syntax as the built-in Simple functions, as follows:
13541362

13551363
[source,text]
13561364
----
13571365
$init{
1358-
$cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()}
1359-
$count ~:= ${cleanUp()} ~> ${split(' ')} ~> ${size()}
1366+
$cleanUp ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()};
1367+
$count ~:= ${cleanUp()} ~> ${split(' ')} ~> ${size()};
13601368
}init$
13611369
----
13621370

core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.ArrayDeque;
2020
import java.util.ArrayList;
21+
import java.util.Arrays;
2122
import java.util.Collections;
2223
import java.util.Deque;
2324
import java.util.List;
@@ -96,6 +97,19 @@ protected void nextToken() {
9697
}
9798
}
9899

100+
protected void skipToken() {
101+
if (index < expression.length()) {
102+
SimpleToken next = tokenizer.nextToken(expression, index, allowEscape);
103+
token = next;
104+
// position index after the token
105+
previousIndex = index;
106+
index += next.getLength();
107+
} else {
108+
// end of tokens
109+
token = new SimpleToken(new SimpleTokenType(TokenType.eol, null), index);
110+
}
111+
}
112+
99113
/**
100114
* Advances the parser position to the next known {@link SimpleToken} in the input.
101115
*
@@ -138,7 +152,7 @@ protected void clear() {
138152
* {@link org.apache.camel.Expression}s to be used by Camel then the AST graph has a linked and prepared graph of
139153
* nodes which represent the input expression.
140154
*/
141-
protected void prepareBlocks() {
155+
protected void prepareBlocks(List<SimpleNode> nodes) {
142156
List<SimpleNode> answer = new ArrayList<>();
143157
Deque<Block> stack = new ArrayDeque<>();
144158

@@ -191,7 +205,7 @@ private static void acceptOrAdd(List<SimpleNode> answer, Deque<Block> stack, Sim
191205
* {@link org.apache.camel.Expression}s to be used by Camel then the AST graph has a linked and prepared graph of
192206
* nodes which represent the input expression.
193207
*/
194-
protected void prepareUnaryExpressions() {
208+
protected void prepareUnaryExpressions(List<SimpleNode> nodes) {
195209
Deque<SimpleNode> stack = new ArrayDeque<>();
196210

197211
for (SimpleNode node : nodes) {
@@ -226,7 +240,7 @@ protected void prepareUnaryExpressions() {
226240
* 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
227241
* linked and prepared graph of nodes which represent the input expression.
228242
*/
229-
protected void prepareChainExpression() {
243+
protected void prepareChainExpression(List<SimpleNode> nodes) {
230244
Deque<SimpleNode> stack = new ArrayDeque<>();
231245

232246
SimpleNode left = null;
@@ -287,7 +301,7 @@ protected void prepareChainExpression() {
287301
Collections.reverse(nodes);
288302
}
289303

290-
protected void prepareOtherExpressions() {
304+
protected void prepareOtherExpressions(List<SimpleNode> nodes) {
291305
Deque<SimpleNode> stack = new ArrayDeque<>();
292306

293307
SimpleNode left = null;
@@ -350,7 +364,7 @@ protected void prepareOtherExpressions() {
350364
* The ternary operator consists of two tokens: ? and : We need to find the pattern: condition ? trueValue :
351365
* falseValue
352366
*/
353-
protected void prepareTernaryExpressions() {
367+
protected void prepareTernaryExpressions(List<SimpleNode> nodes) {
354368
List<SimpleNode> answer = new ArrayList<>();
355369

356370
for (int i = 0; i < nodes.size(); i++) {
@@ -434,6 +448,26 @@ protected boolean accept(TokenType accept) {
434448
return token == null || token.getType().getType() == accept;
435449
}
436450

451+
/**
452+
* Expect any of the given token(s)
453+
*
454+
* @param expect the token(s) to expect
455+
* @throws SimpleParserException is thrown if the token is not as expected
456+
*/
457+
protected void expect(TokenType... expect) throws SimpleParserException {
458+
if (token == null) {
459+
throw new SimpleParserException("expected any symbol " + Arrays.asList(expect) + " but reached eol", previousIndex);
460+
}
461+
for (TokenType target : expect) {
462+
if (token.getType().getType() == target) {
463+
return;
464+
}
465+
}
466+
// use the previous index as that is where the problem is
467+
throw new SimpleParserException(
468+
"expected any symbol " + Arrays.asList(expect) + " but was " + token.getType().getType(), previousIndex);
469+
}
470+
437471
/**
438472
* Expect a given token
439473
*

core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,13 @@ protected List<SimpleNode> parseTokens() {
160160
// turn the tokens into the ast model
161161
parseAndCreateAstModel();
162162
// compact and stack blocks (eg function start/end)
163-
prepareBlocks();
163+
prepareBlocks(nodes);
164164
// compact and stack unary operators
165-
prepareUnaryExpressions();
165+
prepareUnaryExpressions(nodes);
166166
// compact and stack chain expressions
167-
prepareChainExpression();
167+
prepareChainExpression(nodes);
168168
// compact and stack other expressions
169-
prepareOtherExpressions();
169+
prepareOtherExpressions(nodes);
170170

171171
return nodes;
172172
}
@@ -228,7 +228,7 @@ protected void removeIgnorableWhiteSpaceTokens() {
228228
}
229229
}
230230
if (cur.getType().isInitVariable()) {
231-
if (prev.getType().isWhitespace()) {
231+
if (prev.getType().isWhitespace() || " ".equals(prev.getText())) {
232232
toRemove.add(prev);
233233
}
234234
}
@@ -266,6 +266,14 @@ protected void parseAndCreateAstModel() {
266266
continue;
267267
}
268268

269+
if (token.getType().isInitVariable()) {
270+
// we start a new init variable so the current image token need to be added first
271+
if (imageToken != null) {
272+
nodes.add(imageToken);
273+
imageToken = null;
274+
}
275+
}
276+
269277
// if no token was created, then it's a character/whitespace/escaped symbol
270278
// which we need to add together in the same image
271279
if (imageToken == null) {

0 commit comments

Comments
 (0)