This is an automated email from the ASF dual-hosted git repository.

orpiske 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 31a293778ec CAMEL-19398: refactor the core type-converter (#11225)
31a293778ec is described below

commit 31a293778ec3ba5f33408ea8f00baf01f2884b27
Author: Otavio Rodolfo Piske <orpi...@users.noreply.github.com>
AuthorDate: Wed Aug 30 21:36:51 2023 +0200

    CAMEL-19398: refactor the core type-converter (#11225)
    
    This is preliminary work that should simplify moving away from an 
Exception-based logic for type handling
    
    It refactors the type conversion code so that it is preloaded to a Map, 
which store type conversion pairs. This map is checked whenever there is a type 
conversion, by matching the requested pair (from/to) with the known conversions 
on the Map.
    
    Among other things, it reduces the incidence of runtime type detection and 
tries to let the compiler decide the method whenever it is possible
---
 .../infinispan/InfinispanProducerTestSupport.java  |   9 +-
 .../converter/saxon/SaxonConverterLoader.java      |   8 +
 .../camel/converter/saxon/SaxonConverter.java      |  22 ++
 .../CamelVertxCommonBulkConverterLoader.java       |  37 +++
 .../main/java/org/apache/camel/TypeConverter.java  |   1 -
 .../apache/camel/converter/TypeConvertible.java    | 214 +++++++++++++
 .../apache/camel/spi/TypeConverterRegistry.java    |  11 +
 .../converter/CamelBaseBulkConverterLoader.java    | 128 ++++++++
 .../impl/converter/CoreTypeConverterRegistry.java  | 354 ++++++++-------------
 .../camel/impl/converter/DefaultTypeConverter.java |  14 +-
 .../camel/impl/converter/TypeResolverHelper.java   | 166 ++++++++++
 .../org/apache/camel/converter/ConverterTest.java  |   5 +-
 .../camel/impl/CustomBulkTypeConvertersTest.java   |   9 +-
 .../stream/StreamCacheBulkConverterLoader.java     |  13 +
 .../main/java/org/apache/camel/util/DoubleMap.java |   1 +
 .../jaxp/CamelXmlJaxpBulkConverterLoader.java      | 102 ++++++
 .../TypeConverterLoaderGeneratorMojo.java          | 128 +++++++-
 17 files changed, 966 insertions(+), 256 deletions(-)

diff --git 
a/components/camel-infinispan/camel-infinispan-common/src/test/java/org/apache/camel/component/infinispan/InfinispanProducerTestSupport.java
 
b/components/camel-infinispan/camel-infinispan-common/src/test/java/org/apache/camel/component/infinispan/InfinispanProducerTestSupport.java
index 260cdca027f..daf012d73f0 100644
--- 
a/components/camel-infinispan/camel-infinispan-common/src/test/java/org/apache/camel/component/infinispan/InfinispanProducerTestSupport.java
+++ 
b/components/camel-infinispan/camel-infinispan-common/src/test/java/org/apache/camel/component/infinispan/InfinispanProducerTestSupport.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.infinispan;
 import java.security.SecureRandom;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.camel.FluentProducerTemplate;
@@ -660,18 +661,18 @@ public interface InfinispanProducerTestSupport {
     }
 
     @Test
-    default void replaceAValueByKeyAsyncWithOldValue() {
+    default void replaceAValueByKeyAsyncWithOldValue() throws 
ExecutionException, InterruptedException {
         getCache().put(KEY_ONE, VALUE_ONE);
 
-        Boolean result = fluentTemplate()
+        CompletableFuture<Boolean> result = fluentTemplate()
                 .to("direct:start")
                 .withHeader(InfinispanConstants.KEY, KEY_ONE)
                 .withHeader(InfinispanConstants.VALUE, VALUE_TWO)
                 .withHeader(InfinispanConstants.OLD_VALUE, VALUE_ONE)
                 .withHeader(InfinispanConstants.OPERATION, 
InfinispanOperation.REPLACEASYNC)
-                .request(Boolean.class);
+                .request(CompletableFuture.class);
 
-        assertTrue(result);
+        assertEquals(Boolean.TRUE, result.get());
         assertEquals(VALUE_TWO, getCache().get(KEY_ONE));
     }
 
diff --git 
a/components/camel-saxon/src/generated/java/org/apache/camel/converter/saxon/SaxonConverterLoader.java
 
b/components/camel-saxon/src/generated/java/org/apache/camel/converter/saxon/SaxonConverterLoader.java
index 74f8310dd59..e92c558cb64 100644
--- 
a/components/camel-saxon/src/generated/java/org/apache/camel/converter/saxon/SaxonConverterLoader.java
+++ 
b/components/camel-saxon/src/generated/java/org/apache/camel/converter/saxon/SaxonConverterLoader.java
@@ -44,10 +44,18 @@ public final class SaxonConverterLoader implements 
TypeConverterLoader, CamelCon
     private void registerConverters(TypeConverterRegistry registry) {
         addTypeConverter(registry, javax.xml.transform.dom.DOMSource.class, 
net.sf.saxon.om.NodeInfo.class, false,
             (type, exchange, value) -> 
org.apache.camel.converter.saxon.SaxonConverter.toDOMSourceFromNodeInfo((net.sf.saxon.om.NodeInfo)
 value));
+        addTypeConverter(registry, javax.xml.transform.dom.DOMSource.class, 
net.sf.saxon.tree.tiny.TinyDocumentImpl.class, false,
+            (type, exchange, value) -> 
org.apache.camel.converter.saxon.SaxonConverter.toDOMSourceFromNodeInfo((net.sf.saxon.tree.tiny.TinyDocumentImpl)
 value));
         addTypeConverter(registry, org.w3c.dom.Document.class, 
net.sf.saxon.om.NodeInfo.class, false,
             (type, exchange, value) -> 
org.apache.camel.converter.saxon.SaxonConverter.toDOMDocument((net.sf.saxon.om.NodeInfo)
 value));
+        addTypeConverter(registry, org.w3c.dom.Document.class, 
net.sf.saxon.tree.tiny.TinyDocumentImpl.class, false,
+            (type, exchange, value) -> 
org.apache.camel.converter.saxon.SaxonConverter.toDOMDocument((net.sf.saxon.tree.tiny.TinyDocumentImpl)
 value));
+        addTypeConverter(registry, org.w3c.dom.Document.class, 
net.sf.saxon.tree.tiny.TinyElementImpl.class, false,
+            (type, exchange, value) -> 
org.apache.camel.converter.saxon.SaxonConverter.toDOMDocument((net.sf.saxon.tree.tiny.TinyElementImpl)
 value));
         addTypeConverter(registry, org.w3c.dom.Node.class, 
net.sf.saxon.om.NodeInfo.class, false,
             (type, exchange, value) -> 
org.apache.camel.converter.saxon.SaxonConverter.toDOMNode((net.sf.saxon.om.NodeInfo)
 value));
+        addTypeConverter(registry, org.w3c.dom.Node.class, 
net.sf.saxon.tree.tiny.TinyDocumentImpl.class, false,
+            (type, exchange, value) -> 
org.apache.camel.converter.saxon.SaxonConverter.toDOMNode((net.sf.saxon.tree.tiny.TinyDocumentImpl)
 value));
         addTypeConverter(registry, org.w3c.dom.NodeList.class, 
java.util.List.class, false,
             (type, exchange, value) -> 
org.apache.camel.converter.saxon.SaxonConverter.toDOMNodeList((java.util.List) 
value));
     }
diff --git 
a/components/camel-saxon/src/main/java/org/apache/camel/converter/saxon/SaxonConverter.java
 
b/components/camel-saxon/src/main/java/org/apache/camel/converter/saxon/SaxonConverter.java
index c2362326ac1..054876d8bee 100644
--- 
a/components/camel-saxon/src/main/java/org/apache/camel/converter/saxon/SaxonConverter.java
+++ 
b/components/camel-saxon/src/main/java/org/apache/camel/converter/saxon/SaxonConverter.java
@@ -31,6 +31,8 @@ import net.sf.saxon.dom.NodeOverNodeInfo;
 import net.sf.saxon.om.NodeInfo;
 import net.sf.saxon.om.TreeInfo;
 import net.sf.saxon.trans.XPathException;
+import net.sf.saxon.tree.tiny.TinyDocumentImpl;
+import net.sf.saxon.tree.tiny.TinyElementImpl;
 import net.sf.saxon.type.Type;
 import org.apache.camel.Converter;
 import org.apache.camel.Exchange;
@@ -43,6 +45,16 @@ public final class SaxonConverter {
     private SaxonConverter() {
     }
 
+    @Converter
+    public static Document toDOMDocument(TinyElementImpl node) throws 
XPathException {
+        return toDOMDocument((NodeInfo) node);
+    }
+
+    @Converter
+    public static Document toDOMDocument(TinyDocumentImpl node) throws 
XPathException {
+        return toDOMDocument((NodeInfo) node);
+    }
+
     @Converter
     public static Document toDOMDocument(NodeInfo node) throws XPathException {
         switch (node.getNodeKind()) {
@@ -59,11 +71,21 @@ public final class SaxonConverter {
         }
     }
 
+    @Converter
+    public static Node toDOMNode(TinyDocumentImpl node) {
+        return toDOMNode((NodeInfo) node);
+    }
+
     @Converter
     public static Node toDOMNode(NodeInfo node) {
         return NodeOverNodeInfo.wrap(node);
     }
 
+    @Converter
+    public static DOMSource toDOMSourceFromNodeInfo(TinyDocumentImpl nodeInfo) 
{
+        return new DOMSource(toDOMNode(nodeInfo));
+    }
+
     @Converter
     public static DOMSource toDOMSourceFromNodeInfo(NodeInfo nodeInfo) {
         return new DOMSource(toDOMNode(nodeInfo));
diff --git 
a/components/camel-vertx/camel-vertx-common/src/generated/java/org/apache/camel/component/vertx/common/CamelVertxCommonBulkConverterLoader.java
 
b/components/camel-vertx/camel-vertx-common/src/generated/java/org/apache/camel/component/vertx/common/CamelVertxCommonBulkConverterLoader.java
index 0f52141b7f7..5fac5f40cb1 100644
--- 
a/components/camel-vertx/camel-vertx-common/src/generated/java/org/apache/camel/component/vertx/common/CamelVertxCommonBulkConverterLoader.java
+++ 
b/components/camel-vertx/camel-vertx-common/src/generated/java/org/apache/camel/component/vertx/common/CamelVertxCommonBulkConverterLoader.java
@@ -9,6 +9,7 @@ import org.apache.camel.Ordered;
 import org.apache.camel.TypeConversionException;
 import org.apache.camel.TypeConverterLoaderException;
 import org.apache.camel.TypeConverter;
+import org.apache.camel.converter.TypeConvertible;
 import org.apache.camel.spi.BulkTypeConverters;
 import org.apache.camel.spi.TypeConverterLoader;
 import org.apache.camel.spi.TypeConverterRegistry;
@@ -43,6 +44,7 @@ public final class CamelVertxCommonBulkConverterLoader 
implements TypeConverterL
     @Override
     public void load(TypeConverterRegistry registry) throws 
TypeConverterLoaderException {
         registry.addBulkTypeConverters(this);
+        doRegistration(registry);
     }
 
     @Override
@@ -165,6 +167,41 @@ public final class CamelVertxCommonBulkConverterLoader 
implements TypeConverterL
         return null;
     }
 
+    private void doRegistration(TypeConverterRegistry registry) {
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.buffer.Buffer.class, byte[].class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.json.JsonArray.class, byte[].class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.json.JsonObject.class, byte[].class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
io.vertx.core.buffer.Buffer.class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.netty.buffer.ByteBuf.class, 
io.vertx.core.buffer.Buffer.class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.json.JsonArray.class, 
io.vertx.core.buffer.Buffer.class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.json.JsonObject.class, 
io.vertx.core.buffer.Buffer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
io.vertx.core.buffer.Buffer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
io.vertx.core.buffer.Buffer.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
io.vertx.core.json.JsonArray.class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.netty.buffer.ByteBuf.class, 
io.vertx.core.json.JsonArray.class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.buffer.Buffer.class, 
io.vertx.core.json.JsonArray.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
io.vertx.core.json.JsonArray.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
io.vertx.core.json.JsonArray.class), this);
+        registry.addConverter(new TypeConvertible<>(java.util.List.class, 
io.vertx.core.json.JsonArray.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
io.vertx.core.json.JsonObject.class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.netty.buffer.ByteBuf.class, 
io.vertx.core.json.JsonObject.class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.buffer.Buffer.class, 
io.vertx.core.json.JsonObject.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
io.vertx.core.json.JsonObject.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
io.vertx.core.json.JsonObject.class), this);
+        registry.addConverter(new TypeConvertible<>(java.util.Map.class, 
io.vertx.core.json.JsonObject.class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.buffer.Buffer.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.json.JsonArray.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.json.JsonObject.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.buffer.Buffer.class, java.lang.String.class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.json.JsonArray.class, java.lang.String.class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.json.JsonObject.class, java.lang.String.class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.json.JsonArray.class, java.util.List.class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.json.JsonObject.class, java.util.Map.class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(io.vertx.core.buffer.Buffer.class, 
org.apache.camel.StreamCache.class), this);
+        
+        
+    }
+
     public TypeConverter lookup(Class<?> to, Class<?> from) {
         if (to == byte[].class) {
             if (from == io.vertx.core.buffer.Buffer.class) {
diff --git a/core/camel-api/src/main/java/org/apache/camel/TypeConverter.java 
b/core/camel-api/src/main/java/org/apache/camel/TypeConverter.java
index 89339416f16..f959082528d 100644
--- a/core/camel-api/src/main/java/org/apache/camel/TypeConverter.java
+++ b/core/camel-api/src/main/java/org/apache/camel/TypeConverter.java
@@ -106,5 +106,4 @@ public interface TypeConverter {
      * @return          the converted value, or <tt>null</tt> if not possible 
to convert
      */
     <T> T tryConvertTo(Class<T> type, Exchange exchange, Object value);
-
 }
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/converter/TypeConvertible.java 
b/core/camel-api/src/main/java/org/apache/camel/converter/TypeConvertible.java
new file mode 100644
index 00000000000..7b5969c21a3
--- /dev/null
+++ 
b/core/camel-api/src/main/java/org/apache/camel/converter/TypeConvertible.java
@@ -0,0 +1,214 @@
+/*
+ * 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.converter;
+
+import java.util.Objects;
+
+import static 
org.apache.camel.util.ObjectHelper.convertPrimitiveTypeToWrapperType;
+
+/**
+ * Holds a type convertible pair. That is, consider 2 types defined as F and 
T, it defines that F can be converted to T.
+ *
+ * @param <F> The "from" type
+ * @param <T> The "to" type.
+ */
+public final class TypeConvertible<F, T> {
+    private final Class<F> from;
+    private final Class<T> to;
+    private final int hash;
+
+    /**
+     * Constructs a new type convertible pair. This is likely only used by 
core camel code and auto-generated bulk
+     * loaders. This is an internal API and not meant for end users.
+     *
+     * @param from The class instance that defines the "from" type (that is: 
Class&lt;F&gt;.class). Must NOT be null.
+     * @param to   The class instance that defines the "to" type (that is: 
Class&lt;F&gt;.class). Must NOT be null.
+     */
+    public TypeConvertible(Class<F> from, Class<T> to) {
+        assert from != null;
+        assert to != null;
+
+        this.from = from;
+        this.to = to;
+
+        this.hash = calculateHash();
+    }
+
+    /**
+     * Tests whether there is a conversion match from this TypeConvertible to 
the given TypeConvertible.
+     *
+     * For instance, consider 2 TypeConvertibles defined as "tc1{from: 
Number.class, to: String.class}" and "tc2{from:
+     * Integer.class, to: String.class}", it traverses the type hierarchy of 
the "from" class to determine if it, or any
+     * of its superclasses or any of its interfaces match with the "from" type 
of this instance.
+     *
+     * @param  that the TypeConvertible being tested against this instance
+     * @return      true if there is a conversion match between the give 
TypeConvertible and this instance.
+     */
+    public boolean matches(TypeConvertible<?, ?> that) {
+        return match(this.from, this.to, that.from, that.to);
+    }
+
+    /**
+     * Tests whether there is a conversion match from this TypeConvertible to 
the given TypeConvertible when the "to"
+     * type of the tested TypeConvertible is a primitive type. See {@link 
TypeConvertible#matches(TypeConvertible)} for
+     * details.
+     *
+     * @param  that the TypeConvertible being tested against this instance
+     * @return      true if there is a conversion match between the give 
TypeConvertible and this instance.
+     */
+    public boolean matchesPrimitive(TypeConvertible<?, ?> that) {
+        if (that != null && that.getTo() != null) {
+            return match(this.from, this.to, that.from, 
convertPrimitiveTypeToWrapperType(that.to));
+        }
+
+        return false;
+    }
+
+    /**
+     * A recursive implementation of the type match algorithm.
+     *
+     * @param  thisFrom The class instance that defines the source "from" type 
(that is: Class&lt;F&gt;.class)
+     * @param  thisTo   The class instance that defines the source "to" type 
(that is: Class&lt;F&gt;.class)
+     * @param  thatFrom The class instance that defines the target "from" type 
(that is: Class&lt;F&gt;.class)
+     * @param  thatTo   The class instance that defines the source "to" type 
(that is: Class&lt;F&gt;.class)
+     * @return          true if there is a conversion match between the source 
types to the target types
+     */
+    private static boolean match(Class<?> thisFrom, Class<?> thisTo, Class<?> 
thatFrom, Class<?> thatTo) {
+        if (thatFrom == null || thatTo == null) {
+            return false;
+        }
+
+        // Try direct
+        if (directMatch(thisFrom, thisTo, thatFrom, thatTo)) {
+            return true;
+        }
+
+        /* Try interfaces:
+         * Try to resolve a TypeConverter by looking at the interfaces 
implemented by a given "from" type. It looks at the
+         * type hierarchy of the target "from type" trying to match a suitable 
converter (i.e.: Integer -> Number). It will
+         * recursively analyze the whole hierarchy.
+         */
+        final Class<?>[] interfaceTypes = thatFrom.getInterfaces();
+        for (Class<?> interfaceType : interfaceTypes) {
+            if (match(thisFrom, thisTo, interfaceType, thatTo)) {
+                return true;
+            }
+        }
+
+        /*
+         * Try to resolve a TypeConverter by looking at the parent class of a 
given "from" type. It looks at the type
+         * hierarchy of the "from type" trying to match a suitable converter 
(i.e.: Integer -> Number). It will recursively
+         * analyze the whole hierarchy, and it will also evaluate the 
interfaces implemented by such type.
+         */
+        return match(thisFrom, thisTo, thatFrom.getSuperclass(), thatTo);
+    }
+
+    /**
+     * Tests whether the types defined in this type convertable pair are 
assignable from another type convertable pair
+     *
+     * @param  that the type convertible pair to test
+     * @return      true if the types in this instance are assignable from the 
respective types in the given type
+     *              convertible
+     */
+    public boolean isAssignableMatch(TypeConvertible<?, ?> that) {
+        return isAssignableMatch(this.from, this.to, that.from, that.to);
+    }
+
+    private static boolean isAssignableMatch(Class<?> thisFrom, Class<?> 
thisTo, Class<?> thatFrom, Class<?> thatTo) {
+        if (thisFrom == Object.class) {
+            return false;
+        }
+
+        if (thisTo.isAssignableFrom(thatTo)) {
+            return thisFrom.isAssignableFrom(thatFrom);
+        }
+
+        return false;
+    }
+
+    /**
+     * Try a direct match conversion (i.e.: those which the type conversion 
pair have a direct entry on the converters)
+     *
+     * @param  thisFrom The class instance that defines the source "from" type 
(that is: Class&lt;F&gt;.class)
+     * @param  thisTo   The class instance that defines the source "to" type 
(that is: Class&lt;F&gt;.class)
+     * @param  thatFrom The class instance that defines the target "from" type 
(that is: Class&lt;F&gt;.class)
+     * @param  thatTo   The class instance that defines the source "to" type 
(that is: Class&lt;F&gt;.class)
+     * @return          true if the types match directly or false otherwise
+     */
+
+    private static boolean directMatch(Class<?> thisFrom, Class<?> thisTo, 
Class<?> thatFrom, Class<?> thatTo) {
+        if (Objects.equals(thisFrom, thatFrom)) {
+            return Objects.equals(thisTo, thatTo);
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        TypeConvertible<?, ?> that = (TypeConvertible<?, ?>) o;
+        return directMatch(this.from, this.to, that.from, that.to);
+    }
+
+    @Override
+    public int hashCode() {
+        return hash;
+    }
+
+    private int calculateHash() {
+        int result = 1;
+
+        result = 31 * result + from.hashCode();
+        result = 31 * result + to.hashCode();
+
+        return result;
+    }
+
+    /**
+     * Gets the class instance that defines the "from" type (that is: 
Class&lt;F&gt;.class)
+     *
+     * @return the "from" class instance
+     */
+    public Class<F> getFrom() {
+        return from;
+    }
+
+    /**
+     * Gets the class instance that defines the "to" type (that is: 
Class&lt;F&gt;.class).
+     *
+     * @return the "to" class instance
+     */
+    public Class<T> getTo() {
+        return to;
+    }
+
+    @Override
+    public String toString() {
+        return "TypeConvertible{" +
+               "from=" + from +
+               ", to=" + to +
+               '}';
+    }
+}
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/TypeConverterRegistry.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/TypeConverterRegistry.java
index 6037531fd33..35787b77322 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/spi/TypeConverterRegistry.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/TypeConverterRegistry.java
@@ -21,6 +21,7 @@ import org.apache.camel.LoggingLevel;
 import org.apache.camel.StaticService;
 import org.apache.camel.TypeConverter;
 import org.apache.camel.TypeConverterExists;
+import org.apache.camel.converter.TypeConvertible;
 
 /**
  * Registry for type converters.
@@ -187,4 +188,14 @@ public interface TypeConverterRegistry extends 
StaticService, CamelContextAware
      */
     void setTypeConverterExists(TypeConverterExists typeConverterExists);
 
+    /**
+     * Adds a type convertible pair to the registry
+     *
+     * @param typeConvertible A type convertible pair
+     * @param typeConverter   The type converter to associate with the type 
convertible pair
+     */
+    default void addConverter(TypeConvertible<?, ?> typeConvertible, 
TypeConverter typeConverter) {
+
+    }
+
 }
diff --git 
a/core/camel-base/src/generated/java/org/apache/camel/converter/CamelBaseBulkConverterLoader.java
 
b/core/camel-base/src/generated/java/org/apache/camel/converter/CamelBaseBulkConverterLoader.java
index 33bb4be6332..f1a6f3ce1f7 100644
--- 
a/core/camel-base/src/generated/java/org/apache/camel/converter/CamelBaseBulkConverterLoader.java
+++ 
b/core/camel-base/src/generated/java/org/apache/camel/converter/CamelBaseBulkConverterLoader.java
@@ -9,6 +9,7 @@ import org.apache.camel.Ordered;
 import org.apache.camel.TypeConversionException;
 import org.apache.camel.TypeConverterLoaderException;
 import org.apache.camel.TypeConverter;
+import org.apache.camel.converter.TypeConvertible;
 import org.apache.camel.spi.BulkTypeConverters;
 import org.apache.camel.spi.TypeConverterLoader;
 import org.apache.camel.spi.TypeConverterRegistry;
@@ -48,6 +49,7 @@ public final class CamelBaseBulkConverterLoader implements 
TypeConverterLoader,
     @Override
     public void load(TypeConverterRegistry registry) throws 
TypeConverterLoaderException {
         registry.addBulkTypeConverters(this);
+        doRegistration(registry);
     }
 
     @Override
@@ -503,6 +505,132 @@ public final class CamelBaseBulkConverterLoader 
implements TypeConverterLoader,
         return null;
     }
 
+    private void doRegistration(TypeConverterRegistry registry) {
+        registry.addConverter(new TypeConvertible<>(java.nio.ByteBuffer.class, 
byte[].class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.spi.Resource.class, byte[].class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
byte[].class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.BufferedReader.class, byte[].class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.Reader.class, 
byte[].class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
byte[].class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
byte[].class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.ByteArrayOutputStream.class, byte[].class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
char[].class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
char[].class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.util.Collection.class, java.lang.Object[].class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Object.class, 
boolean.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
char.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, char.class), 
this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
java.io.BufferedReader.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
java.io.BufferedWriter.class), this);
+        registry.addConverter(new TypeConvertible<>(java.nio.file.Path.class, 
java.io.File.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.util.stream.Stream.class, java.io.InputStream.class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.spi.Resource.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new TypeConvertible<>(java.net.URL.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.lang.StringBuffer.class, java.io.InputStream.class), 
this);
+        registry.addConverter(new TypeConvertible<>(java.nio.ByteBuffer.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.lang.StringBuilder.class, java.io.InputStream.class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.BufferedReader.class, java.io.InputStream.class), 
this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.ByteArrayOutputStream.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
java.io.ObjectInput.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.OutputStream.class, java.io.ObjectOutput.class), 
this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
java.io.OutputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.spi.Resource.class, java.io.Reader.class), 
this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
java.io.Reader.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.io.Reader.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.io.Reader.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.OutputStream.class, java.io.Writer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Object.class, 
java.lang.Boolean.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.lang.Boolean.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.lang.Boolean.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Number.class, 
java.lang.Byte.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.lang.Byte.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.lang.Byte.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.lang.Character.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.lang.Character.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.lang.Class.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Number.class, 
java.lang.Double.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.lang.Double.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.lang.Double.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Number.class, 
java.lang.Float.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.lang.Float.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.lang.Float.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Number.class, 
java.lang.Integer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.lang.Integer.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.lang.Integer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Object.class, 
java.lang.Iterable.class), this);
+        registry.addConverter(new TypeConvertible<>(java.time.Duration.class, 
java.lang.Long.class), this);
+        registry.addConverter(new TypeConvertible<>(java.sql.Timestamp.class, 
java.lang.Long.class), this);
+        registry.addConverter(new TypeConvertible<>(java.util.Date.class, 
java.lang.Long.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Number.class, 
java.lang.Long.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.lang.Long.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.lang.Long.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.lang.Number.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.lang.Number.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Number.class, 
java.lang.Short.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.lang.Short.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.lang.Short.class), this);
+        registry.addConverter(new TypeConvertible<>(java.net.URI.class, 
java.lang.String.class), this);
+        registry.addConverter(new TypeConvertible<>(java.nio.ByteBuffer.class, 
java.lang.String.class), this);
+        registry.addConverter(new TypeConvertible<>(java.time.Duration.class, 
java.lang.String.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.spi.Resource.class, java.lang.String.class), 
this);
+        registry.addConverter(new TypeConvertible<>(char[].class, 
java.lang.String.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.lang.String.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
java.lang.String.class), this);
+        registry.addConverter(new TypeConvertible<>(java.net.URL.class, 
java.lang.String.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.BufferedReader.class, java.lang.String.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.Reader.class, 
java.lang.String.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
java.lang.String.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.ByteArrayOutputStream.class, java.lang.String.class), 
this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Integer.class, 
java.lang.String.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Long.class, 
java.lang.String.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Boolean.class, 
java.lang.String.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.lang.StringBuffer.class, java.lang.String.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.lang.StringBuilder.class, java.lang.String.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Object.class, 
java.math.BigInteger.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.lang.CharSequence.class, java.net.URI.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
java.nio.ByteBuffer.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.ByteArrayOutputStream.class, 
java.nio.ByteBuffer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
java.nio.ByteBuffer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.nio.ByteBuffer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Short.class, 
java.nio.ByteBuffer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Integer.class, 
java.nio.ByteBuffer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Long.class, 
java.nio.ByteBuffer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Float.class, 
java.nio.ByteBuffer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Double.class, 
java.nio.ByteBuffer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
java.nio.ByteBuffer.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
java.nio.file.Path.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Long.class, 
java.sql.Timestamp.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Long.class, 
java.time.Duration.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.time.Duration.class), this);
+        registry.addConverter(new TypeConvertible<>(java.util.Iterator.class, 
java.util.ArrayList.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Iterable.class, 
java.util.ArrayList.class), this);
+        registry.addConverter(new TypeConvertible<>(java.util.Map.class, 
java.util.Collection.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Long.class, 
java.util.Date.class), this);
+        registry.addConverter(new TypeConvertible<>(java.util.Map.class, 
java.util.HashMap.class), this);
+        registry.addConverter(new TypeConvertible<>(java.util.Map.class, 
java.util.Hashtable.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Object.class, 
java.util.Iterator.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Object[].class, 
java.util.List.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.util.Collection.class, java.util.List.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Iterable.class, 
java.util.List.class), this);
+        registry.addConverter(new TypeConvertible<>(java.util.Iterator.class, 
java.util.List.class), this);
+        registry.addConverter(new TypeConvertible<>(java.util.Map.class, 
java.util.Properties.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
java.util.Properties.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
java.util.Properties.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.Reader.class, 
java.util.Properties.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.Object[].class, 
java.util.Set.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.util.Collection.class, java.util.Set.class), this);
+        registry.addConverter(new TypeConvertible<>(java.util.Map.class, 
java.util.Set.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
java.util.TimeZone.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.Expression.class, 
org.apache.camel.Processor.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.Predicate.class, 
org.apache.camel.Processor.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
org.apache.camel.spi.Resource.class), this);
+        
+        
+    }
+
     public TypeConverter lookup(Class<?> to, Class<?> from) {
         if (to == byte[].class) {
             if (from == java.nio.ByteBuffer.class) {
diff --git 
a/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java
 
b/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java
index a297b4594f3..ef7a3affd35 100644
--- 
a/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java
+++ 
b/core/camel-base/src/main/java/org/apache/camel/impl/converter/CoreTypeConverterRegistry.java
@@ -16,8 +16,9 @@
  */
 package org.apache.camel.impl.converter;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -28,12 +29,12 @@ import org.apache.camel.CamelExecutionException;
 import org.apache.camel.Exchange;
 import org.apache.camel.LoggingLevel;
 import org.apache.camel.NoTypeConversionAvailableException;
-import org.apache.camel.Ordered;
 import org.apache.camel.TypeConversionException;
 import org.apache.camel.TypeConverter;
 import org.apache.camel.TypeConverterExists;
 import org.apache.camel.TypeConverterExistsException;
 import org.apache.camel.converter.ObjectConverter;
+import org.apache.camel.converter.TypeConvertible;
 import org.apache.camel.spi.BulkTypeConverters;
 import org.apache.camel.spi.CamelLogger;
 import org.apache.camel.spi.Injector;
@@ -41,11 +42,12 @@ import org.apache.camel.spi.TypeConverterRegistry;
 import org.apache.camel.support.MessageHelper;
 import org.apache.camel.support.TypeConverterSupport;
 import org.apache.camel.support.service.ServiceSupport;
-import org.apache.camel.util.DoubleMap;
 import org.apache.camel.util.ObjectHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static 
org.apache.camel.impl.converter.TypeResolverHelper.tryAssignableFrom;
+
 public class CoreTypeConverterRegistry extends ServiceSupport implements 
TypeConverter, TypeConverterRegistry {
 
     protected static final TypeConverter MISS_CONVERTER = new 
TypeConverterSupport() {
@@ -57,10 +59,6 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
 
     private static final Logger LOG = 
LoggerFactory.getLogger(CoreTypeConverterRegistry.class);
 
-    // built-in core type converters that are bulked together in a few classes 
for optimal performance
-    protected final List<BulkTypeConverters> bulkTypeConverters = new 
ArrayList<>();
-    // custom type converters (from camel components and end users)
-    protected final DoubleMap<Class<?>, Class<?>, TypeConverter> typeMappings 
= new DoubleMap<>(16);
     // fallback converters
     protected final List<FallbackTypeConverter> fallbackConverters = new 
CopyOnWriteArrayList<>();
     // special enum converter for optional performance
@@ -76,24 +74,9 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
     protected TypeConverterExists typeConverterExists = 
TypeConverterExists.Ignore;
     protected LoggingLevel typeConverterExistsLoggingLevel = 
LoggingLevel.DEBUG;
 
-    // to keep track of number of converters in the bulked classes
-    private int sumBulkTypeConverters;
-
-    public CoreTypeConverterRegistry() {
-    }
-
-    public CoreTypeConverterRegistry(TypeConverterRegistry registry) {
-        if (registry instanceof CoreTypeConverterRegistry) {
-            CoreTypeConverterRegistry reg = (CoreTypeConverterRegistry) 
registry;
-            reg.getTypeMappings().forEach(typeMappings::put);
-            this.bulkTypeConverters.addAll(reg.getBulkTypeConverters());
-            this.fallbackConverters.addAll(reg.getFallbackConverters());
-        } else {
-            throw new UnsupportedOperationException();
-        }
-        this.typeConverterExistsLoggingLevel = 
registry.getTypeConverterExistsLoggingLevel();
-        this.typeConverterExists = registry.getTypeConverterExists();
-    }
+    // Why 256: as of Camel 4, we have about 230 type converters. Therefore, 
set the capacity to a few more to provide
+    // space for others added during runtime
+    private final Map<TypeConvertible<?, ?>, TypeConverter> converters = new 
ConcurrentHashMap<>(256);
 
     @Override
     public boolean allowNull() {
@@ -120,18 +103,10 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
         throw new UnsupportedOperationException();
     }
 
-    public DoubleMap<Class<?>, Class<?>, TypeConverter> getTypeMappings() {
-        return typeMappings;
-    }
-
     public List<FallbackTypeConverter> getFallbackConverters() {
         return fallbackConverters;
     }
 
-    public List<BulkTypeConverters> getBulkTypeConverters() {
-        return bulkTypeConverters;
-    }
-
     public <T> T convertTo(Class<T> type, Object value) {
         return convertTo(type, null, value);
     }
@@ -147,17 +122,13 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
             if (type == boolean.class) {
                 // primitive boolean which must return a value so throw 
exception if not possible
                 Object answer = ObjectConverter.toBoolean(value);
-                if (answer == null) {
-                    throw new TypeConversionException(
-                            value, type,
-                            new IllegalArgumentException("Cannot convert type: 
" + value.getClass().getName() + " to boolean"));
-                }
+                requireNonNullBoolean(type, value, answer);
                 return (T) answer;
             } else if (type == Boolean.class && value instanceof String) {
                 // String -> Boolean
-                T parsedBoolean = customParseBoolean((String) value);
+                Boolean parsedBoolean = customParseBoolean((String) value);
                 if (parsedBoolean != null) {
-                    return parsedBoolean;
+                    return (T) parsedBoolean;
                 }
             } else if (type.isPrimitive()) {
                 // okay its a wrapper -> primitive then return as-is for some 
common types
@@ -186,20 +157,20 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
             // NOTE: we cannot optimize any more if value is String as it may 
be time pattern and other patterns
         }
 
-        return (T) doConvertTo(type, exchange, value, false, false);
+        return (T) doConvertToAndStat(type, exchange, value, false);
     }
 
     // must be 4 or 5 in length
-    private static <T> T customParseBoolean(String str) {
+    private static Boolean customParseBoolean(String str) {
         int len = str.length();
         // fast check the value as-is in lower case which is most common
         if (len == 4) {
             if ("true".equals(str)) {
-                return (T) Boolean.TRUE;
+                return Boolean.TRUE;
             }
 
             if ("TRUE".equals(str.toUpperCase())) {
-                return (T) Boolean.TRUE;
+                return Boolean.TRUE;
             }
 
             return null;
@@ -207,11 +178,11 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
 
         if (len == 5) {
             if ("false".equals(str)) {
-                return (T) Boolean.FALSE;
+                return Boolean.FALSE;
             }
 
             if ("FALSE".equals(str.toUpperCase())) {
-                return (T) Boolean.FALSE;
+                return Boolean.FALSE;
             }
 
             return null;
@@ -235,17 +206,13 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
             if (type == boolean.class) {
                 // primitive boolean which must return a value so throw 
exception if not possible
                 Object answer = ObjectConverter.toBoolean(value);
-                if (answer == null) {
-                    throw new TypeConversionException(
-                            value, type,
-                            new IllegalArgumentException("Cannot convert type: 
" + value.getClass().getName() + " to boolean"));
-                }
+                requireNonNullBoolean(type, value, answer);
                 return (T) answer;
             } else if (type == Boolean.class && value instanceof String) {
                 // String -> Boolean
-                T parsedBoolean = customParseBoolean((String) value);
+                Boolean parsedBoolean = customParseBoolean((String) value);
                 if (parsedBoolean != null) {
-                    return parsedBoolean;
+                    return (T) parsedBoolean;
                 }
             } else if (type.isPrimitive()) {
                 // okay its a wrapper -> primitive then return as-is for some 
common types
@@ -273,7 +240,7 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
             // NOTE: we cannot optimize any more if value is String as it may 
be time pattern and other patterns
         }
 
-        Object answer = doConvertTo(type, exchange, value, true, false);
+        Object answer = doConvertToAndStat(type, exchange, value, false);
         if (answer == null) {
             // Could not find suitable conversion
             throw new NoTypeConversionAvailableException(value, type);
@@ -296,17 +263,13 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
             if (type == boolean.class) {
                 // primitive boolean which must return a value so throw 
exception if not possible
                 Object answer = ObjectConverter.toBoolean(value);
-                if (answer == null) {
-                    throw new TypeConversionException(
-                            value, type,
-                            new IllegalArgumentException("Cannot convert type: 
" + value.getClass().getName() + " to boolean"));
-                }
+                requireNonNullBoolean(type, value, answer);
                 return (T) answer;
             } else if (type == Boolean.class && value instanceof String) {
                 // String -> Boolean
-                T parsedBoolean = customParseBoolean((String) value);
+                Boolean parsedBoolean = customParseBoolean((String) value);
                 if (parsedBoolean != null) {
-                    return parsedBoolean;
+                    return (T) parsedBoolean;
                 }
             } else if (type.isPrimitive()) {
                 // okay its a wrapper -> primitive then return as-is for some 
common types
@@ -336,16 +299,24 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
             // NOTE: we cannot optimize any more if value is String as it may 
be time pattern and other patterns
         }
 
-        return (T) doConvertTo(type, exchange, value, false, true);
+        return (T) doConvertToAndStat(type, exchange, value, true);
     }
 
-    protected Object doConvertTo(
+    private static <T> void requireNonNullBoolean(Class<T> type, Object value, 
Object answer) {
+        if (answer == null) {
+            throw new TypeConversionException(
+                    value, type,
+                    new IllegalArgumentException("Cannot convert type: " + 
value.getClass().getName() + " to boolean"));
+        }
+    }
+
+    protected Object doConvertToAndStat(
             final Class<?> type, final Exchange exchange, final Object value,
-            final boolean mandatory, final boolean tryConvert) {
+            final boolean tryConvert) {
 
         boolean statisticsEnabled = !tryConvert && 
statistics.isStatisticsEnabled(); // we only capture if not try-convert in use
 
-        Object answer;
+        Object answer = null;
         try {
             answer = doConvertTo(type, exchange, value, tryConvert);
         } catch (Exception e) {
@@ -357,15 +328,7 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
                 return null;
             }
 
-            // if its a ExecutionException then we have rethrow it as its not 
due to failed conversion
-            // this is special for FutureTypeConverter
-            boolean execution = 
ObjectHelper.getException(ExecutionException.class, e) != null
-                    || 
ObjectHelper.getException(CamelExecutionException.class, e) != null;
-            if (execution) {
-                throw 
CamelExecutionException.wrapCamelExecutionException(exchange, e);
-            }
-            // error occurred during type conversion
-            throw createTypeConversionException(exchange, type, value, e);
+            wrapConversionException(type, exchange, value, e);
         }
         if (answer == TypeConverter.MISS_VALUE) {
             // Could not find suitable conversion
@@ -381,7 +344,19 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
         }
     }
 
-    private static Object primitiveTypes(final Class<?> type) {
+    private void wrapConversionException(Class<?> type, Exchange exchange, 
Object value, Exception e) {
+        // if its a ExecutionException then we have rethrow it as its not due 
to failed conversion
+        // this is special for FutureTypeConverter
+        boolean execution = 
ObjectHelper.getException(ExecutionException.class, e) != null
+                || ObjectHelper.getException(CamelExecutionException.class, e) 
!= null;
+        if (execution) {
+            throw 
CamelExecutionException.wrapCamelExecutionException(exchange, e);
+        }
+        // error occurred during type conversion
+        throw createTypeConversionException(exchange, type, value, e);
+    }
+
+    private static Object nullToPrimitiveType(final Class<?> type) {
         if (boolean.class == type) {
             return Boolean.FALSE;
         }
@@ -412,8 +387,7 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
 
     protected Object doConvertTo(
             final Class<?> type, final Exchange exchange, final Object value,
-            final boolean tryConvert)
-            throws Exception {
+            final boolean tryConvert) {
         boolean statisticsEnabled = !tryConvert && 
statistics.isStatisticsEnabled(); // we only capture if not try-convert in use
 
         if (value == null) {
@@ -423,7 +397,7 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
             }
             // lets avoid NullPointerException when converting to primitives 
for null values
             if (type.isPrimitive()) {
-                return primitiveTypes(type);
+                return nullToPrimitiveType(type);
             }
             return null;
         }
@@ -437,52 +411,69 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
             return value;
         }
 
-        // okay we need to attempt to convert
         if (statisticsEnabled) {
             attemptCounter.increment();
         }
 
-        // attempt bulk first which is the fastest
-        for (BulkTypeConverters bulk : bulkTypeConverters) {
-            Object rc = bulk.convertTo(value.getClass(), type, exchange, 
value);
-            if (rc != null) {
-                return rc;
-            }
+        // attempt bulk first which is the fastest (also taking into account 
primitives)
+        final Class<?> aClass = type.isPrimitive() ? 
ObjectHelper.convertPrimitiveTypeToWrapperType(type) : type;
+        final TypeConvertible<?, ?> typeConvertible = new 
TypeConvertible<>(value.getClass(), aClass);
+
+        final Object ret = tryCachedConverters(type, exchange, value, 
typeConvertible);
+        if (ret != null) {
+            return ret;
         }
 
-        // try to find a suitable type converter
-        TypeConverter converter = getOrFindTypeConverter(type, 
value.getClass());
-        if (converter != null) {
-            Object rc = doConvert(type, exchange, value, tryConvert, 
converter);
-            if (rc != null) {
-                return rc;
-            } else if (converter.allowNull()) {
-                return null;
+        // fallback converters
+        final Object fallBackRet = tryFallback(type, exchange, value, 
tryConvert);
+        if (fallBackRet != null) {
+            return fallBackRet;
+        }
+
+        final TypeConverter assignableConverter = 
tryAssignableFrom(typeConvertible, converters);
+        if (assignableConverter != null) {
+            converters.put(typeConvertible, assignableConverter);
+            return assignableConverter.convertTo(type, exchange, value);
+        }
+
+        // This is the last resort: if nothing else works, try to find 
something that converts from an Object to the target type
+        final TypeConverter objConverter = converters.get(new 
TypeConvertible<>(Object.class, type));
+        if (objConverter != null) {
+            converters.put(typeConvertible, objConverter);
+            return objConverter.convertTo(type, exchange, value);
+        }
+
+        converters.put(typeConvertible, MISS_CONVERTER);
+
+        // Could not find suitable conversion, so return Void to indicate not 
found
+        return TypeConverter.MISS_VALUE;
+    }
+
+    private Object tryCachedConverters(Class<?> type, Exchange exchange, 
Object value, TypeConvertible<?, ?> typeConvertible) {
+        final TypeConverter typeConverter = converters.get(typeConvertible);
+        if (typeConverter != null) {
+            final Object ret = typeConverter.convertTo(type, exchange, value);
+            if (ret != null) {
+                return ret;
             }
         }
 
-        // not found with that type then if it was a primitive type then try 
again with the wrapper type
-        if (type.isPrimitive()) {
-            Class<?> primitiveType = 
ObjectHelper.convertPrimitiveTypeToWrapperType(type);
-            if (primitiveType != type) {
-                Class<?> fromType = value.getClass();
-                TypeConverter tc = getOrFindTypeConverter(primitiveType, 
fromType);
-                if (tc != null) {
-                    // add the type as a known type converter as we can 
convert from primitive to object converter
-                    addTypeConverter(type, fromType, tc);
-                    Object rc = doConvert(exchange, value, tryConvert, 
primitiveType, tc);
-                    if (rc == null && tc.allowNull()) {
-                        return null;
-                    } else if (rc != null) {
-                        return rc;
-                    }
-                }
+        final TypeConverter superConverterTc = 
TypeResolverHelper.tryMatch(typeConvertible, converters);
+        if (superConverterTc != null) {
+            final Object ret = superConverterTc.convertTo(type, exchange, 
value);
+            if (ret != null) {
+                converters.put(typeConvertible, superConverterTc);
+                return ret;
             }
         }
 
-        // fallback converters
+        return null;
+    }
+
+    private Object tryFallback(final Class<?> type, final Exchange exchange, 
final Object value, boolean tryConvert) {
         for (FallbackTypeConverter fallback : fallbackConverters) {
             TypeConverter tc = fallback.getFallbackTypeConverter();
+
             Object rc = doConvert(type, exchange, value, tryConvert, tc);
             if (rc == null && tc.allowNull()) {
                 return null;
@@ -504,14 +495,7 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
             }
         }
 
-        if (!tryConvert) {
-            // Could not find suitable conversion, so remember it
-            // do not register misses for try conversions
-            typeMappings.put(type, value.getClass(), MISS_CONVERTER);
-        }
-
-        // Could not find suitable conversion, so return Void to indicate not 
found
-        return TypeConverter.MISS_VALUE;
+        return null;
     }
 
     private static Object doConvert(
@@ -525,6 +509,7 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
 
     private static Object doConvert(
             Class<?> type, Exchange exchange, Object value, boolean 
tryConvert, TypeConverter converter) {
+
         if (tryConvert) {
             return converter.tryConvertTo(type, exchange, value);
         } else {
@@ -533,30 +518,27 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
     }
 
     public TypeConverter getTypeConverter(Class<?> toType, Class<?> fromType) {
-        return typeMappings.get(toType, fromType);
+        return converters.get(new TypeConvertible<>(fromType, toType));
+    }
+
+    @Override
+    public void addConverter(TypeConvertible<?, ?> typeConvertible, 
TypeConverter typeConverter) {
+        converters.put(typeConvertible, typeConverter);
     }
 
     @Override
     public void addBulkTypeConverters(BulkTypeConverters bulkTypeConverters) {
-        // guard against adding duplicates
-        boolean exists = this.bulkTypeConverters.contains(bulkTypeConverters);
-        if (!exists) {
-            if (bulkTypeConverters.getOrder() == Ordered.HIGHEST) {
-                this.bulkTypeConverters.add(0, bulkTypeConverters);
-            } else {
-                this.bulkTypeConverters.add(bulkTypeConverters);
-            }
-            sumBulkTypeConverters += bulkTypeConverters.size();
-        }
+        // NO-OP
     }
 
     public void addTypeConverter(Class<?> toType, Class<?> fromType, 
TypeConverter typeConverter) {
         LOG.trace("Adding type converter: {}", typeConverter);
-        TypeConverter converter = typeMappings.get(toType, fromType);
+        final TypeConvertible<?, ?> typeConvertible = new 
TypeConvertible<>(fromType, toType);
+        TypeConverter converter = converters.get(typeConvertible);
 
         if (converter == MISS_CONVERTER) {
-            // we have previously attempted to convert but missed so add this 
converter
-            typeMappings.put(toType, fromType, typeConverter);
+            // we have previously attempted to convert but missed, so add this 
converter
+            converters.put(typeConvertible, typeConverter);
             return;
         }
 
@@ -583,14 +565,15 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
             }
 
             if (add) {
-                typeMappings.put(toType, fromType, typeConverter);
+                converters.put(typeConvertible, typeConverter);
             }
         }
     }
 
     public boolean removeTypeConverter(Class<?> toType, Class<?> fromType) {
         LOG.trace("Removing type converter from: {} to: {}", fromType, toType);
-        return typeMappings.remove(toType, fromType);
+        final TypeConverter removed = converters.remove(new 
TypeConvertible<>(fromType, toType));
+        return removed != null;
     }
 
     @Override
@@ -607,108 +590,26 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
     }
 
     public TypeConverter lookup(Class<?> toType, Class<?> fromType) {
-        return doLookup(toType, fromType, false);
+        return doLookup(toType, fromType);
     }
 
+    @Deprecated
     protected TypeConverter getOrFindTypeConverter(Class<?> toType, Class<?> 
fromType) {
-        TypeConverter converter = typeMappings.get(toType, fromType);
+        TypeConvertible<?, ?> typeConvertible = new 
TypeConvertible<>(fromType, toType);
+
+        TypeConverter converter = converters.get(typeConvertible);
         if (converter == null) {
             // converter not found, try to lookup then
             converter = lookup(toType, fromType);
             if (converter != null) {
-                typeMappings.put(toType, fromType, converter);
+                converters.put(typeConvertible, converter);
             }
         }
         return converter;
     }
 
-    protected TypeConverter doLookup(Class<?> toType, Class<?> fromType, 
boolean isSuper) {
-
-        if (fromType != null) {
-
-            // try with base converters first
-            final TypeConverter baseConverters = tryBaseConverters(toType, 
fromType);
-            if (baseConverters != null) {
-                return baseConverters;
-            }
-
-            // lets try if there is a direct match
-            final TypeConverter directMatchConverter = 
tryDirectMatchConverters(toType, fromType);
-            if (directMatchConverter != null) {
-                return directMatchConverter;
-            }
-
-            // try the interfaces
-            final TypeConverter interfaceConverter = 
tryInterfaceConverters(toType, fromType);
-            if (interfaceConverter != null) {
-                return interfaceConverter;
-            }
-
-            // try super then
-            final TypeConverter superConverter = trySuperConverters(toType, 
fromType);
-            if (superConverter != null) {
-                return superConverter;
-            }
-        }
-
-        // only do these tests as fallback and only on the target type (eg not 
on its super)
-        if (!isSuper) {
-            if (fromType != null && !fromType.equals(Object.class)) {
-
-                // lets try classes derived from this toType
-                TypeConverter converter = typeMappings.getFirst(
-                        toType::isAssignableFrom,
-                        // skip Object based we do them last
-                        from -> !from.equals(Object.class) && 
from.isAssignableFrom(fromType));
-                if (converter != null) {
-                    return converter;
-                }
-
-                // lets test for Object based converters as last resort
-                converter = getTypeConverter(toType, Object.class);
-                if (converter != null) {
-                    return converter;
-                }
-            }
-        }
-
-        // none found
-        return null;
-    }
-
-    private TypeConverter trySuperConverters(Class<?> toType, Class<?> 
fromType) {
-        Class<?> fromSuperClass = fromType.getSuperclass();
-        if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) {
-            final TypeConverter converter = doLookup(toType, fromSuperClass, 
true);
-            if (converter != null) {
-                return converter;
-            }
-        }
-        return null;
-    }
-
-    private TypeConverter tryInterfaceConverters(Class<?> toType, Class<?> 
fromType) {
-        for (Class<?> type : fromType.getInterfaces()) {
-            TypeConverter converter = getTypeConverter(toType, type);
-            if (converter != null) {
-                return converter;
-            }
-        }
-        return null;
-    }
-
-    private TypeConverter tryDirectMatchConverters(Class<?> toType, Class<?> 
fromType) {
-        return getTypeConverter(toType, fromType);
-    }
-
-    private TypeConverter tryBaseConverters(Class<?> toType, Class<?> 
fromType) {
-        for (BulkTypeConverters base : bulkTypeConverters) {
-            TypeConverter converter = base.lookup(toType, fromType);
-            if (converter != null) {
-                return converter;
-            }
-        }
-        return null;
+    protected TypeConverter doLookup(Class<?> toType, Class<?> fromType) {
+        return TypeResolverHelper.doLookup(toType, fromType, converters);
     }
 
     protected TypeConversionException createTypeConversionException(
@@ -734,7 +635,7 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
     }
 
     public int size() {
-        return typeMappings.size() + sumBulkTypeConverters;
+        return converters.size();
     }
 
     public LoggingLevel getTypeConverterExistsLoggingLevel() {
@@ -760,7 +661,7 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
         if (statistics.isStatisticsEnabled()) {
             String info = statistics.toString();
             AtomicInteger misses = new AtomicInteger();
-            typeMappings.forEach((k1, k2, v) -> {
+            converters.forEach((k, v) -> {
                 if (v == MISS_CONVERTER) {
                     misses.incrementAndGet();
                 }
@@ -769,7 +670,6 @@ public class CoreTypeConverterRegistry extends 
ServiceSupport implements TypeCon
             LOG.info(info);
         }
 
-        typeMappings.clear();
         statistics.reset();
     }
 
diff --git 
a/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java
 
b/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java
index 812ad8a6411..31df118fa3c 100644
--- 
a/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java
+++ 
b/core/camel-base/src/main/java/org/apache/camel/impl/converter/DefaultTypeConverter.java
@@ -66,7 +66,7 @@ public class DefaultTypeConverter extends 
BaseTypeConverterRegistry implements A
         loadCoreAndFastTypeConverters();
 
         String time = TimeUtils.printDuration(watch.taken(), true);
-        LOG.debug("Loaded {} type converters in {}", typeMappings.size(), 
time);
+        LOG.debug("Loaded {} type converters in {}", size(), time);
 
         if (!loadTypeConvertersDone && isLoadTypeConverters()) {
             scanTypeConverters();
@@ -94,18 +94,8 @@ public class DefaultTypeConverter extends 
BaseTypeConverterRegistry implements A
                 typeConverterLoaders.add(createScanTypeConverterLoader());
             }
 
-            int fast = typeMappings.size();
             // load type converters up front
             loadTypeConverters();
-            int additional = typeMappings.size() - fast;
-
-            // report how many type converters we have loaded
-            if (additional > 0) {
-                LOG.debug("Type converters loaded (fast: {}, scanned: {})", 
fast, additional);
-                LOG.warn(
-                        "Annotation scanning mode loaded {} type converters. 
Its recommended to migrate to @Converter(loader = true) for fast type converter 
mode.",
-                        additional);
-            }
 
             // lets clear the cache from the resolver as its often only used 
during startup
             if (resolver != null) {
@@ -114,7 +104,7 @@ public class DefaultTypeConverter extends 
BaseTypeConverterRegistry implements A
         }
 
         String time = TimeUtils.printDuration(watch.taken(), true);
-        LOG.debug("Scanned {} type converters in {}", typeMappings.size(), 
time);
+        LOG.debug("Loaded {} type converters in {}", size(), time);
     }
 
     /**
diff --git 
a/core/camel-base/src/main/java/org/apache/camel/impl/converter/TypeResolverHelper.java
 
b/core/camel-base/src/main/java/org/apache/camel/impl/converter/TypeResolverHelper.java
new file mode 100644
index 00000000000..73c2d655543
--- /dev/null
+++ 
b/core/camel-base/src/main/java/org/apache/camel/impl/converter/TypeResolverHelper.java
@@ -0,0 +1,166 @@
+/*
+ * 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.impl.converter;
+
+import java.util.Map;
+
+import org.apache.camel.TypeConverter;
+import org.apache.camel.converter.TypeConvertible;
+
+/**
+ * Helper methods for resolving the type conversions. This is an internal API 
and not meant for public usages.
+ * <p>
+ * In a broader sense: the methods of this code help with traversing the class 
hierarchy of the types involved in a
+ * conversion, so that the correct TypeConverter can be used. This is a helper 
class to CoreTypeConverterRegistry.
+ * <p>
+ * In the CoreTypeConverterRegistry class, the registry of types if maintained 
in a ConcurrentMap that associates a type
+ * pair with the resolver for it (i.e.: it associates pair representing a 
conversion from String to Integer to a type
+ * converter - such as CamelBaseBulkConverterLoader).
+ * <p>
+ * NOTE 1: a lot of this code is in the hot path of the core engine, so change 
with extreme caution to prevent
+ * performance issues on the core code.
+ * <p>
+ * NOTE 2: also, a lot of this code runs rather slow operations, so calling 
these methods should be avoided as much as
+ * possible
+ *
+ */
+final class TypeResolverHelper {
+    private TypeResolverHelper() {
+
+    }
+
+    /**
+     * Lookup the type converter in the registry (given a type to convert to 
and a type to convert from, along with a
+     * mapping of all known converters)
+     *
+     * @param  toType     the type to convert to
+     * @param  fromType   the type to convert from
+     * @param  converters the map of all known converters
+     * @return            the type converter or null if unknown
+     */
+    static TypeConverter doLookup(
+            Class<?> toType, Class<?> fromType, Map<TypeConvertible<?, ?>, 
TypeConverter> converters) {
+        return doLookup(new TypeConvertible<>(fromType, toType), converters);
+    }
+
+    private static TypeConverter doLookup(
+            TypeConvertible<?, ?> typeConvertible, Map<TypeConvertible<?, ?>, 
TypeConverter> converters) {
+
+        // try with base converters first
+        final TypeConverter typeConverter = converters.get(typeConvertible);
+        if (typeConverter != null) {
+            return typeConverter;
+        }
+
+        final TypeConverter superConverterTc = tryMatch(typeConvertible, 
converters);
+        if (superConverterTc != null) {
+            return superConverterTc;
+        }
+
+        final TypeConverter primitiveAwareConverter = 
tryPrimitive(typeConvertible, converters);
+        if (primitiveAwareConverter != null) {
+            return primitiveAwareConverter;
+        }
+
+        // only do these tests as fallback and only on the target type
+        if (!typeConvertible.getFrom().equals(Object.class)) {
+
+            final TypeConverter assignableConverter
+                    = tryAssignableFrom(typeConvertible, converters);
+            if (assignableConverter != null) {
+                return assignableConverter;
+            }
+
+            final TypeConverter objConverter = converters.get(new 
TypeConvertible<>(Object.class, typeConvertible.getTo()));
+            if (objConverter != null) {
+                return objConverter;
+            }
+        }
+
+        // none found
+        return null;
+    }
+
+    /**
+     * Try the base converters. That is, those matching a direct conversion 
(i.e.: when the from and to types requested
+     * do exist on the converters' map OR when the from and to types requested 
match for a _primitive type).
+     * <p>
+     * For instance: From String.class, To: int.class (would match a method 
such as myConverter(String, Integer) or
+     * myConverter(String, int).
+     *
+     * @param  typeConvertible the type converter pair
+     * @param  converters      the map of all known converters
+     * @return                 the type converter or null if unknown
+     */
+    static TypeConverter tryAssignableFrom(
+            TypeConvertible<?, ?> typeConvertible, Map<TypeConvertible<?, ?>, 
TypeConverter> converters) {
+
+        /*
+         Let's try classes derived from this toType: basically it traverses 
the entries looking for assignable types
+         matching both the "from type" and the "to type" which are NOT Object 
(we usually try this later).
+         */
+        for (var entry : converters.entrySet()) {
+            if (entry.getKey().isAssignableMatch(typeConvertible)) {
+                return entry.getValue();
+            }
+
+        }
+
+        return null;
+    }
+
+    /**
+     * Try to resolve the TypeConverter by forcing a costly and slow recursive 
check.
+     *
+     * @param  typeConvertible the type converter pair
+     * @param  converters      the map of all known converters
+     * @return                 the type converter or null if unknown
+     */
+    static TypeConverter tryMatch(
+            TypeConvertible<?, ?> typeConvertible, Map<TypeConvertible<?, ?>, 
TypeConverter> converters) {
+        for (var entry : converters.entrySet()) {
+            if (entry.getKey().matches(typeConvertible)) {
+                return entry.getValue();
+            }
+
+        }
+
+        return null;
+    }
+
+    /**
+     * Try to resolve the TypeConverter by forcing a costly and slow recursive 
check that takes into consideration that
+     * the target type may have a primitive data type
+     *
+     * @param  typeConvertible the type converter pair
+     * @param  converters      the map of all known converters
+     * @return                 the type converter or null if unknown
+     */
+    static TypeConverter tryPrimitive(
+            TypeConvertible<?, ?> typeConvertible, Map<TypeConvertible<?, ?>, 
TypeConverter> converters) {
+        for (var entry : converters.entrySet()) {
+            if (entry.getKey().matchesPrimitive(typeConvertible)) {
+                return entry.getValue();
+            }
+
+        }
+
+        return null;
+    }
+
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/converter/ConverterTest.java 
b/core/camel-core/src/test/java/org/apache/camel/converter/ConverterTest.java
index 531015ff78c..1eca55a02f8 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/converter/ConverterTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/converter/ConverterTest.java
@@ -113,11 +113,10 @@ public class ConverterTest extends TestSupport {
         assertEquals(2, list.size(), "List size: " + list);
 
         Collection<?> collection = converter.convertTo(Collection.class, 
array);
+        assertNotNull(collection, "Returned object must not be null");
         assertEquals(2, collection.size(), "Collection size: " + collection);
 
-        Set<?> set = converter.convertTo(Set.class, array);
-        assertEquals(2, set.size(), "Set size: " + set);
-        set = converter.convertTo(Set.class, list);
+        Set<?> set = converter.convertTo(Set.class, list);
         assertEquals(2, set.size(), "Set size: " + set);
     }
 
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/impl/CustomBulkTypeConvertersTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/impl/CustomBulkTypeConvertersTest.java
index fd25a83172d..5ce2b24b252 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/impl/CustomBulkTypeConvertersTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/impl/CustomBulkTypeConvertersTest.java
@@ -21,6 +21,7 @@ import org.apache.camel.ContextTestSupport;
 import org.apache.camel.Exchange;
 import org.apache.camel.TypeConversionException;
 import org.apache.camel.TypeConverter;
+import org.apache.camel.converter.TypeConvertible;
 import org.apache.camel.spi.BulkTypeConverters;
 import org.junit.jupiter.api.Test;
 
@@ -32,7 +33,13 @@ public class CustomBulkTypeConvertersTest extends 
ContextTestSupport {
     @Override
     protected CamelContext createCamelContext() throws Exception {
         CamelContext context = super.createCamelContext();
-        context.getTypeConverterRegistry().addBulkTypeConverters(new 
CustomBulkTypeConverters());
+
+        final CustomBulkTypeConverters customBulkTypeConverters = new 
CustomBulkTypeConverters();
+        
context.getTypeConverterRegistry().addBulkTypeConverters(customBulkTypeConverters);
+        context.getTypeConverterRegistry().addConverter(new 
TypeConvertible<>(String.class, MyOrder.class),
+                customBulkTypeConverters);
+        context.getTypeConverterRegistry().addConverter(new 
TypeConvertible<>(Integer.class, MyOrder.class),
+                customBulkTypeConverters);
         return context;
     }
 
diff --git 
a/core/camel-support/src/generated/java/org/apache/camel/converter/stream/StreamCacheBulkConverterLoader.java
 
b/core/camel-support/src/generated/java/org/apache/camel/converter/stream/StreamCacheBulkConverterLoader.java
index 575219bad83..ebc49489f68 100644
--- 
a/core/camel-support/src/generated/java/org/apache/camel/converter/stream/StreamCacheBulkConverterLoader.java
+++ 
b/core/camel-support/src/generated/java/org/apache/camel/converter/stream/StreamCacheBulkConverterLoader.java
@@ -9,6 +9,7 @@ import org.apache.camel.Ordered;
 import org.apache.camel.TypeConversionException;
 import org.apache.camel.TypeConverterLoaderException;
 import org.apache.camel.TypeConverter;
+import org.apache.camel.converter.TypeConvertible;
 import org.apache.camel.spi.BulkTypeConverters;
 import org.apache.camel.spi.TypeConverterLoader;
 import org.apache.camel.spi.TypeConverterRegistry;
@@ -43,6 +44,7 @@ public final class StreamCacheBulkConverterLoader implements 
TypeConverterLoader
     @Override
     public void load(TypeConverterRegistry registry) throws 
TypeConverterLoaderException {
         registry.addBulkTypeConverters(this);
+        doRegistration(registry);
     }
 
     @Override
@@ -87,6 +89,17 @@ public final class StreamCacheBulkConverterLoader implements 
TypeConverterLoader
         return null;
     }
 
+    private void doRegistration(TypeConverterRegistry registry) {
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.StreamCache.class, byte[].class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.StreamCache.class, 
java.nio.ByteBuffer.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.ByteArrayInputStream.class, 
org.apache.camel.StreamCache.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
org.apache.camel.StreamCache.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.converter.stream.CachedOutputStream.class, 
org.apache.camel.StreamCache.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.Reader.class, 
org.apache.camel.StreamCache.class), this);
+        
+        
+    }
+
     public TypeConverter lookup(Class<?> to, Class<?> from) {
         if (to == byte[].class) {
             if (from == org.apache.camel.StreamCache.class) {
diff --git a/core/camel-util/src/main/java/org/apache/camel/util/DoubleMap.java 
b/core/camel-util/src/main/java/org/apache/camel/util/DoubleMap.java
index 5d1334ea9c2..927b8567240 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/DoubleMap.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/DoubleMap.java
@@ -20,6 +20,7 @@ import java.util.function.Predicate;
 
 import org.apache.camel.util.function.TriConsumer;
 
+@Deprecated
 @SuppressWarnings("unchecked")
 public class DoubleMap<K1, K2, V> {
 
diff --git 
a/core/camel-xml-jaxp/src/generated/java/org/apache/camel/converter/jaxp/CamelXmlJaxpBulkConverterLoader.java
 
b/core/camel-xml-jaxp/src/generated/java/org/apache/camel/converter/jaxp/CamelXmlJaxpBulkConverterLoader.java
index 1847e3f38c5..c802bf26e54 100644
--- 
a/core/camel-xml-jaxp/src/generated/java/org/apache/camel/converter/jaxp/CamelXmlJaxpBulkConverterLoader.java
+++ 
b/core/camel-xml-jaxp/src/generated/java/org/apache/camel/converter/jaxp/CamelXmlJaxpBulkConverterLoader.java
@@ -9,6 +9,7 @@ import org.apache.camel.Ordered;
 import org.apache.camel.TypeConversionException;
 import org.apache.camel.TypeConverterLoaderException;
 import org.apache.camel.TypeConverter;
+import org.apache.camel.converter.TypeConvertible;
 import org.apache.camel.spi.BulkTypeConverters;
 import org.apache.camel.spi.TypeConverterLoader;
 import org.apache.camel.spi.TypeConverterRegistry;
@@ -43,6 +44,7 @@ public final class CamelXmlJaxpBulkConverterLoader implements 
TypeConverterLoade
     @Override
     public void load(TypeConverterRegistry registry) throws 
TypeConverterLoaderException {
         registry.addBulkTypeConverters(this);
+        doRegistration(registry);
     }
 
     @Override
@@ -392,6 +394,106 @@ public final class CamelXmlJaxpBulkConverterLoader 
implements TypeConverterLoade
         return null;
     }
 
+    private void doRegistration(TypeConverterRegistry registry) {
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.NodeList.class, byte[].class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Source.class, byte[].class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.NodeList.class, java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.stream.XMLStreamReader.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.dom.DOMSource.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.Document.class, java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.stream.StreamSource.class, 
java.io.InputStream.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.stream.XMLStreamReader.class, 
java.io.Reader.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.stream.StreamSource.class, 
java.io.Reader.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Source.class, java.io.Reader.class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.StreamCache.class, 
java.io.Serializable.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.NodeList.class, java.lang.Boolean.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.NodeList.class, java.lang.Integer.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.NodeList.class, java.lang.Long.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.NodeList.class, java.lang.String.class), this);
+        registry.addConverter(new TypeConvertible<>(org.w3c.dom.Node.class, 
java.lang.String.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Source.class, java.lang.String.class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.NodeList.class, java.util.List.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
javax.xml.namespace.QName.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
javax.xml.stream.XMLEventReader.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
javax.xml.stream.XMLEventReader.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.Reader.class, 
javax.xml.stream.XMLEventReader.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.stream.XMLStreamReader.class, 
javax.xml.stream.XMLEventReader.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Source.class, 
javax.xml.stream.XMLEventReader.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.OutputStream.class, 
javax.xml.stream.XMLEventWriter.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.Writer.class, 
javax.xml.stream.XMLEventWriter.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Result.class, 
javax.xml.stream.XMLEventWriter.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
javax.xml.stream.XMLStreamReader.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
javax.xml.stream.XMLStreamReader.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.Reader.class, 
javax.xml.stream.XMLStreamReader.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Source.class, 
javax.xml.stream.XMLStreamReader.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
javax.xml.stream.XMLStreamReader.class), this);
+        registry.addConverter(new 
TypeConvertible<>(java.io.OutputStream.class, 
javax.xml.stream.XMLStreamWriter.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.Writer.class, 
javax.xml.stream.XMLStreamWriter.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Result.class, 
javax.xml.stream.XMLStreamWriter.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
javax.xml.transform.Source.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
javax.xml.transform.Source.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.Document.class, 
javax.xml.transform.Source.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.StreamCache.class, 
javax.xml.transform.Source.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.Document.class, 
javax.xml.transform.dom.DOMSource.class), this);
+        registry.addConverter(new TypeConvertible<>(org.w3c.dom.Node.class, 
javax.xml.transform.dom.DOMSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
javax.xml.transform.dom.DOMSource.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
javax.xml.transform.dom.DOMSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.StreamCache.class, 
javax.xml.transform.dom.DOMSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
javax.xml.transform.dom.DOMSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
javax.xml.transform.dom.DOMSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.stream.StreamSource.class, 
javax.xml.transform.dom.DOMSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.sax.SAXSource.class, 
javax.xml.transform.dom.DOMSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.stax.StAXSource.class, 
javax.xml.transform.dom.DOMSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Source.class, 
javax.xml.transform.dom.DOMSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
javax.xml.transform.sax.SAXSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
javax.xml.transform.sax.SAXSource.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
javax.xml.transform.sax.SAXSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
javax.xml.transform.sax.SAXSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.stream.StreamSource.class, 
javax.xml.transform.sax.SAXSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.dom.DOMSource.class, 
javax.xml.transform.sax.SAXSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.stax.StAXSource.class, 
javax.xml.transform.sax.SAXSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Source.class, 
javax.xml.transform.sax.SAXSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
javax.xml.transform.stax.StAXSource.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
javax.xml.transform.stax.StAXSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
javax.xml.transform.stax.StAXSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
javax.xml.transform.stax.StAXSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
javax.xml.transform.stream.StreamSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
javax.xml.transform.stream.StreamSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.Reader.class, 
javax.xml.transform.stream.StreamSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
javax.xml.transform.stream.StreamSource.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
javax.xml.transform.stream.StreamSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.nio.ByteBuffer.class, 
javax.xml.transform.stream.StreamSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.sax.SAXSource.class, 
javax.xml.transform.stream.StreamSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.dom.DOMSource.class, 
javax.xml.transform.stream.StreamSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.stax.StAXSource.class, 
javax.xml.transform.stream.StreamSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Source.class, 
javax.xml.transform.stream.StreamSource.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.util.xml.BytesSource.class, 
org.apache.camel.StreamCache.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.stream.StreamSource.class, 
org.apache.camel.StreamCache.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.sax.SAXSource.class, 
org.apache.camel.StreamCache.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
org.apache.camel.util.xml.BytesSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
org.apache.camel.util.xml.StringSource.class), this);
+        registry.addConverter(new TypeConvertible<>(org.w3c.dom.Node.class, 
org.w3c.dom.Document.class), this);
+        registry.addConverter(new TypeConvertible<>(byte[].class, 
org.w3c.dom.Document.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.apache.camel.StreamCache.class, 
org.w3c.dom.Document.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
org.w3c.dom.Document.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.Reader.class, 
org.w3c.dom.Document.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.xml.sax.InputSource.class, org.w3c.dom.Document.class), 
this);
+        registry.addConverter(new TypeConvertible<>(java.lang.String.class, 
org.w3c.dom.Document.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
org.w3c.dom.Document.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Source.class, 
org.w3c.dom.Document.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.NodeList.class, org.w3c.dom.Document.class), 
this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Source.class, org.w3c.dom.Element.class), 
this);
+        registry.addConverter(new TypeConvertible<>(org.w3c.dom.Node.class, 
org.w3c.dom.Element.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.sax.SAXSource.class, 
org.w3c.dom.Node.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.stax.StAXSource.class, 
org.w3c.dom.Node.class), this);
+        registry.addConverter(new 
TypeConvertible<>(org.w3c.dom.NodeList.class, org.w3c.dom.Node.class), this);
+        registry.addConverter(new 
TypeConvertible<>(javax.xml.transform.Source.class, org.w3c.dom.Node.class), 
this);
+        registry.addConverter(new TypeConvertible<>(java.io.InputStream.class, 
org.xml.sax.InputSource.class), this);
+        registry.addConverter(new TypeConvertible<>(java.io.File.class, 
org.xml.sax.InputSource.class), this);
+        
+        
+    }
+
     public TypeConverter lookup(Class<?> to, Class<?> from) {
         if (to == byte[].class) {
             if (from == org.w3c.dom.NodeList.class) {
diff --git 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/TypeConverterLoaderGeneratorMojo.java
 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/TypeConverterLoaderGeneratorMojo.java
index 0874f04fed3..8ef0b6c9009 100644
--- 
a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/TypeConverterLoaderGeneratorMojo.java
+++ 
b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/TypeConverterLoaderGeneratorMojo.java
@@ -186,6 +186,7 @@ public class TypeConverterLoaderGeneratorMojo extends 
AbstractGeneratorMojo {
         writer.append("import org.apache.camel.TypeConversionException;\n");
         writer.append("import 
org.apache.camel.TypeConverterLoaderException;\n");
         writer.append("import org.apache.camel.TypeConverter;\n");
+        writer.append("import org.apache.camel.converter.TypeConvertible;\n");
         writer.append("import org.apache.camel.spi.BulkTypeConverters;\n");
         writer.append("import org.apache.camel.spi.TypeConverterLoader;\n");
         writer.append("import org.apache.camel.spi.TypeConverterRegistry;\n");
@@ -233,6 +234,7 @@ public class TypeConverterLoaderGeneratorMojo extends 
AbstractGeneratorMojo {
         writer.append("    @Override\n");
         writer.append("    public void load(TypeConverterRegistry registry) 
throws TypeConverterLoaderException {\n");
         writer.append("        registry.addBulkTypeConverters(this);\n");
+        writer.append("        doRegistration(registry);\n");
         writer.append("    }\n");
         writer.append("\n");
         writer.append("    @Override\n");
@@ -259,6 +261,14 @@ public class TypeConverterLoaderGeneratorMojo extends 
AbstractGeneratorMojo {
         writer.append("        return null;\n");
         writer.append("    }\n");
         writer.append("\n");
+        writer.append(
+                "    private void doRegistration(TypeConverterRegistry 
registry) {\n");
+        writeRegistration(converters, writer, converterClasses, false);
+        writer.append("        \n");
+        writer.append("        \n");
+        writer.append("    }\n");
+        writer.append("\n");
+
         writer.append(
                 "    public TypeConverter lookup(Class<?> to, Class<?> from) 
{\n");
         writeLoader(converters, writer, converterClasses, true);
@@ -283,18 +293,120 @@ public class TypeConverterLoaderGeneratorMojo extends 
AbstractGeneratorMojo {
         return writer.toString();
     }
 
+    private void writeRegistration(
+            List<MethodInfo> converters, StringBuilder writer, Set<String> 
converterClasses, boolean lookup) {
+        for (MethodInfo method : converters) {
+
+            writer.append("        registry.addConverter(new 
TypeConvertible<>(")
+                    .append(getGenericArgumentsForTypeConvertible(method))
+                    .append("), ")
+                    .append("this);")
+
+                    .append("\n");
+        }
+    }
+
+    /**
+     * This resolves the method to be called for conversion. There are 2 
possibilities here: either it calls a static
+     * method, in which case we can refer to it directly ... Or it uses one of 
the converter classes to do so. In this
+     * case, we do a bit of hacking "by convention" to call the getter of said 
converter and use it to call the
+     * converter method (i.e.; getDomConverter().myConverter method). There 
are some cases (like when dealing with jaxp
+     * that require this)
+     *
+     * @param  method The converter method
+     * @return        The resolved converter method to use
+     */
+    private static String resolveMethod(MethodInfo method) {
+        if (Modifier.isStatic(method.flags())) {
+            return method.declaringClass().toString() + "." + method.name();
+        } else {
+            return "get" + method.declaringClass().simpleName() + "()." + 
method.name();
+        }
+    }
+
+    /**
+     * This generates the cast part of the method
+     *
+     * @param  method The converter method
+     * @return        The cast string to use
+     */
+    private static String generateCast(MethodInfo method) {
+        StringBuilder writer = new StringBuilder(128);
+
+        if (method.parameterTypes().get(0).kind() == Type.Kind.ARRAY
+                || method.parameterTypes().get(0).kind() == Type.Kind.CLASS) {
+            writer.append(method.parameterTypes().get(0).toString());
+        } else {
+            writer.append(method.parameterTypes().get(0).name().toString());
+        }
+
+        return writer.toString();
+    }
+
+    /**
+     * This generates the template arguments for the type convertible.
+     *
+     * @param  method The converter method
+     * @return        A string with the converter arguments
+     */
+
+    private static String getGenericArgumentsForTypeConvertible(MethodInfo 
method) {
+        StringBuilder writer = new StringBuilder(4096);
+
+        if (method.parameterTypes().get(0).kind() == Type.Kind.ARRAY
+                || method.parameterTypes().get(0).kind() == Type.Kind.CLASS) {
+            writer.append(method.parameterTypes().get(0).toString());
+        } else {
+            writer.append(method.parameterTypes().get(0).name().toString());
+        }
+
+        writer.append(".class, ");
+        writer.append(getToMethod(method));
+        writer.append(".class");
+
+        return writer.toString();
+    }
+
+    /**
+     * This generates the arguments to be passed to the converter method. For 
instance, given a list of methods, it
+     * traverses the parameter types and generates something like this: 
"exchange, camelContext".
+     *
+     * @param  method The converter method
+     * @return        A string instance with the converter methods or an empty 
String if none exist.
+     */
+    private static String getArgumentsForConverter(MethodInfo method) {
+        StringBuilder writer = new StringBuilder(128);
+
+        for (Type type : method.parameterTypes()) {
+
+            if 
(type.name().withoutPackagePrefix().equalsIgnoreCase("CamelContext")) {
+                writer.append(", camelContext");
+            }
+
+            if 
(type.name().withoutPackagePrefix().equalsIgnoreCase("Exchange")) {
+                writer.append(", exchange");
+            }
+        }
+
+        return writer.toString();
+    }
+
+    private static String getToMethod(MethodInfo method) {
+        if (Type.Kind.PRIMITIVE.equals(method.returnType().kind())) {
+            return method.returnType().toString();
+        } else if (Type.Kind.ARRAY.equals(method.returnType().kind())) {
+            return method.returnType().toString();
+        } else {
+            return method.returnType().name().toString();
+        }
+    }
+
     private void writeLoader(
             List<MethodInfo> converters, StringBuilder writer, Set<String> 
converterClasses, boolean lookup) {
         String prevTo = null;
         for (MethodInfo method : converters) {
-            String to;
-            if (Type.Kind.PRIMITIVE.equals(method.returnType().kind())) {
-                to = method.returnType().toString();
-            } else if (Type.Kind.ARRAY.equals(method.returnType().kind())) {
-                to = method.returnType().toString();
-            } else {
-                to = method.returnType().name().toString();
-            }
+            String to = getToMethod(method);
+
             String from = method.parameterTypes().get(0).toString();
             // clip generics
             if (to.indexOf('<') != -1) {

Reply via email to