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 d3a492c6234a CAMEL-22894: Refactor `SimpleFunctionExpression`: 
introduce function-factory dispatcher (part 1) (#23263)
d3a492c6234a is described below

commit d3a492c6234a26e63d40741caa37df51f82e345a
Author: Adriano Machado <[email protected]>
AuthorDate: Wed May 20 02:51:27 2026 -0400

    CAMEL-22894: Refactor `SimpleFunctionExpression`: introduce 
function-factory dispatcher (part 1) (#23263)
    
    * CAMEL-22894: Deprecate SimpleLanguageFunctionFactory#createCode
    
    The createCode(...) method exists solely to support csimple, which is
    deprecated since 4.19 and slated for removal in 5.0. Mark the method
    @Deprecated(since = "4.21") and make it a default returning null so
    implementations that no longer care about csimple don't need to
    override it. The three in-tree implementations (camel-attachments,
    camel-base64, camel-jsoup) keep their existing overrides and pick up
    the deprecation marker via inheritance.
    
    This is the first step toward refactoring the dual-responsibility
    SimpleFunctionExpression class.
    
    rh-pre-commit.version: 2.3.2
    rh-pre-commit.check-secrets: ENABLED
    
    * CAMEL-22894: Centralize external-component function dispatch in 
SimpleFunctionDispatcher
    
    SimpleFunctionExpression had six near-identical methods plus six gating
    predicate blocks dedicated to dispatching function strings to the three
    SimpleLanguageFunctionFactory implementations shipped by external
    components (camel-attachments, camel-base64, camel-jsoup). Move the
    dispatch into a new SimpleFunctionDispatcher, which owns the list of
    known component factories and their gating predicates and exposes
    tryCreate(...) / tryCreateCode(...) entry points.
    
    Behavior preserved exactly:
    - Same factories consulted in the same order.
    - Same gating predicates (so a function string belonging to a missing
      component still surfaces the helpful "add this JAR" error via
      ResolverHelper.resolveMandatoryBootstrapService).
    - camel-jsoup remains excluded from the csimple createCode dispatch
      (it has no csimple support and would throw).
    
    Net: SimpleFunctionExpression shrinks by ~80 lines and adding a fourth
    external factory is now a one-line edit in SimpleFunctionDispatcher
    instead of a six-block change spread across SimpleFunctionExpression.
    
    rh-pre-commit.version: 2.3.2
    rh-pre-commit.check-secrets: ENABLED
    
    * CAMEL-22894: Extract random, skip, collate, join into 
SimpleLanguageFunctionFactory classes
    
    Move four self-contained Simple functions out of SimpleFunctionExpression
    into their own SimpleLanguageFunctionFactory implementations under
    org.apache.camel.language.simple.functions, registered as built-ins on
    SimpleFunctionDispatcher. The dispatcher consults built-ins before
    external component factories, preserving today's priority.
    
    Both createFunction (Simple) and createCode (csimple) are moved together
    into each class, keeping the runtime and code-generation logic for the
    same function side by side instead of ~2,000 lines apart in
    SimpleFunctionExpression.
    
    Behavior preserved: each factory short-circuits on its function prefix
    and returns null on non-match, exactly mirroring the inline blocks they
    replace. All Simple and CSimple tests continue to pass.
    
    SimpleFunctionExpression shrinks by ~147 lines.
    
    rh-pre-commit.version: 2.3.2
    rh-pre-commit.check-secrets: ENABLED
    
    * CAMEL-22894: Address review feedback on simple function extraction
    
    - restore built-in function dispatch ordering relative to misc/math parsing
    - move codeSplitSafe into SimpleFunctionHelper to decouple 
JoinFunctionFactory from SimpleFunctionExpression
    - align SkipFunctionFactory runtime parsing with createCode using beforeLast
    - move random/skip/collate/join coverage into dedicated factory unit tests
    
    rh-pre-commit.version: 2.3.2
    rh-pre-commit.check-secrets: ENABLED
    
    * CAMEL-22894: Route factory tests through full Simple language pipeline
    
    `AbstractSimpleFunctionFactoryTestSupport.evaluate()` previously called
    the factory directly, bypassing `SimpleFunctionExpression` and the
    dispatcher entirely. The four factory tests therefore did not exercise
    the dispatch ordering (built-ins before misc/math) that the review
    flagged.
    
    Route `evaluate()` through `context.resolveLanguage("simple")` so each
    factory test exercises the full tokenizer -> parser -> dispatcher ->
    factory chain. `createCode()` continues to call the factory directly
    since there is no equivalent full pipeline for csimple.
    
    The two error-message assertions in `RandomFunctionFactoryTest` are
    updated to check `getCause().getMessage()`: the full pipeline wraps
    the factory's `SimpleParserException` inside a
    `SimpleIllegalSyntaxException` that appends location context.
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
    
    rh-pre-commit.version: 2.3.2
    rh-pre-commit.check-secrets: ENABLED
---
 .../camel/spi/SimpleLanguageFunctionFactory.java   |  14 +-
 .../language/simple/SimpleFunctionDispatcher.java  | 179 +++++++++++++++
 .../language/simple/SimpleFunctionHelper.java      | 142 ++++++++++++
 .../simple/ast/SimpleFunctionExpression.java       | 255 ++-------------------
 .../simple/functions/CollateFunctionFactory.java   |  59 +++++
 .../simple/functions/JoinFunctionFactory.java      | 107 +++++++++
 .../simple/functions/RandomFunctionFactory.java    |  77 +++++++
 .../simple/functions/SkipFunctionFactory.java      |  59 +++++
 .../csimple/CSimpleExpressionParserTest.java       |  23 +-
 .../apache/camel/language/simple/SimpleTest.java   | 194 ----------------
 .../AbstractSimpleFunctionFactoryTestSupport.java  |  45 ++++
 .../functions/CollateFunctionFactoryTest.java      | 128 +++++++++++
 .../simple/functions/JoinFunctionFactoryTest.java  |  64 ++++++
 .../functions/RandomFunctionFactoryTest.java       |  80 +++++++
 .../simple/functions/SkipFunctionFactoryTest.java  |  79 +++++++
 15 files changed, 1047 insertions(+), 458 deletions(-)

diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java
 
b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java
index 9532159c51a7..9667ccb3409f 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java
@@ -49,12 +49,16 @@ public interface SimpleLanguageFunctionFactory {
     /**
      * Creates the Java source code that performs the function (for csimple).
      *
-     * @param  camelContext the camel context
-     * @param  function     the function
-     * @param  index        index of the function in the literal input
-     * @return              the source code or <tt>null</tt> if not supported 
by this factory.
+     * @param      camelContext the camel context
+     * @param      function     the function
+     * @param      index        index of the function in the literal input
+     * @return                  the source code or <tt>null</tt> if not 
supported by this factory.
+     * @deprecated              will be removed in 5.0 along with csimple
      */
     @Nullable
-    String createCode(CamelContext camelContext, String function, int index);
+    @Deprecated(since = "4.21")
+    default String createCode(CamelContext camelContext, String function, int 
index) {
+        return null;
+    }
 
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionDispatcher.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionDispatcher.java
new file mode 100644
index 000000000000..65057ecb8124
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionDispatcher.java
@@ -0,0 +1,179 @@
+/*
+ * 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 java.util.List;
+import java.util.function.Predicate;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.functions.CollateFunctionFactory;
+import org.apache.camel.language.simple.functions.JoinFunctionFactory;
+import org.apache.camel.language.simple.functions.RandomFunctionFactory;
+import org.apache.camel.language.simple.functions.SkipFunctionFactory;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.support.ResolverHelper;
+
+import static 
org.apache.camel.language.simple.ast.SimpleFunctionExpression.ifStartsWithReturnRemainder;
+
+/**
+ * Dispatches Simple/CSimple function lookup to built-in function factories 
and to {@link SimpleLanguageFunctionFactory}
+ * implementations shipped by external Camel components (currently 
camel-attachments, camel-base64, camel-jsoup).
+ * <p>
+ * Each entry carries a gate that decides whether its factory is consulted for 
a given function string; gates mirror the
+ * inline checks that previously lived in {@code SimpleFunctionExpression}, so 
a function belonging to a missing
+ * component still surfaces the "add the JAR to your classpath" error from
+ * {@link ResolverHelper#resolveMandatoryBootstrapService}. Factory resolution 
is cached by the bootstrap factory
+ * finder, so repeated dispatch is cheap.
+ */
+public final class SimpleFunctionDispatcher {
+
+    /**
+     * Built-in factories shipped by camel-core-languages itself. Iterated 
before {@link #EXPRESSION_ENTRIES}, matching
+     * the original priority of these functions inside {@code 
SimpleFunctionExpression}. Each factory returns
+     * {@code null} for inputs it does not recognise, so no gating predicate 
is needed.
+     */
+    private static final List<SimpleLanguageFunctionFactory> BUILT_INS = 
List.of(
+            new RandomFunctionFactory(),
+            new SkipFunctionFactory(),
+            new CollateFunctionFactory(),
+            new JoinFunctionFactory());
+
+    private static final List<Entry> EXPRESSION_ENTRIES = List.of(
+            new Entry("camel-attachments", 
SimpleFunctionDispatcher::isAttachmentFunction),
+            new Entry("camel-base64", 
SimpleFunctionDispatcher::isBase64Function),
+            new Entry("camel-jsoup", 
SimpleFunctionDispatcher::isHtmlFunction));
+
+    /**
+     * Code-generation entries exclude camel-jsoup deliberately: it has no 
csimple support and its {@code createCode}
+     * throws.
+     */
+    private static final List<Entry> CODE_ENTRIES = List.of(
+            new Entry("camel-attachments", 
SimpleFunctionDispatcher::isAttachmentFunction),
+            new Entry("camel-base64", 
SimpleFunctionDispatcher::isBase64Function));
+
+    private SimpleFunctionDispatcher() {
+    }
+
+    public static Expression tryCreate(CamelContext camelContext, String 
function, int index) {
+        Expression answer = tryCreateBuiltIn(camelContext, function, index);
+        if (answer != null) {
+            return answer;
+        }
+        return tryCreateExternal(camelContext, function, index);
+    }
+
+    public static Expression tryCreateBuiltIn(CamelContext camelContext, 
String function, int index) {
+        for (SimpleLanguageFunctionFactory factory : BUILT_INS) {
+            Expression answer = factory.createFunction(camelContext, function, 
index);
+            if (answer != null) {
+                return answer;
+            }
+        }
+        return null;
+    }
+
+    public static Expression tryCreateExternal(CamelContext camelContext, 
String function, int index) {
+        for (Entry entry : EXPRESSION_ENTRIES) {
+            if (!entry.claims.test(function)) {
+                continue;
+            }
+            SimpleLanguageFunctionFactory factory = resolve(camelContext, 
entry.jarName);
+            Expression answer = factory.createFunction(camelContext, function, 
index);
+            if (answer != null) {
+                return answer;
+            }
+        }
+        return null;
+    }
+
+    public static String tryCreateCode(CamelContext camelContext, String 
function, int index) {
+        String code = tryCreateCodeBuiltIn(camelContext, function, index);
+        if (code != null) {
+            return code;
+        }
+        return tryCreateCodeExternal(camelContext, function, index);
+    }
+
+    public static String tryCreateCodeBuiltIn(CamelContext camelContext, 
String function, int index) {
+        for (SimpleLanguageFunctionFactory factory : BUILT_INS) {
+            @SuppressWarnings("deprecation")
+            String code = factory.createCode(camelContext, function, index);
+            if (code != null) {
+                return code;
+            }
+        }
+        return null;
+    }
+
+    public static String tryCreateCodeExternal(CamelContext camelContext, 
String function, int index) {
+        for (Entry entry : CODE_ENTRIES) {
+            if (!entry.claims.test(function)) {
+                continue;
+            }
+            SimpleLanguageFunctionFactory factory = resolve(camelContext, 
entry.jarName);
+            @SuppressWarnings("deprecation")
+            String code = factory.createCode(camelContext, function, index);
+            if (code != null) {
+                return code;
+            }
+        }
+        return null;
+    }
+
+    private static SimpleLanguageFunctionFactory resolve(CamelContext 
camelContext, String jarName) {
+        return ResolverHelper.resolveMandatoryBootstrapService(
+                camelContext,
+                SimpleLanguageFunctionFactory.FACTORY + "/" + jarName,
+                SimpleLanguageFunctionFactory.class,
+                jarName);
+    }
+
+    private static boolean isAttachmentFunction(String function) {
+        return "attachments".equals(function)
+                || "clearAttachments".equals(function)
+                || ifStartsWithReturnRemainder("setAttachment", function) != 
null
+                || ifStartsWithReturnRemainder("attachment", function) != null;
+    }
+
+    private static boolean isBase64Function(String function) {
+        return "base64Encode".equals(function)
+                || "base64Decode".equals(function)
+                || ifStartsWithReturnRemainder("base64Encode", function) != 
null
+                || ifStartsWithReturnRemainder("base64Decode", function) != 
null;
+    }
+
+    private static boolean isHtmlFunction(String function) {
+        return "htmlClean".equals(function)
+                || "htmlParse".equals(function)
+                || "htmlDecode".equals(function)
+                || ifStartsWithReturnRemainder("htmlClean", function) != null
+                || ifStartsWithReturnRemainder("htmlParse", function) != null
+                || ifStartsWithReturnRemainder("htmlDecode", function) != null;
+    }
+
+    private static final class Entry {
+
+        private final String jarName;
+        private final Predicate<String> claims;
+
+        private Entry(String jarName, Predicate<String> claims) {
+            this.jarName = jarName;
+            this.claims = claims;
+        }
+    }
+}
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
new file mode 100644
index 000000000000..d6ae1b132dce
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
@@ -0,0 +1,142 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+public final class SimpleFunctionHelper {
+
+    private SimpleFunctionHelper() {
+    }
+
+    public static String[] codeSplitSafe(String input, char separator, boolean 
trim, boolean keepQuotes) {
+        if (input == null) {
+            return null;
+        }
+
+        if (input.indexOf(separator) == -1) {
+            if (input.length() > 1) {
+                char ch = input.charAt(0);
+                char ch2 = input.charAt(input.length() - 1);
+                boolean singleQuoted = ch == '\'' && ch2 == '\'';
+                boolean doubleQuoted = ch == '"' && ch2 == '"';
+                if (!keepQuotes && (singleQuoted || doubleQuoted)) {
+                    input = input.substring(1, input.length() - 1);
+                    // do not trim quoted text
+                } else if (trim) {
+                    input = input.trim();
+                }
+            }
+            return new String[] { input };
+        }
+
+        List<String> answer = new ArrayList<>();
+        StringBuilder sb = new StringBuilder(256);
+
+        int codeLevel = 0;
+        boolean singleQuoted = false;
+        boolean doubleQuoted = false;
+        boolean separating = false;
+
+        for (int i = 0; i < input.length(); i++) {
+            char ch = input.charAt(i);
+            char prev = i > 0 ? input.charAt(i - 1) : 0;
+            boolean isQuoting = singleQuoted || doubleQuoted;
+            boolean last = i == input.length() - 1;
+
+            if (input.indexOf(BaseSimpleParser.CODE_START, i) == i) {
+                codeLevel++;
+                sb.append(BaseSimpleParser.CODE_START);
+                i = i + BaseSimpleParser.CODE_START.length() - 1;
+                continue;
+            } else if (input.indexOf(BaseSimpleParser.CODE_END, i) == i) {
+                codeLevel--;
+                sb.append(BaseSimpleParser.CODE_END);
+                i = i + BaseSimpleParser.CODE_END.length() - 1;
+                continue;
+            }
+            if (codeLevel > 0) {
+                sb.append(ch);
+                continue;
+            }
+
+            if (!doubleQuoted && ch == '\'') {
+                if (!singleQuoted) {
+                    singleQuoted = true;
+                    if (keepQuotes) {
+                        sb.append(ch);
+                    }
+                    continue;
+                } else if (prev != '\\') {
+                    singleQuoted = false;
+                    if (keepQuotes) {
+                        sb.append(ch);
+                    }
+                    continue;
+                }
+            } else if (!singleQuoted && ch == '"') {
+                if (!doubleQuoted) {
+                    doubleQuoted = true;
+                    if (keepQuotes) {
+                        sb.append(ch);
+                    }
+                    continue;
+                } else if (prev != '\\') {
+                    doubleQuoted = false;
+                    if (keepQuotes) {
+                        sb.append(ch);
+                    }
+                    continue;
+                }
+            }
+
+            if (isQuoting) {
+                sb.append(ch);
+                continue;
+            }
+
+            if (ch == separator) {
+                if (separating) {
+                    continue;
+                }
+                String s = sb.toString();
+                if (trim) {
+                    s = s.trim();
+                }
+                answer.add(s);
+                sb.setLength(0);
+                separating = true;
+                continue;
+            } else if (separating) {
+                separating = false;
+            }
+
+            sb.append(ch);
+
+            if (last) {
+                String s = sb.toString();
+                if (trim) {
+                    s = s.trim();
+                }
+                answer.add(s);
+            }
+        }
+
+        return answer.toArray(new String[0]);
+    }
+}
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 825478e0fd76..843691c5a6b3 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
@@ -29,13 +29,12 @@ import org.apache.camel.Expression;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.language.simple.BaseSimpleParser;
 import org.apache.camel.language.simple.SimpleExpressionBuilder;
+import org.apache.camel.language.simple.SimpleFunctionDispatcher;
 import org.apache.camel.language.simple.SimplePredicateParser;
 import org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.language.simple.types.SimpleToken;
 import org.apache.camel.spi.Language;
-import org.apache.camel.spi.SimpleLanguageFunctionFactory;
 import org.apache.camel.support.PluginHelper;
-import org.apache.camel.support.ResolverHelper;
 import org.apache.camel.support.builder.ExpressionBuilder;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.OgnlHelper;
@@ -391,6 +390,10 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         }
 
         // miscellaneous functions
+        Expression builtIn = 
SimpleFunctionDispatcher.tryCreateBuiltIn(camelContext, function, 
token.getIndex());
+        if (builtIn != null) {
+            return builtIn;
+        }
         Expression misc = createSimpleExpressionMisc(function);
         if (misc != null) {
             return misc;
@@ -401,33 +404,10 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return math;
         }
 
-        // attachments
-        if ("attachments".equals(function) || 
"clearAttachments".equals(function)
-                || ifStartsWithReturnRemainder("setAttachment", function) != 
null
-                || ifStartsWithReturnRemainder("attachment", function) != 
null) {
-            Expression exp = createSimpleAttachments(camelContext, function);
-            if (exp != null) {
-                return exp;
-            }
-        }
-        // base64
-        if ("base64Encode".equals(function) || "base64Decode".equals(function)
-                || ifStartsWithReturnRemainder("base64Encode", function) != 
null
-                || ifStartsWithReturnRemainder("base64Decode", function) != 
null) {
-            Expression exp = createSimpleBase64(camelContext, function);
-            if (exp != null) {
-                return exp;
-            }
-        }
-        // html
-        if ("htmlClean".equals(function) || "htmlParse".equals(function) || 
"htmlDecode".equals(function)
-                || ifStartsWithReturnRemainder("htmlClean", function) != null
-                || ifStartsWithReturnRemainder("htmlParse", function) != null
-                || ifStartsWithReturnRemainder("htmlDecode", function) != 
null) {
-            Expression exp = createSimpleHtml(camelContext, function);
-            if (exp != null) {
-                return exp;
-            }
+        // functions from external components (attachments, base64, html, ...)
+        Expression external = 
SimpleFunctionDispatcher.tryCreateExternal(camelContext, function, 
token.getIndex());
+        if (external != null) {
+            return external;
         }
 
         // it may be a custom function
@@ -452,33 +432,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         }
     }
 
-    private Expression createSimpleAttachments(CamelContext camelContext, 
String function) {
-        SimpleLanguageFunctionFactory factory = 
ResolverHelper.resolveMandatoryBootstrapService(
-                camelContext,
-                SimpleLanguageFunctionFactory.FACTORY + "/camel-attachments",
-                SimpleLanguageFunctionFactory.class,
-                "camel-attachments");
-        return factory.createFunction(camelContext, function, 
token.getIndex());
-    }
-
-    private Expression createSimpleBase64(CamelContext camelContext, String 
function) {
-        SimpleLanguageFunctionFactory factory = 
ResolverHelper.resolveMandatoryBootstrapService(
-                camelContext,
-                SimpleLanguageFunctionFactory.FACTORY + "/camel-base64",
-                SimpleLanguageFunctionFactory.class,
-                "camel-base64");
-        return factory.createFunction(camelContext, function, 
token.getIndex());
-    }
-
-    private Expression createSimpleHtml(CamelContext camelContext, String 
function) {
-        SimpleLanguageFunctionFactory factory = 
ResolverHelper.resolveMandatoryBootstrapService(
-                camelContext,
-                SimpleLanguageFunctionFactory.FACTORY + "/camel-jsoup",
-                SimpleLanguageFunctionFactory.class,
-                "camel-jsoup");
-        return factory.createFunction(camelContext, function, 
token.getIndex());
-    }
-
     private Expression createSimpleExpressionMessage(CamelContext 
camelContext, String function, boolean strict) {
         // messageAs
         String remainder = ifStartsWithReturnRemainder("messageAs(", function);
@@ -1115,25 +1068,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return SimpleExpressionBuilder.containsExpression(exp, pattern);
         }
 
-        // random function
-        remainder = ifStartsWithReturnRemainder("random(", function);
-        if (remainder != null) {
-            String values = StringHelper.beforeLast(remainder, ")");
-            if (values == null || ObjectHelper.isEmpty(values)) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${random(min,max)} or ${random(max)} 
was: " + function, token.getIndex());
-            }
-            if (values.contains(",")) {
-                String[] tokens = values.split(",", 3);
-                if (tokens.length > 2) {
-                    throw new SimpleParserException(
-                            "Valid syntax: ${random(min,max)} or 
${random(max)} was: " + function, token.getIndex());
-                }
-                return 
SimpleExpressionBuilder.randomExpression(tokens[0].trim(), tokens[1].trim());
-            } else {
-                return SimpleExpressionBuilder.randomExpression("0", 
values.trim());
-            }
-        }
         // range function
         remainder = ifStartsWithReturnRemainder("range(", function);
         if (remainder != null) {
@@ -1190,54 +1124,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             }
             return SimpleExpressionBuilder.shuffleExpression(tokens);
         }
