This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24962 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 3bd9ca7e4aadb5b53dc9396d9e58afe5bc82e10a Author: Claus Ibsen <[email protected]> AuthorDate: Wed Sep 23 18:59:58 2026 +0200 CAMEL-24965: simple - fix init block comments, one-liners, CRLF, predicates and local functions - a // comment in the init block is ignored, so a commented out assignment no longer runs - several statements on one line work ($init{ $a := 1; $b := 2; }init$), and Windows line endings - a predicate whose init block only defines functions ($f ~:= ...) parses, and $f() works in predicates - a custom function from an init block is not shared through the cache with another expression that defines a function with the same name - $fn('x') in a chain keeps its argument - errors in the init block point at the right place - sync the catalog copies of the simple docs and metadata Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../apache/camel/catalog/docs/simple-advanced.adoc | 2 +- .../camel/catalog/docs/simple-functions.adoc | 7 +- .../org/apache/camel/catalog/languages/simple.json | 2 +- .../modules/languages/pages/simple-advanced.adoc | 2 +- .../language/simple/MiscExpressionBuilder.java | 1 - .../language/simple/SimpleExpressionParser.java | 26 +---- .../language/simple/SimpleInitBlockParser.java | 128 +++++++++++++++------ .../language/simple/SimplePredicateParser.java | 20 +--- .../language/simple/StringExpressionBuilder.java | 1 - .../camel/language/simple/ast/ChainExpression.java | 2 +- .../simple/ast/SimpleFunctionExpression.java | 4 +- .../simple/SimpleInitBlockEdgeCasesTest.java | 111 ++++++++++++++++++ 12 files changed, 220 insertions(+), 86 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc index daed623fcfef..1e22bcd72c3e 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc @@ -29,7 +29,7 @@ Notice how the block uses the `$init{` ... `}init$` markers to indicate the star 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`). You can only have 1 statement per line. +IMPORTANT: Each statement must end with a semicolon (`;`). Statements are usually one per line, but several statements can also be on one line when separated by a space, such as `$init{ $a := 1; $b := 2; }init$`. 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. diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc index 525d77ecd2f1..41d80e7bbcf8 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc @@ -520,7 +520,7 @@ The string functions is various functions to work with values that are String an |`lowercase(exp)` | `String` | Lowercases the expression |`normalizeWhitespace()` | `String` | Normalizes the whitespace in the message body by cleaning up excess whitespaces. |`normalizeWhitespace(exp)` | `String` | Normalizes the whitespace in the expression by cleaning up excess whitespaces. -|`pad(exp,width,separator)` | `String` | Pads the expression with extra padding if necessary, according the total width The separator is by default a space. If the width is negative then padding to the right, otherwise to the left. +|`pad(exp,width,separator)` | `String` | Pads the expression with extra padding if necessary, according to the total width. The separator is by default a space. A positive width pads to the right (after the value), and a negative width pads to the left (before the value). |`replace(from,to)` | `String` | Replace all the string values in the message body. To make it easier to replace single and double quotes, then you can use XML escaped values `\"` as double quote, `\'` as single quote, and `\∅` as empty value. |`replace(from,to,exp)` | `String` | Replace all the string values in the given expression. To make it easier to replace single and double quotes, then you can use XML escaped values `\"` as double quote, `\'` as single quote, and `\∅` as empty value. |`safeQuote()` | `String` | Returns the message body safely quoted if needed @@ -636,13 +636,16 @@ If the value is currently in single quote, then this will be converted to double If the message body contains `Hello World` then `${quote()}` returns `"Hello World"`. And using `${quote('Hi from me')}` then `"Hi from me"` is returned. +The `quote` function does not escape the value, so a value with double quotes or backslashes is not a valid JSON string; +use `safeQuote` (or `escape(json)`) for JSON. The `unquote` function removes any surrounding single or double quotes from a value. If the body contains `'hello'` then `${unquote()}` returns `hello` (without the quotes). The `safeQuote` function is quoting the value depending on the value type (uses the same logic as `kindOfType` function). In essence values that are null, boolean or numbers are not quoted, -while everything else is. The `safeQuote` function is useful when working with JSON data. +while everything else is. When quoted, double quotes, backslashes and control characters in the value are escaped, +so the result is a valid JSON string. The `safeQuote` function is useful when working with JSON data. For example when doing JSON to JSON mapping you can extract values form the source document, to be included in the output, but the values may or may not need to be quoted. Then you can use the `~>` chain operator diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json index a1df2fe174f9..dfc3d543b094 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json @@ -131,7 +131,7 @@ "not": { "index": 102, "kind": "function", "displayName": "Not", "group": "condition", "label": "condition", "required": false, "javaType": "boolean", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Evaluates the predicate and returns the opposite.", "ognl": false, "suffix": "}" }, "null": { "index": 103, "kind": "function", "displayName": "Null", "group": "other", "label": "other", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a null value", "ognl": false, "suffix": "}", "examples": [ "${null} -> null" ] }, "originalBody": { "index": 104, "kind": "function", "displayName": "Original Body", "group": "core", "label": "core", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The original incoming body (only available if allowUseOriginalMessage=true).", "ognl": false, "suffix": "}" }, - "pad(exp,width,separator)": { "index": 105, "kind": "function", "displayName": "Pad String", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Pads the expression with extra padding if necessary, according the the total width. The separator is by default a space. If the width is negative then padding to the right, otherwise to the left.", "ognl [...] + "pad(exp,width,separator)": { "index": 105, "kind": "function", "displayName": "Pad String", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Pads the expression with extra padding if necessary, according to the total width. The separator is by default a space. A positive width pads to the right (after the value), and a negative width pads to [...] "pretty(exp)": { "index": 106, "kind": "function", "displayName": "Pretty Print", "group": "xml", "label": "json,xml", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Converts the expression to a String, and attempts to pretty print if JSon or XML, otherwise the expression is returned as the String value.", "ognl": false, "suffix": "}", "params": [ { "name": "exp", "javaType": "O [...] "prettyBody": { "index": 107, "kind": "function", "displayName": "Pretty Body", "group": "xml", "label": "json,xml", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Converts the body to a String, and attempts to pretty print if JSon or XML; otherwise the body is returned as the String value.", "ognl": false, "suffix": "}" }, "sort(exp,reverse)": { "index": 108, "kind": "function", "displayName": "Sort", "group": "collection", "label": "collection", "required": false, "javaType": "List", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sorts the message body or expression in natural order", "ognl": false, "suffix": "}", "params": [ { "name": "exp", "javaType": "Object", "required": false, "defaultValue": "body", "description": "The expression [...] diff --git a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc index daed623fcfef..1e22bcd72c3e 100644 --- a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc +++ b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc @@ -29,7 +29,7 @@ Notice how the block uses the `$init{` ... `}init$` markers to indicate the star 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`). You can only have 1 statement per line. +IMPORTANT: Each statement must end with a semicolon (`;`). Statements are usually one per line, but several statements can also be on one line when separated by a space, such as `$init{ $a := 1; $b := 2; }init$`. 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. diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java index 5adb2c781128..6e497e5dc05f 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java @@ -27,7 +27,6 @@ import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; -import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; 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 eacaf07a7e8e..93809e9e5f08 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 @@ -17,7 +17,6 @@ package org.apache.camel.language.simple; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -81,31 +80,8 @@ public class SimpleExpressionParser extends BaseSimpleParser { if (SimpleInitBlockTokenizer.hasInitBlock(expression)) { SimpleInitBlockParser initParser = new SimpleInitBlockParser(camelContext, expression, allowEscape, skipFileFunctions, cacheExpression); - // the init block should be parsed in predicate mode as that is needed to fully parse with all the operators and functions init = initParser.parseExpression(); - String part = StringHelper.after(expression, SimpleInitBlockTokenizer.INIT_END); - if (part.startsWith("\n")) { - // skip newline after ending init block - part = part.substring(1); - } - this.expression = part; - // use $$key as local variable in the expression afterwards. - // Sort by descending length so a longer key (e.g. "$ab") is replaced before any - // shorter prefix (e.g. "$a"), preventing "$ab" from becoming "${variable.a}b". - List<String> sortedKeys = new ArrayList<>(initParser.getInitKeys()); - sortedKeys.sort(Comparator.comparingInt(String::length).reversed()); - for (String key : sortedKeys) { - this.expression = this.expression.replace("$" + key, "${variable." + key + "}"); - } - // use $$key() as local function in the expression afterwards - List<String> sortedFunctions = new ArrayList<>(initParser.getInitFunctions()); - sortedFunctions.sort(Comparator.comparingInt(String::length).reversed()); - for (String key : sortedFunctions) { - // no-arg functions - this.expression = this.expression.replace("$" + key + "()", "${function(" + key + ")}"); - // arg functions - this.expression = this.expression.replace("${" + key + "(", "${function(" + key + ","); - } + this.expression = initParser.rewriteExpressionAfterInitBlock(expression); } // parse simple expression 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 8bbc06dfdeff..75a509ce4c19 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 @@ -17,6 +17,7 @@ package org.apache.camel.language.simple; import java.util.ArrayList; +import java.util.Comparator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -30,6 +31,7 @@ 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.SimpleParserException; import org.apache.camel.language.simple.types.SimpleToken; import org.apache.camel.language.simple.types.TokenType; import org.apache.camel.util.StringHelper; @@ -42,10 +44,93 @@ class SimpleInitBlockParser extends SimpleExpressionParser { public SimpleInitBlockParser(CamelContext camelContext, String expression, boolean allowEscape, boolean skipFileFunctions, Map<String, Expression> cacheExpression) { super(camelContext, - StringHelper.between(expression, SimpleInitBlockTokenizer.INIT_START, SimpleInitBlockTokenizer.INIT_END), + normalize(StringHelper.between(expression, SimpleInitBlockTokenizer.INIT_START, + SimpleInitBlockTokenizer.INIT_END)), allowEscape, skipFileFunctions, cacheExpression, new SimpleInitBlockTokenizer()); } + /** + * Prepares the text of the init block for the tokenizer, keeping every character at its index so errors point at + * the right place. + * <ul> + * <li>a // comment (outside quotes and functions) is blanked out, so a commented out assignment does not run</li> + * <li>Windows line endings are turned into unix line endings</li> + * <li>a ; followed by a space ends the statement as a new line does, so one-liners such as {@code $init{ $a := 1; + * $b := 2; }init$} work</li> + * </ul> + */ + static String normalize(String text) { + if (text == null) { + return null; + } + char[] chars = text.toCharArray(); + boolean single = false; + boolean dubble = false; + int depth = 0; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + char next = i + 1 < chars.length ? chars[i + 1] : 0; + if (c == '\r' && next == '\n') { + // keep the new line right after the statement end (;) + chars[i] = '\n'; + chars[i + 1] = ' '; + } else if (c == '\'' && !dubble) { + single = !single; + } else if (c == '"' && !single) { + dubble = !dubble; + } else if (single || dubble) { + continue; + } else if (c == '$' && next == '{') { + depth++; + } else if (c == '}' && depth > 0) { + depth--; + } else if (depth == 0 && c == '/' && next == '/') { + // blank out the comment until the end of the line + while (i < chars.length && chars[i] != '\n') { + chars[i++] = ' '; + } + } else if (depth == 0 && c == ';' && (next == ' ' || next == '\t')) { + chars[i + 1] = '\n'; + } + } + String answer = new String(chars); + if (answer.stripTrailing().endsWith(";")) { + // the last statement ends without a new line + answer = answer.stripTrailing() + "\n"; + } + return answer; + } + + /** + * The expression after the init block, where the init variables ($key) and init functions ($key() and ${key(...)}) + * are rewritten to ${variable.key} and ${function(key)}. + */ + String rewriteExpressionAfterInitBlock(String expression) { + String answer = StringHelper.after(expression, SimpleInitBlockTokenizer.INIT_END); + if (answer.startsWith("\r\n")) { + // skip newline after ending init block + answer = answer.substring(2); + } else if (answer.startsWith("\n")) { + answer = answer.substring(1); + } + // Sort by descending length so a longer key (e.g. "$ab") is replaced before any + // shorter prefix (e.g. "$a"), preventing "$ab" from becoming "${variable.a}b". + List<String> sortedKeys = new ArrayList<>(initKeys); + sortedKeys.sort(Comparator.comparingInt(String::length).reversed()); + for (String key : sortedKeys) { + answer = answer.replace("$" + key, "${variable." + key + "}"); + } + List<String> sortedFunctions = new ArrayList<>(initFunctions); + sortedFunctions.sort(Comparator.comparingInt(String::length).reversed()); + for (String key : sortedFunctions) { + // no-arg functions + answer = answer.replace("$" + key + "()", "${function(" + key + ")}"); + // arg functions + answer = answer.replace("${" + key + "(", "${function(" + key + ","); + } + return answer; + } + public Set<String> getInitKeys() { return initKeys; } @@ -60,9 +145,14 @@ class SimpleInitBlockParser extends SimpleExpressionParser { @Override public Expression parseExpression() { - // parse init block - parseInitTokens(); - return doParseInitExpression(); + try { + // parse init block + parseInitTokens(); + return doParseInitExpression(); + } catch (SimpleParserException e) { + // the index is within the init block, so make it point into the whole expression + throw new SimpleParserException(e.getMessage(), e.getIndex() + SimpleInitBlockTokenizer.INIT_START.length()); + } } protected List<SimpleNode> parseInitTokens() { @@ -201,34 +291,4 @@ class SimpleInitBlockParser extends SimpleExpressionParser { return text; } - @Deprecated - protected void prepareInitBlocks() { - List<SimpleNode> answer = new ArrayList<>(); - 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); - SimpleNode next = nodes.get(i + 1); - ie.acceptLeftNode(prev); - ie.acceptRightNode(next); - answer.add(ie); - - // 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); - } - } - } - } - } - nodes.clear(); - nodes.addAll(answer); - } - } 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 6cea00cfba2b..c9bde8c99ae8 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 @@ -19,7 +19,6 @@ package org.apache.camel.language.simple; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.Deque; import java.util.Iterator; import java.util.List; @@ -58,7 +57,6 @@ import org.apache.camel.language.simple.types.SimpleToken; import org.apache.camel.language.simple.types.TokenType; import org.apache.camel.support.ExpressionToPredicateAdapter; import org.apache.camel.support.builder.PredicateBuilder; -import org.apache.camel.util.StringHelper; /** * A parser to parse simple language as a Camel {@link Predicate} @@ -91,22 +89,8 @@ public class SimplePredicateParser extends BaseSimpleParser { SimpleInitBlockParser initParser = new SimpleInitBlockParser(camelContext, expression, allowEscape, skipFileFunctions, cacheExpression); init = initParser.parseExpression(); - if (init != null) { - String part = StringHelper.after(expression, SimpleInitBlockTokenizer.INIT_END); - if (part.startsWith("\n")) { - // skip newline after ending init block - part = part.substring(1); - } - this.expression = part; - // use $$key as local variable in the expression afterwards. - // Sort by descending length so a longer key (e.g. "$ab") is replaced before any - // shorter prefix (e.g. "$a"), preventing "$ab" from becoming "${variable.a}b". - List<String> sortedKeys = new ArrayList<>(initParser.getInitKeys()); - sortedKeys.sort(Comparator.comparingInt(String::length).reversed()); - for (String key : sortedKeys) { - this.expression = this.expression.replace("$" + key, "${variable." + key + "}"); - } - } + // the init block may only define functions ($f ~:= ...) and then there is no init expression + this.expression = initParser.rewriteExpressionAfterInitBlock(expression); } parseTokens(); diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/StringExpressionBuilder.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/StringExpressionBuilder.java index 2c66feb6d0eb..e6bbf4d0bed6 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/StringExpressionBuilder.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/StringExpressionBuilder.java @@ -31,7 +31,6 @@ import org.apache.camel.support.builder.ExpressionBuilder; import org.apache.camel.support.builder.PredicateBuilder; import org.apache.camel.util.EscapeHelper; import org.apache.camel.util.IOHelper; -import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.StringHelper; import org.apache.camel.util.StringQuoteHelper; diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java index b383d0772cd9..34847991d205 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java @@ -134,7 +134,7 @@ public class ChainExpression extends BaseSimpleNode { if (PluginHelper.getSimpleFunctionRegistry(camelContext).getCustomFunctionNames().contains(key)) { String changed = text.replace("$" + key + "()", "function(" + key + ")"); if (changed.equals(text)) { - changed = text.replace("$" + key + "(", "function(" + key); + changed = text.replace("$" + key + "(", "function(" + key + ","); } le.replaceText(changed); diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java index 0b8f61958605..dc5204024177 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java @@ -65,7 +65,9 @@ public class SimpleFunctionExpression extends LiteralExpression { if (answer != null) { answer.init(camelContext); } - if (cacheExpression != null && answer != null) { + // a custom function from an init block ($f ~:= ...) is bound to the definition of that block, + // so it is not shared with another expression that defines a function with the same name + if (cacheExpression != null && answer != null && !function.startsWith("function(")) { cacheExpression.put(function, answer); } } diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockEdgeCasesTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockEdgeCasesTest.java new file mode 100644 index 000000000000..31f7fe420f56 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockEdgeCasesTest.java @@ -0,0 +1,111 @@ +/* + * 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; + +import org.apache.camel.LanguageTestSupport; +import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * CAMEL-24965: edge cases of the init block. + */ +public class SimpleInitBlockEdgeCasesTest extends LanguageTestSupport { + + @Override + protected String getLanguageName() { + return "simple"; + } + + @Test + public void testCommentedOutAssignmentDoesNotRun() { + assertExpression(""" + $init{ + // $foo := 'x'; + $bar := 'y'; + }init$ + [${variable.foo}][$bar]""", "[][y]"); + } + + @Test + public void testCommentWithDollar() { + assertExpression(""" + $init{ + // costs $5 + $bar := 'y'; // a trailing comment + }init$ + [$bar]""", "[y]"); + } + + @Test + public void testCommentInsideQuotesIsText() { + assertExpression(""" + $init{ + $url := 'http://camel.apache.org'; + }init$ + $url""", "http://camel.apache.org"); + } + + @Test + public void testOneLiner() { + assertExpression("$init{ $minAge := 18; }init$[$minAge]", "[18]"); + assertExpression("$init{ $a := 'x'; $b := 'y'; }init$[$a$b]", "[xy]"); + // a ; inside a function or quotes is not the end of the statement + assertExpression("$init{ $a := 'x; y'; }init$[$a]", "[x; y]"); + } + + @Test + public void testWindowsLineEndings() { + assertExpression("$init{\r\n $a := 'x';\r\n $b := 'y';\r\n}init$\r\n[$a$b]", "[xy]"); + } + + @Test + public void testPredicateWithOnlyFunctions() { + exchange.getMessage().setBody(" A "); + assertPredicate(""" + $init{ + $clean ~:= ${trim()}; + }init$ + $clean() == 'A'""", true); + } + + @Test + public void testCustomFunctionIsLocalToTheExpression() { + exchange.getMessage().setBody(" abc "); + assertExpression(""" + $init{ + $f ~:= ${trim()}; + }init$ + [$f()]""", "[abc]"); + assertExpression(""" + $init{ + $f ~:= ${uppercase()}; + }init$ + [$f()]""", "[ ABC ]"); + } + + @Test + public void testErrorPointsIntoTheInitBlock() { + String text = "$init{\n $a = 5;\n}init$"; + SimpleIllegalSyntaxException e = assertThrows(SimpleIllegalSyntaxException.class, + () -> context.resolveLanguage("simple").createExpression(text)); + // the error is at the = which is not an init operator + assertEquals(text.indexOf('='), e.getIndex()); + } +}
