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 28e3825dc4c55d64c6b158ab5ec3cf304e25da73
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Sep 23 19:09:18 2026 +0200

    CAMEL-24967: simple - fix how function names are matched and arguments are 
split
    
    - quotes inside a nested function argument are kept: 
${size(${body.split(',')})}
    - convertTo, headerAs and variableAs find the closing parenthesis of the 
argument list:
      ${convertTo(${header.foo.trim()},Integer)}
    - a comma inside quotes is part of the value in concat, throwException and 
hash
    - iif and replace with too few arguments report the valid syntax
    - ${bean:type:com.foo.MyClass.myMethod} splits the method from the class 
name at the last dot
    - a quoted key such as ${header['a.b']} is the name, not OGNL (also 
variable and exchangeProperty)
    - a name glued to a function prefix is unknown: ${headerfoo}, ${uuidv7}, 
${exceptionInfo}
      (so custom functions with such names can be called)
    - fix a SimpleTest typo that asserted ${variableAA(...)}
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../language/simple/SimpleFunctionHelper.java      |  45 +++++++-
 .../simple/functions/BeanFunctionFactory.java      |  11 ++
 .../simple/functions/ExchangeFunctionFactory.java  |  22 +++-
 .../simple/functions/HeaderFunctionFactory.java    |  11 +-
 .../simple/functions/MathFunctionFactory.java      |   6 +-
 .../simple/functions/MiscFunctionFactory.java      |  72 +++++++------
 .../simple/functions/StringFunctionFactory.java    |  57 +++++-----
 .../simple/functions/VariableFunctionFactory.java  |  11 +-
 .../simple/SimpleFunctionArgumentsTest.java        | 117 +++++++++++++++++++++
 .../apache/camel/language/simple/SimpleTest.java   |   2 +-
 10 files changed, 279 insertions(+), 75 deletions(-)

diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
index ab78e630b7ce..64f1336abe24 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
@@ -43,7 +43,7 @@ public final class SimpleFunctionHelper {
         if (remainder == null) {
             remainder = ifStartsWithReturnRemainder("header", function);
         }
-        return remainder;
+        return keyRemainder(remainder);
     }
 
     public static String parseVariable(String function) {
@@ -51,6 +51,49 @@ public final class SimpleFunctionHelper {
         if (remainder == null) {
             remainder = ifStartsWithReturnRemainder("variable", function);
         }
+        return keyRemainder(remainder);
+    }
+
+    /**
+     * The remainder after a name such as header must start the key, so 
${headerfoo} is not the header foo (and a
+     * custom function named such as headerCount can be called).
+     */
+    private static String keyRemainder(String remainder) {
+        if (remainder != null && !remainder.isEmpty()) {
+            char c = remainder.charAt(0);
+            if (c != '.' && c != ':' && c != '?' && c != '[') {
+                return null;
+            }
+        }
         return remainder;
     }
+
+    /**
+     * The index of the parenthesis that closes the argument list, where the 
text is what comes after the opening
+     * parenthesis. Parentheses inside nested functions and quotes are 
skipped, so the type in
+     * ${convertTo(${header.foo.trim()},Integer)} is found. Returns -1 if 
there is no closing parenthesis.
+     */
+    public static int indexOfClosingParenthesis(String text) {
+        int depth = 0;
+        boolean single = false;
+        boolean dubble = false;
+        for (int i = 0; i < text.length(); i++) {
+            char c = text.charAt(i);
+            if (c == '\'' && !dubble) {
+                single = !single;
+            } else if (c == '"' && !single) {
+                dubble = !dubble;
+            } else if (!single && !dubble) {
+                if (c == '(') {
+                    depth++;
+                } else if (c == ')') {
+                    if (depth == 0) {
+                        return i;
+                    }
+                    depth--;
+                }
+            }
+        }
+        return -1;
+    }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/BeanFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/BeanFunctionFactory.java
index 0ed4f8e09f43..92e15b06e3ac 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/BeanFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/BeanFunctionFactory.java
@@ -91,6 +91,17 @@ public final class BeanFunctionFactory implements 
SimpleLanguageFunctionFactory
             if (doubleColonIndex > 0 && (!remainder.contains("(") || 
doubleColonIndex < beginOfParameterDeclaration)) {
                 ref = remainder.substring(0, doubleColonIndex);
                 method = remainder.substring(doubleColonIndex + 2);
+            } else if (remainder.startsWith("type:")) {
+                // type:com.foo.MyClass.myMethod: the class name has dots, so 
the method is the last part
+                // when it starts with a lower case letter, as Java method 
names do
+                String beforeParams = beginOfParameterDeclaration > 0
+                        ? remainder.substring(0, beginOfParameterDeclaration) 
: remainder;
+                int idx = beforeParams.lastIndexOf('.');
+                if (idx > 0 && idx + 1 < beforeParams.length()
+                        && Character.isLowerCase(beforeParams.charAt(idx + 
1))) {
+                    ref = remainder.substring(0, idx);
+                    method = remainder.substring(idx + 1);
+                }
             } else {
                 int idx = remainder.indexOf('.');
                 if (idx > 0) {
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/ExchangeFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/ExchangeFunctionFactory.java
index 6b2cdd98bdcd..324cb1424b16 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/ExchangeFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/ExchangeFunctionFactory.java
@@ -23,6 +23,7 @@ import 
org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.spi.SimpleLanguageFunctionFactory;
 import org.apache.camel.support.builder.ExpressionBuilder;
 import org.apache.camel.util.OgnlHelper;
+import org.apache.camel.util.StringHelper;
 
 import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
 
@@ -38,7 +39,7 @@ public final class ExchangeFunctionFactory implements 
SimpleLanguageFunctionFact
     public Expression createFunction(CamelContext camelContext, String 
function, int index) {
         // camelContext OGNL
         String remainder = ifStartsWithReturnRemainder("camelContext", 
function);
-        if (remainder != null) {
+        if (remainder != null && startsOgnl(remainder)) {
             boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
             if (invalid) {
                 throw new SimpleParserException("Valid syntax: 
${camelContext.OGNL} was: " + function, index);
@@ -48,7 +49,7 @@ public final class ExchangeFunctionFactory implements 
SimpleLanguageFunctionFact
 
         // Exception OGNL — exchangeProperty/exchange checked separately, no 
prefix clash here
         remainder = ifStartsWithReturnRemainder("exception", function);
-        if (remainder != null) {
+        if (remainder != null && startsOgnl(remainder)) {
             boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
             if (invalid) {
                 throw new SimpleParserException("Valid syntax: 
${exception.OGNL} was: " + function, index);
@@ -58,7 +59,7 @@ public final class ExchangeFunctionFactory implements 
SimpleLanguageFunctionFact
 
         // exchangeProperty must be checked before exchange to avoid prefix 
clash
         remainder = ifStartsWithReturnRemainder("exchangeProperty", function);
-        if (remainder != null) {
+        if (remainder != null && (startsOgnl(remainder) || 
remainder.startsWith(":"))) {
             // remove leading character (dot, colon or ?)
             if (remainder.startsWith(".") || remainder.startsWith(":") || 
remainder.startsWith("?")) {
                 remainder = remainder.substring(1);
@@ -66,6 +67,11 @@ public final class ExchangeFunctionFactory implements 
SimpleLanguageFunctionFact
             // remove starting and ending brackets
             if (remainder.startsWith("[") && remainder.endsWith("]")) {
                 remainder = remainder.substring(1, remainder.length() - 1);
+                String unquoted = 
StringHelper.removeLeadingAndEndingQuotes(remainder);
+                if (!unquoted.equals(remainder)) {
+                    // a quoted key such as ['a.b'] is the name, not an OGNL 
expression
+                    return 
ExpressionBuilder.exchangePropertyExpression(unquoted);
+                }
             }
 
             boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
@@ -82,7 +88,7 @@ public final class ExchangeFunctionFactory implements 
SimpleLanguageFunctionFact
 
         // exchange OGNL
         remainder = ifStartsWithReturnRemainder("exchange", function);
-        if (remainder != null && (remainder.startsWith(".") || 
remainder.startsWith("?") || remainder.startsWith("["))) {
+        if (remainder != null && startsOgnl(remainder)) {
             // only ${exchange.OGNL}: ${exchangeCounter} is not an exchange 
OGNL but an unknown function
             boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
             if (invalid) {
@@ -93,4 +99,12 @@ public final class ExchangeFunctionFactory implements 
SimpleLanguageFunctionFact
 
         return null;
     }
+
+    /**
+     * Whether the remainder after the function name starts an OGNL 
expression, so ${exchangeCounter} or
+     * ${exceptionInfo} are not taken as ${exchange} or ${exception} with some 
OGNL glued on.
+     */
+    private static boolean startsOgnl(String remainder) {
+        return remainder.startsWith(".") || remainder.startsWith("?") || 
remainder.startsWith("[");
+    }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/HeaderFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/HeaderFunctionFactory.java
index 05b7f0ce5be1..2882dea4df8d 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/HeaderFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/HeaderFunctionFactory.java
@@ -19,6 +19,7 @@ package org.apache.camel.language.simple.functions;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Expression;
 import org.apache.camel.language.simple.OgnlExpressionBuilder;
+import org.apache.camel.language.simple.SimpleFunctionHelper;
 import org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.spi.SimpleLanguageFunctionFactory;
 import org.apache.camel.support.builder.ExpressionBuilder;
@@ -40,13 +41,14 @@ public final class HeaderFunctionFactory implements 
SimpleLanguageFunctionFactor
         // headerAs
         String remainder = ifStartsWithReturnRemainder("headerAs(", function);
         if (remainder != null) {
-            String keyAndType = StringHelper.before(remainder, ")");
+            int end = 
SimpleFunctionHelper.indexOfClosingParenthesis(remainder);
+            String keyAndType = end >= 0 ? remainder.substring(0, end) : null;
             if (keyAndType == null) {
                 throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type)} was: " + function, index);
             }
             String key = StringHelper.before(keyAndType, ",");
             String type = StringHelper.after(keyAndType, ",");
-            remainder = StringHelper.after(remainder, ")");
+            remainder = remainder.substring(end + 1);
             if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || 
ObjectHelper.isNotEmpty(remainder)) {
                 throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type)} was: " + function, index);
             }
@@ -71,6 +73,11 @@ public final class HeaderFunctionFactory implements 
SimpleLanguageFunctionFactor
             }
             if (remainder.startsWith("[") && remainder.endsWith("]")) {
                 remainder = remainder.substring(1, remainder.length() - 1);
+                String unquoted = 
StringHelper.removeLeadingAndEndingQuotes(remainder);
+                if (!unquoted.equals(remainder)) {
+                    // a quoted key such as ['a.b'] is the name, not an OGNL 
expression
+                    return ExpressionBuilder.headerExpression(unquoted);
+                }
             }
             String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
 
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MathFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MathFunctionFactory.java
index 175b624c2b97..ff4c848cfa31 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MathFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MathFunctionFactory.java
@@ -40,19 +40,19 @@ public final class MathFunctionFactory implements 
SimpleLanguageFunctionFactory
         if (remainder != null) {
             String value = StringHelper.beforeLast(remainder, ")");
             return MathExpressionBuilder.absExpression(
-                    ObjectHelper.isNotEmpty(value) ? 
StringHelper.removeQuotes(value) : null);
+                    ObjectHelper.isNotEmpty(value) ? 
StringHelper.removeLeadingAndEndingQuotes(value) : null);
         }
         remainder = ifStartsWithReturnRemainder("floor(", function);
         if (remainder != null) {
             String value = StringHelper.beforeLast(remainder, ")");
             return MathExpressionBuilder.floorExpression(
-                    ObjectHelper.isNotEmpty(value) ? 
StringHelper.removeQuotes(value) : null);
+                    ObjectHelper.isNotEmpty(value) ? 
StringHelper.removeLeadingAndEndingQuotes(value) : null);
         }
         remainder = ifStartsWithReturnRemainder("ceil(", function);
         if (remainder != null) {
             String value = StringHelper.beforeLast(remainder, ")");
             return MathExpressionBuilder.ceilExpression(
-                    ObjectHelper.isNotEmpty(value) ? 
StringHelper.removeQuotes(value) : null);
+                    ObjectHelper.isNotEmpty(value) ? 
StringHelper.removeLeadingAndEndingQuotes(value) : null);
         }
         remainder = ifStartsWithReturnRemainder("sum(", function);
         if (remainder != null) {
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MiscFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MiscFunctionFactory.java
index 05708f1528df..bfb7f8ec1f4b 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MiscFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/MiscFunctionFactory.java
@@ -19,6 +19,7 @@ package org.apache.camel.language.simple.functions;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Expression;
 import org.apache.camel.language.simple.MiscExpressionBuilder;
+import org.apache.camel.language.simple.SimpleFunctionHelper;
 import org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.spi.SimpleLanguageFunctionFactory;
 import org.apache.camel.util.ObjectHelper;
@@ -47,7 +48,7 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return MiscExpressionBuilder.isEmptyExpression(exp);
         }
@@ -57,7 +58,7 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return MiscExpressionBuilder.isAlphaExpression(exp);
         }
@@ -67,7 +68,7 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return MiscExpressionBuilder.isAlphaNumericExpression(exp);
         }
@@ -77,7 +78,7 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return MiscExpressionBuilder.isNumericExpression(exp);
         }
@@ -97,7 +98,7 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return MiscExpressionBuilder.kindOfTypeExpression(exp);
         }
@@ -109,18 +110,17 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
             String values = StringHelper.beforeLast(remainder, ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException(
-                        "Valid syntax: ${throwException(msg)} or 
${throwException(type,msg)} was: " + function, index);
+                        "Valid syntax: ${throwException(msg)} or 
${throwException(msg,type)} was: " + function, index);
             }
-            if (values.contains(",")) {
-                String[] tokens = StringQuoteHelper.splitSafeQuote(values, 
',', true, true);
-                if (tokens.length > 2) {
-                    throw new SimpleParserException(
-                            "Valid syntax: ${throwException(msg)} or 
${throwException(type,msg)} was: " + function, index);
-                }
-                msg = StringHelper.removeQuotes(tokens[0]);
-                type = StringHelper.removeQuotes(tokens[1]);
-            } else {
-                msg = StringHelper.removeQuotes(values.trim());
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
true, true);
+            if (tokens.length > 2) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${throwException(msg)} or 
${throwException(msg,type)} was: " + function, index);
+            }
+            // a comma inside a quoted message is part of the message
+            msg = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
+            if (tokens.length == 2) {
+                type = StringHelper.removeLeadingAndEndingQuotes(tokens[1]);
             }
             return MiscExpressionBuilder.throwExceptionExpression(msg, type);
         }
@@ -135,14 +135,15 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
             if (tokens.length != 2) {
                 throw new SimpleParserException("Valid syntax: 
${assert(exp,msg)} was: " + function, index);
             }
-            return MiscExpressionBuilder.assertExpression(tokens[0], 
StringHelper.removeQuotes(tokens[1]));
+            return MiscExpressionBuilder.assertExpression(tokens[0], 
StringHelper.removeLeadingAndEndingQuotes(tokens[1]));
         }
 
         remainder = ifStartsWithReturnRemainder("convertTo(", function);
         if (remainder != null) {
             String exp = "${body}";
             String type;
-            String values = StringHelper.before(remainder, ")");
+            int end = 
SimpleFunctionHelper.indexOfClosingParenthesis(remainder);
+            String values = end >= 0 ? remainder.substring(0, end) : null;
             if (values == null || ObjectHelper.isEmpty(values)) {
                 throw new SimpleParserException(
                         "Valid syntax: ${convertTo(type)} or 
${convertTo(exp,type)} was: " + function, index);
@@ -153,12 +154,12 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
                     throw new SimpleParserException(
                             "Valid syntax: ${convertTo(type)} or 
${convertTo(exp,type)} was: " + function, index);
                 }
-                exp = StringHelper.removeQuotes(tokens[0]);
-                type = StringHelper.removeQuotes(tokens[1]);
+                exp = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
+                type = StringHelper.removeLeadingAndEndingQuotes(tokens[1]);
             } else {
-                type = StringHelper.removeQuotes(values.trim());
+                type = 
StringHelper.removeLeadingAndEndingQuotes(values.trim());
             }
-            remainder = StringHelper.after(remainder, ")");
+            remainder = remainder.substring(end + 1);
             if (ObjectHelper.isNotEmpty(remainder)) {
                 boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
                 if (invalid) {
@@ -172,7 +173,7 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
         }
 
         remainder = ifStartsWithReturnRemainder("messageHistory", function);
-        if (remainder != null) {
+        if (remainder != null && remainder.startsWith("(")) {
             boolean detailed;
             String values = StringHelper.between(remainder, "(", ")");
             if (values == null || ObjectHelper.isEmpty(values)) {
@@ -186,7 +187,8 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
         }
 
         remainder = ifStartsWithReturnRemainder("uuid", function);
-        if (remainder != null) {
+        // ${uuid(kind)}, but ${uuidv7} is not the uuid function
+        if (remainder != null && remainder.startsWith("(")) {
             String values = StringHelper.between(remainder, "(", ")");
             return MiscExpressionBuilder.uuidExpression(values);
         } else if (ObjectHelper.equal(function, "uuid")) {
@@ -200,15 +202,17 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
                 throw new SimpleParserException(
                         "Valid syntax: ${hash(value,algorithm)} or 
${hash(value)} was: " + function, index);
             }
-            if (values.contains(",")) {
-                String[] tokens = values.split(",", 2);
-                if (tokens.length > 2) {
-                    throw new SimpleParserException(
-                            "Valid syntax: ${hash(value,algorithm)} or 
${hash(value)} was: " + function, index);
-                }
-                return MiscExpressionBuilder.hashExpression(tokens[0].trim(), 
tokens[1].trim());
+            // a comma inside quotes is part of the value
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
true, true);
+            if (tokens.length > 2) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${hash(value,algorithm)} or 
${hash(value)} was: " + function, index);
+            }
+            String value = 
StringHelper.removeLeadingAndEndingQuotes(tokens[0].trim());
+            if (tokens.length == 2) {
+                return MiscExpressionBuilder.hashExpression(value, 
StringHelper.removeLeadingAndEndingQuotes(tokens[1].trim()));
             } else {
-                return MiscExpressionBuilder.hashExpression(values.trim(), 
"SHA-256");
+                return MiscExpressionBuilder.hashExpression(value, "SHA-256");
             }
         }
 
@@ -238,7 +242,7 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
                         "Valid syntax: 
${iif(predicate,trueExpression,falseExpression)} was: " + function, index);
             }
             String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
true, true);
-            if (tokens.length > 3) {
+            if (tokens.length != 3) {
                 throw new SimpleParserException(
                         "Valid syntax: 
${iif(predicate,trueExpression,falseExpression)} was: " + function, index);
             }
@@ -251,7 +255,7 @@ public final class MiscFunctionFactory implements 
SimpleLanguageFunctionFactory
             if (ObjectHelper.isEmpty(value)) {
                 throw new SimpleParserException("Valid syntax: ${load(name)} 
but was: " + function, index);
             }
-            return 
MiscExpressionBuilder.loadExpression(StringHelper.removeQuotes(value));
+            return 
MiscExpressionBuilder.loadExpression(StringHelper.removeLeadingAndEndingQuotes(value));
         }
 
         return null;
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
index b19fdad17973..e32ed2227139 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/StringFunctionFactory.java
@@ -50,9 +50,9 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
                         "Valid syntax: ${replace(from,to)} or 
${replace(from,to,expression)} was: " + function, index);
             }
             String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
-            if (tokens.length > 3) {
+            if (tokens.length < 2 || tokens.length > 3) {
                 throw new SimpleParserException(
-                        "Valid syntax: ${replace(from,to,expression)} was: " + 
function, index);
+                        "Valid syntax: ${replace(from,to)} or 
${replace(from,to,expression)} was: " + function, index);
             }
             String from = StringHelper.xmlDecode(tokens[0]);
             String to = StringHelper.xmlDecode(tokens[1]);
@@ -196,7 +196,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return StringExpressionBuilder.trimExpression(exp);
         }
@@ -216,7 +216,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return StringExpressionBuilder.capitalizeExpression(exp);
         }
@@ -237,10 +237,10 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
                 throw new SimpleParserException(
                         "Valid syntax: ${pad(exp,len)} or 
${pad(exp,len,separator)} was: " + function, index);
             }
-            exp = StringHelper.removeQuotes(tokens[0]);
-            len = StringHelper.removeQuotes(tokens[1]);
+            exp = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
+            len = StringHelper.removeLeadingAndEndingQuotes(tokens[1]);
             if (tokens.length == 3) {
-                separator = StringHelper.removeQuotes(tokens[2]);
+                separator = 
StringHelper.removeLeadingAndEndingQuotes(tokens[2]);
             }
             return StringExpressionBuilder.padExpression(exp, len, separator);
         }
@@ -257,21 +257,22 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
                                                 + function,
                         index);
             }
-            if (values.contains(",")) {
-                String[] tokens = StringQuoteHelper.splitSafeQuote(values, 
',', true, true);
-                if (tokens.length > 3) {
-                    throw new SimpleParserException(
-                            "Valid syntax: ${concat(exp)} or 
${concat(exp,exp)} or ${concat(exp,exp,separator)} was: "
-                                                    + function,
-                            index);
-                }
-                exp1 = StringHelper.removeQuotes(tokens[0]);
-                exp2 = StringHelper.removeQuotes(tokens[1]);
+            // a comma inside quotes is part of the value, such as 
${concat('Hello, ')}
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
true, true);
+            if (tokens.length > 3) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${concat(exp)} or ${concat(exp,exp)} or 
${concat(exp,exp,separator)} was: "
+                                                + function,
+                        index);
+            }
+            if (tokens.length >= 2) {
+                exp1 = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
+                exp2 = StringHelper.removeLeadingAndEndingQuotes(tokens[1]);
                 if (tokens.length == 3) {
-                    separator = StringHelper.removeQuotes(tokens[2]);
+                    separator = 
StringHelper.removeLeadingAndEndingQuotes(tokens[2]);
                 }
             } else {
-                exp2 = StringHelper.removeQuotes(values.trim());
+                exp2 = StringHelper.removeLeadingAndEndingQuotes(tokens[0]);
             }
             return StringExpressionBuilder.concatExpression(exp1, exp2, 
separator);
         }
@@ -281,7 +282,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return StringExpressionBuilder.quoteExpression(exp);
         }
@@ -291,7 +292,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return StringExpressionBuilder.safeQuoteExpression(exp);
         }
@@ -301,7 +302,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return StringExpressionBuilder.unquoteExpression(exp);
         }