-        // skip function
-        remainder = ifStartsWithReturnRemainder("skip(", function);
-        if (remainder != null) {
-            String values = StringHelper.before(remainder, ")");
-            if (values == null || ObjectHelper.isEmpty(values)) {
-                throw new SimpleParserException("Valid syntax: ${skip(number)} 
was: " + function, token.getIndex());
-            }
-            String exp = "${body}";
-            return SimpleExpressionBuilder.skipExpression(exp, values.trim());
-        }
-
-        // collate function
-        remainder = ifStartsWithReturnRemainder("collate(", function);
-        if (remainder != null) {
-            String values = StringHelper.beforeLast(remainder, ")");
-            if (values == null || ObjectHelper.isEmpty(values)) {
-                throw new SimpleParserException("Valid syntax: 
${collate(group)} was: " + function, token.getIndex());
-            }
-            String exp = "${body}";
-            return SimpleExpressionBuilder.collateExpression(exp, 
values.trim());
-        }
-
-        // join function
-        remainder = ifStartsWithReturnRemainder("join(", function);
-        if (remainder != null) {
-            String values = StringHelper.beforeLast(remainder, ")");
-            String separator = ",";
-            String prefix = null;
-            String exp = "${body}";
-            if (ObjectHelper.isNotEmpty(values)) {
-                String[] tokens = StringQuoteHelper.splitSafeQuote(values, 
',', false);
-                if (tokens.length > 3) {
-                    throw new SimpleParserException(
-                            "Valid syntax: 
${join(separator,prefix,expression)} was: " + function, token.getIndex());
-                }
-                if (tokens.length == 3) {
-                    separator = tokens[0];
-                    prefix = tokens[1];
-                    exp = tokens[2];
-                } else if (tokens.length == 2) {
-                    separator = tokens[0];
-                    prefix = tokens[1];
-                } else {
-                    separator = tokens[0];
-                }
-            }
-            return SimpleExpressionBuilder.joinExpression(exp, separator, 
prefix);
-        }
         // split function
         remainder = ifStartsWithReturnRemainder("split(", function);
         if (remainder != null) {
@@ -2153,6 +2039,10 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         }
 
         // miscellaneous functions
+        String builtIn = 
SimpleFunctionDispatcher.tryCreateCodeBuiltIn(camelContext, function, 
token.getIndex());
+        if (builtIn != null) {
+            return builtIn;
+        }
         String misc = createCodeExpressionMisc(camelContext, function);
         if (misc != null) {
             return misc;
@@ -2163,23 +2053,10 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return math;
         }
 
-        // attachments
-        if ("attachments".equals(function) || 
"clearAttachments".equals(function)
-                || ifStartsWithReturnRemainder("setAttachment", function) != 
null
-                || ifStartsWithReturnRemainder("attachment", function) != 
null) {
-            String code = createCodeAttachments(camelContext, function);
-            if (code != null) {
-                return code;
-            }
-        }
-        // base64
-        if ("base64Encode".equals(function) || "base64Decode".equals(function)
-                || ifStartsWithReturnRemainder("base64Encode", function) != 
null
-                || ifStartsWithReturnRemainder("base64Decode", function) != 
null) {
-            String code = createCodeBase64(camelContext, function);
-            if (code != null) {
-                return code;
-            }
+        // code from external components (attachments, base64, ...)
+        String external = 
SimpleFunctionDispatcher.tryCreateCodeExternal(camelContext, function, 
token.getIndex());
+        if (external != null) {
+            return external;
         }
 
         throw new SimpleParserException("Unknown function: " + function, 
token.getIndex());
@@ -2791,24 +2668,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         throw new SimpleParserException("Unknown file language syntax: " + 
remainder, token.getIndex());
     }
 
-    private String createCodeAttachments(CamelContext camelContext, String 
function) {
-        SimpleLanguageFunctionFactory factory = 
ResolverHelper.resolveMandatoryBootstrapService(
-                camelContext,
-                SimpleLanguageFunctionFactory.FACTORY + "/camel-attachments",
-                SimpleLanguageFunctionFactory.class,
-                "camel-attachments");
-        return factory.createCode(camelContext, function, token.getIndex());
-    }
-
-    private String createCodeBase64(CamelContext camelContext, String 
function) {
-        SimpleLanguageFunctionFactory factory = 
ResolverHelper.resolveMandatoryBootstrapService(
-                camelContext,
-                SimpleLanguageFunctionFactory.FACTORY + "/camel-base64",
-                SimpleLanguageFunctionFactory.class,
-                "camel-base64");
-        return factory.createCode(camelContext, function, token.getIndex());
-    }
-
     private String createCodeExpressionMisc(CamelContext camelContext, String 
function) {
         String remainder;
 
@@ -3123,27 +2982,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return "Object value = " + exp + ";\n        return 
containsIgnoreCase(exchange, value, " + pattern + ");";
         }
 
-        // random function
-        remainder = ifStartsWithReturnRemainder("random(", function);
-        if (remainder != null) {
-            String values = StringHelper.beforeLast(remainder, ")");
-            if (values == null || ObjectHelper.isEmpty(values)) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${random(min,max)} or ${random(max)} 
was: " + function, token.getIndex());
-            }
-            if (values.contains(",")) {
-                String before = StringHelper.before(remainder, ",");
-                before = before.trim();
-                String after = StringHelper.after(remainder, ",");
-                after = after.trim();
-                if (after.endsWith(")")) {
-                    after = after.substring(0, after.length() - 1);
-                }
-                return "random(exchange, " + before + ", " + after + ")";
-            } else {
-                return "random(exchange, 0, " + values.trim() + ")";
-            }
-        }
         remainder = ifStartsWithReturnRemainder("range(", function);
         if (remainder != null) {
             String values = StringHelper.beforeLast(remainder, ")");
@@ -3260,16 +3098,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return "shuffle(exchange, " + p + ")";
         }
 
-        // skip function
-        remainder = ifStartsWithReturnRemainder("skip(", function);
-        if (remainder != null) {
-            String values = StringHelper.beforeLast(remainder, ")");
-            if (values == null || ObjectHelper.isEmpty(values)) {
-                throw new SimpleParserException("Valid syntax: ${skip(number)} 
was: " + function, token.getIndex());
-            }
-            return "skip(exchange, " + values.trim() + ")";
-        }
-
         // pad function
         remainder = ifStartsWithReturnRemainder("pad(", function);
         if (remainder != null) {
@@ -3845,16 +3673,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return "Object o = " + exp + ";\n        return 
normalizeWhitespace(exchange, o);";
         }
 
-        // collate function
-        remainder = ifStartsWithReturnRemainder("collate(", function);
-        if (remainder != null) {
-            String values = StringHelper.beforeLast(remainder, ")");
-            if (values == null || ObjectHelper.isEmpty(values)) {
-                throw new SimpleParserException("Valid syntax: 
${collate(group)} was: " + function, token.getIndex());
-            }
-            return "collate(exchange, " + values.trim() + ")";
-        }
-
         // messageHistory function
         remainder = ifStartsWithReturnRemainder("messageHistory", function);
         if (remainder != null) {
@@ -3870,45 +3688,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return "messageHistory(exchange, true)";
         }
 
-        // join function
-        remainder = ifStartsWithReturnRemainder("join(", function);
-        if (remainder != null) {
-            String values = StringHelper.beforeLast(remainder, ")");
-            String separator = "\",\"";
-            String prefix = null;
-            String exp = "body";
-            if (ObjectHelper.isNotEmpty(values)) {
-                String[] tokens = codeSplitSafe(values, ',', true, true);
-                if (tokens.length > 3) {
-                    throw new SimpleParserException(
-                            "Valid syntax: 
${join(separator,prefix,expression)} was: " + function, token.getIndex());
-                }
-                // single quotes should be double quotes
-                for (int i = 0; i < tokens.length; i++) {
-                    String s = tokens[i];
-                    if (StringHelper.isSingleQuoted(s)) {
-                        s = StringHelper.removeLeadingAndEndingQuotes(s);
-                        s = StringQuoteHelper.doubleQuote(s);
-                        tokens[i] = s;
-                    } else if (i < 2 && !StringHelper.isDoubleQuoted(s)) {
-                        s = StringQuoteHelper.doubleQuote(s);
-                        tokens[i] = s;
-                    }
-                }
-                if (tokens.length == 3) {
-                    separator = tokens[0];
-                    prefix = tokens[1];
-                    exp = tokens[2];
-                } else if (tokens.length == 2) {
-                    separator = tokens[0];
-                    prefix = tokens[1];
-                } else {
-                    separator = tokens[0];
-                }
-            }
-            return "var val = " + exp + ";\n        return join(exchange, val, 
" + separator + ", " + prefix + ");";
-        }
-
         // empty function
         remainder = ifStartsWithReturnRemainder("empty(", function);
         if (remainder != null) {
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollateFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollateFunctionFactory.java
new file mode 100644
index 000000000000..c2a3a43c5894
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollateFunctionFactory.java
@@ -0,0 +1,59 @@
+/*
+ * 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.functions;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.SimpleExpressionBuilder;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+
+import static 
org.apache.camel.language.simple.ast.SimpleFunctionExpression.ifStartsWithReturnRemainder;
+
+/**
+ * Built-in Simple function: {@code ${collate(group)}}.
+ */
+public final class CollateFunctionFactory implements 
SimpleLanguageFunctionFactory {
+
+    @Override
+    public Expression createFunction(CamelContext camelContext, String 
function, int index) {
+        String remainder = ifStartsWithReturnRemainder("collate(", function);
+        if (remainder == null) {
+            return null;
+        }
+        String values = StringHelper.beforeLast(remainder, ")");
+        if (values == null || ObjectHelper.isEmpty(values)) {
+            throw new SimpleParserException("Valid syntax: ${collate(group)} 
was: " + function, index);
+        }
+        return SimpleExpressionBuilder.collateExpression("${body}", 
values.trim());
+    }
+
+    @Override
+    public String createCode(CamelContext camelContext, String function, int 
index) {
+        String remainder = ifStartsWithReturnRemainder("collate(", function);
+        if (remainder == null) {
+            return null;
+        }
+        String values = StringHelper.beforeLast(remainder, ")");
+        if (values == null || ObjectHelper.isEmpty(values)) {
+            throw new SimpleParserException("Valid syntax: ${collate(group)} 
was: " + function, index);
+        }
+        return "collate(exchange, " + values.trim() + ")";
+    }
+}
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/JoinFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/JoinFunctionFactory.java
new file mode 100644
index 000000000000..ea124ccdd6d2
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/JoinFunctionFactory.java
@@ -0,0 +1,107 @@
+/*
+ * 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.functions;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.SimpleExpressionBuilder;
+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;
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.StringQuoteHelper;
+
+import static 
org.apache.camel.language.simple.ast.SimpleFunctionExpression.ifStartsWithReturnRemainder;
+
+/**
+ * Built-in Simple function: {@code ${join()}} / {@code ${join(separator)}} / 
{@code ${join(separator,prefix)}} /
+ * {@code ${join(separator,prefix,expression)}}.
+ */
+public final class JoinFunctionFactory implements 
SimpleLanguageFunctionFactory {
+
+    @Override
+    public Expression createFunction(CamelContext camelContext, String 
function, int index) {
+        String remainder = ifStartsWithReturnRemainder("join(", function);
+        if (remainder == null) {
+            return null;
+        }
+        String values = StringHelper.beforeLast(remainder, ")");
+        String separator = ",";
+        String prefix = null;
+        String exp = "${body}";
+        if (ObjectHelper.isNotEmpty(values)) {
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            if (tokens.length > 3) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${join(separator,prefix,expression)} 
was: " + function, index);
+            }
+            if (tokens.length == 3) {
+                separator = tokens[0];
+                prefix = tokens[1];
+                exp = tokens[2];
+            } else if (tokens.length == 2) {
+                separator = tokens[0];
+                prefix = tokens[1];
+            } else {
+                separator = tokens[0];
+            }
+        }
+        return SimpleExpressionBuilder.joinExpression(exp, separator, prefix);
+    }
+
+    @Override
+    public String createCode(CamelContext camelContext, String function, int 
index) {
+        String remainder = ifStartsWithReturnRemainder("join(", function);
+        if (remainder == null) {
+            return null;
+        }
+        String values = StringHelper.beforeLast(remainder, ")");
+        String separator = "\",\"";
+        String prefix = null;
+        String exp = "body";
+        if (ObjectHelper.isNotEmpty(values)) {
+            String[] tokens = SimpleFunctionHelper.codeSplitSafe(values, ',', 
true, true);
+            if (tokens.length > 3) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${join(separator,prefix,expression)} 
was: " + function, index);
+            }
+            for (int i = 0; i < tokens.length; i++) {
+                String s = tokens[i];
+                if (StringHelper.isSingleQuoted(s)) {
+                    s = StringHelper.removeLeadingAndEndingQuotes(s);
+                    s = StringQuoteHelper.doubleQuote(s);
+                    tokens[i] = s;
+                } else if (i < 2 && !StringHelper.isDoubleQuoted(s)) {
+                    s = StringQuoteHelper.doubleQuote(s);
+                    tokens[i] = s;
+                }
+            }
+            if (tokens.length == 3) {
+                separator = tokens[0];
+                prefix = tokens[1];
+                exp = tokens[2];
+            } else if (tokens.length == 2) {
+                separator = tokens[0];
+                prefix = tokens[1];
+            } else {
+                separator = tokens[0];
+            }
+        }
+        return "var val = " + exp + ";\n        return join(exchange, val, " + 
separator + ", " + prefix + ");";
+    }
+}
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/RandomFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/RandomFunctionFactory.java
new file mode 100644
index 000000000000..81e00c9aa521
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/RandomFunctionFactory.java
@@ -0,0 +1,77 @@
+/*
+ * 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.functions;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.SimpleExpressionBuilder;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+
+import static 
org.apache.camel.language.simple.ast.SimpleFunctionExpression.ifStartsWithReturnRemainder;
+
+/**
+ * Built-in Simple function: {@code ${random(max)}} / {@code 
${random(min,max)}}.
+ */
+public final class RandomFunctionFactory implements 
SimpleLanguageFunctionFactory {
+
+    @Override
+    public Expression createFunction(CamelContext camelContext, String 
function, int index) {
+        String remainder = ifStartsWithReturnRemainder("random(", function);
+        if (remainder == null) {
+            return null;
+        }
+        String values = StringHelper.beforeLast(remainder, ")");
+        if (values == null || ObjectHelper.isEmpty(values)) {
+            throw new SimpleParserException(
+                    "Valid syntax: ${random(min,max)} or ${random(max)} was: " 
+ function, index);
+        }
+        if (values.contains(",")) {
+            String[] tokens = values.split(",", 3);
+            if (tokens.length > 2) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${random(min,max)} or ${random(max)} 
was: " + function, index);
+            }
+            return SimpleExpressionBuilder.randomExpression(tokens[0].trim(), 
tokens[1].trim());
+        }
+        return SimpleExpressionBuilder.randomExpression("0", values.trim());
+    }
+
+    @Override
+    public String createCode(CamelContext camelContext, String function, int 
index) {
+        String remainder = ifStartsWithReturnRemainder("random(", function);
+        if (remainder == null) {
+            return null;
+        }
+        String values = StringHelper.beforeLast(remainder, ")");
+        if (values == null || ObjectHelper.isEmpty(values)) {
+            throw new SimpleParserException(
+                    "Valid syntax: ${random(min,max)} or ${random(max)} was: " 
+ function, index);
+        }
+        if (values.contains(",")) {
+            String before = StringHelper.before(remainder, ",").trim();
+            String after = StringHelper.after(remainder, ",").trim();
+            if (after.endsWith(")")) {
+                after = after.substring(0, after.length() - 1);
+            }
+            return "random(exchange, " + before + ", " + after + ")";
+        }
+        return "random(exchange, 0, " + values.trim() + ")";
+    }
+}
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/SkipFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/SkipFunctionFactory.java
new file mode 100644
index 000000000000..e5fb4073fcd8
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/SkipFunctionFactory.java
@@ -0,0 +1,59 @@
+/*
+ * 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.functions;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.SimpleExpressionBuilder;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+
+import static 
org.apache.camel.language.simple.ast.SimpleFunctionExpression.ifStartsWithReturnRemainder;
+
+/**
+ * Built-in Simple function: {@code ${skip(number)}}.
+ */
+public final class SkipFunctionFactory implements 
SimpleLanguageFunctionFactory {
+
+    @Override
+    public Expression createFunction(CamelContext camelContext, String 
function, int index) {
+        String remainder = ifStartsWithReturnRemainder("skip(", function);
+        if (remainder == null) {
+            return null;
+        }
+        String values = StringHelper.beforeLast(remainder, ")");
+        if (values == null || ObjectHelper.isEmpty(values)) {
+            throw new SimpleParserException("Valid syntax: ${skip(number)} 
was: " + function, index);
+        }
+        return SimpleExpressionBuilder.skipExpression("${body}", 
values.trim());
+    }
+
+    @Override
+    public String createCode(CamelContext camelContext, String function, int 
index) {
+        String remainder = ifStartsWithReturnRemainder("skip(", function);
+        if (remainder == null) {
+            return null;
+        }
+        String values = StringHelper.beforeLast(remainder, ")");
+        if (values == null || ObjectHelper.isEmpty(values)) {
+            throw new SimpleParserException("Valid syntax: ${skip(number)} 
was: " + function, index);
+        }
+        return "skip(exchange, " + values.trim() + ")";
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/csimple/CSimpleExpressionParserTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/csimple/CSimpleExpressionParserTest.java
index 3997120b316b..2d5dbaf53b53 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/csimple/CSimpleExpressionParserTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/csimple/CSimpleExpressionParserTest.java
@@ -46,29 +46,10 @@ public class CSimpleExpressionParserTest {
     }
 
     @Test
-    public void testMisc() {
+    public void testMessageHistory() {
         CSimpleExpressionParser parser = new CSimpleExpressionParser();
 
-        String code = parser.parseExpression("${random(10)}");
-        Assertions.assertEquals("random(exchange, 0, 10)", code);
-        code = parser.parseExpression("${random(10, 20)}");
-        Assertions.assertEquals("random(exchange, 10, 20)", code);
-        code = parser.parseExpression("${random(10, ${header.max})}");
-        Assertions.assertEquals("random(exchange, 10, header(message, 
\"max\"))", code);
-        code = parser.parseExpression("${random(${header.min}, 
${header.max})}");
-        Assertions.assertEquals("random(exchange, header(message, \"min\"), 
header(message, \"max\"))", code);
-
-        code = parser.parseExpression("${skip(10)}");
-        Assertions.assertEquals("skip(exchange, 10)", code);
-        code = parser.parseExpression("${skip(${header.max})}");
-        Assertions.assertEquals("skip(exchange, header(message, \"max\"))", 
code);
-
-        code = parser.parseExpression("${collate(10)}");
-        Assertions.assertEquals("collate(exchange, 10)", code);
-        code = parser.parseExpression("${collate(${header.max})}");
-        Assertions.assertEquals("collate(exchange, header(message, \"max\"))", 
code);
-
-        code = parser.parseExpression("${messageHistory}");
+        String code = parser.parseExpression("${messageHistory}");
         Assertions.assertEquals("messageHistory(exchange, true)", code);
         code = parser.parseExpression("${messageHistory(false)}");
         Assertions.assertEquals("messageHistory(exchange, false)", code);
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 1bd1a04f4faa..7255773cf3f2 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
@@ -26,7 +26,6 @@ import java.util.Calendar;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -1959,199 +1958,6 @@ public class SimpleTest extends LanguageTestSupport {
         assertPredicate("${body} regex 
'^(tel:\\+)(974)(44)(\\d+)|^(974)(44)(\\d+)'", false);
     }
 
-    @Test
-    public void testCollateEven() {
-        List<Object> data = new ArrayList<>();
-        data.add("A");
-        data.add("B");
-        data.add("C");
-        data.add("D");
-        data.add("E");
-        data.add("F");
-        exchange.getIn().setBody(data);
-
-        Iterator it = (Iterator) evaluateExpression("${collate(3)}", null);
-        List chunk = (List) it.next();
-        List chunk2 = (List) it.next();
-        assertFalse(it.hasNext());
-
-        assertEquals(3, chunk.size());
-        assertEquals(3, chunk2.size());
-
-        assertEquals("A", chunk.get(0));
-        assertEquals("B", chunk.get(1));
-        assertEquals("C", chunk.get(2));
-        assertEquals("D", chunk2.get(0));
-        assertEquals("E", chunk2.get(1));
-        assertEquals("F", chunk2.get(2));
-    }
-
-    @Test
-    public void testCollateOdd() {
-        List<Object> data = new ArrayList<>();
-        data.add("A");
-        data.add("B");
-        data.add("C");
-        data.add("D");
-        data.add("E");
-        data.add("F");
-        data.add("G");
-        exchange.getIn().setBody(data);
-
-        Iterator it = (Iterator) evaluateExpression("${collate(3)}", null);
-        List chunk = (List) it.next();
-        List chunk2 = (List) it.next();
-        List chunk3 = (List) it.next();
-        assertFalse(it.hasNext());
-
-        assertEquals(3, chunk.size());
-        assertEquals(3, chunk2.size());
-        assertEquals(1, chunk3.size());
-
-        assertEquals("A", chunk.get(0));
-        assertEquals("B", chunk.get(1));
-        assertEquals("C", chunk.get(2));
-        assertEquals("D", chunk2.get(0));
-        assertEquals("E", chunk2.get(1));
-        assertEquals("F", chunk2.get(2));
-        assertEquals("G", chunk3.get(0));
-    }
-
-    @Test
-    public void testCollateDynamic() {
-        List<Object> data = new ArrayList<>();
-        data.add("A");
-        data.add("B");
-        data.add("C");
-        data.add("D");
-        data.add("E");
-        data.add("F");
-        data.add("G");
-        exchange.getIn().setBody(data);
-
-        exchange.getIn().setHeader("num", 3);
-
-        Iterator it = (Iterator) 
evaluateExpression("${collate(${header.num})}", null);
-        List chunk = (List) it.next();
-        List chunk2 = (List) it.next();
-        List chunk3 = (List) it.next();
-        assertFalse(it.hasNext());
-
-        assertEquals(3, chunk.size());
-        assertEquals(3, chunk2.size());
-        assertEquals(1, chunk3.size());
-
-        assertEquals("A", chunk.get(0));
-        assertEquals("B", chunk.get(1));
-        assertEquals("C", chunk.get(2));
-        assertEquals("D", chunk2.get(0));
-        assertEquals("E", chunk2.get(1));
-        assertEquals("F", chunk2.get(2));
-        assertEquals("G", chunk3.get(0));
-    }
-
-    @Test
-    public void testSkip() {
-        List<Object> data = new ArrayList<>();
-        data.add("A");
-        data.add("B");
-        data.add("C");
-        data.add("D");
-        data.add("E");
-        data.add("F");
-        exchange.getIn().setBody(data);
-
-        Iterator it = (Iterator) evaluateExpression("${skip(2)}", null);
-        assertEquals("C", it.next());
-        assertEquals("D", it.next());
-        assertEquals("E", it.next());
-        assertEquals("F", it.next());
-        assertFalse(it.hasNext());
-    }
-
-    @Test
-    public void testSkipDynamic() {
-        List<Object> data = new ArrayList<>();
-        data.add("A");
-        data.add("B");
-        data.add("C");
-        data.add("D");
-        data.add("E");
-        data.add("F");
-        exchange.getIn().setBody(data);
-        exchange.getIn().setHeader("num", 4);
-
-        Iterator it = (Iterator) evaluateExpression("${skip(${header.num})}", 
null);
-        assertEquals("E", it.next());
-        assertEquals("F", it.next());
-        assertFalse(it.hasNext());
-    }
-
-    @Test
-    public void testJoinBody() {
-        List<Object> data = new ArrayList<>();
-        data.add("A");
-        data.add("B");
-        data.add("C");
-        exchange.getIn().setBody(data);
-
-        assertExpression("${join()}", "A,B,C");
-        assertExpression("${join(;)}", "A;B;C");
-        assertExpression("${join(' ')}", "A B C");
-        assertExpression("${join(',','id=')}", "id=A,id=B,id=C");
-        assertExpression("${join(&,id=)}", "id=A&id=B&id=C");
-    }
-
-    @Test
-    public void testJoinHeader() {
-        List<Object> data = new ArrayList<>();
-        data.add("A");
-        data.add("B");
-        data.add("C");
-        exchange.getIn().setHeader("id", data);
-
-        assertExpression("${join('&','id=','${header.id}')}", 
"id=A&id=B&id=C");
-    }
-
-    @Test
-    public void testRandomExpression() {
-        int min = 1;
-        int max = 10;
-        int iterations = 30;
-        int i = 0;
-        for (i = 0; i < iterations; i++) {
-            Expression expression = 
context.resolveLanguage("simple").createExpression("${random(1,10)}");
-            assertTrue(
-                    min <= expression.evaluate(exchange, Integer.class) && 
expression.evaluate(exchange, Integer.class) < max);
-        }
-        for (i = 0; i < iterations; i++) {
-            Expression expression = 
context.resolveLanguage("simple").createExpression("${random(10)}");
-            assertTrue(0 <= expression.evaluate(exchange, Integer.class) && 
expression.evaluate(exchange, Integer.class) < max);
-        }
-        Expression expression = 
context.resolveLanguage("simple").createExpression("${random(1, 10)}");
-        assertTrue(min <= expression.evaluate(exchange, Integer.class) && 
expression.evaluate(exchange, Integer.class) < max);
-
-        Expression expression1 = 
context.resolveLanguage("simple").createExpression("${random( 10)}");
-        assertTrue(0 <= expression1.evaluate(exchange, Integer.class) && 
expression1.evaluate(exchange, Integer.class) < max);
-
-        Exception e1 = assertThrows(Exception.class,
-                () -> assertExpression("${random(10,21,30)}", null),
-                "Should have thrown exception");
-
-        assertEquals("Valid syntax: ${random(min,max)} or ${random(max)} was: 
random(10,21,30)", e1.getCause().getMessage());
-
-        Exception e2 = assertThrows(Exception.class,
-                () -> assertExpression("${random()}", null),
-                "Should have thrown exception");
-
-        assertEquals("Valid syntax: ${random(min,max)} or ${random(max)} was: 
random()", e2.getCause().getMessage());
-
-        exchange.getIn().setHeader("max", 20);
-        Expression expression3 = 
context.resolveLanguage("simple").createExpression("${random(10,${header.max})}");
-        int num = expression3.evaluate(exchange, Integer.class);
-        assertTrue(num >= 0 && num < 20, "Should be 10..20");
-    }
-
     @Test
     public void testReplaceAllExpression() {
         exchange.getMessage().setBody("Hello a how are you");
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/AbstractSimpleFunctionFactoryTestSupport.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/AbstractSimpleFunctionFactoryTestSupport.java
new file mode 100644
index 000000000000..041975cb2d3c
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/AbstractSimpleFunctionFactoryTestSupport.java
@@ -0,0 +1,45 @@
+/*
+ * 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.functions;
+
+import org.apache.camel.ExchangeTestSupport;
+import org.apache.camel.Expression;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public abstract class AbstractSimpleFunctionFactoryTestSupport extends 
ExchangeTestSupport {
+
+    protected abstract SimpleLanguageFunctionFactory createFactory();
+
+    protected Object evaluate(String function) {
+        return evaluate(function, Object.class);
+    }
+
+    protected <T> T evaluate(String function, Class<T> type) {
+        Expression expression = 
context.resolveLanguage("simple").createExpression("${" + function + "}");
+        expression.init(context);
+        return expression.evaluate(exchange, type);
+    }
+
+    @SuppressWarnings("deprecation")
+    protected String createCode(String function) {
+        String code = createFactory().createCode(context, function, 0);
+        assertNotNull(code, "No code could be created for function: " + 
function);
+        return code;
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/CollateFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/CollateFunctionFactoryTest.java
new file mode 100644
index 000000000000..65d959725568
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/CollateFunctionFactoryTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.functions;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+public class CollateFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTestSupport {
+
+    @Override
+    protected SimpleLanguageFunctionFactory createFactory() {
+        return new CollateFunctionFactory();
+    }
+
+    @Test
+    public void testCollateEven() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        data.add("D");
+        data.add("E");
+        data.add("F");
+        exchange.getIn().setBody(data);
+
+        Iterator<?> it = (Iterator<?>) evaluate("collate(3)");
+        List<?> chunk = (List<?>) it.next();
+        List<?> chunk2 = (List<?>) it.next();
+        assertFalse(it.hasNext());
+
+        assertEquals(3, chunk.size());
+        assertEquals(3, chunk2.size());
+        assertEquals("A", chunk.get(0));
+        assertEquals("B", chunk.get(1));
+        assertEquals("C", chunk.get(2));
+        assertEquals("D", chunk2.get(0));
+        assertEquals("E", chunk2.get(1));
+        assertEquals("F", chunk2.get(2));
+    }
+
+    @Test
+    public void testCollateOdd() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        data.add("D");
+        data.add("E");
+        data.add("F");
+        data.add("G");
+        exchange.getIn().setBody(data);
+
+        Iterator<?> it = (Iterator<?>) evaluate("collate(3)");
+        List<?> chunk = (List<?>) it.next();
+        List<?> chunk2 = (List<?>) it.next();
+        List<?> chunk3 = (List<?>) it.next();
+        assertFalse(it.hasNext());
+
+        assertEquals(3, chunk.size());
+        assertEquals(3, chunk2.size());
+        assertEquals(1, chunk3.size());
+        assertEquals("A", chunk.get(0));
+        assertEquals("B", chunk.get(1));
+        assertEquals("C", chunk.get(2));
+        assertEquals("D", chunk2.get(0));
+        assertEquals("E", chunk2.get(1));
+        assertEquals("F", chunk2.get(2));
+        assertEquals("G", chunk3.get(0));
+    }
+
+    @Test
+    public void testCollateDynamic() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        data.add("D");
+        data.add("E");
+        data.add("F");
+        data.add("G");
+        exchange.getIn().setBody(data);
+        exchange.getIn().setHeader("num", 3);
+
+        Iterator<?> it = (Iterator<?>) evaluate("collate(${header.num})");
+        List<?> chunk = (List<?>) it.next();
+        List<?> chunk2 = (List<?>) it.next();
+        List<?> chunk3 = (List<?>) it.next();
+        assertFalse(it.hasNext());
+
+        assertEquals(3, chunk.size());
+        assertEquals(3, chunk2.size());
+        assertEquals(1, chunk3.size());
+        assertEquals("A", chunk.get(0));
+        assertEquals("B", chunk.get(1));
+        assertEquals("C", chunk.get(2));
+        assertEquals("D", chunk2.get(0));
+        assertEquals("E", chunk2.get(1));
+        assertEquals("F", chunk2.get(2));
+        assertEquals("G", chunk3.get(0));
+    }
+
+    @Test
+    public void testCreateCode() {
+        assertEquals("collate(exchange, 10)", createCode("collate(10)"));
+        assertEquals("collate(exchange, ${header.max})", 
createCode("collate(${header.max})"));
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/JoinFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/JoinFunctionFactoryTest.java
new file mode 100644
index 000000000000..ef1389ac0434
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/JoinFunctionFactoryTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.functions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class JoinFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTestSupport {
+
+    @Override
+    protected SimpleLanguageFunctionFactory createFactory() {
+        return new JoinFunctionFactory();
+    }
+
+    @Test
+    public void testJoinBody() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        exchange.getIn().setBody(data);
+
+        assertEquals("A,B,C", evaluate("join()", String.class));
+        assertEquals("A;B;C", evaluate("join(;)", String.class));
+        assertEquals("A B C", evaluate("join(' ')", String.class));
+        assertEquals("id=A,id=B,id=C", evaluate("join(',','id=')", 
String.class));
+        assertEquals("id=A&id=B&id=C", evaluate("join(&,id=)", String.class));
+    }
+
+    @Test
+    public void testJoinHeader() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        exchange.getIn().setHeader("id", data);
+
+        assertEquals("id=A&id=B&id=C", 
evaluate("join('&','id=','${header.id}')", String.class));
+    }
+
+    @Test
+    public void testCreateCode() {
+        assertEquals("var val = body;\n        return join(exchange, val, 
\",\", null);", createCode("join()"));
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/RandomFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/RandomFunctionFactoryTest.java
new file mode 100644
index 000000000000..72c01212584f
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/RandomFunctionFactoryTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.functions;
+
+import org.apache.camel.Expression;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+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;
+
+public class RandomFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTestSupport {
+
+    @Override
+    protected SimpleLanguageFunctionFactory createFactory() {
+        return new RandomFunctionFactory();
+    }
+
+    @Test
+    public void testRandomExpression() {
+        int min = 1;
+        int max = 10;
+
+        for (int i = 0; i < 30; i++) {
+            Expression expression = createFactory().createFunction(context, 
"random(1,10)", 0);
+            expression.init(context);
+            int num = expression.evaluate(exchange, Integer.class);
+            assertTrue(min <= num && num < max);
+        }
+        for (int i = 0; i < 30; i++) {
+            Expression expression = createFactory().createFunction(context, 
"random(10)", 0);
+            expression.init(context);
+            int num = expression.evaluate(exchange, Integer.class);
+            assertTrue(0 <= num && num < max);
+        }
+
+        int num1 = evaluate("random(1, 10)", Integer.class);
+        assertTrue(min <= num1 && num1 < max);
+
+        int num2 = evaluate("random( 10)", Integer.class);
+        assertTrue(0 <= num2 && num2 < max);
+
+        Exception e1 = assertThrows(Exception.class,
+                () -> evaluate("random(10,21,30)", Object.class),
+                "Should have thrown exception");
+        assertEquals("Valid syntax: ${random(min,max)} or ${random(max)} was: 
random(10,21,30)", e1.getCause().getMessage());
+
+        Exception e2 = assertThrows(Exception.class,
+                () -> evaluate("random()", Object.class),
+                "Should have thrown exception");
+        assertEquals("Valid syntax: ${random(min,max)} or ${random(max)} was: 
random()", e2.getCause().getMessage());
+
+        exchange.getIn().setHeader("max", 20);
+        int num = evaluate("random(10,${header.max})", Integer.class);
+        assertTrue(num >= 0 && num < 20, "Should be 10..20");
+    }
+
+    @Test
+    public void testCreateCode() {
+        assertEquals("random(exchange, 0, 10)", createCode("random(10)"));
+        assertEquals("random(exchange, 10, 20)", createCode("random(10, 20)"));
+        assertEquals("random(exchange, 10, ${header.max})", 
createCode("random(10, ${header.max})"));
+        assertEquals("random(exchange, ${header.min}, ${header.max})", 
createCode("random(${header.min}, ${header.max})"));
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/SkipFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/SkipFunctionFactoryTest.java
new file mode 100644
index 000000000000..59e5a8a51d83
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/SkipFunctionFactoryTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.functions;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+public class SkipFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTestSupport {
+
+    @Override
+    protected SimpleLanguageFunctionFactory createFactory() {
+        return new SkipFunctionFactory();
+    }
+
+    @Test
+    public void testSkip() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        data.add("D");
+        data.add("E");
+        data.add("F");
+        exchange.getIn().setBody(data);
+
+        Iterator<?> it = (Iterator<?>) evaluate("skip(2)");
+        assertEquals("C", it.next());
+        assertEquals("D", it.next());
+        assertEquals("E", it.next());
+        assertEquals("F", it.next());
+        assertFalse(it.hasNext());
+    }
+
+    @Test
+    public void testSkipDynamic() {
+        List<Object> data = new ArrayList<>();
+        data.add("A");
+        data.add("B");
+        data.add("C");
+        data.add("D");
+        data.add("E");
+        data.add("F");
+        exchange.getIn().setBody(data);
+        exchange.getIn().setHeader("num", 4);
+
+        Iterator<?> it = (Iterator<?>) evaluate("skip(${header.num})");
+        assertEquals("E", it.next());
+        assertEquals("F", it.next());
+        assertFalse(it.hasNext());
+    }
+
+    @Test
+    public void testCreateCode() {
+        assertEquals("skip(exchange, 10)", createCode("skip(10)"));
+        assertEquals("skip(exchange, ${header.max})", 
createCode("skip(${header.max})"));
+        assertEquals("skip(exchange, ${random(2,3)})", 
createCode("skip(${random(2,3)})"));
+    }
+}

Reply via email to