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

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 34bc06c849 New BasicConverter class
34bc06c849 is described below

commit 34bc06c84979ae537eb71d57037373402d231810
Author: James Bognar <[email protected]>
AuthorDate: Thu Apr 2 08:05:57 2026 -0700

    New BasicConverter class
---
 .../juneau/commons/conversion/BasicConverter.java  | 102 ++++++++++++---------
 .../commons/conversion/CachingConverter.java       |  61 +++++++++++-
 .../juneau/commons/conversion/Conversion.java      |   3 +-
 .../juneau/commons/conversion/Converter.java       |  39 ++++++++
 .../apache/juneau/utils/BeanContextConverter.java  |  35 +++++++
 .../commons/conversion/BasicConverter_Test.java    |  51 ++++++++++-
 .../commons/conversion/CachingConverter_Test.java  |  72 +++++++++++++++
 .../juneau/commons/conversion/Converter_Test.java  |  23 +++++
 8 files changed, 333 insertions(+), 53 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/BasicConverter.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/BasicConverter.java
index 822b43ffe6..9bb4e16e99 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/BasicConverter.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/BasicConverter.java
@@ -181,7 +181,7 @@ public class BasicConverter extends CachingConverter {
                if ((c = findSpecialConversion(inType, out)) != null) return c;
 
                if (out.isAssignableFrom(inType) && 
!Collection.class.isAssignableFrom(out) && !Map.class.isAssignableFrom(out))
-                       return (in, args) -> (O) in;
+                       return (in, memberOf, args) -> (O) in;
 
                if (Number.class.isAssignableFrom(out) && (c = 
findNumberConversion(inType, out)) != null) return c;
 
@@ -224,31 +224,31 @@ public class BasicConverter extends CachingConverter {
        }
 
        private <I, O> Conversion<I, O> findNumberFromNumber(Class<O> outType) {
-               if (outType == Integer.class) return (in, args) -> (O) 
Integer.valueOf(((Number) in).intValue());
-               if (outType == Long.class) return (in, args) -> (O) 
Long.valueOf(((Number) in).longValue());
-               if (outType == Short.class) return (in, args) -> (O) 
Short.valueOf(((Number) in).shortValue());
-               if (outType == Float.class) return (in, args) -> (O) 
Float.valueOf(((Number) in).floatValue());
-               if (outType == Double.class) return (in, args) -> (O) 
Double.valueOf(((Number) in).doubleValue());
-               if (outType == Byte.class) return (in, args) -> (O) 
Byte.valueOf(((Number) in).byteValue());
-               if (outType == AtomicInteger.class) return (in, args) -> (O) 
new AtomicInteger(((Number) in).intValue());
-               if (outType == AtomicLong.class) return (in, args) -> (O) new 
AtomicLong(((Number) in).longValue());
+               if (outType == Integer.class) return (in, memberOf, args) -> 
(O) Integer.valueOf(((Number) in).intValue());
+               if (outType == Long.class) return (in, memberOf, args) -> (O) 
Long.valueOf(((Number) in).longValue());
+               if (outType == Short.class) return (in, memberOf, args) -> (O) 
Short.valueOf(((Number) in).shortValue());
+               if (outType == Float.class) return (in, memberOf, args) -> (O) 
Float.valueOf(((Number) in).floatValue());
+               if (outType == Double.class) return (in, memberOf, args) -> (O) 
Double.valueOf(((Number) in).doubleValue());
+               if (outType == Byte.class) return (in, memberOf, args) -> (O) 
Byte.valueOf(((Number) in).byteValue());
+               if (outType == AtomicInteger.class) return (in, memberOf, args) 
-> (O) new AtomicInteger(((Number) in).intValue());
+               if (outType == AtomicLong.class) return (in, memberOf, args) -> 
(O) new AtomicLong(((Number) in).longValue());
                return null;
        }
 
        private <O> Conversion<Boolean, O> findNumberFromBoolean(Class<O> 
outType) {
-               if (outType == Integer.class) return (in, args) -> (O) 
Integer.valueOf(in.booleanValue() ? 1 : 0);
-               if (outType == Long.class) return (in, args) -> (O) 
Long.valueOf(in.booleanValue() ? 1L : 0L);
-               if (outType == Short.class) return (in, args) -> (O) 
Short.valueOf(in.booleanValue() ? (short) 1 : (short) 0);
-               if (outType == Float.class) return (in, args) -> (O) 
Float.valueOf(in.booleanValue() ? 1f : 0f);
-               if (outType == Double.class) return (in, args) -> (O) 
Double.valueOf(in.booleanValue() ? 1d : 0d);
-               if (outType == Byte.class) return (in, args) -> (O) 
Byte.valueOf(in.booleanValue() ? (byte) 1 : (byte) 0);
-               if (outType == AtomicInteger.class) return (in, args) -> (O) 
new AtomicInteger(in.booleanValue() ? 1 : 0);
-               if (outType == AtomicLong.class) return (in, args) -> (O) new 
AtomicLong(in.booleanValue() ? 1L : 0L);
+               if (outType == Integer.class) return (in, memberOf, args) -> 
(O) Integer.valueOf(in.booleanValue() ? 1 : 0);
+               if (outType == Long.class) return (in, memberOf, args) -> (O) 
Long.valueOf(in.booleanValue() ? 1L : 0L);
+               if (outType == Short.class) return (in, memberOf, args) -> (O) 
Short.valueOf(in.booleanValue() ? (short) 1 : (short) 0);
+               if (outType == Float.class) return (in, memberOf, args) -> (O) 
Float.valueOf(in.booleanValue() ? 1f : 0f);
+               if (outType == Double.class) return (in, memberOf, args) -> (O) 
Double.valueOf(in.booleanValue() ? 1d : 0d);
+               if (outType == Byte.class) return (in, memberOf, args) -> (O) 
Byte.valueOf(in.booleanValue() ? (byte) 1 : (byte) 0);
+               if (outType == AtomicInteger.class) return (in, memberOf, args) 
-> (O) new AtomicInteger(in.booleanValue() ? 1 : 0);
+               if (outType == AtomicLong.class) return (in, memberOf, args) -> 
(O) new AtomicLong(in.booleanValue() ? 1L : 0L);
                return null;
        }
 
        private <O> Conversion<CharSequence, O> findNumberFromString(Class<O> 
outType) {
-               return (in, args) -> (O) parseNumber(in.toString(), (Class<? 
extends Number>) outType);
+               return (in, memberOf, args) -> (O) parseNumber(in.toString(), 
(Class<? extends Number>) outType);
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -257,7 +257,7 @@ public class BasicConverter extends CachingConverter {
 
        private <I, O> Conversion<I, O> findBooleanConversion(Class<I> inType) {
                if (Number.class.isAssignableFrom(inType))
-                       return (in, args) -> (O) Boolean.valueOf(((Number) 
in).intValue() != 0);
+                       return (in, memberOf, args) -> (O) 
Boolean.valueOf(((Number) in).intValue() != 0);
                return null;
        }
 
@@ -267,12 +267,12 @@ public class BasicConverter extends CachingConverter {
 
        private <I, O> Conversion<I, O> findCharacterConversion(Class<I> 
inType) {
                if (CharSequence.class.isAssignableFrom(inType))
-                       return (in, args) -> {
+                       return (in, memberOf, args) -> {
                                var s = in.toString();
                                return s.length() == 1 ? (O) 
Character.valueOf(s.charAt(0)) : null;
                        };
                if (Number.class.isAssignableFrom(inType))
-                       return (in, args) -> (O) Character.valueOf((char) 
((Number) in).intValue());
+                       return (in, memberOf, args) -> (O) 
Character.valueOf((char) ((Number) in).intValue());
                return null;
        }
 
@@ -282,17 +282,17 @@ public class BasicConverter extends CachingConverter {
 
        private <I> Conversion<I, String> findToStringConversion(Class<I> 
inType) {
                if (inType.isArray()) {
-                       if (inType == int[].class) return (in, args) -> 
Arrays.toString((int[]) in);
-                       if (inType == long[].class) return (in, args) -> 
Arrays.toString((long[]) in);
-                       if (inType == double[].class) return (in, args) -> 
Arrays.toString((double[]) in);
-                       if (inType == float[].class) return (in, args) -> 
Arrays.toString((float[]) in);
-                       if (inType == boolean[].class) return (in, args) -> 
Arrays.toString((boolean[]) in);
-                       if (inType == byte[].class) return (in, args) -> 
Arrays.toString((byte[]) in);
-                       if (inType == short[].class) return (in, args) -> 
Arrays.toString((short[]) in);
-                       if (inType == char[].class) return (in, args) -> 
Arrays.toString((char[]) in);
-                       return (in, args) -> Arrays.deepToString((Object[]) in);
+                       if (inType == int[].class) return (in, memberOf, args) 
-> Arrays.toString((int[]) in);
+                       if (inType == long[].class) return (in, memberOf, args) 
-> Arrays.toString((long[]) in);
+                       if (inType == double[].class) return (in, memberOf, 
args) -> Arrays.toString((double[]) in);
+                       if (inType == float[].class) return (in, memberOf, 
args) -> Arrays.toString((float[]) in);
+                       if (inType == boolean[].class) return (in, memberOf, 
args) -> Arrays.toString((boolean[]) in);
+                       if (inType == byte[].class) return (in, memberOf, args) 
-> Arrays.toString((byte[]) in);
+                       if (inType == short[].class) return (in, memberOf, 
args) -> Arrays.toString((short[]) in);
+                       if (inType == char[].class) return (in, memberOf, args) 
-> Arrays.toString((char[]) in);
+                       return (in, memberOf, args) -> 
Arrays.deepToString((Object[]) in);
                }
-               return (in, args) -> in.toString();
+               return (in, memberOf, args) -> in.toString();
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -301,7 +301,7 @@ public class BasicConverter extends CachingConverter {
 
        private <I, O> Conversion<I, O> findEnumConversion(Class<I> inType, 
Class<O> outType) {
                if (CharSequence.class.isAssignableFrom(inType))
-                       return (in, args) -> (O) Enum.valueOf((Class<Enum>) 
outType, in.toString());
+                       return (in, memberOf, args) -> (O) 
Enum.valueOf((Class<Enum>) outType, in.toString());
                return null;
        }
 
@@ -311,7 +311,7 @@ public class BasicConverter extends CachingConverter {
 
        private <I, O> Conversion<I, O> findCollectionConversion(Class<I> 
inType, Class<O> outType) {
                if (Collection.class.isAssignableFrom(inType) || 
inType.isArray()) {
-                       return (in, args) -> {
+                       return (in, memberOf, args) -> {
                                var elemType = args.length > 0 ? args[0] : null;
                                if (elemType == null && !inType.isArray() && 
outType.isAssignableFrom(inType))
                                        return (O) in;
@@ -350,7 +350,7 @@ public class BasicConverter extends CachingConverter {
 
        private <I, O> Conversion<I, O> findMapConversion(Class<I> inType, 
Class<O> outType) {
                if (Map.class.isAssignableFrom(inType)) {
-                       return (in, args) -> {
+                       return (in, memberOf, args) -> {
                                var keyType = args.length > 0 ? args[0] : null;
                                var valType = args.length > 1 ? args[1] : null;
                                if (keyType == null && 
outType.isAssignableFrom(inType))
@@ -383,7 +383,7 @@ public class BasicConverter extends CachingConverter {
        private <I, O> Conversion<I, O> findArrayConversion(Class<I> inType, 
Class<O> outType) {
                if (Collection.class.isAssignableFrom(inType) || 
inType.isArray()) {
                        var componentType = outType.getComponentType();
-                       return (in, args) -> {
+                       return (in, memberOf, args) -> {
                                if (Collection.class.isAssignableFrom(inType)) {
                                        var list = (Collection<?>) in;
                                        var arr = 
Array.newInstance(componentType, list.size());
@@ -408,13 +408,13 @@ public class BasicConverter extends CachingConverter {
 
        private <I, O> Conversion<I, O> findSpecialConversion(Class<I> inType, 
Class<O> outType) {
                if (inType == String.class && outType == TimeZone.class)
-                       return (Conversion<I, O>) (Conversion<String, 
TimeZone>) (in, args) -> TimeZone.getTimeZone(in);
+                       return (Conversion<I, O>) (Conversion<String, 
TimeZone>) (in, memberOf, args) -> TimeZone.getTimeZone(in);
                if (TimeZone.class.isAssignableFrom(inType) && outType == 
String.class)
-                       return (Conversion<I, O>) (Conversion<TimeZone, 
String>) (in, args) -> in.getID();
+                       return (Conversion<I, O>) (Conversion<TimeZone, 
String>) (in, memberOf, args) -> in.getID();
                if (inType == String.class && outType == Locale.class)
-                       return (Conversion<I, O>) (Conversion<String, Locale>) 
(in, args) -> Locale.forLanguageTag(in.replace('_', '-'));
+                       return (Conversion<I, O>) (Conversion<String, Locale>) 
(in, memberOf, args) -> Locale.forLanguageTag(in.replace('_', '-'));
                if (CharSequence.class.isAssignableFrom(inType) && outType == 
Boolean.class)
-                       return (in, args) -> {
+                       return (in, memberOf, args) -> {
                                var s = in.toString();
                                if (s.isEmpty() || "null".equals(s))
                                        return null;
@@ -433,14 +433,14 @@ public class BasicConverter extends CachingConverter {
                for (var name : FACTORY_METHOD_NAMES) {
                        var opt = findStaticMethod(ci, name, inType, outType);
                        if (opt.isPresent())
-                               return (in, args) -> opt.get().invoke(null, in);
+                               return (in, memberOf, args) -> 
opt.get().invoke(null, in);
                }
 
                var inName = inType.getSimpleName();
                for (var prefix : new String[]{"from", "for", "parse"}) {
                        var opt = findStaticMethod(ci, prefix + inName, inType, 
outType);
                        if (opt.isPresent())
-                               return (in, args) -> opt.get().invoke(null, in);
+                               return (in, memberOf, args) -> 
opt.get().invoke(null, in);
                }
 
                return null;
@@ -462,6 +462,20 @@ public class BasicConverter extends CachingConverter {
        
//-----------------------------------------------------------------------------------------------------------------
 
        private <I, O> Conversion<I, O> findConstructorConversion(Class<I> 
inType, Class<O> outType) {
+               if (outType.getEnclosingClass() != null && 
!Modifier.isStatic(outType.getModifiers())) {
+                       var enclosing = outType.getEnclosingClass();
+                       var opt = info(outType).getPublicConstructor(c ->
+                               c.getParameterCount() == 2
+                               && c.isNotDeprecated()
+                               && c.getParameterTypes().get(0).inner() == 
enclosing
+                               && 
c.getParameterTypes().get(1).isAssignableFrom(inType)
+                       );
+                       if (opt.isPresent()) {
+                               var ctor = opt.get();
+                               return (in, memberOf, args) -> 
ctor.newInstance(memberOf, in);
+                       }
+               }
+
                var opt = info(outType).getPublicConstructor(c ->
                        c.getParameterCount() == 1
                        && c.isNotDeprecated()
@@ -469,7 +483,7 @@ public class BasicConverter extends CachingConverter {
                );
                if (opt.isPresent()) {
                        var ctor = opt.get();
-                       return (in, args) -> ctor.newInstance(in);
+                       return (in, memberOf, args) -> ctor.newInstance(in);
                }
                return null;
        }
@@ -489,7 +503,7 @@ public class BasicConverter extends CachingConverter {
                );
                if (opt.isPresent()) {
                        var method = opt.get();
-                       return (in, args) -> method.invoke(in);
+                       return (in, memberOf, args) -> method.invoke(in);
                }
                return null;
        }
@@ -504,7 +518,7 @@ public class BasicConverter extends CachingConverter {
                        var ctor = opt.get();
                try {
                        return ctor.newInstance();
-               } catch (@SuppressWarnings("unused") Exception e) {
+               } catch (@SuppressWarnings("unused") Exception e) { // HTT: 
constructor would have to throw to reach here
                        return defaultSupplier.get(); // HTT
                }
                }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/CachingConverter.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/CachingConverter.java
index 6f9a94ee38..cdd37efa1c 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/CachingConverter.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/CachingConverter.java
@@ -39,7 +39,7 @@ import java.util.stream.*;
  *             <ja>@Override</ja>
  *             <jk>protected</jk> &lt;I,O&gt; Conversion&lt;I,O&gt; 
findConversion(Class&lt;I&gt; inType, Class&lt;O&gt; outType) {
  *                     <jk>if</jk> (inType == String.<jk>class</jk> &amp;&amp; 
outType == Integer.<jk>class</jk>)
- *                             <jk>return</jk> (Conversion&lt;I,O&gt;) 
(Conversion&lt;String,Integer&gt;) (<jv>s</jv>, <jv>args</jv>) -&gt; 
Integer.<jsm>valueOf</jsm>(<jv>s</jv>);
+ *                             <jk>return</jk> (Conversion&lt;I,O&gt;) 
(Conversion&lt;String,Integer&gt;) (<jv>s</jv>, <jv>memberOf</jv>, 
<jv>args</jv>) -&gt; Integer.<jsm>valueOf</jsm>(<jv>s</jv>);
  *                     <jk>return null</jk>;
  *             }
  *     }
@@ -55,7 +55,7 @@ public abstract class CachingConverter implements Converter {
        // Sentinel stored in the cache when findConversion() returns null.
        // ConcurrentHashMap does not permit null values, so we cannot store 
null directly.
        // Using this sentinel avoids re-invoking findConversion() for 
unconvertable type pairs.
-       private static final Conversion<?,?> NO_CONVERSION = (in, args) -> null;
+       private static final Conversion<?,?> NO_CONVERSION = (in, memberOf, 
args) -> null; // HTT: sentinel body is never invoked; ConcurrentHashMap 
prohibits null values so we use this placeholder
 
        // Two-level cache: input type -> output type -> conversion function 
(or NO_CONVERSION sentinel).
        private final Map<Class<?>, Map<Class<?>, Conversion<?,?>>> conversions 
= new ConcurrentHashMap<>();
@@ -124,7 +124,7 @@ public abstract class CachingConverter implements Converter 
{
                var fn = (Conversion<Object, T>) lookupConversion(inType, type);
                if (fn == null)
                        throw new InvalidConversionException(inType, type);
-               return fn.to(o);
+               return fn.to(o, null);
        }
 
        /**
@@ -160,6 +160,59 @@ public abstract class CachingConverter implements 
Converter {
                var fn = (Conversion<Object, T>) lookupConversion(inType, 
rawType);
                if (fn == null)
                        throw new InvalidConversionException(inType, rawType);
-               return fn.to(o, argClasses);
+               return fn.to(o, null, argClasses);
+       }
+
+       /**
+        * Converts the specified object to the specified type, passing the 
outer instance for
+        * non-static inner class construction.
+        *
+        * @param o The object to convert. Can be <jk>null</jk>.
+        * @param memberOf The outer instance for non-static inner class 
construction, or <jk>null</jk>.
+        * @param type The target type.
+        * @param <T> The target type.
+        * @return The converted object, or <jk>null</jk> if the input is 
<jk>null</jk>.
+        * @throws InvalidConversionException If no conversion path exists from 
the input type to the target type.
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public <T> T to(Object o, Object memberOf, Class<T> type) {
+               if (o == null)
+                       return null;
+               var inType = o.getClass();
+               if (inType == type)
+                       return (T) o;
+               var fn = (Conversion<Object, T>) lookupConversion(inType, type);
+               if (fn == null)
+                       throw new InvalidConversionException(inType, type);
+               return fn.to(o, memberOf);
+       }
+
+       /**
+        * Converts the specified object to the specified parameterized type, 
passing the outer instance for
+        * non-static inner class construction.
+        *
+        * @param o The object to convert. Can be <jk>null</jk>.
+        * @param memberOf The outer instance for non-static inner class 
construction, or <jk>null</jk>.
+        * @param mainType The target type. May be a {@link Class} or {@link 
ParameterizedType}.
+        * @param args The type arguments of the target type (e.g. element type 
for collections).
+        * @param <T> The target type.
+        * @return The converted object, or <jk>null</jk> if the input is 
<jk>null</jk>.
+        * @throws InvalidConversionException If no conversion path exists from 
the input type to the target type.
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public <T> T to(Object o, Object memberOf, Type mainType, Type... args) 
{
+               if (o == null)
+                       return null;
+               var rawType = (Class<T>) (mainType instanceof ParameterizedType 
pt ? pt.getRawType() : (Class<?>) mainType);
+               var argClasses = Stream.of(args)
+                       .map(t -> (Class<?>) (t instanceof ParameterizedType 
pt2 ? pt2.getRawType() : t))
+                       .toArray(Class[]::new);
+               var inType = o.getClass();
+               var fn = (Conversion<Object, T>) lookupConversion(inType, 
rawType);
+               if (fn == null)
+                       throw new InvalidConversionException(inType, rawType);
+               return fn.to(o, memberOf, argClasses);
        }
 }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Conversion.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Conversion.java
index f758fd8e0b..b1480aff73 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Conversion.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Conversion.java
@@ -40,8 +40,9 @@ public interface Conversion<I, O> {
         * Converts the input object to the output type.
         *
         * @param in The input object.
+        * @param memberOf The outer instance for non-static inner class 
construction, or <jk>null</jk>.
         * @param args Optional type arguments for parameterized output types 
(e.g. element type for collections).
         * @return The converted object.
         */
-       O to(I in, Class<?>... args);
+       O to(I in, Object memberOf, Class<?>... args);
 }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Converter.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Converter.java
index f67b158512..7db0a003f2 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Converter.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Converter.java
@@ -54,6 +54,25 @@ public interface Converter {
         */
        <T> T to(Object o, Class<T> type);
 
+       /**
+        * Converts the specified object to the specified type, using the given 
outer instance for
+        * non-static inner class construction.
+        *
+        * <p>
+        * Returns <jk>null</jk> only when the input object is <jk>null</jk>.
+        * Throws {@link InvalidConversionException} when no conversion path 
exists.
+        *
+        * @param o The object to convert.
+        * @param memberOf The outer instance for non-static inner class 
construction, or <jk>null</jk>.
+        * @param type The type to convert to.
+        * @param <T> The type to convert to.
+        * @return The converted object, or <jk>null</jk> if the input is 
<jk>null</jk>.
+        * @throws InvalidConversionException If no conversion path exists from 
the input type to the target type.
+        */
+       default <T> T to(Object o, Object memberOf, Class<T> type) {
+               return to(o, type);
+       }
+
        /**
         * Converts the specified object to the specified parameterized type.
         *
@@ -82,4 +101,24 @@ public interface Converter {
         * @throws InvalidConversionException If no conversion path exists from 
the input type to the target type.
         */
        <T> T to(Object o, Type mainType, Type...args);
+
+       /**
+        * Converts the specified object to the specified parameterized type, 
using the given outer instance for
+        * non-static inner class construction.
+        *
+        * <p>
+        * Returns <jk>null</jk> only when the input object is <jk>null</jk>.
+        * Throws {@link InvalidConversionException} when no conversion path 
exists.
+        *
+        * @param o The object to convert.
+        * @param memberOf The outer instance for non-static inner class 
construction, or <jk>null</jk>.
+        * @param mainType The main type to convert to.
+        * @param args The type parameters of the main type.
+        * @param <T> The type to convert to.
+        * @return The converted object, or <jk>null</jk> if the input is 
<jk>null</jk>.
+        * @throws InvalidConversionException If no conversion path exists from 
the input type to the target type.
+        */
+       default <T> T to(Object o, Object memberOf, Type mainType, Type...args) 
{
+               return to(o, mainType, args);
+       }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/BeanContextConverter.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/BeanContextConverter.java
index c6c5385dcb..ea70f714af 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/BeanContextConverter.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/BeanContextConverter.java
@@ -119,4 +119,39 @@ public class BeanContextConverter implements Converter {
        public <T> T to(Object o, Type mainType, Type... args) {
                return BeanContext.DEFAULT_SESSION.convertToType(o, mainType, 
args);
        }
+
+       /**
+        * Converts the specified object to the specified type, using the given 
outer instance for
+        * non-static inner class construction.
+        *
+        * @param o The object to convert.
+        * @param memberOf The outer instance for non-static inner class 
construction, or <jk>null</jk>.
+        * @param type The target class type.
+        * @param <T> The target type to convert to.
+        * @return The converted object, or <jk>null</jk> if the input object 
is <jk>null</jk>.
+        * @throws InvalidDataConversionException If the object cannot be 
converted to the specified type.
+        */
+       @Override
+       public <T> T to(Object o, Object memberOf, Class<T> type) {
+               return 
BeanContext.DEFAULT_SESSION.convertToMemberType(memberOf, o, type);
+       }
+
+       /**
+        * Converts the specified object to the specified parameterized type, 
using the given outer instance for
+        * non-static inner class construction.
+        *
+        * @param o The object to convert.
+        * @param memberOf The outer instance for non-static inner class 
construction, or <jk>null</jk>.
+        * @param mainType The main type to convert to.
+        * @param args The type parameters of the main type.
+        * @param <T> The target type to convert to.
+        * @return The converted object, or <jk>null</jk> if the input object 
is <jk>null</jk>.
+        * @throws InvalidDataConversionException If the object cannot be 
converted to the specified type.
+        */
+       @SuppressWarnings("unchecked")
+       @Override
+       public <T> T to(Object o, Object memberOf, Type mainType, Type... args) 
{
+               var rawType = (Class<T>) (mainType instanceof ParameterizedType 
pt ? pt.getRawType() : (Class<?>) mainType);
+               return 
BeanContext.DEFAULT_SESSION.convertToMemberType(memberOf, o, rawType);
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/BasicConverter_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/BasicConverter_Test.java
index be46a4a37c..cdf2875c02 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/BasicConverter_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/BasicConverter_Test.java
@@ -18,6 +18,7 @@ package org.apache.juneau.commons.conversion;
 
 import static org.junit.jupiter.api.Assertions.*;
 
+import java.lang.reflect.*;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.*;
@@ -195,7 +196,7 @@ class BasicConverter_Test extends TestBase {
        }
 
        @Test void f02_listToListTyped() {
-               var a = C.to(List.of("1", "2", "3"), List.class, Integer.class);
+               var a = C.to(List.of("1", "2", "3"), (Type) List.class, new 
Type[]{Integer.class});
                assertNotNull(a);
                assertEquals(3, ((List<?>) a).size());
                assertEquals(1, ((List<?>) a).get(0));
@@ -211,7 +212,7 @@ class BasicConverter_Test extends TestBase {
 
        @Test void f03b_arrayToTypedList() {
                // array branch with elemType != null — covers line 326 true 
branch of ternary
-               var a = C.to(new String[]{"1", "2", "3"}, List.class, 
Integer.class);
+               var a = C.to(new String[]{"1", "2", "3"}, (Type) List.class, 
new Type[]{Integer.class});
                assertNotNull(a);
                assertEquals(List.of(1, 2, 3), a);
        }
@@ -318,7 +319,7 @@ class BasicConverter_Test extends TestBase {
        }
 
        @Test void g02_mapToMapTyped() {
-               var a = C.to(Map.of(1, "100", 2, "200"), Map.class, 
String.class, Integer.class);
+               var a = C.to(Map.of(1, "100", 2, "200"), (Type) Map.class, new 
Type[]{String.class, Integer.class});
                assertNotNull(a);
                var map = (Map<?, ?>) a;
                assertTrue(map.containsKey("1") || map.containsKey("2"));
@@ -369,7 +370,7 @@ class BasicConverter_Test extends TestBase {
 
        @Test void g08_mapToAbstractMap() {
                // Hits line 370 C=true branch (AbstractMap.class); use typed 
args to bypass short-circuit
-               var a = (Map<?, ?>) C.to(Map.of("a", 1), AbstractMap.class, 
String.class, Integer.class);
+               var a = (Map<?, ?>) C.to(Map.of("a", 1), (Type) 
AbstractMap.class, new Type[]{String.class, Integer.class});
                assertNotNull(a);
                assertInstanceOf(AbstractMap.class, a);
                assertEquals(1, a.size());
@@ -762,4 +763,46 @@ class BasicConverter_Test extends TestBase {
        @Test void o01_noConversion() {
                assertThrows(InvalidConversionException.class, () -> C.to(new 
Object(), BasicConverter_Test.class));
        }
+
+       
//====================================================================================================
+       // p - Non-static inner class constructor (memberOf path)
+       
//====================================================================================================
+
+       // Non-static inner class with a String constructor; the JVM 
synthesizes Inner(BasicConverter_Test, String).
+       public class P01_Inner {
+               public final String value;
+               public P01_Inner(String s) { this.value = s; }
+       }
+
+       // Non-static inner class whose only constructor takes a different 
input type (no match for String).
+       public class P02_InnerNoMatch {
+               public final int value;
+               public P02_InnerNoMatch(int n) { this.value = n; }
+       }
+
+       @Test void p01_innerClassConstructorWithMemberOf() {
+               // to(o, memberOf, Class) routes through the inner-class path 
in findConstructorConversion;
+               // memberOf (this) is passed as the synthetic outer-instance 
parameter.
+               var x = C.to("hello", this, P01_Inner.class);
+               assertEquals("hello", x.value);
+       }
+
+       @Test void p02_innerClassNoCtorMatchFallsThrough() {
+               // The inner-class branch is entered (P02 has an enclosing 
class) but the 2-param ctor
+               // (BasicConverter_Test, String) does not exist — falls through 
to the 1-param check which also
+               // fails → InvalidConversionException.
+               assertThrows(InvalidConversionException.class, () -> 
C.to("hello", this, P02_InnerNoMatch.class));
+       }
+
+       
//====================================================================================================
+       // q - newCollection AbstractList branch
+       
//====================================================================================================
+
+       @Test void q01_collectionToAbstractList() {
+               // Hits the AbstractList.class branch in newCollection, which 
returns a new ArrayList.
+               var result = C.to(List.of("a", "b"), (Type) AbstractList.class);
+               assertNotNull(result);
+               assertInstanceOf(List.class, result);
+               assertEquals(2, ((List<?>) result).size());
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/CachingConverter_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/CachingConverter_Test.java
index 91caefca46..7501573dac 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/CachingConverter_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/CachingConverter_Test.java
@@ -105,4 +105,76 @@ class CachingConverter_Test extends TestBase {
                assertNotNull(result);
                assertInstanceOf(List.class, result);
        }
+
+       
//====================================================================================================
+       // c - to(Object, Object, Class)
+       
//====================================================================================================
+
+       @Test void c01_toMemberOfNullInput() {
+               assertNull(C.to(null, "ignored", String.class));
+       }
+
+       @Test void c02_toMemberOfSameType() {
+               var s = "hello";
+               assertSame(s, C.to(s, null, String.class));
+       }
+
+       @Test void c03_toMemberOfConversion() {
+               assertEquals("42", C.to(42, null, String.class));
+       }
+
+       @Test void c04_toMemberOfNoConversion() {
+               assertThrows(InvalidConversionException.class, () -> C.to(new 
StringBuilder("x"), null, java.net.URI.class));
+       }
+
+       
//====================================================================================================
+       // d - to(Object, Object, Type, Type...)
+       
//====================================================================================================
+
+       // Sentinel used as the memberOf argument to avoid null-vs-Type 
overload ambiguity.
+       private static final Object D_MEMBER_OF = new Object();
+
+       @Test void d01_toMemberOfTypeNullInput() {
+               assertNull(C.to(null, D_MEMBER_OF, (Type) String.class));
+       }
+
+       @Test void d02_toMemberOfTypePlainClass() {
+               // mainType is a plain Class (false branch of ParameterizedType 
ternary)
+               assertEquals("42", C.to(42, D_MEMBER_OF, (Type) String.class));
+       }
+
+       @SuppressWarnings("unused")
+       private List<String> d03_listField;
+       private static final Type D03_LIST_TYPE;
+       static {
+               try {
+                       D03_LIST_TYPE = 
CachingConverter_Test.class.getDeclaredField("d03_listField").getGenericType();
+               } catch (NoSuchFieldException e) {
+                       throw new ExceptionInInitializerError(e);
+               }
+       }
+
+       @Test void d03_toMemberOfTypeWithParameterizedType() {
+               // mainType is a ParameterizedType (true branch of ternary); 
raw type List is extracted
+               var result = C.to(List.of("a", "b"), D_MEMBER_OF, 
D03_LIST_TYPE);
+               assertNotNull(result);
+               assertInstanceOf(List.class, result);
+       }
+
+       @Test void d04_toMemberOfTypeWithPlainClassArg() {
+               // args contains a plain Class (false branch of arg ternary)
+               var result = C.to(List.of("1", "2"), D_MEMBER_OF, (Type) 
List.class, (Type) Integer.class);
+               assertEquals(List.of(1, 2), result);
+       }
+
+       @Test void d05_toMemberOfTypeWithParameterizedTypeArg() {
+               // args contains a ParameterizedType (true branch of arg 
ternary); raw type List is extracted
+               var result = C.to(List.of(List.of("a", "b")), D_MEMBER_OF, 
(Type) List.class, LIST_ARG_TYPE);
+               assertNotNull(result);
+               assertInstanceOf(List.class, result);
+       }
+
+       @Test void d06_toMemberOfTypeNoConversion() {
+               assertThrows(InvalidConversionException.class, () -> C.to(new 
StringBuilder("x"), D_MEMBER_OF, (Type) java.net.URI.class));
+       }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/Converter_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/Converter_Test.java
index a592d83fc5..ddb072a68e 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/Converter_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/Converter_Test.java
@@ -19,6 +19,7 @@ package org.apache.juneau.commons.conversion;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.lang.reflect.*;
+import java.util.*;
 
 import org.apache.juneau.*;
 import org.junit.jupiter.api.*;
@@ -38,4 +39,26 @@ class Converter_Test extends TestBase {
                assertTrue(STUB.canConvert(String.class, String.class));
                assertTrue(STUB.canConvert(String.class, Integer.class));
        }
+
+       
//====================================================================================================
+       // b - default memberOf methods
+       
//====================================================================================================
+
+       // Minimal converter that uses BasicConverter internally but does NOT 
override the memberOf overloads,
+       // so the default methods on the Converter interface are exercised 
directly.
+       private static final Converter B_STUB = new Converter() {
+               @Override public <T> T to(Object o, Class<T> type) { return 
BasicConverter.INSTANCE.to(o, type); }
+               @Override public <T> T to(Object o, Type mainType, Type... 
args) { return BasicConverter.INSTANCE.to(o, mainType, args); }
+       };
+
+       @Test void b01_defaultToMemberOfClass() {
+               // default to(o, memberOf, Class) ignores memberOf and 
delegates to to(o, Class)
+               assertEquals(Integer.valueOf(42), B_STUB.to("42", new Object(), 
Integer.class));
+       }
+
+       @Test void b02_defaultToMemberOfType() {
+               // default to(o, memberOf, Type, Type...) ignores memberOf and 
delegates to to(o, Type, Type...)
+               var result = B_STUB.to(List.of("1", "2"), new Object(), (Type) 
List.class, new Type[]{Integer.class});
+               assertEquals(List.of(1, 2), result);
+       }
 }


Reply via email to