@@ -311,7 +312,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return StringExpressionBuilder.uppercaseExpression(exp);
         }
@@ -321,7 +322,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return StringExpressionBuilder.lowercaseExpression(exp);
         }
@@ -331,7 +332,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return StringExpressionBuilder.lengthExpression(exp);
         }
@@ -341,7 +342,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return StringExpressionBuilder.sizeExpression(exp);
         }
@@ -364,7 +365,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
                         index);
             }
             if (ObjectHelper.isNotEmpty(exp)) {
-                exp = StringHelper.removeQuotes(exp.trim());
+                exp = StringHelper.removeLeadingAndEndingQuotes(exp.trim());
             } else {
                 exp = null;
             }
@@ -376,7 +377,7 @@ public final class StringFunctionFactory implements 
SimpleLanguageFunctionFactor
             String exp = null;
             String value = StringHelper.beforeLast(remainder, ")");
             if (ObjectHelper.isNotEmpty(value)) {
-                exp = StringHelper.removeQuotes(value);
+                exp = StringHelper.removeLeadingAndEndingQuotes(value);
             }
             return StringExpressionBuilder.normalizeWhitespaceExpression(exp);
         }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/VariableFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/VariableFunctionFactory.java
index 4b5b7c2f20e2..4d9b8671e277 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/VariableFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/VariableFunctionFactory.java
@@ -19,6 +19,7 @@ package org.apache.camel.language.simple.functions;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Expression;
 import org.apache.camel.language.simple.OgnlExpressionBuilder;
