This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new 26aff3ce211 CAMEL-21818: camel-core: Add list/map functions to simple/csimple languages 26aff3ce211 is described below commit 26aff3ce211c73b634955b20d73a34b49b198e29 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Fri Feb 28 20:46:50 2025 +0100 CAMEL-21818: camel-core: Add list/map functions to simple/csimple languages --- .../org/apache/camel/catalog/languages/simple.json | 20 +++--- .../language/csimple/joor/OriginalSimpleTest.java | 17 +++++ .../org/apache/camel/language/simple/simple.json | 20 +++--- .../modules/languages/pages/simple-language.adoc | 6 +- .../camel/language/csimple/CSimpleHelper.java | 19 ++++++ .../camel/language/simple/SimpleConstants.java | 8 ++- .../language/simple/SimpleExpressionBuilder.java | 78 ++++++++++++++++++++++ .../simple/ast/SimpleFunctionExpression.java | 60 +++++++++++++++++ .../apache/camel/language/simple/SimpleTest.java | 17 +++++ 9 files changed, 225 insertions(+), 20 deletions(-) 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 86bacbf7bdd..dc32cebe45e 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 @@ -75,15 +75,17 @@ "messageHistory(boolean)": { "index": 50, "kind": "function", "displayName": "Print Message History", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The message history of the current exchange (how it has been routed). This is similar to the route stack-trace message history the error handler logs in case of an unhandled exception. The b [...] "uuid(type)": { "index": 51, "kind": "function", "displayName": "Generate UUID", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a UUID using the Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and `simple` as the type. If no type is given, the default is used. It is also possible to use a custom `UuidG [...] "hash(exp,algorithm)": { "index": 52, "kind": "function", "displayName": "Compute Hash Value", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a hashed value (string in hex decimal) of the message body\/expression using JDK MessageDigest. The algorithm can be SHA-256 (default) or SHA3-256.", "ognl": false, "suffix": "}" }, - "empty(type)": { "index": 53, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.HashMap`.", "ognl": false [...] + "empty(type)": { "index": 53, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.LinkedHashMap`.", "ognl": [...] "iif(predicate,trueExp,falseExp)": { "index": 54, "kind": "function", "displayName": "If Then Else", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Evaluates the predicate and returns the value of trueExp or falseExp. This function is similar to the ternary operator in Java.", "ognl": false, "suffix": "}" }, - "attachments": { "index": 55, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" }, - "attachments.size": { "index": 56, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" }, - "attachmentContentAsText": { "index": 57, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" }, - "attachmentContent": { "index": 58, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" }, - "attachmentContentAs(type)": { "index": 59, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" }, - "attachmentHeader(key,name)": { "index": 60, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" }, - "attachmentHeader(key,name,type)": { "index": 61, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" }, - "attachment(key)": { "index": 62, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" } + "list(val...)": { "index": 55, "kind": "function", "displayName": "Create List of values", "group": "function", "label": "function", "required": false, "javaType": "java.util.ArrayList", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list function creates an ArrayList with the given set of values.", "ognl": false, "suffix": "}" }, + "map(key1,value1,...)": { "index": 56, "kind": "function", "displayName": "Create Map of pairs", "group": "function", "label": "function", "required": false, "javaType": "java.util.LinkedHashMap", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The map function creates a LinkedHashMap with the given set of pairs.", "ognl": false, "suffix": "}" }, + "attachments": { "index": 57, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" }, + "attachments.size": { "index": 58, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" }, + "attachmentContentAsText": { "index": 59, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" }, + "attachmentContent": { "index": 60, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" }, + "attachmentContentAs(type)": { "index": 61, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" }, + "attachmentHeader(key,name)": { "index": 62, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" }, + "attachmentHeader(key,name,type)": { "index": 63, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" }, + "attachment(key)": { "index": 64, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" } } } 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 68cb1ff109d..b81c45826d5 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 @@ -2002,6 +2002,23 @@ public class OriginalSimpleTest extends LanguageTestSupport { assertThrows(ExpressionEvaluationException.class, () -> evaluateExpression("${empty(unknownType)}", null)); } + @Test + public void testList() { + exchange.getMessage().setBody("4"); + assertExpression("${list(1,2,3)}", "[1, 2, 3]"); + assertExpression("${list(1,2,3,${body})}", "[1, 2, 3, 4]"); + assertExpression("${list('a','b','c')}", "[a, b, c]"); + assertExpression("${list()}", "[]"); + } + + @Test + public void testMap() { + exchange.getMessage().setBody("d"); + assertExpression("${map(1,'a',2,'b',3,'c')}", "{1=a, 2=b, 3=c}"); + assertExpression("${map(1,'a',2,'b',3,'c',4,${body})}", "{1=a, 2=b, 3=c, 4=d}"); + assertExpression("${map()}", "{}"); + } + private void assertExpressionCreateNewEmpty( String type, Class<?> expectedClass, java.util.function.Predicate<Object> isEmptyAssertion) { Object value = evaluateExpression("${empty(%s)}".formatted(type), null); diff --git a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json index 86bacbf7bdd..dc32cebe45e 100644 --- a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json +++ b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json @@ -75,15 +75,17 @@ "messageHistory(boolean)": { "index": 50, "kind": "function", "displayName": "Print Message History", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The message history of the current exchange (how it has been routed). This is similar to the route stack-trace message history the error handler logs in case of an unhandled exception. The b [...] "uuid(type)": { "index": 51, "kind": "function", "displayName": "Generate UUID", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a UUID using the Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and `simple` as the type. If no type is given, the default is used. It is also possible to use a custom `UuidG [...] "hash(exp,algorithm)": { "index": 52, "kind": "function", "displayName": "Compute Hash Value", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a hashed value (string in hex decimal) of the message body\/expression using JDK MessageDigest. The algorithm can be SHA-256 (default) or SHA3-256.", "ognl": false, "suffix": "}" }, - "empty(type)": { "index": 53, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.HashMap`.", "ognl": false [...] + "empty(type)": { "index": 53, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.LinkedHashMap`.", "ognl": [...] "iif(predicate,trueExp,falseExp)": { "index": 54, "kind": "function", "displayName": "If Then Else", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Evaluates the predicate and returns the value of trueExp or falseExp. This function is similar to the ternary operator in Java.", "ognl": false, "suffix": "}" }, - "attachments": { "index": 55, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" }, - "attachments.size": { "index": 56, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" }, - "attachmentContentAsText": { "index": 57, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" }, - "attachmentContent": { "index": 58, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" }, - "attachmentContentAs(type)": { "index": 59, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" }, - "attachmentHeader(key,name)": { "index": 60, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" }, - "attachmentHeader(key,name,type)": { "index": 61, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" }, - "attachment(key)": { "index": 62, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" } + "list(val...)": { "index": 55, "kind": "function", "displayName": "Create List of values", "group": "function", "label": "function", "required": false, "javaType": "java.util.ArrayList", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list function creates an ArrayList with the given set of values.", "ognl": false, "suffix": "}" }, + "map(key1,value1,...)": { "index": 56, "kind": "function", "displayName": "Create Map of pairs", "group": "function", "label": "function", "required": false, "javaType": "java.util.LinkedHashMap", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The map function creates a LinkedHashMap with the given set of pairs.", "ognl": false, "suffix": "}" }, + "attachments": { "index": 57, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" }, + "attachments.size": { "index": 58, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" }, + "attachmentContentAsText": { "index": 59, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" }, + "attachmentContent": { "index": 60, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" }, + "attachmentContentAs(type)": { "index": 61, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" }, + "attachmentHeader(key,name)": { "index": 62, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" }, + "attachmentHeader(key,name,type)": { "index": 63, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" }, + "attachment(key)": { "index": 64, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" } } } 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 3d9afef38d0..1ec24b1a428 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 @@ -234,7 +234,11 @@ constant field from Exchange as: `org.apache.camel.Exchange.FILE_NAME` `string` -> empty String + `list` -> empty ArrayList + -`map` -> empty HashMap + +`map` -> empty LinkedHashMap + + +|list(val1,val2,...) | java.util.ArrayList | The list function creates an ArrayList with the given set of values. + +|map(key1,value1,...) | java.util.LinkedHashMap | The map function creates a LinkedHashMap with the given set of pairs. |null |null |represents a *null* 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 428f79be280..1bf95915039 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 @@ -504,6 +504,25 @@ public final class CSimpleHelper { throw new IllegalArgumentException("function empty(%s) has unknown type".formatted(type)); } + public static List<Object> list(Exchange exchange, Object... args) { + List<Object> answer = new ArrayList<>(); + for (int i = 0; args != null && i < args.length; i++) { + answer.add(args[i]); + } + return answer; + } + + public static Map<String, Object> map(Exchange exchange, Object... args) { + Map<String, Object> answer = new LinkedHashMap<>(); + for (int i = 0, j = 0; args != null && i < args.length - 1; j++) { + String key = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, args[i]); + Object value = args[i + 1]; + answer.put(key, value); + i = i + 2; + } + return answer; + } + public static String substring(Exchange exchange, Object num1, Object num2) { int head = exchange.getContext().getTypeConverter().tryConvertTo(int.class, exchange, num1); int tail = exchange.getContext().getTypeConverter().tryConvertTo(int.class, exchange, num2); diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java index bbfb12048eb..cfed2884a95 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java @@ -206,12 +206,18 @@ public final class SimpleConstants { @Metadata(description = "Returns a hashed value (string in hex decimal) of the message body/expression using JDK MessageDigest. The algorithm can be SHA-256 (default) or SHA3-256.", label = "function", javaType = "String", displayName = "Compute Hash Value") public static final String HASH = "hash(exp,algorithm)"; - @Metadata(description = "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.HashMap`.", + @Metadata(description = "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.LinkedHashMap`.", label = "function", javaType = "Object", displayName = "Create Empty Object") public static final String EMPTY = "empty(type)"; @Metadata(description = "Evaluates the predicate and returns the value of trueExp or falseExp. This function is similar to the ternary operator in Java.", label = "function", javaType = "Object", displayName = "If Then Else") public static final String IIF = "iif(predicate,trueExp,falseExp)"; + @Metadata(description = "The list function creates an ArrayList with the given set of values.", + label = "function", javaType = "java.util.ArrayList", displayName = "Create List of values") + public static final String LIST = "list(val...)"; + @Metadata(description = "The map function creates a LinkedHashMap with the given set of pairs.", + label = "function", javaType = "java.util.LinkedHashMap", displayName = "Create Map of pairs") + public static final String MAP = "map(key1,value1,...)"; @Metadata(description = "All the attachments as a Map<String,DataHandler.", javaType = "java.util.Map", label = "function") public static final String ATTACHMENTS = "attachments"; @Metadata(description = "The number of attachments. Is 0 if there are no attachments.", javaType = "int", diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java index e616167f8d1..0e583d41d80 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java @@ -18,10 +18,12 @@ package org.apache.camel.language.simple; import java.security.MessageDigest; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; @@ -212,6 +214,82 @@ public final class SimpleExpressionBuilder { }; } + /** + * An expression that creates an ArrayList + */ + public static Expression listExpression(String[] values) { + return new ExpressionAdapter() { + + private final Expression[] exps = new Expression[values != null ? values.length : 0]; + + @Override + public void init(CamelContext context) { + for (int i = 0; values != null && i < values.length; i++) { + Expression exp = context.resolveLanguage("simple").createExpression(values[i]); + exp.init(context); + exps[i] = exp; + } + } + + @Override + public Object evaluate(Exchange exchange) { + List<Object> answer = new ArrayList<>(values != null ? values.length : 0); + for (Expression exp : exps) { + Object val = exp.evaluate(exchange, Object.class); + answer.add(val); + } + return answer; + } + + @Override + public String toString() { + return "list(" + Arrays.toString(values) + ")"; + } + }; + } + + /** + * An expression that creates an LinkedHashMap + */ + public static Expression mapExpression(String[] pairs) { + return new ExpressionAdapter() { + + private final Expression[] keys = new Expression[pairs != null ? pairs.length / 2 : 0]; + private final Expression[] values = new Expression[pairs != null ? pairs.length / 2 : 0]; + + @Override + public void init(CamelContext context) { + for (int i = 0, j = 0; pairs != null && i < pairs.length - 1; j++) { + String key = pairs[i]; + String value = pairs[i + 1]; + Expression exp = context.resolveLanguage("simple").createExpression(key); + exp.init(context); + keys[j] = exp; + exp = context.resolveLanguage("simple").createExpression(value); + exp.init(context); + values[j] = exp; + i = i + 2; + } + } + + @Override + public Object evaluate(Exchange exchange) { + Map<String, Object> answer = new LinkedHashMap<>(keys.length); + for (int i = 0; i < keys.length; i++) { + String key = keys[i].evaluate(exchange, String.class); + Object val = values[i].evaluate(exchange, Object.class); + answer.put(key, val); + } + return answer; + } + + @Override + public String toString() { + return "map(" + Arrays.toString(values) + ")"; + } + }; + } + /** * Joins together the values from the expression */ 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 55c6701ce6d..24127aab861 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 @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.StringJoiner; import org.apache.camel.CamelContext; import org.apache.camel.Expression; @@ -891,6 +892,33 @@ public class SimpleFunctionExpression extends LiteralExpression { return SimpleExpressionBuilder.iifExpression(tokens[0].trim(), tokens[1].trim(), tokens[2].trim()); } + // list function + remainder = ifStartsWithReturnRemainder("list(", function); + if (remainder != null) { + String values = StringHelper.beforeLast(remainder, ")"); + String[] tokens = null; + if (ObjectHelper.isNotEmpty(values)) { + tokens = StringQuoteHelper.splitSafeQuote(values, ','); + } + return SimpleExpressionBuilder.listExpression(tokens); + } + // map function + remainder = ifStartsWithReturnRemainder("map(", function); + if (remainder != null) { + String values = StringHelper.beforeLast(remainder, ")"); + String[] tokens = null; + if (ObjectHelper.isNotEmpty(values)) { + tokens = StringQuoteHelper.splitSafeQuote(values, ','); + } + // there must be an even number of tokens as each map element is a pair + if (tokens != null && tokens.length % 2 == 1) { + throw new SimpleParserException( + "Map function must have an even number of values, was: " + tokens.length + " values.", + token.getIndex()); + } + return SimpleExpressionBuilder.mapExpression(tokens); + } + return null; } @@ -1913,6 +1941,38 @@ public class SimpleFunctionExpression extends LiteralExpression { return "empty(exchange, " + value + ")"; } + // list function + remainder = ifStartsWithReturnRemainder("list(", function); + if (remainder != null) { + String values = StringHelper.beforeLast(remainder, ")"); + String[] tokens = null; + if (ObjectHelper.isNotEmpty(values)) { + tokens = codeSplitSafe(values, ',', true, true); + } + StringJoiner sj = new StringJoiner(", "); + for (int i = 0; tokens != null && i < tokens.length; i++) { + sj.add(tokens[i]); + } + String p = sj.length() > 0 ? sj.toString() : "null"; + return "list(exchange, " + p + ")"; + } + + // map function + remainder = ifStartsWithReturnRemainder("map(", function); + if (remainder != null) { + String values = StringHelper.beforeLast(remainder, ")"); + String[] tokens = null; + if (ObjectHelper.isNotEmpty(values)) { + tokens = codeSplitSafe(values, ',', true, true); + } + StringJoiner sj = new StringJoiner(", "); + for (int i = 0; tokens != null && i < tokens.length; i++) { + sj.add(tokens[i]); + } + String p = sj.length() > 0 ? sj.toString() : "null"; + return "map(exchange, " + p + ")"; + } + // hash function remainder = ifStartsWithReturnRemainder("hash(", function); if (remainder != null) { 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 fbcc49b19a1..c6a2d27650a 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 @@ -2045,6 +2045,23 @@ public class SimpleTest extends LanguageTestSupport { assertExpression("${replace(",',${header.foo})}", "{'foo': 'cheese'}"); } + @Test + public void testList() { + exchange.getMessage().setBody("4"); + assertExpression("${list(1,2,3)}", "[1, 2, 3]"); + assertExpression("${list(1,2,3,${body})}", "[1, 2, 3, 4]"); + assertExpression("${list('a','b','c')}", "[a, b, c]"); + assertExpression("${list()}", "[]"); + } + + @Test + public void testMap() { + exchange.getMessage().setBody("d"); + assertExpression("${map(1,a,2,b,3,c)}", "{1=a, 2=b, 3=c}"); + assertExpression("${map(1,a,2,b,3,c,4,${body})}", "{1=a, 2=b, 3=c, 4=d}"); + assertExpression("${map()}", "{}"); + } + @Test public void testSubstringExpression() { exchange.getMessage().setBody("ABCDEFGHIJK");