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 130d3624133e CAMEL-24752: camel-core - generated name to artifact 
table so unknown component, language and data format errors say which jar to 
add (#26459)
130d3624133e is described below

commit 130d3624133efdce3bfcf09dcf43b7b9e8cec701
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Sep 15 15:07:37 2026 +0200

    CAMEL-24752: camel-core - generated name to artifact table so unknown 
component, language and data format errors say which jar to add (#26459)
    
    ArtifactUtils is generated from the catalog at build time 
(update-artifact-helper goal) with the artifact of every component scheme, 
language, data format and built-in bean. NoSuchEndpointException, 
NoSuchLanguageException, DataFormatReifier and the YAML DSL use it to say which 
jar to add, or the nearest built-in name for a typo.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 catalog/camel-catalog/pom.xml                      |   2 +
 ...gDeadLetterChannelInvalidDeadLetterUriTest.java |   3 +-
 .../org/apache/camel/NoSuchEndpointException.java  |  22 +-
 .../org/apache/camel/NoSuchLanguageException.java  |   5 +-
 .../reifier/dataformat/DataFormatReifier.java      |  14 +-
 .../camel/impl/engine/DefaultCamelContextTest.java |  29 +-
 .../language/simple/SimpleSyntaxHintsTest.java     |  17 +-
 .../processor/DataFormatMissingJarHintTest.java    |  90 +++
 .../java/org/apache/camel/util/ArtifactUtils.java  | 892 +++++++++++++++++++++
 .../org/apache/camel/util/ArtifactUtilsTest.java   | 140 ++++
 .../deserializers/ExpressionDeserializers.java     |   2 +-
 .../dsl/yaml/GenerateYamlDeserializersMojo.java    |   2 +-
 .../apache/camel/dsl/yaml/ExpressionTest.groovy    |  20 +
 .../maven/packaging/UpdateArtifactHelper.java      | 170 ++++
 14 files changed, 1396 insertions(+), 12 deletions(-)

diff --git a/catalog/camel-catalog/pom.xml b/catalog/camel-catalog/pom.xml
index b17d57f913da..4b48280945d3 100644
--- a/catalog/camel-catalog/pom.xml
+++ b/catalog/camel-catalog/pom.xml
@@ -157,6 +157,8 @@
                             <goal>prepare-catalog</goal>
                             <!-- update secrets in camel-util -->
                             <goal>update-sensitive-helper</goal>
+                            <!-- update artifact names in camel-util -->
+                            <goal>update-artifact-helper</goal>
                             <!-- update mime-types in camel-util -->
                             <goal>update-mime-type-helper</goal>
                             <!-- update important headers in camel-util -->
diff --git 
a/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringDeadLetterChannelInvalidDeadLetterUriTest.java
 
b/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringDeadLetterChannelInvalidDeadLetterUriTest.java
index c54afaf2a29e..170f425e5288 100644
--- 
a/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringDeadLetterChannelInvalidDeadLetterUriTest.java
+++ 
b/components/camel-spring-parent/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringDeadLetterChannelInvalidDeadLetterUriTest.java
@@ -49,7 +49,8 @@ class SpringDeadLetterChannelInvalidDeadLetterUriTest extends 
SpringTestSupport
         FailedToCreateRouteException ftcre = 
assertIsInstanceOf(FailedToCreateRouteException.class, e);
         NoSuchEndpointException cause = 
assertIsInstanceOf(NoSuchEndpointException.class, ftcre.getCause());
         assertEquals(
-                "No endpoint could be found for: xxx, please check your 
classpath contains the needed Camel component jar.",
+                "No endpoint could be found for: xxx, please check your 
classpath contains the needed Camel component jar"
+                     + " (not a built-in Camel component).",
                 cause.getMessage());
     }
 
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/NoSuchEndpointException.java 
b/core/camel-api/src/main/java/org/apache/camel/NoSuchEndpointException.java
index 484abd52f085..da0bbabd0d0d 100644
--- a/core/camel-api/src/main/java/org/apache/camel/NoSuchEndpointException.java
+++ b/core/camel-api/src/main/java/org/apache/camel/NoSuchEndpointException.java
@@ -18,6 +18,8 @@ package org.apache.camel;
 
 import java.util.Objects;
 
+import org.apache.camel.util.ArtifactUtils;
+
 import static org.apache.camel.util.URISupport.sanitizeUri;
 
 /**
@@ -39,10 +41,28 @@ public class NoSuchEndpointException extends 
RuntimeCamelException {
      */
     public NoSuchEndpointException(String uri) {
         super("No endpoint could be found for: " + 
sanitizeUri(Objects.requireNonNull(uri, "uri"))
-              + ", please check your classpath contains the needed Camel 
component jar.");
+              + ", please check your classpath contains the needed Camel 
component jar"
+              + ArtifactUtils.componentHint(scheme(uri)) + ".");
         this.uri = sanitizeUri(uri);
     }
 
+    /**
+     * The component scheme the endpoint would be resolved by: the text before 
the first colon or question mark, or the
+     * whole uri when it has neither.
+     */
+    private static String scheme(String uri) {
+        int pos1 = uri.indexOf(':');
+        int pos2 = uri.indexOf('?');
+        if (pos1 != -1 && pos2 != -1) {
+            return uri.substring(0, Math.min(pos1, pos2));
+        } else if (pos1 != -1) {
+            return uri.substring(0, pos1);
+        } else if (pos2 != -1) {
+            return uri.substring(0, pos2);
+        }
+        return uri;
+    }
+
     /**
      * @param uri           the endpoint URI that could not be found
      * @param resolveMethod a resolution instruction appended after "please" 
in the error message
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/NoSuchLanguageException.java 
b/core/camel-api/src/main/java/org/apache/camel/NoSuchLanguageException.java
index 846593469527..75c3edbe10a3 100644
--- a/core/camel-api/src/main/java/org/apache/camel/NoSuchLanguageException.java
+++ b/core/camel-api/src/main/java/org/apache/camel/NoSuchLanguageException.java
@@ -18,6 +18,8 @@ package org.apache.camel;
 
 import java.util.Objects;
 
+import org.apache.camel.util.ArtifactUtils;
+
 /**
  * A runtime exception thrown if an attempt is made to resolve an unknown 
language definition.
  *
@@ -32,8 +34,7 @@ public class NoSuchLanguageException extends 
RuntimeCamelException {
      */
     public NoSuchLanguageException(String language) {
         super("No language could be found for: " + 
Objects.requireNonNull(language, "language")
-              + (language.matches("[a-z0-9-]+")
-                      ? " (a Camel language needs its dependency on the 
classpath, e.g. camel-" + language + ")" : ""));
+              + ArtifactUtils.languageHint(language));
         this.language = language;
     }
 
diff --git 
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/DataFormatReifier.java
 
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/DataFormatReifier.java
index 285a3f21845e..6db4f512f39b 100644
--- 
a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/DataFormatReifier.java
+++ 
b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/DataFormatReifier.java
@@ -35,6 +35,7 @@ import org.apache.camel.support.CamelContextHelper;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.PropertyBindingSupport;
 import org.apache.camel.support.PropertyConfigurerHelper;
+import org.apache.camel.util.ArtifactUtils;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 import org.slf4j.Logger;
@@ -97,7 +98,12 @@ public abstract class DataFormatReifier<T extends 
DataFormatDefinition> extends
             if (type == null) {
                 dataFormat = camelContext.resolveDataFormat(ref);
                 if (dataFormat == null) {
-                    throw new IllegalArgumentException("Cannot find data 
format in registry with ref: " + ref);
+                    // hint only when the ref names a built-in data format 
whose jar is missing; a custom
+                    // registry ref is a bean name, so it must not get a 
did-you-mean for a data format
+                    String artifact = ArtifactUtils.dataFormatArtifact(ref);
+                    throw new IllegalArgumentException(
+                            "Cannot find data format in registry with ref: " + 
ref
+                                                       + (artifact != null ? 
ArtifactUtils.dataFormatHint(ref) : ""));
                 }
 
                 return dataFormat;
@@ -253,10 +259,12 @@ public abstract class DataFormatReifier<T extends 
DataFormatDefinition> extends
                 // configure the rest of the options
                 configureDataFormat(dataFormat, 
definition.getDataFormatName());
             } else {
+                String name = definition.getDataFormatName();
                 throw new IllegalArgumentException(
-                        "Data format '" + (definition.getDataFormatName() != 
null ? definition.getDataFormatName() : "<null>")
+                        "Data format '" + (name != null ? name : "<null>")
                                                    + "' could not be created. "
-                                                   + "Ensure that the data 
format is valid and the associated Camel component is present on the 
classpath");
+                                                   + "Ensure that the data 
format is valid and the associated Camel component is present on the classpath"
+                                                   + 
ArtifactUtils.dataFormatHint(name));
             }
         }
         return dataFormat;
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/impl/engine/DefaultCamelContextTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/impl/engine/DefaultCamelContextTest.java
index 6f76552dd33a..c61cd2859be3 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/impl/engine/DefaultCamelContextTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/impl/engine/DefaultCamelContextTest.java
@@ -139,7 +139,34 @@ public class DefaultCamelContextTest extends TestSupport {
                 () -> camelContext.getEndpoint("xxx", Endpoint.class));
 
         assertEquals(
-                "No endpoint could be found for: xxx, please check your 
classpath contains the needed Camel component jar.",
+                "No endpoint could be found for: xxx, please check your 
classpath contains the needed Camel component jar"
+                     + " (not a built-in Camel component).",
+                e.getMessage());
+    }
+
+    @Test
+    public void testGetEndpointUnknownSchemeSaysWhatToAdd() {
+        DefaultCamelContext camelContext = new DefaultCamelContext();
+
+        // a built-in component whose jar is not on the classpath
+        NoSuchEndpointException e = assertThrows(NoSuchEndpointException.class,
+                () -> 
camelContext.getEndpoint("kafka:myTopic?brokers=localhost"));
+        assertEquals(
+                "No endpoint could be found for: 
kafka://myTopic?brokers=localhost, please check your classpath contains"
+                     + " the needed Camel component jar (the kafka component 
is in camel-kafka; add camel-kafka to the"
+                     + " classpath).",
+                e.getMessage());
+
+        // an alternative scheme is in the same jar as its main scheme
+        e = assertThrows(NoSuchEndpointException.class, () -> 
camelContext.getEndpoint("coaps://localhost/foo"));
+        assertTrue(e.getMessage().contains("the coaps component is in 
camel-coap; add camel-coap to the classpath"),
+                e.getMessage());
+
+        // a typo of a built-in scheme
+        e = assertThrows(NoSuchEndpointException.class, () -> 
camelContext.getEndpoint("kafak:myTopic"));
+        assertEquals(
+                "No endpoint could be found for: kafak://myTopic, please check 
your classpath contains the needed Camel"
+                     + " component jar (not a built-in Camel component; did 
you mean 'kafka'?).",
                 e.getMessage());
     }
 
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
index 73ce4fd93499..ab250e61ce9d 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java
@@ -146,8 +146,21 @@ public class SimpleSyntaxHintsTest extends 
ExchangeTestSupport {
 
     @Test
     public void testMissingLanguageNamesTheDependency() {
-        Exception e = assertThrows(Exception.class, () -> 
context.resolveLanguage("cheese"));
-        assertThat(e.getMessage()).contains("No language could be found for: 
cheese").contains("camel-cheese");
+        // xquery is a built-in language whose jar (camel-saxon, not 
camel-xquery) is not on the classpath
+        Exception e = assertThrows(Exception.class, () -> 
context.resolveLanguage("xquery"));
+        assertThat(e.getMessage()).contains("No language could be found for: 
xquery")
+                .contains("the xquery language is in camel-saxon; add 
camel-saxon to the classpath)");
+    }
+
+    @Test
+    public void testUnknownLanguageSaysDidYouMean() {
+        Exception e = assertThrows(Exception.class, () -> 
context.resolveLanguage("simpel"));
+        assertThat(e.getMessage()).contains("No language could be found for: 
simpel")
+                .contains("not a built-in Camel language; did you mean 
'simple'?");
+
+        e = assertThrows(Exception.class, () -> 
context.resolveLanguage("cheese"));
+        assertThat(e.getMessage()).contains("No language could be found for: 
cheese")
+                .contains("(not a built-in Camel 
language)").doesNotContain("did you mean");
     }
 
     @Test
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/DataFormatMissingJarHintTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/DataFormatMissingJarHintTest.java
new file mode 100644
index 000000000000..120581bdf899
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/DataFormatMissingJarHintTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.processor;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A data format whose jar is not on the classpath fails with the artifact to 
add.
+ */
+public class DataFormatMissingJarHintTest extends ContextTestSupport {
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @Test
+    public void testModelDataFormatSaysWhatToAdd() {
+        String msg = messages(assertThrows(Exception.class, () -> {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    
from("direct:start").unmarshal().jaxb("com.foo").to("mock:result");
+                }
+            });
+            context.start();
+        }));
+
+        assertTrue(msg.contains("Data format 'jaxb' could not be created."), 
msg);
+        assertTrue(msg.contains("(the jaxb data format is in camel-jaxb; add 
camel-jaxb to the classpath)"), msg);
+    }
+
+    @Test
+    public void testDataFormatByRefSaysWhatToAdd() {
+        String msg = messages(assertThrows(Exception.class, () -> {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    
from("direct:start").unmarshal().custom("jaxb").to("mock:result");
+                }
+            });
+            context.start();
+        }));
+
+        assertTrue(msg.contains("Cannot find data format in registry with ref: 
jaxb (the jaxb data format is in camel-jaxb;"),
+                msg);
+    }
+
+    @Test
+    public void testDataFormatByRefThatIsNotBuiltInHasNoHint() {
+        String msg = messages(assertThrows(Exception.class, () -> {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    
from("direct:start").unmarshal().custom("myFormat").to("mock:result");
+                }
+            });
+            context.start();
+        }));
+
+        assertTrue(msg.contains("Cannot find data format in registry with ref: 
myFormat\n"), msg);
+    }
+
+    private static String messages(Throwable e) {
+        StringBuilder sb = new StringBuilder();
+        for (Throwable t = e; t != null; t = t.getCause()) {
+            sb.append(t.getMessage()).append('\n');
+        }
+        return sb.toString();
+    }
+}
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java 
b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
new file mode 100644
index 000000000000..577a1b4f88fc
--- /dev/null
+++ b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
@@ -0,0 +1,892 @@
+/*
+ * 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.util;
+
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Which Camel artifact (Maven artifactId) ships a component, data format, 
language or built-in bean.
+ * <p/>
+ * The runtime cannot tell this on its own: the metadata that knows is in the 
jar that is missing. So the tables are
+ * generated from the catalog at build time (like {@link SensitiveUtils}) and 
used by the error messages that say what
+ * to add to the classpath when a name cannot be resolved, or which known name 
was likely meant.
+ */
+public final class ArtifactUtils {
+
+    // scheme -> artifactId, including alternative schemes
+    private static final Map<String, String> COMPONENTS = Map.ofEntries(
+            // Generated by camel build tools - do NOT edit this list!
+            // COMPONENTS: START
+            Map.entry("a2a", "camel-a2a"),
+            Map.entry("activemq", "camel-activemq"),
+            Map.entry("activemq6", "camel-activemq6"),
+            Map.entry("ai-resource", "camel-ai-resource"),
+            Map.entry("ai-tool", "camel-ai-tool"),
+            Map.entry("alibaba-eventbridge", "camel-alibaba-eventbridge"),
+            Map.entry("alibaba-fc", "camel-alibaba-fc"),
+            Map.entry("alibaba-kms", "camel-alibaba-kms"),
+            Map.entry("alibaba-mns", "camel-alibaba-mns"),
+            Map.entry("alibaba-oss", "camel-alibaba-oss"),
+            Map.entry("alibaba-ots", "camel-alibaba-ots"),
+            Map.entry("alibaba-sls", "camel-alibaba-sls"),
+            Map.entry("alibaba-sms", "camel-alibaba-sms"),
+            Map.entry("amqp", "camel-amqp"),
+            Map.entry("arangodb", "camel-arangodb"),
+            Map.entry("as2", "camel-as2"),
+            Map.entry("asterisk", "camel-asterisk"),
+            Map.entry("atmosphere-websocket", "camel-atmosphere-websocket"),
+            Map.entry("atom", "camel-atom"),
+            Map.entry("avro", "camel-avro-rpc"),
+            Map.entry("aws-bedrock", "camel-aws-bedrock"),
+            Map.entry("aws-bedrock-agent", "camel-aws-bedrock"),
+            Map.entry("aws-bedrock-agent-runtime", "camel-aws-bedrock"),
+            Map.entry("aws-cloudtrail", "camel-aws-cloudtrail"),
+            Map.entry("aws-config", "camel-aws-config"),
+            Map.entry("aws-secrets-manager", "camel-aws-secrets-manager"),
+            Map.entry("aws-security-hub", "camel-aws-security-hub"),
+            Map.entry("aws2-athena", "camel-aws2-athena"),
+            Map.entry("aws2-comprehend", "camel-aws2-comprehend"),
+            Map.entry("aws2-cw", "camel-aws2-cw"),
+            Map.entry("aws2-ddb", "camel-aws2-ddb"),
+            Map.entry("aws2-ddbstream", "camel-aws2-ddb"),
+            Map.entry("aws2-ec2", "camel-aws2-ec2"),
+            Map.entry("aws2-ecs", "camel-aws2-ecs"),
+            Map.entry("aws2-eks", "camel-aws2-eks"),
+            Map.entry("aws2-eventbridge", "camel-aws2-eventbridge"),
+            Map.entry("aws2-iam", "camel-aws2-iam"),
+            Map.entry("aws2-kinesis", "camel-aws2-kinesis"),
+            Map.entry("aws2-kinesis-firehose", "camel-aws2-kinesis"),
+            Map.entry("aws2-kms", "camel-aws2-kms"),
+            Map.entry("aws2-lambda", "camel-aws2-lambda"),
+            Map.entry("aws2-mq", "camel-aws2-mq"),
+            Map.entry("aws2-msk", "camel-aws2-msk"),
+            Map.entry("aws2-polly", "camel-aws2-polly"),
+            Map.entry("aws2-redshift-data", "camel-aws2-redshift"),
+            Map.entry("aws2-rekognition", "camel-aws2-rekognition"),
+            Map.entry("aws2-s3", "camel-aws2-s3"),
+            Map.entry("aws2-s3-vectors", "camel-aws2-s3-vectors"),
+            Map.entry("aws2-ses", "camel-aws2-ses"),
+            Map.entry("aws2-sns", "camel-aws2-sns"),
+            Map.entry("aws2-sqs", "camel-aws2-sqs"),
+            Map.entry("aws2-step-functions", "camel-aws2-step-functions"),
+            Map.entry("aws2-sts", "camel-aws2-sts"),
+            Map.entry("aws2-textract", "camel-aws2-textract"),
+            Map.entry("aws2-timestream", "camel-aws2-timestream"),
+            Map.entry("aws2-transcribe", "camel-aws2-transcribe"),
+            Map.entry("aws2-translate", "camel-aws2-translate"),
+            Map.entry("azure-cosmosdb", "camel-azure-cosmosdb"),
+            Map.entry("azure-eventhubs", "camel-azure-eventhubs"),
+            Map.entry("azure-files", "camel-azure-files"),
+            Map.entry("azure-functions", "camel-azure-functions"),
+            Map.entry("azure-key-vault", "camel-azure-key-vault"),
+            Map.entry("azure-servicebus", "camel-azure-servicebus"),
+            Map.entry("azure-storage-blob", "camel-azure-storage-blob"),
+            Map.entry("azure-storage-datalake", 
"camel-azure-storage-datalake"),
+            Map.entry("azure-storage-queue", "camel-azure-storage-queue"),
+            Map.entry("bean", "camel-bean"),
+            Map.entry("bean-validator", "camel-bean-validator"),
+            Map.entry("bonita", "camel-bonita"),
+            Map.entry("box", "camel-box"),
+            Map.entry("braintree", "camel-braintree"),
+            Map.entry("browse", "camel-browse"),
+            Map.entry("caffeine-cache", "camel-caffeine"),
+            Map.entry("caffeine-loadcache", "camel-caffeine"),
+            Map.entry("camunda", "camel-camunda"),
+            Map.entry("chatscript", "camel-chatscript"),
+            Map.entry("chunk", "camel-chunk"),
+            Map.entry("class", "camel-bean"),
+            Map.entry("clickhouse", "camel-clickhouse"),
+            Map.entry("clickup", "camel-clickup"),
+            Map.entry("cm-sms", "camel-cm-sms"),
+            Map.entry("coap", "camel-coap"),
+            Map.entry("coap+tcp", "camel-coap"),
+            Map.entry("coaps", "camel-coap"),
+            Map.entry("coaps+tcp", "camel-coap"),
+            Map.entry("cometd", "camel-cometd"),
+            Map.entry("cometds", "camel-cometd"),
+            Map.entry("consul", "camel-consul"),
+            Map.entry("controlbus", "camel-controlbus"),
+            Map.entry("couchbase", "camel-couchbase"),
+            Map.entry("couchdb", "camel-couchdb"),
+            Map.entry("cql", "camel-cassandraql"),
+            Map.entry("cron", "camel-cron"),
+            Map.entry("crypto", "camel-crypto"),
+            Map.entry("cxf", "camel-cxf-soap"),
+            Map.entry("cxfrs", "camel-cxf-rest"),
+            Map.entry("cyberark-vault", "camel-cyberark-vault"),
+            Map.entry("dapr", "camel-dapr"),
+            Map.entry("dataformat", "camel-dataformat"),
+            Map.entry("dataset", "camel-dataset"),
+            Map.entry("dataset-test", "camel-dataset"),
+            Map.entry("debezium-db2", "camel-debezium-db2"),
+            Map.entry("debezium-mongodb", "camel-debezium-mongodb"),
+            Map.entry("debezium-mysql", "camel-debezium-mysql"),
+            Map.entry("debezium-oracle", "camel-debezium-oracle"),
+            Map.entry("debezium-postgres", "camel-debezium-postgres"),
+            Map.entry("debezium-sqlserver", "camel-debezium-sqlserver"),
+            Map.entry("dfdl", "camel-dfdl"),
+            Map.entry("dhis2", "camel-dhis2"),
+            Map.entry("direct", "camel-direct"),
+            Map.entry("disruptor", "camel-disruptor"),
+            Map.entry("disruptor-vm", "camel-disruptor"),
+            Map.entry("djl", "camel-djl"),
+            Map.entry("dns", "camel-dns"),
+            Map.entry("docker", "camel-docker"),
+            Map.entry("docling", "camel-docling"),
+            Map.entry("drill", "camel-drill"),
+            Map.entry("dropbox", "camel-dropbox"),
+            Map.entry("duckdb", "camel-duckdb"),
+            Map.entry("dynamic-router", "camel-dynamic-router"),
+            Map.entry("dynamic-router-control", "camel-dynamic-router"),
+            Map.entry("ehcache", "camel-ehcache"),
+            Map.entry("elasticsearch", "camel-elasticsearch"),
+            Map.entry("elasticsearch-rest-client", 
"camel-elasticsearch-rest-client"),
+            Map.entry("event", "camel-event"),
+            Map.entry("exec", "camel-exec"),
+            Map.entry("fhir", "camel-fhir"),
+            Map.entry("file", "camel-file"),
+            Map.entry("file-watch", "camel-file-watch"),
+            Map.entry("flatpack", "camel-flatpack"),
+            Map.entry("flink", "camel-flink"),
+            Map.entry("flowable", "camel-flowable"),
+            Map.entry("fop", "camel-fop"),
+            Map.entry("freemarker", "camel-freemarker"),
+            Map.entry("ftp", "camel-ftp"),
+            Map.entry("ftps", "camel-ftp"),
+            Map.entry("geocoder", "camel-geocoder"),
+            Map.entry("git", "camel-git"),
+            Map.entry("github2", "camel-github2"),
+            Map.entry("google-bigquery", "camel-google-bigquery"),
+            Map.entry("google-bigquery-sql", "camel-google-bigquery"),
+            Map.entry("google-calendar", "camel-google-calendar"),
+            Map.entry("google-calendar-stream", "camel-google-calendar"),
+            Map.entry("google-drive", "camel-google-drive"),
+            Map.entry("google-firestore", "camel-google-firestore"),
+            Map.entry("google-functions", "camel-google-functions"),
+            Map.entry("google-mail", "camel-google-mail"),
+            Map.entry("google-mail-stream", "camel-google-mail"),
+            Map.entry("google-pubsub", "camel-google-pubsub"),
+            Map.entry("google-secret-manager", "camel-google-secret-manager"),
+            Map.entry("google-sheets", "camel-google-sheets"),
+            Map.entry("google-sheets-stream", "camel-google-sheets"),
+            Map.entry("google-speech-to-text", "camel-google-speech-to-text"),
+            Map.entry("google-storage", "camel-google-storage"),
+            Map.entry("google-text-to-speech", "camel-google-text-to-speech"),
+            Map.entry("google-vertexai", "camel-google-vertexai"),
+            Map.entry("google-vision", "camel-google-vision"),
+            Map.entry("graphql", "camel-graphql"),
+            Map.entry("grpc", "camel-grpc"),
+            Map.entry("hashicorp-vault", "camel-hashicorp-vault"),
+            Map.entry("hazelcast-atomicvalue", "camel-hazelcast"),
+            Map.entry("hazelcast-instance", "camel-hazelcast"),
+            Map.entry("hazelcast-list", "camel-hazelcast"),
+            Map.entry("hazelcast-map", "camel-hazelcast"),
+            Map.entry("hazelcast-multimap", "camel-hazelcast"),
+            Map.entry("hazelcast-pncounter", "camel-hazelcast"),
+            Map.entry("hazelcast-queue", "camel-hazelcast"),
+            Map.entry("hazelcast-replicatedmap", "camel-hazelcast"),
+            Map.entry("hazelcast-ringbuffer", "camel-hazelcast"),
+            Map.entry("hazelcast-seda", "camel-hazelcast"),
+            Map.entry("hazelcast-set", "camel-hazelcast"),
+            Map.entry("hazelcast-topic", "camel-hazelcast"),
+            Map.entry("hivemq", "camel-hivemq"),
+            Map.entry("http", "camel-http"),
+            Map.entry("https", "camel-http"),
+            Map.entry("huggingface", "camel-huggingface"),
+            Map.entry("hwcloud-dms", "camel-huaweicloud-dms"),
+            Map.entry("hwcloud-frs", "camel-huaweicloud-frs"),
+            Map.entry("hwcloud-functiongraph", 
"camel-huaweicloud-functiongraph"),
+            Map.entry("hwcloud-iam", "camel-huaweicloud-iam"),
+            Map.entry("hwcloud-imagerecognition", 
"camel-huaweicloud-imagerecognition"),
+            Map.entry("hwcloud-obs", "camel-huaweicloud-obs"),
+            Map.entry("hwcloud-smn", "camel-huaweicloud-smn"),
+            Map.entry("ibm-cos", "camel-ibm-cos"),
+            Map.entry("ibm-secrets-manager", "camel-ibm-secrets-manager"),
+            Map.entry("ibm-watson-discovery", "camel-ibm-watson-discovery"),
+            Map.entry("ibm-watson-language", "camel-ibm-watson-language"),
+            Map.entry("ibm-watson-speech-to-text", 
"camel-ibm-watson-speech-to-text"),
+            Map.entry("ibm-watson-text-to-speech", 
"camel-ibm-watson-text-to-speech"),
+            Map.entry("ibm-watsonx-ai", "camel-ibm-watsonx-ai"),
+            Map.entry("ibm-watsonx-data", "camel-ibm-watsonx-data"),
+            Map.entry("iggy", "camel-iggy"),
+            Map.entry("ignite-cache", "camel-ignite"),
+            Map.entry("ignite-compute", "camel-ignite"),
+            Map.entry("ignite-events", "camel-ignite"),
+            Map.entry("ignite-idgen", "camel-ignite"),
+            Map.entry("ignite-messaging", "camel-ignite"),
+            Map.entry("ignite-queue", "camel-ignite"),
+            Map.entry("ignite-set", "camel-ignite"),
+            Map.entry("imap", "camel-mail"),
+            Map.entry("imaps", "camel-mail"),
+            Map.entry("infinispan", "camel-infinispan"),
+            Map.entry("infinispan-embedded", "camel-infinispan-embedded"),
+            Map.entry("influxdb", "camel-influxdb"),
+            Map.entry("influxdb2", "camel-influxdb2"),
+            Map.entry("jcache", "camel-jcache"),
+            Map.entry("jcr", "camel-jcr"),
+            Map.entry("jdbc", "camel-jdbc"),
+            Map.entry("jetty", "camel-jetty"),
+            Map.entry("jgroups", "camel-jgroups"),
+            Map.entry("jgroups-raft", "camel-jgroups-raft"),
+            Map.entry("jira", "camel-jira"),
+            Map.entry("jms", "camel-jms"),
+            Map.entry("jmx", "camel-jmx"),
+            Map.entry("jolt", "camel-jolt"),
+            Map.entry("jooq", "camel-jooq"),
+            Map.entry("jpa", "camel-jpa"),
+            Map.entry("jslt", "camel-jslt"),
+            Map.entry("json-validator", "camel-json-validator"),
+            Map.entry("jsonata", "camel-jsonata"),
+            Map.entry("jt400", "camel-jt400"),
+            Map.entry("jte", "camel-jte"),
+            Map.entry("kafka", "camel-kafka"),
+            Map.entry("kamelet", "camel-kamelet"),
+            Map.entry("keycloak", "camel-keycloak"),
+            Map.entry("knative", "camel-knative"),
+            Map.entry("kserve", "camel-kserve"),
+            Map.entry("kubernetes-config-maps", "camel-kubernetes"),
+            Map.entry("kubernetes-cronjob", "camel-kubernetes"),
+            Map.entry("kubernetes-custom-resources", "camel-kubernetes"),
+            Map.entry("kubernetes-deployments", "camel-kubernetes"),
+            Map.entry("kubernetes-events", "camel-kubernetes"),
+            Map.entry("kubernetes-hpa", "camel-kubernetes"),
+            Map.entry("kubernetes-job", "camel-kubernetes"),
+            Map.entry("kubernetes-namespaces", "camel-kubernetes"),
+            Map.entry("kubernetes-nodes", "camel-kubernetes"),
+            Map.entry("kubernetes-persistent-volumes", "camel-kubernetes"),
+            Map.entry("kubernetes-persistent-volumes-claims", 
"camel-kubernetes"),
+            Map.entry("kubernetes-pods", "camel-kubernetes"),
+            Map.entry("kubernetes-replication-controllers", 
"camel-kubernetes"),
+            Map.entry("kubernetes-resources-quota", "camel-kubernetes"),
+            Map.entry("kubernetes-secrets", "camel-kubernetes"),
+            Map.entry("kubernetes-service-accounts", "camel-kubernetes"),
+            Map.entry("kubernetes-services", "camel-kubernetes"),
+            Map.entry("kudu", "camel-kudu"),
+            Map.entry("langchain4j-agent", "camel-langchain4j-agent"),
+            Map.entry("langchain4j-chat", "camel-langchain4j-chat"),
+            Map.entry("langchain4j-embeddings", 
"camel-langchain4j-embeddings"),
+            Map.entry("langchain4j-embeddingstore", 
"camel-langchain4j-embeddingstore"),
+            Map.entry("langchain4j-ingest", "camel-langchain4j-ingest"),
+            Map.entry("langchain4j-web-search", 
"camel-langchain4j-web-search"),
+            Map.entry("language", "camel-language"),
+            Map.entry("ldap", "camel-ldap"),
+            Map.entry("ldif", "camel-ldif"),
+            Map.entry("log", "camel-log"),
+            Map.entry("lpr", "camel-printer"),
+            Map.entry("lucene", "camel-lucene"),
+            Map.entry("lumberjack", "camel-lumberjack"),
+            Map.entry("mapstruct", "camel-mapstruct"),
+            Map.entry("master", "camel-master"),
+            Map.entry("metrics", "camel-metrics"),
+            Map.entry("micrometer", "camel-micrometer"),
+            Map.entry("milo-browse", "camel-milo"),
+            Map.entry("milo-client", "camel-milo"),
+            Map.entry("milo-server", "camel-milo"),
+            Map.entry("milvus", "camel-milvus"),
+            Map.entry("mina", "camel-mina"),
+            Map.entry("mina-sftp", "camel-mina-sftp"),
+            Map.entry("minio", "camel-minio"),
+            Map.entry("mllp", "camel-mllp"),
+            Map.entry("mock", "camel-mock"),
+            Map.entry("mongodb", "camel-mongodb"),
+            Map.entry("mongodb-gridfs", "camel-mongodb-gridfs"),
+            Map.entry("mustache", "camel-mustache"),
+            Map.entry("mvel", "camel-mvel"),
+            Map.entry("mybatis", "camel-mybatis"),
+            Map.entry("mybatis-bean", "camel-mybatis"),
+            Map.entry("nats", "camel-nats"),
+            Map.entry("neo4j", "camel-neo4j"),
+            Map.entry("netty", "camel-netty"),
+            Map.entry("netty-http", "camel-netty-http"),
+            Map.entry("oaipmh", "camel-oaipmh"),
+            Map.entry("olingo2", "camel-olingo2"),
+            Map.entry("olingo4", "camel-olingo4"),
+            Map.entry("once", "camel-once"),
+            Map.entry("opa", "camel-opa"),
+            Map.entry("openai", "camel-openai"),
+            Map.entry("opensearch", "camel-opensearch"),
+            Map.entry("openshift-build-configs", "camel-kubernetes"),
+            Map.entry("openshift-builds", "camel-kubernetes"),
+            Map.entry("openshift-deploymentconfigs", "camel-kubernetes"),
+            Map.entry("openstack-cinder", "camel-openstack"),
+            Map.entry("openstack-glance", "camel-openstack"),
+            Map.entry("openstack-keystone", "camel-openstack"),
+            Map.entry("openstack-neutron", "camel-openstack"),
+            Map.entry("openstack-nova", "camel-openstack"),
+            Map.entry("openstack-swift", "camel-openstack"),
+            Map.entry("opentelemetry-metrics", "camel-opentelemetry-metrics"),
+            Map.entry("optaplanner", "camel-optaplanner"),
+            Map.entry("paho", "camel-paho"),
+            Map.entry("paho-mqtt5", "camel-paho-mqtt5"),
+            Map.entry("pdf", "camel-pdf"),
+            Map.entry("pg-replication-slot", "camel-pg-replication-slot"),
+            Map.entry("pgevent", "camel-pgevent"),
+            Map.entry("pgvector", "camel-pgvector"),
+            Map.entry("pinecone", "camel-pinecone"),
+            Map.entry("platform-http", "camel-platform-http"),
+            Map.entry("plc4x", "camel-plc4x"),
+            Map.entry("pop3", "camel-mail"),
+            Map.entry("pop3s", "camel-mail"),
+            Map.entry("pqc", "camel-pqc"),
+            Map.entry("pubnub", "camel-pubnub"),
+            Map.entry("pulsar", "camel-pulsar"),
+            Map.entry("qdrant", "camel-qdrant"),
+            Map.entry("quartz", "camel-quartz"),
+            Map.entry("quickfix", "camel-quickfix"),
+            Map.entry("reactive-streams", "camel-reactive-streams"),
+            Map.entry("ref", "camel-ref"),
+            Map.entry("rest", "camel-rest"),
+            Map.entry("rest-api", "camel-rest"),
+            Map.entry("rest-openapi", "camel-rest-openapi"),
+            Map.entry("rest-postman", "camel-rest-postman"),
+            Map.entry("robotframework", "camel-robotframework"),
+            Map.entry("rocketmq", "camel-rocketmq"),
+            Map.entry("rss", "camel-rss"),
+            Map.entry("saga", "camel-saga"),
+            Map.entry("salesforce", "camel-salesforce"),
+            Map.entry("sap-netweaver", "camel-sap-netweaver"),
+            Map.entry("scheduler", "camel-scheduler"),
+            Map.entry("schematron", "camel-schematron"),
+            Map.entry("scp", "camel-jsch"),
+            Map.entry("seda", "camel-seda"),
+            Map.entry("servicenow", "camel-servicenow"),
+            Map.entry("servlet", "camel-servlet"),
+            Map.entry("sftp", "camel-ftp"),
+            Map.entry("shell", "camel-shell"),
+            Map.entry("sjms", "camel-sjms"),
+            Map.entry("sjms2", "camel-sjms2"),
+            Map.entry("slack", "camel-slack"),
+            Map.entry("smb", "camel-smb"),
+            Map.entry("smooks", "camel-smooks"),
+            Map.entry("smpp", "camel-smpp"),
+            Map.entry("smpps", "camel-smpp"),
+            Map.entry("smtp", "camel-mail"),
+            Map.entry("smtps", "camel-mail"),
+            Map.entry("snmp", "camel-snmp"),
+            Map.entry("solr", "camel-solr"),
+            Map.entry("spiffe", "camel-spiffe"),
+            Map.entry("splunk-hec", "camel-splunk-hec"),
+            Map.entry("spring-ai-chat", "camel-spring-ai-chat"),
+            Map.entry("spring-ai-embeddings", "camel-spring-ai-embeddings"),
+            Map.entry("spring-ai-image", "camel-spring-ai-image"),
+            Map.entry("spring-ai-vector-store", 
"camel-spring-ai-vector-store"),
+            Map.entry("spring-batch", "camel-spring-batch"),
+            Map.entry("spring-event", "camel-spring"),
+            Map.entry("spring-jdbc", "camel-spring-jdbc"),
+            Map.entry("spring-ldap", "camel-spring-ldap"),
+            Map.entry("spring-rabbitmq", "camel-spring-rabbitmq"),
+            Map.entry("spring-redis", "camel-spring-redis"),
+            Map.entry("spring-ws", "camel-spring-ws"),
+            Map.entry("sql", "camel-sql"),
+            Map.entry("sql-stored", "camel-sql"),
+            Map.entry("ssh", "camel-ssh"),
+            Map.entry("state-store", "camel-state-store"),
+            Map.entry("stax", "camel-stax"),
+            Map.entry("stitch", "camel-stitch"),
+            Map.entry("stream", "camel-stream"),
+            Map.entry("string-template", "camel-stringtemplate"),
+            Map.entry("stripe", "camel-stripe"),
+            Map.entry("stub", "camel-stub"),
+            Map.entry("tahu-edge", "camel-tahu"),
+            Map.entry("tahu-host", "camel-tahu"),
+            Map.entry("telegram", "camel-telegram"),
+            Map.entry("tensorflow-serving", "camel-tensorflow-serving"),
+            Map.entry("thrift", "camel-thrift"),
+            Map.entry("thymeleaf", "camel-thymeleaf"),
+            Map.entry("tika", "camel-tika"),
+            Map.entry("timer", "camel-timer"),
+            Map.entry("twilio", "camel-twilio"),
+            Map.entry("twitter-directmessage", "camel-twitter"),
+            Map.entry("twitter-search", "camel-twitter"),
+            Map.entry("twitter-timeline", "camel-twitter"),
+            Map.entry("undertow", "camel-undertow"),
+            Map.entry("validator", "camel-validator"),
+            Map.entry("velocity", "camel-velocity"),
+            Map.entry("vertx", "camel-vertx"),
+            Map.entry("vertx-http", "camel-vertx-http"),
+            Map.entry("vertx-websocket", "camel-vertx-websocket"),
+            Map.entry("wasm", "camel-wasm"),
+            Map.entry("weather", "camel-weather"),
+            Map.entry("weaviate", "camel-weaviate"),
+            Map.entry("web3j", "camel-web3j"),
+            Map.entry("webhook", "camel-webhook"),
+            Map.entry("whatsapp", "camel-whatsapp"),
+            Map.entry("wordpress", "camel-wordpress"),
+            Map.entry("workday", "camel-workday"),
+            Map.entry("xchange", "camel-xchange"),
+            Map.entry("xj", "camel-xj"),
+            Map.entry("xmlsecurity-sign", "camel-xmlsecurity"),
+            Map.entry("xmlsecurity-verify", "camel-xmlsecurity"),
+            Map.entry("xmpp", "camel-xmpp"),
+            Map.entry("xquery", "camel-saxon"),
+            Map.entry("xslt", "camel-xslt"),
+            Map.entry("xslt-saxon", "camel-xslt-saxon"),
+            Map.entry("zendesk", "camel-zendesk"),
+            Map.entry("zookeeper", "camel-zookeeper"),
+            Map.entry("zookeeper-master", "camel-zookeeper-master")
+    // COMPONENTS: END
+    );
+
+    // name -> artifactId
+    private static final Map<String, String> LANGUAGES = Map.ofEntries(
+            // Generated by camel build tools - do NOT edit this list!
+            // LANGUAGES: START
+            Map.entry("bean", "camel-bean"),
+            Map.entry("constant", "camel-core-languages"),
+            Map.entry("datasonnet", "camel-datasonnet"),
+            Map.entry("exchangeProperty", "camel-core-languages"),
+            Map.entry("file", "camel-core-languages"),
+            Map.entry("groovy", "camel-groovy"),
+            Map.entry("header", "camel-core-languages"),
+            Map.entry("hl7terser", "camel-hl7"),
+            Map.entry("jactl", "camel-jactl"),
+            Map.entry("java", "camel-joor"),
+            Map.entry("joor", "camel-joor"),
+            Map.entry("jq", "camel-jq"),
+            Map.entry("js", "camel-javascript"),
+            Map.entry("jsonpath", "camel-jsonpath"),
+            Map.entry("mvel", "camel-mvel"),
+            Map.entry("ognl", "camel-ognl"),
+            Map.entry("python", "camel-python"),
+            Map.entry("python3", "camel-python3"),
+            Map.entry("quickjs", "camel-quickjs"),
+            Map.entry("ref", "camel-core-languages"),
+            Map.entry("simple", "camel-core-languages"),
+            Map.entry("spel", "camel-spring"),
+            Map.entry("tokenize", "camel-core-languages"),
+            Map.entry("variable", "camel-core-languages"),
+            Map.entry("wasm", "camel-wasm"),
+            Map.entry("xpath", "camel-xpath"),
+            Map.entry("xquery", "camel-saxon"),
+            Map.entry("xtokenize", "camel-stax")
+    // LANGUAGES: END
+    );
+
+    // name -> artifactId
+    private static final Map<String, String> DATAFORMATS = Map.ofEntries(
+            // Generated by camel build tools - do NOT edit this list!
+            // DATAFORMATS: START
+            Map.entry("asn1", "camel-asn1"),
+            Map.entry("avro", "camel-avro"),
+            Map.entry("avroJackson", "camel-jackson-avro"),
+            Map.entry("barcode", "camel-barcode"),
+            Map.entry("base64", "camel-base64"),
+            Map.entry("beanio", "camel-beanio"),
+            Map.entry("bindyCsv", "camel-bindy"),
+            Map.entry("bindyFixed", "camel-bindy"),
+            Map.entry("bindyKvp", "camel-bindy"),
+            Map.entry("cbor", "camel-cbor"),
+            Map.entry("crypto", "camel-crypto"),
+            Map.entry("csv", "camel-csv"),
+            Map.entry("dfdl", "camel-dfdl"),
+            Map.entry("fastjson", "camel-fastjson"),
+            Map.entry("fhirJson", "camel-fhir"),
+            Map.entry("fhirXml", "camel-fhir"),
+            Map.entry("flatpack", "camel-flatpack"),
+            Map.entry("fory", "camel-fory"),
+            Map.entry("grok", "camel-grok"),
+            Map.entry("groovyJson", "camel-groovy"),
+            Map.entry("groovyXml", "camel-groovy"),
+            Map.entry("gson", "camel-gson"),
+            Map.entry("gzipDeflater", "camel-zip-deflater"),
+            Map.entry("hl7", "camel-hl7"),
+            Map.entry("ical", "camel-ical"),
+            Map.entry("iso8583", "camel-iso8583"),
+            Map.entry("jackson", "camel-jackson"),
+            Map.entry("jacksonXml", "camel-jacksonxml"),
+            Map.entry("jaxb", "camel-jaxb"),
+            Map.entry("jsonApi", "camel-jsonapi"),
+            Map.entry("jsonb", "camel-jsonb"),
+            Map.entry("lzf", "camel-lzf"),
+            Map.entry("mimeMultipart", "camel-mail"),
+            Map.entry("ocsf", "camel-ocsf"),
+            Map.entry("parquetAvro", "camel-parquet-avro"),
+            Map.entry("pgp", "camel-crypto-pgp"),
+            Map.entry("pqc", "camel-pqc"),
+            Map.entry("protobuf", "camel-protobuf"),
+            Map.entry("protobufJackson", "camel-jackson-protobuf"),
+            Map.entry("rss", "camel-rss"),
+            Map.entry("smooks", "camel-smooks"),
+            Map.entry("snakeYaml", "camel-snakeyaml"),
+            Map.entry("soap", "camel-soap"),
+            Map.entry("swiftMt", "camel-swift"),
+            Map.entry("swiftMx", "camel-swift"),
+            Map.entry("syslog", "camel-syslog"),
+            Map.entry("tarFile", "camel-tarfile"),
+            Map.entry("thrift", "camel-thrift"),
+            Map.entry("toon", "camel-toon"),
+            Map.entry("ubl", "camel-ubl"),
+            Map.entry("univocityCsv", "camel-univocity-parsers"),
+            Map.entry("univocityFixed", "camel-univocity-parsers"),
+            Map.entry("univocityTsv", "camel-univocity-parsers"),
+            Map.entry("xmlSecurity", "camel-xmlsecurity"),
+            Map.entry("zipDeflater", "camel-zip-deflater"),
+            Map.entry("zipFile", "camel-zipfile")
+    // DATAFORMATS: END
+    );
+
+    // simple class name -> javaType|interfaceType|artifactId
+    private static final Map<String, String> BEANS = Map.ofEntries(
+            // Generated by camel build tools - do NOT edit this list!
+            // BEANS: START
+            Map.entry("AcceptAllHeaderFilterStrategy",
+                    
"org.apache.camel.support.AcceptAllHeaderFilterStrategy|org.apache.camel.spi.HeaderFilterStrategy|camel-support"),
+            Map.entry("CaffeineAggregationRepository",
+                    
"org.apache.camel.component.caffeine.processor.aggregate.CaffeineAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-caffeine"),
+            Map.entry("CaffeineIdempotentRepository",
+                    
"org.apache.camel.component.caffeine.processor.idempotent.CaffeineIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-caffeine"),
+            Map.entry("CaffeineKeyValueRepository",
+                    
"org.apache.camel.component.caffeine.processor.CaffeineKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-caffeine"),
+            Map.entry("CassandraAggregationRepository",
+                    
"org.apache.camel.processor.aggregate.cassandra.CassandraAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-cassandraql"),
+            Map.entry("CassandraIdempotentRepository",
+                    
"org.apache.camel.processor.idempotent.cassandra.CassandraIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-cassandraql"),
+            Map.entry("CassandraKeyValueRepository",
+                    
"org.apache.camel.processor.keyvalue.cassandra.CassandraKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-cassandraql"),
+            Map.entry("ConsulClusterService",
+                    
"org.apache.camel.component.consul.cluster.ConsulClusterService|org.apache.camel.cluster.CamelClusterService|camel-consul"),
+            Map.entry("CronScheduledRoutePolicy",
+                    
"org.apache.camel.routepolicy.quartz.CronScheduledRoutePolicy|org.apache.camel.spi.RoutePolicy|camel-quartz"),
+            Map.entry("DefaultHeaderFilterStrategy",
+                    
"org.apache.camel.support.DefaultHeaderFilterStrategy|org.apache.camel.spi.HeaderFilterStrategy|camel-support"),
+            Map.entry("DurationRoutePolicy",
+                    
"org.apache.camel.impl.engine.DurationRoutePolicy|org.apache.camel.spi.RoutePolicy|camel-base-engine"),
+            Map.entry("DurationRoutePolicyFactory",
+                    
"org.apache.camel.impl.engine.DurationRoutePolicyFactory|org.apache.camel.spi.RoutePolicyFactory|camel-base-engine"),
+            Map.entry("EhcacheAggregationRepository",
+                    
"org.apache.camel.component.ehcache.processor.aggregate.EhcacheAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-ehcache"),
+            Map.entry("EhcacheIdempotentRepository",
+                    
"org.apache.camel.component.ehcache.processor.idempotent.EhcacheIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-ehcache"),
+            Map.entry("EhcacheKeyValueRepository",
+                    
"org.apache.camel.component.ehcache.processor.EhcacheKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-ehcache"),
+            Map.entry("ElasticsearchBulkRequestAggregationStrategy",
+                    
"org.apache.camel.component.es.aggregation.ElasticsearchBulkRequestAggregationStrategy|org.apache.camel.AggregationStrategy|camel-elasticsearch"),
+            Map.entry("FileIdempotentRepository",
+                    
"org.apache.camel.support.processor.idempotent.FileIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-support"),
+            Map.entry("FileLockClusterService",
+                    
"org.apache.camel.component.file.cluster.FileLockClusterService|org.apache.camel.cluster.CamelClusterService|camel-file"),
+            Map.entry("GroupedBodyAggregationStrategy",
+                    
"org.apache.camel.processor.aggregate.GroupedBodyAggregationStrategy|org.apache.camel.AggregationStrategy|camel-core-processor"),
+            Map.entry("GroupedExchangeAggregationStrategy",
+                    
"org.apache.camel.processor.aggregate.GroupedExchangeAggregationStrategy|org.apache.camel.AggregationStrategy|camel-core-processor"),
+            Map.entry("GroupedMessageAggregationStrategy",
+                    
"org.apache.camel.processor.aggregate.GroupedMessageAggregationStrategy|org.apache.camel.AggregationStrategy|camel-core-processor"),
+            Map.entry("HazelcastAggregationRepository",
+                    
"org.apache.camel.processor.aggregate.hazelcast.HazelcastAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-hazelcast"),
+            Map.entry("HazelcastIdempotentRepository",
+                    
"org.apache.camel.processor.idempotent.hazelcast.HazelcastIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-hazelcast"),
+            Map.entry("HazelcastKeyValueRepository",
+                    
"org.apache.camel.component.hazelcast.HazelcastKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-hazelcast"),
+            Map.entry("InfinispanEmbeddedAggregationRepository",
+                    
"org.apache.camel.component.infinispan.embedded.InfinispanEmbeddedAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-infinispan-embedded"),
+            Map.entry("InfinispanEmbeddedClusterService",
+                    
"org.apache.camel.component.infinispan.embedded.cluster.InfinispanEmbeddedClusterService|org.apache.camel.cluster.CamelClusterService|camel-infinispan-embedded"),
+            Map.entry("InfinispanEmbeddedIdempotentRepository",
+                    
"org.apache.camel.component.infinispan.embedded.InfinispanEmbeddedIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-infinispan-embedded"),
+            Map.entry("InfinispanRemoteAggregationRepository",
+                    
"org.apache.camel.component.infinispan.remote.InfinispanRemoteAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-infinispan"),
+            Map.entry("InfinispanRemoteClusterService",
+                    
"org.apache.camel.component.infinispan.remote.cluster.InfinispanRemoteClusterService|org.apache.camel.cluster.CamelClusterService|camel-infinispan"),
+            Map.entry("InfinispanRemoteIdempotentRepository",
+                    
"org.apache.camel.component.infinispan.remote.InfinispanRemoteIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-infinispan"),
+            Map.entry("InfinispanRemoteKeyValueRepository",
+                    
"org.apache.camel.component.infinispan.remote.InfinispanRemoteKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-infinispan"),
+            Map.entry("JCacheAggregationRepository",
+                    
"org.apache.camel.component.jcache.processor.aggregate.JCacheAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-jcache"),
+            Map.entry("JCacheIdempotentRepository",
+                    
"org.apache.camel.component.jcache.processor.idempotent.JCacheIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-jcache"),
+            Map.entry("JCacheKeyValueRepository",
+                    
"org.apache.camel.component.jcache.processor.JCacheKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-jcache"),
+            Map.entry("JGroupsRaftClusterService",
+                    
"org.apache.camel.component.jgroups.raft.cluster.JGroupsRaftClusterService|org.apache.camel.cluster.CamelClusterService|camel-jgroups-raft"),
+            Map.entry("JdbcAggregationRepository",
+                    
"org.apache.camel.processor.aggregate.jdbc.JdbcAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-sql"),
+            Map.entry("JdbcKeyValueRepository",
+                    
"org.apache.camel.processor.keyvalue.jdbc.JdbcKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-sql"),
+            Map.entry("JdbcMessageIdRepository",
+                    
"org.apache.camel.processor.idempotent.jdbc.JdbcMessageIdRepository|org.apache.camel.spi.IdempotentRepository|camel-sql"),
+            Map.entry("JpaKeyValueRepository",
+                    
"org.apache.camel.processor.keyvalue.jpa.JpaKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-jpa"),
+            Map.entry("KafkaIdempotentRepository",
+                    
"org.apache.camel.processor.idempotent.kafka.KafkaIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-kafka"),
+            Map.entry("KafkaKeyValueRepository",
+                    
"org.apache.camel.processor.keyvalue.kafka.KafkaKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-kafka"),
+            Map.entry("KeyValueAggregationRepository",
+                    
"org.apache.camel.support.KeyValueAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-support"),
+            Map.entry("KeyValueIdempotentRepository",
+                    
"org.apache.camel.support.KeyValueIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-support"),
+            Map.entry("KubernetesClusterService",
+                    
"org.apache.camel.component.kubernetes.cluster.KubernetesClusterService|org.apache.camel.cluster.CamelClusterService|camel-kubernetes"),
+            Map.entry("LoggingHttpActivityListener",
+                    
"org.apache.camel.component.http.LoggingHttpActivityListener|org.apache.camel.component.http.HttpActivityListener|camel-http"),
+            Map.entry("MemoryAggregationRepository",
+                    
"org.apache.camel.processor.aggregate.MemoryAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-core-processor"),
+            Map.entry("MemoryIdempotentRepository",
+                    
"org.apache.camel.support.processor.idempotent.MemoryIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-support"),
+            Map.entry("MemoryKeyValueRepository",
+                    
"org.apache.camel.support.MemoryKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-support"),
+            Map.entry("MongoDbIdempotentRepository",
+                    
"org.apache.camel.component.mongodb.processor.idempotent.MongoDbIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-mongodb"),
+            Map.entry("OpensearchBulkRequestAggregationStrategy",
+                    
"org.apache.camel.component.opensearch.aggregation.OpensearchBulkRequestAggregationStrategy|org.apache.camel.AggregationStrategy|camel-opensearch"),
+            Map.entry("RedisAggregationRepository",
+                    
"org.apache.camel.component.redis.processor.aggregate.RedisAggregationRepository|org.apache.camel.spi.AggregationRepository|camel-redis"),
+            Map.entry("RedisKeyValueRepository",
+                    
"org.apache.camel.component.redis.RedisKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-redis"),
+            Map.entry("SimpleScheduledRoutePolicy",
+                    
"org.apache.camel.routepolicy.quartz.SimpleScheduledRoutePolicy|org.apache.camel.spi.RoutePolicy|camel-quartz"),
+            Map.entry("SpringCacheIdempotentRepository",
+                    
"org.apache.camel.spring.processor.idempotent.SpringCacheIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-spring"),
+            Map.entry("SpringRedisIdempotentRepository",
+                    
"org.apache.camel.component.redis.processor.idempotent.SpringRedisIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-spring-redis"),
+            Map.entry("StringAggregationStrategy",
+                    
"org.apache.camel.processor.aggregate.StringAggregationStrategy|org.apache.camel.AggregationStrategy|camel-core-processor"),
+            Map.entry("TarAggregationStrategy",
+                    
"org.apache.camel.processor.aggregate.tarfile.TarAggregationStrategy|org.apache.camel.AggregationStrategy|camel-tarfile"),
+            Map.entry("ThrottlingExceptionRoutePolicy",
+                    
"org.apache.camel.throttling.ThrottlingExceptionRoutePolicy|org.apache.camel.spi.RoutePolicy|camel-support"),
+            Map.entry("ThrottlingInflightRoutePolicy",
+                    
"org.apache.camel.throttling.ThrottlingInflightRoutePolicy|org.apache.camel.spi.RoutePolicy|camel-support"),
+            Map.entry("UseLatestAggregationStrategy",
+                    
"org.apache.camel.processor.aggregate.UseLatestAggregationStrategy|org.apache.camel.AggregationStrategy|camel-core-processor"),
+            Map.entry("UseOriginalAggregationStrategy",
+                    
"org.apache.camel.processor.aggregate.UseOriginalAggregationStrategy|org.apache.camel.AggregationStrategy|camel-core-processor"),
+            Map.entry("XsltAggregationStrategy",
+                    
"org.apache.camel.component.xslt.XsltAggregationStrategy|org.apache.camel.AggregationStrategy|camel-xslt"),
+            Map.entry("XsltSaxonAggregationStrategy",
+                    
"org.apache.camel.component.xslt.saxon.XsltSaxonAggregationStrategy|org.apache.camel.AggregationStrategy|camel-xslt-saxon"),
+            Map.entry("ZipAggregationStrategy",
+                    
"org.apache.camel.processor.aggregate.zipfile.ZipAggregationStrategy|org.apache.camel.AggregationStrategy|camel-zipfile"),
+            Map.entry("ZooKeeperClusterService",
+                    
"org.apache.camel.component.zookeeper.cluster.ZooKeeperClusterService|org.apache.camel.cluster.CamelClusterService|camel-zookeeper")
+    // BEANS: END
+    );
+
+    private ArtifactUtils() {
+    }
+
+    /** The artifactId of the component with the given scheme, or null if it 
is not a built-in Camel component. */
+    public static String componentArtifact(String scheme) {
+        return scheme != null ? COMPONENTS.get(scheme) : null;
+    }
+
+    /** The artifactId of the language with the given name, or null if it is 
not a built-in Camel language. */
+    public static String languageArtifact(String name) {
+        return name != null ? LANGUAGES.get(name) : null;
+    }
+
+    /** The artifactId of the data format with the given name, or null if it 
is not a built-in Camel data format. */
+    public static String dataFormatArtifact(String name) {
+        return name != null ? DATAFORMATS.get(name) : null;
+    }
+
+    /** The artifactId of the built-in bean with the given simple class name, 
or null if there is none. */
+    public static String beanArtifact(String name) {
+        return beanField(name, 2);
+    }
+
+    /** The fully qualified class name of the built-in bean with the given 
simple class name, or null. */
+    public static String beanJavaType(String name) {
+        return beanField(name, 0);
+    }
+
+    /** The interface the built-in bean with the given simple class name 
implements, or null. */
+    public static String beanInterface(String name) {
+        return beanField(name, 1);
+    }
+
+    private static String beanField(String name, int index) {
+        String value = name != null ? BEANS.get(name) : null;
+        if (value == null) {
+            return null;
+        }
+        String[] parts = value.split("\\|", -1);
+        return parts[index].isEmpty() ? null : parts[index];
+    }
+
+    /** The schemes of all built-in components, including alternative schemes. 
*/
+    public static Set<String> componentNames() {
+        return COMPONENTS.keySet();
+    }
+
+    /** The names of all built-in languages. */
+    public static Set<String> languageNames() {
+        return LANGUAGES.keySet();
+    }
+
+    /** The names of all built-in data formats. */
+    public static Set<String> dataFormatNames() {
+        return DATAFORMATS.keySet();
+    }
+
+    /** The simple class names of all built-in beans. */
+    public static Set<String> beanNames() {
+        return BEANS.keySet();
+    }
+
+    /**
+     * What to add to the classpath for the artifact: the plain artifactId, 
which is what the runtime knows. The Spring
+     * Boot starter (artifactId-starter) and Quarkus extension 
(camel-quarkus-name) are a convention for the tooling
+     * that can check they exist, as camel-jbang export does; not every 
artifact has one. A core artifact is noted as
+     * part of camel-core.
+     */
+    public static String dependencyHint(String artifactId) {
+        if (artifactId == null || artifactId.isBlank()) {
+            return "";
+        }
+        if (isCoreArtifact(artifactId)) {
+            return "add " + artifactId + " (part of camel-core) to the 
classpath";
+        }
+        return "add " + artifactId + " to the classpath";
+    }
+
+    private static boolean isCoreArtifact(String artifactId) {
+        return artifactId.startsWith("camel-core-") || 
artifactId.equals("camel-core") || artifactId.equals("camel-base")
+                || artifactId.equals("camel-base-engine") || 
artifactId.equals("camel-support")
+                || artifactId.equals("camel-api") || 
artifactId.equals("camel-util");
+    }
+
+    /**
+     * The hint for a component scheme that could not be resolved: the 
artifact to add when the scheme is a built-in
+     * component, or else the built-in scheme that was likely meant.
+     *
+     * @return the hint in parentheses with a leading space, or an empty 
string if there is nothing to say
+     */
+    public static String componentHint(String scheme) {
+        return hint(scheme, "component", COMPONENTS);
+    }
+
+    /**
+     * The hint for a language name that could not be resolved: the artifact 
to add when the name is a built-in
+     * language, or else the built-in language that was likely meant.
+     *
+     * @return the hint in parentheses with a leading space, or an empty 
string if there is nothing to say
+     */
+    public static String languageHint(String name) {
+        return hint(name, "language", LANGUAGES);
+    }
+
+    /**
+     * The hint for a data format name that could not be resolved: the 
artifact to add when the name is a built-in data
+     * format, or else the built-in data format that was likely meant.
+     *
+     * @return the hint in parentheses with a leading space, or an empty 
string if there is nothing to say
+     */
+    public static String dataFormatHint(String name) {
+        return hint(name, "data format", DATAFORMATS);
+    }
+
+    private static String hint(String name, String kind, Map<String, String> 
table) {
+        if (name == null || name.isBlank()) {
+            return "";
+        }
+        String artifact = table.get(name);
+        if (artifact != null) {
+            return " (the " + name + " " + kind + " is in " + artifact + "; " 
+ dependencyHint(artifact) + ")";
+        }
+        String best = closest(name, table.keySet());
+        if (best != null) {
+            return " (not a built-in Camel " + kind + "; did you mean '" + 
best + "'?)";
+        }
+        return " (not a built-in Camel " + kind + ")";
+    }
+
+    /**
+     * The candidate closest to the name, case-insensitively, when it is 
within a few edits (one for names up to three
+     * characters, two, or a third of the name's length for longer names). Of 
candidates at the same distance the one
+     * sharing the longer prefix with the name wins, as typos are seldom in 
the first letters (htps is https, not ftps);
+     * when that still ties there is no answer, as a wrong guess is worse than 
none.
+     *
+     * @return the closest candidate, or null if none is close enough or the 
closest is ambiguous
+     */
+    public static String closest(String name, Collection<String> candidates) {
+        if (name == null || name.isBlank() || candidates == null) {
+            return null;
+        }
+        String best = null;
+        int bestDistance = Integer.MAX_VALUE;
+        int bestPrefix = -1;
+        boolean tie = false;
+        int threshold = name.length() <= 3 ? 1 : Math.max(2, name.length() / 
3);
+        String n = name.toLowerCase(Locale.ROOT);
+        for (String candidate : candidates) {
+            if (candidate == null) {
+                continue;
+            }
+            String c = candidate.toLowerCase(Locale.ROOT);
+            int d = distance(n, c);
+            if (d > threshold || d > bestDistance) {
+                continue;
+            }
+            int prefix = commonPrefix(n, c);
+            if (d < bestDistance || prefix > bestPrefix) {
+                best = candidate;
+                bestDistance = d;
+                bestPrefix = prefix;
+                tie = false;
+            } else if (prefix == bestPrefix) {
+                tie = true;
+            }
+        }
+        return tie ? null : best;
+    }
+
+    private static int commonPrefix(String a, String b) {
+        int max = Math.min(a.length(), b.length());
+        int i = 0;
+        while (i < max && a.charAt(i) == b.charAt(i)) {
+            i++;
+        }
+        return i;
+    }
+
+    /**
+     * The Damerau-Levenshtein distance (optimal string alignment): 
insertions, deletions, substitutions and
+     * transpositions of adjacent characters each count as one edit.
+     */
+    static int distance(String a, String b) {
+        int n = a.length();
+        int m = b.length();
+        if (n == 0) {
+            return m;
+        }
+        if (m == 0) {
+            return n;
+        }
+        int[][] d = new int[n + 1][m + 1];
+        for (int i = 0; i <= n; i++) {
+            d[i][0] = i;
+        }
+        for (int j = 0; j <= m; j++) {
+            d[0][j] = j;
+        }
+        for (int i = 1; i <= n; i++) {
+            for (int j = 1; j <= m; j++) {
+                int cost = a.charAt(i - 1) == b.charAt(j - 1) ? 0 : 1;
+                d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), 
d[i - 1][j - 1] + cost);
+                if (i > 1 && j > 1 && a.charAt(i - 1) == b.charAt(j - 2) && 
a.charAt(i - 2) == b.charAt(j - 1)) {
+                    d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
+                }
+            }
+        }
+        return d[n][m];
+    }
+
+}
diff --git 
a/core/camel-util/src/test/java/org/apache/camel/util/ArtifactUtilsTest.java 
b/core/camel-util/src/test/java/org/apache/camel/util/ArtifactUtilsTest.java
new file mode 100644
index 000000000000..2228c1c5d56f
--- /dev/null
+++ b/core/camel-util/src/test/java/org/apache/camel/util/ArtifactUtilsTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.util;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ArtifactUtilsTest {
+
+    @Test
+    public void testComponentArtifact() {
+        
assertThat(ArtifactUtils.componentArtifact("kafka")).isEqualTo("camel-kafka");
+        // the artifact is not always camel-<scheme>
+        
assertThat(ArtifactUtils.componentArtifact("aws2-ddbstream")).isEqualTo("camel-aws2-ddb");
+        
assertThat(ArtifactUtils.componentArtifact("class")).isEqualTo("camel-bean");
+        // alternative schemes are listed too
+        
assertThat(ArtifactUtils.componentArtifact("coaps+tcp")).isEqualTo("camel-coap");
+        assertThat(ArtifactUtils.componentArtifact("cheese")).isNull();
+        assertThat(ArtifactUtils.componentArtifact(null)).isNull();
+        
assertThat(ArtifactUtils.componentNames()).hasSizeGreaterThan(300).contains("kafka",
 "direct", "coaps+tcp");
+    }
+
+    @Test
+    public void testLanguageArtifact() {
+        
assertThat(ArtifactUtils.languageArtifact("simple")).isEqualTo("camel-core-languages");
+        
assertThat(ArtifactUtils.languageArtifact("xquery")).isEqualTo("camel-saxon");
+        
assertThat(ArtifactUtils.languageArtifact("jsonpath")).isEqualTo("camel-jsonpath");
+        assertThat(ArtifactUtils.languageArtifact("cheese")).isNull();
+        
assertThat(ArtifactUtils.languageNames()).hasSizeGreaterThan(20).contains("simple",
 "xquery", "bean");
+    }
+
+    @Test
+    public void testDataFormatArtifact() {
+        
assertThat(ArtifactUtils.dataFormatArtifact("jaxb")).isEqualTo("camel-jaxb");
+        
assertThat(ArtifactUtils.dataFormatArtifact("jackson")).isEqualTo("camel-jackson");
+        assertThat(ArtifactUtils.dataFormatArtifact("cheese")).isNull();
+        
assertThat(ArtifactUtils.dataFormatNames()).hasSizeGreaterThan(40).contains("jaxb",
 "csv");
+    }
+
+    @Test
+    public void testBeans() {
+        assertThat(ArtifactUtils.beanJavaType("UseLatestAggregationStrategy"))
+                
.isEqualTo("org.apache.camel.processor.aggregate.UseLatestAggregationStrategy");
+        assertThat(ArtifactUtils.beanInterface("UseLatestAggregationStrategy"))
+                .isEqualTo("org.apache.camel.AggregationStrategy");
+        
assertThat(ArtifactUtils.beanArtifact("UseLatestAggregationStrategy")).isEqualTo("camel-core-processor");
+        
assertThat(ArtifactUtils.beanArtifact("ZipAggregationStrategy")).isEqualTo("camel-zipfile");
+        assertThat(ArtifactUtils.beanArtifact("Cheese")).isNull();
+        assertThat(ArtifactUtils.beanJavaType(null)).isNull();
+        
assertThat(ArtifactUtils.beanNames()).hasSizeGreaterThan(50).contains("MemoryIdempotentRepository");
+    }
+
+    @Test
+    public void testDependencyHint() {
+        assertThat(ArtifactUtils.dependencyHint("camel-saxon")).isEqualTo("add 
camel-saxon to the classpath");
+        // core artifacts are noted as part of camel-core
+        assertThat(ArtifactUtils.dependencyHint("camel-core-languages"))
+                .isEqualTo("add camel-core-languages (part of camel-core) to 
the classpath");
+        assertThat(ArtifactUtils.dependencyHint("camel-support"))
+                .isEqualTo("add camel-support (part of camel-core) to the 
classpath");
+        // camel-base64 is a data format, not a core artifact
+        
assertThat(ArtifactUtils.dependencyHint("camel-base64")).isEqualTo("add 
camel-base64 to the classpath");
+        assertThat(ArtifactUtils.dependencyHint(null)).isEmpty();
+        assertThat(ArtifactUtils.dependencyHint(" ")).isEmpty();
+    }
+
+    @Test
+    public void testHints() {
+        assertThat(ArtifactUtils.componentHint("kafka"))
+                .isEqualTo(" (the kafka component is in camel-kafka; add 
camel-kafka to the classpath)");
+        assertThat(ArtifactUtils.componentHint("kafak")).isEqualTo(" (not a 
built-in Camel component; did you mean 'kafka'?)");
+        assertThat(ArtifactUtils.componentHint("Kafka")).isEqualTo(" (not a 
built-in Camel component; did you mean 'kafka'?)");
+        assertThat(ArtifactUtils.componentHint("cheese")).isEqualTo(" (not a 
built-in Camel component)");
+        assertThat(ArtifactUtils.componentHint("")).isEmpty();
+        assertThat(ArtifactUtils.componentHint(null)).isEmpty();
+
+        assertThat(ArtifactUtils.languageHint("xquery")).startsWith(" (the 
xquery language is in camel-saxon; add camel-saxon");
+        assertThat(ArtifactUtils.languageHint("simpel")).isEqualTo(" (not a 
built-in Camel language; did you mean 'simple'?)");
+        assertThat(ArtifactUtils.languageHint("simple")).startsWith(" (the 
simple language is in camel-core-languages;");
+
+        assertThat(ArtifactUtils.dataFormatHint("jaxb")).startsWith(" (the 
jaxb data format is in camel-jaxb; add camel-jaxb");
+        assertThat(ArtifactUtils.dataFormatHint("jakson"))
+                .isEqualTo(" (not a built-in Camel data format; did you mean 
'jackson'?)");
+    }
+
+    @Test
+    public void testClosest() {
+        List<String> names = List.of("kafka", "kamelet", "file", "ftp", 
"sftp", "xj", "xslt");
+        assertThat(ArtifactUtils.closest("kafak", names)).isEqualTo("kafka");
+        assertThat(ArtifactUtils.closest("KAFKA", names)).isEqualTo("kafka");
+        assertThat(ArtifactUtils.closest("files", names)).isEqualTo("file");
+        // short names allow one edit only, so xxx is nothing
+        assertThat(ArtifactUtils.closest("xxx", names)).isNull();
+        assertThat(ArtifactUtils.closest("ftps", names)).isEqualTo("ftp");
+        assertThat(ArtifactUtils.closest("cheese", names)).isNull();
+        assertThat(ArtifactUtils.closest("", names)).isNull();
+        assertThat(ArtifactUtils.closest("kafka", null)).isNull();
+    }
+
+    @Test
+    public void testClosestTie() {
+        List<String> names = List.of("ftp", "ftps", "http", "https", "jms", 
"jmx");
+        // same distance to ftps and https: the longer shared prefix wins, as 
typos are seldom in the first letters
+        assertThat(ArtifactUtils.closest("htps", names)).isEqualTo("https");
+        assertThat(ArtifactUtils.closest("htp", names)).isEqualTo("http");
+        // same distance and same prefix to jms and jmx: no guess rather than 
a wrong one
+        assertThat(ArtifactUtils.closest("jm", names)).isNull();
+        // the real table, which has all of these
+        assertThat(ArtifactUtils.componentHint("htps")).isEqualTo(" (not a 
built-in Camel component; did you mean 'https'?)");
+        // null candidates are skipped
+        assertThat(ArtifactUtils.closest("kafak", Arrays.asList(null, 
"kafka"))).isEqualTo("kafka");
+    }
+
+    @Test
+    public void testDistance() {
+        assertThat(ArtifactUtils.distance("kafka", "kafka")).isZero();
+        assertThat(ArtifactUtils.distance("kafak", "kafka")).isEqualTo(1);
+        assertThat(ArtifactUtils.distance("", "abc")).isEqualTo(3);
+        assertThat(ArtifactUtils.distance("abc", "")).isEqualTo(3);
+        assertThat(ArtifactUtils.distance("simple", "sample")).isEqualTo(1);
+    }
+}
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
index cffcaf708cfd..8931b52b2e4b 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ExpressionDeserializers.java
@@ -39,7 +39,7 @@ public final class ExpressionDeserializers extends 
YamlDeserializerSupport {
         Node val = setDeserializationContext(nt.getValueNode(), dc);
         ExpressionDefinition answer = constructExpressionType(key, val);
         if (answer == null) {
-            throw new 
org.apache.camel.dsl.yaml.common.exception.InvalidExpressionException(node, 
"Unknown expression with id: " + key + ("bean".equals(key) ? " (the bean 
language is written as method: {ref: myBean, method: process})" : ""));
+            throw new 
org.apache.camel.dsl.yaml.common.exception.InvalidExpressionException(node, 
"Unknown expression with id: " + key + ("bean".equals(key) ? " (the bean 
language is written as method: {ref: myBean, method: process})" : 
org.apache.camel.util.ArtifactUtils.languageHint(key)));
         }
         return answer;
     }
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
index 633e2f2e4ed3..3c01b90d1e7e 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlDeserializersMojo.java
@@ -185,7 +185,7 @@ public class GenerateYamlDeserializersMojo extends 
GenerateYamlSupportMojo {
                                 .addStatement("ExpressionDefinition answer = 
constructExpressionType(key, val)")
                                 .beginControlFlow("if (answer == null)")
                                 .addStatement(
-                                        "throw new 
org.apache.camel.dsl.yaml.common.exception.InvalidExpressionException(node, 
\"Unknown expression with id: \" + key + (\"bean\".equals(key) ? \" (the bean 
language is written as method: {ref: myBean, method: process})\" : \"\"))")
+                                        "throw new 
org.apache.camel.dsl.yaml.common.exception.InvalidExpressionException(node, 
\"Unknown expression with id: \" + key + (\"bean\".equals(key) ? \" (the bean 
language is written as method: {ref: myBean, method: process})\" : 
org.apache.camel.util.ArtifactUtils.languageHint(key)))")
                                 .endControlFlow()
                                 .addStatement("return answer")
                                 .build())
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/ExpressionTest.groovy
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/ExpressionTest.groovy
index 278968204b4a..0ea5d4eb91db 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/ExpressionTest.groovy
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/ExpressionTest.groovy
@@ -171,4 +171,24 @@ class ExpressionTest extends YamlTestSupport {
         context.routeDefinitions.size() == 1
     }
 
+    // CAMEL-24752: an unknown expression id says which built-in language was 
likely meant
+    def "Error: explicit not existing says did you mean"() {
+        when:
+        loadRoutesNoValidate('''
+                - from:
+                    uri: "direct:start"
+                    steps:
+                      - setBody:
+                          expression:
+                            simpel: "${body}"
+            ''')
+        then:
+        def e = thrown(Exception)
+        def messages = []
+        for (Throwable t = e; t != null; t = t.cause) {
+            messages << t.message
+        }
+        messages.any { it != null && it.contains("Unknown expression with id: 
simpel (not a built-in Camel language; did you mean 'simple'?)") }
+    }
+
 }
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateArtifactHelper.java
 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateArtifactHelper.java
new file mode 100644
index 000000000000..2599553bea29
--- /dev/null
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/UpdateArtifactHelper.java
@@ -0,0 +1,170 @@
+/*
+ * 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.maven.packaging;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.TreeMap;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+import org.apache.camel.tooling.util.PackageHelper;
+import org.apache.camel.tooling.util.Strings;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProjectHelper;
+import org.codehaus.plexus.build.BuildContext;
+
+import static org.apache.camel.tooling.util.PackageHelper.findCamelDirectory;
+
+/**
+ * Updates ArtifactUtils.java in camel-util with which artifact ships each 
component, language, data format and built-in
+ * bean, from the catalog, so runtime errors can say what to add to the 
classpath.
+ */
+@Mojo(name = "update-artifact-helper", threadSafe = true)
+public class UpdateArtifactHelper extends AbstractGeneratorMojo {
+
+    private static final String JAVA_FILE = 
"src/main/java/org/apache/camel/util/ArtifactUtils.java";
+
+    @Parameter(defaultValue = 
"${project.basedir}/src/generated/resources/org/apache/camel/catalog/")
+    protected File jsonDir;
+
+    @Parameter(defaultValue = "${project.basedir}/")
+    protected File baseDir;
+
+    @Inject
+    public UpdateArtifactHelper(MavenProjectHelper projectHelper, BuildContext 
buildContext) {
+        super(projectHelper, buildContext);
+    }
+
+    @Override
+    public void execute() throws MojoExecutionException {
+        File camelDir = findCamelDirectory(baseDir, "core/camel-util");
+        if (camelDir == null) {
+            getLog().debug("No core/camel-util folder found, skipping 
execution");
+            return;
+        }
+        List<Path> jsonFiles;
+        try (Stream<Path> stream = 
PackageHelper.findJsonFiles(jsonDir.toPath())) {
+            jsonFiles = stream.toList();
+        }
+
+        Map<String, String> components = new TreeMap<>();
+        Map<String, String> languages = new TreeMap<>();
+        Map<String, String> dataformats = new TreeMap<>();
+        Map<String, String> beans = new TreeMap<>();
+
+        for (Path file : jsonFiles) {
+            final String name = PackageHelper.asName(file);
+            try {
+                Object jo = 
Jsoner.deserialize(PackageHelper.loadText(file.toFile()));
+                if (!(jo instanceof JsonObject obj)) {
+                    continue;
+                }
+                JsonObject component = obj.getMap("component");
+                JsonObject language = obj.getMap("language");
+                JsonObject dataformat = obj.getMap("dataformat");
+                JsonObject bean = obj.getMap("bean");
+                if (component != null) {
+                    // every alternative scheme has its own json file, so the 
scheme is the key
+                    put(components, component.getString("scheme"), 
component.getString("artifactId"));
+                } else if (language != null) {
+                    put(languages, language.getString("name"), 
language.getString("artifactId"));
+                } else if (dataformat != null) {
+                    put(dataformats, dataformat.getString("name"), 
dataformat.getString("artifactId"));
+                } else if (bean != null) {
+                    String javaType = bean.getString("javaType");
+                    String interfaceType = 
bean.getStringOrDefault("interfaceType", "");
+                    String artifactId = bean.getString("artifactId");
+                    if (javaType != null && artifactId != null) {
+                        put(beans, bean.getString("name"), javaType + "|" + 
interfaceType + "|" + artifactId);
+                    }
+                }
+            } catch (Exception e) {
+                throw new MojoExecutionException("Error loading json: " + 
name, e);
+            }
+        }
+
+        getLog().info("There are " + components.size() + " component schemes, 
" + languages.size() + " languages, "
+                      + dataformats.size() + " data formats and " + 
beans.size() + " beans with a known artifact");
+
+        try {
+            boolean updated = update(camelDir, "COMPONENTS", components);
+            updated |= update(camelDir, "LANGUAGES", languages);
+            updated |= update(camelDir, "DATAFORMATS", dataformats);
+            updated |= update(camelDir, "BEANS", beans);
+            if (updated) {
+                getLog().info("Updated camel-util/" + JAVA_FILE + " file");
+            } else {
+                getLog().debug("No changes to camel-util/" + JAVA_FILE + " 
file");
+            }
+        } catch (Exception e) {
+            throw new MojoExecutionException("Error updating 
ArtifactUtils.java", e);
+        }
+    }
+
+    private static void put(Map<String, String> table, String key, String 
artifactId) {
+        if (key != null && artifactId != null) {
+            table.put(key, artifactId);
+        }
+    }
+
+    /**
+     * Rewrites the Map.entry lines between the START and END tokens of the 
given table, in the layout the formatter
+     * keeps (so a rebuild does not change the file again).
+     */
+    private static boolean update(File camelDir, String token, Map<String, 
String> table) throws Exception {
+        File java = new File(camelDir, JAVA_FILE);
+        String text = PackageHelper.loadText(java);
+        String startToken = "// " + token + ": START";
+        String endToken = "// " + token + ": END";
+        String spaces12 = "            ";
+        String spaces4 = "    ";
+
+        StringJoiner sb = new StringJoiner(",\n");
+        for (Map.Entry<String, String> entry : table.entrySet()) {
+            String line = spaces12 + "Map.entry(\"" + entry.getKey() + "\", 
\"" + entry.getValue() + "\")";
+            if (line.length() > 120) {
+                // the formatter wraps a long entry after the key
+                line = spaces12 + "Map.entry(\"" + entry.getKey() + "\",\n" + 
spaces12 + spaces4 + spaces4
+                       + "\"" + entry.getValue() + "\")";
+            }
+            sb.add(line);
+        }
+        String changed = sb.toString().trim();
+
+        String existing = Strings.between(text, startToken, endToken);
+        if (existing == null) {
+            return false;
+        }
+        if (existing.trim().equals(changed)) {
+            return false;
+        }
+        String before = Strings.before(text, startToken);
+        String after = Strings.after(text, endToken);
+        text = before + startToken + "\n" + spaces12 + changed + "\n" + 
spaces4 + endToken + after;
+        PackageHelper.writeText(java, text);
+        return true;
+    }
+}

Reply via email to