+import org.apache.camel.language.simple.SimpleFunctionHelper;
 import org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.spi.SimpleLanguageFunctionFactory;
 import org.apache.camel.support.builder.ExpressionBuilder;
@@ -40,13 +41,14 @@ public final class VariableFunctionFactory implements 
SimpleLanguageFunctionFact
         // variableAs
         String remainder = ifStartsWithReturnRemainder("variableAs(", 
function);
         if (remainder != null) {
-            String keyAndType = StringHelper.before(remainder, ")");
+            int end = 
SimpleFunctionHelper.indexOfClosingParenthesis(remainder);
+            String keyAndType = end >= 0 ? remainder.substring(0, end) : null;
             if (keyAndType == null) {
                 throw new SimpleParserException("Valid syntax: 
${variableAs(key, type)} was: " + function, index);
             }
             String key = StringHelper.before(keyAndType, ",");
             String type = StringHelper.after(keyAndType, ",");
-            remainder = StringHelper.after(remainder, ")");
+            remainder = remainder.substring(end + 1);
             if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || 
ObjectHelper.isNotEmpty(remainder)) {
                 throw new SimpleParserException("Valid syntax: 
${variableAs(key, type)} was: " + function, index);
             }
@@ -71,6 +73,11 @@ public final class VariableFunctionFactory implements 
SimpleLanguageFunctionFact
             }
             if (remainder.startsWith("[") && remainder.endsWith("]")) {
                 remainder = remainder.substring(1, remainder.length() - 1);
+                String unquoted = 
StringHelper.removeLeadingAndEndingQuotes(remainder);
+                if (!unquoted.equals(remainder)) {
+                    // a quoted key such as ['a.b'] is the name, not an OGNL 
expression
+                    return ExpressionBuilder.variableExpression(unquoted);
+                }
             }
             String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
 
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleFunctionArgumentsTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleFunctionArgumentsTest.java
new file mode 100644
index 000000000000..c14b50aa6a8a
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleFunctionArgumentsTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * CAMEL-24967: how the function names are matched and the arguments are split.
+ */
+public class SimpleFunctionArgumentsTest extends LanguageTestSupport {
+
+    @Override
+    protected String getLanguageName() {
+        return "simple";
+    }
+
+    @Test
+    public void testQuotesInsideANestedFunctionAreKept() {
+        exchange.getMessage().setBody("a,b,c");
+        assertExpression("${size(${body.split(',')})}", 3);
+    }
+
+    @Test
+    public void testConvertToWithParenthesesInTheExpression() {
+        exchange.getMessage().setHeader("foo", " 42 ");
+        assertExpression("${convertTo(${header.foo.trim()},Integer)}", 42);
+    }
+
+    @Test
+    public void testCommaInsideQuotes() {
+        exchange.getMessage().setBody("World");
+        // concat(exp) appends to the message body
+        assertExpression("${concat('Hello, ')}", "WorldHello, ");
+        Exception e = assertThrows(Exception.class,
+                () -> 
context.resolveLanguage("simple").createExpression("${throwException('Order 
failed, retry later')}")
+                        .evaluate(exchange, Object.class));
+        assertEquals("Order failed, retry later", e.getMessage());
+        String hash = 
context.resolveLanguage("simple").createExpression("${hash('a,b')}").evaluate(exchange,
 String.class);
+        exchange.getMessage().setBody("a,b");
+        
assertEquals(context.resolveLanguage("simple").createExpression("${hash(${body})}").evaluate(exchange,
 String.class),
+                hash);
+    }
+
+    @Test
+    public void testTooFewArguments() {
+        exchange.getMessage().setHeader("foo", 1);
+        // with a nested function the arguments are parsed when evaluated
+        Exception e = assertThrows(Exception.class,
+                () -> 
context.resolveLanguage("simple").createExpression("${iif(${header.foo} > 
0,'yes')}")
+                        .evaluate(exchange, Object.class));
+        assertTrue(e.getMessage().contains("Valid syntax: 
${iif(predicate,trueExpression,falseExpression)}"), e.getMessage());
+        e = assertThrows(SimpleIllegalSyntaxException.class,
+                () -> 
context.resolveLanguage("simple").createExpression("${replace(a)}"));
+        assertTrue(e.getMessage().contains("Valid syntax: 
${replace(from,to)}"), e.getMessage());
+    }
+
+    @Test
+    public void testBeanTypeWithPackage() {
+        assertExpression("${bean:type:java.lang.System.lineSeparator}", 
System.lineSeparator());
+        
assertExpression("${bean:type:org.apache.camel.language.simple.SimpleFunctionArgumentsTest$MyStatic.hello}",
 "Hi");
+    }
+
+    @Test
+    public void testQuotedKeyIsNotOgnl() {
+        exchange.getMessage().setHeader("a", "xyz");
+        exchange.getMessage().setHeader("a.b", "yes");
+        assertExpression("${header['a.b']}", "yes");
+        exchange.getMessage().removeHeader("a.b");
+        assertExpression("${header['a.b']}", null);
+        exchange.setVariable("x.y", "var");
+        assertExpression("${variable['x.y']}", "var");
+        exchange.setProperty("p.q", "prop");
+        assertExpression("${exchangeProperty['p.q']}", "prop");
+    }
+
+    @Test
+    public void testNamesMustNotBeGluedToAPrefix() {
+        exchange.getMessage().setHeader("foo", "bar");
+        assertThrows(SimpleIllegalSyntaxException.class,
+                () -> 
context.resolveLanguage("simple").createExpression("${headerfoo}"));
+        assertThrows(SimpleIllegalSyntaxException.class,
+                () -> 
context.resolveLanguage("simple").createExpression("${uuidv7}"));
+        assertThrows(SimpleIllegalSyntaxException.class,
+                () -> 
context.resolveLanguage("simple").createExpression("${exceptionInfo}"));
+        // the proper forms still work
+        assertExpression("${header.foo}", "bar");
+        assertExpression("${header:foo}", "bar");
+        assertExpression("${header[foo]}", "bar");
+        assertExpression("${headers.foo}", "bar");
+    }
+
+    public static class MyStatic {
+        public static String hello() {
+            return "Hi";
+        }
+    }
+}
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 e693c5f6719b..67f3b96d87de 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
@@ -864,7 +864,7 @@ public class SimpleTest extends LanguageTestSupport {
         // exchange scoped
         assertExpression("${variableAs('cheese', 'String')}", "gauda");
         assertExpression("${variableAs('foo', 'int')}", null);
-        assertExpression("${variableAA('bar', 'int')}", null);
+        assertExpression("${variableAs('bar', 'int')}", null);
 
         // global scoped
         assertExpression("${variableAs('global:cheese', 'String')}", 
"gorgonzola");

Reply via email to