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 af63064744 Replace Mutaters with BasicConverter
af63064744 is described below
commit af6306474452f039270ed49e106529b15047c37f
Author: James Bognar <[email protected]>
AuthorDate: Fri Apr 3 17:57:44 2026 -0400
Replace Mutaters with BasicConverter
---
AGENTS.md | 26 +-
.../juneau/commons/conversion/BasicConverter.java | 68 ++--
.../commons/conversion/CachingConverter.java | 4 +-
.../commons/conversion/ConfigurableConverter.java | 4 +-
.../commons/conversion/ConversionFinder.java | 5 +-
.../juneau/commons/conversion/Converter.java | 6 +-
.../commons/conversion/ConverterSession.java | 16 +-
.../apache/juneau/commons/reflect/ClassInfo.java | 5 +-
.../main/java/org/apache/juneau/BeanContext.java | 440 ++++++++++++++++++++-
.../main/java/org/apache/juneau/BeanSession.java | 354 +----------------
.../org/apache/juneau/cbor/CborOutputStream.java | 16 +-
.../java/org/apache/juneau/swap/ObjectSwap.java | 2 +-
.../apache/juneau/toml/TomlSerializerSession.java | 12 +
.../java/org/apache/juneau/utils/Iso8601Utils.java | 4 +
.../juneau/utils/StringExpressionMatcher.java | 4 +
.../org/apache/juneau/xml/XmlBeanPropertyMeta.java | 4 +-
.../org/apache/juneau/yaml/YamlParserSession.java | 7 +-
17 files changed, 546 insertions(+), 431 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 373a11d0eb..bd9cf064f4 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -41,7 +41,7 @@ This document outlines the rules, guidelines, and best
practices that AI assista
- **"start jetty"** - Runs `scripts/start-examples-rest-jetty.py`
- **"start springboot"** - Runs `scripts/start-examples-springboot.py`
- **"push"** or **"push changes"** - When the user asks to push changes, use
`./scripts/push.py "Commit message"` to push changes. **Always prompt the user
for the commit message** and provide 2-3 suggested commit message options based
on the changes made. Example: `./scripts/push.py "Replace
Stream.collect(Collectors.toList()) with Stream.toList()"`
-- **"test"** - Runs `scripts/build-and-test.py`
+- **"test"** - Runs `scripts/test.py`
### Documentation Commands
- **"save a rule"** or **"save this rule"** - Add the rule/information to
`AGENTS.md` (permanent/general)
@@ -362,32 +362,32 @@ If you can't find Java on the file system using standard
commands, look for it i
### 5. Helper Scripts
**Build and Test Script:**
-A reusable Python script is available at `scripts/build-and-test.py` for
common Maven operations.
+A reusable Python script is available at `scripts/test.py` for common Maven
operations.
**Usage:**
```bash
# Default: Clean build + run tests
-./scripts/build-and-test.py
+./scripts/test.py
# Build only (skip tests)
-./scripts/build-and-test.py --build-only
-./scripts/build-and-test.py -b
+./scripts/test.py --build-only
+./scripts/test.py -b
# Test only (no build)
-./scripts/build-and-test.py --test-only
-./scripts/build-and-test.py -t
+./scripts/test.py --test-only
+./scripts/test.py -t
# Full build and test (explicit)
-./scripts/build-and-test.py --full
-./scripts/build-and-test.py -f
+./scripts/test.py --full
+./scripts/test.py -f
# Verbose output (show full Maven output)
-./scripts/build-and-test.py --verbose
-./scripts/build-and-test.py -v
+./scripts/test.py --verbose
+./scripts/test.py -v
# Help
-./scripts/build-and-test.py --help
-./scripts/build-and-test.py -h
+./scripts/test.py --help
+./scripts/test.py -h
```
**What it does:**
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 ad948e9c9a..f6dcd26ed2 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
@@ -46,19 +46,21 @@ import org.apache.juneau.commons.reflect.*;
* <td>{@link Number}</td>
* <td>
* {@link Integer}, {@link Long}, {@link Short}, {@link
Float}, {@link Double}, {@link Byte},
- * {@link AtomicInteger}, {@link AtomicLong}, and
primitive equivalents
+ * {@link AtomicInteger}, {@link AtomicLong}, {@link
BigDecimal}, {@link BigInteger},
+ * and primitive equivalents
* </td>
* <td>Narrowing/widening numeric conversion</td>
* </tr>
* <tr>
* <td>{@link Boolean}</td>
- * <td>{@link Number} types</td>
+ * <td>{@link Number} types (including {@link BigDecimal}, {@link
BigInteger})</td>
* <td><c>true=1, false=0</c></td>
* </tr>
* <tr>
* <td>{@link CharSequence}</td>
* <td>{@link Number} types</td>
- * <td>Parsed via {@link
org.apache.juneau.commons.utils.StringUtils#parseNumber(String, Class)}</td>
+ * <td>Parsed via {@link
org.apache.juneau.commons.utils.StringUtils#parseNumber(String, Class)};
+ * empty string or {@code "null"} returns
<jk>null</jk></td>
* </tr>
* <tr>
* <td>{@link Number}</td>
@@ -78,7 +80,7 @@ import org.apache.juneau.commons.reflect.*;
* <tr>
* <td>{@link Number}</td>
* <td>{@link Character}</td>
- * <td><c>(char) intValue()</c></td>
+ * <td>First character of {@code toString()} (e.g. {@code 65} →
{@code '6'})</td>
* </tr>
* <tr>
* <td>Any</td>
@@ -87,6 +89,11 @@ import org.apache.juneau.commons.reflect.*;
* </tr>
* <tr>
* <td>{@link CharSequence}</td>
+ * <td>{@code byte[]}</td>
+ * <td>UTF-8 encoding via {@link
String#getBytes(java.nio.charset.Charset)}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link CharSequence}</td>
* <td>Any {@link Enum}</td>
* <td>{@link Enum#valueOf(Class, String)}</td>
* </tr>
@@ -146,7 +153,8 @@ import org.apache.juneau.commons.reflect.*;
"rawtypes", // Raw types necessary for generic conversion dispatch
"unchecked", // Type erasure requires unchecked casts throughout
conversion logic
"java:S3776", // Cognitive complexity of conversion dispatch methods is
inherent to the number of supported type pairs
- "java:S1067" // Complex boolean expressions in conversion checks
reflect the natural type hierarchy
+ "java:S1067", // Complex boolean expressions in conversion checks
reflect the natural type hierarchy
+ "java:S1192" // "parse" literal is used in multiple independent
Sets/arrays; extracting it would reduce clarity
})
public class BasicConverter extends CachingConverter {
@@ -197,7 +205,7 @@ public class BasicConverter extends CachingConverter {
if (out.isEnum() && (c = findEnumConversion(inType, out)) !=
null) return c;
- if (out == Optional.class && (c =
findOptionalConversion(inType)) != null) return c;
+ if (out == Optional.class && (c = findOptionalConversion()) !=
null) return c;
if (Collection.class.isAssignableFrom(out) && (c =
findCollectionConversion(inType, out)) != null) return c;
@@ -237,7 +245,7 @@ public class BasicConverter extends CachingConverter {
if (outType == Byte.class) return (in, memberOf, session, args)
-> (O) Byte.valueOf(((Number) in).byteValue());
if (outType == AtomicInteger.class) return (in, memberOf,
session, args) -> (O) new AtomicInteger(((Number) in).intValue());
if (outType == AtomicLong.class) return (in, memberOf, session,
args) -> (O) new AtomicLong(((Number) in).longValue());
- if (outType == BigDecimal.class) return (in, memberOf, session,
args) -> (O) new BigDecimal(((Number) in).toString());
+ if (outType == BigDecimal.class) return (in, memberOf, session,
args) -> (O) new BigDecimal(in.toString());
if (outType == BigInteger.class) return (in, memberOf, session,
args) -> {
var n = (Number) in;
if (n instanceof BigDecimal bd) return (O)
bd.toBigInteger();
@@ -262,7 +270,12 @@ public class BasicConverter extends CachingConverter {
}
private <O> Conversion<CharSequence, O> findNumberFromString(Class<O>
outType) {
- return (in, memberOf, session, args) -> (O)
parseNumber(in.toString(), (Class<? extends Number>) outType);
+ return (in, memberOf, session, args) -> {
+ var s = in.toString();
+ if (s.isEmpty() || "null".equals(s))
+ return null;
+ return (O) parseNumber(s, (Class<? extends Number>)
outType);
+ };
}
//-----------------------------------------------------------------------------------------------------------------
@@ -328,17 +341,16 @@ public class BasicConverter extends CachingConverter {
// Optional conversions
//-----------------------------------------------------------------------------------------------------------------
- @SuppressWarnings("unchecked")
- private <I, O> Conversion<I, O> findOptionalConversion(Class<I> inType)
{
- return (Conversion<I, O>) (Conversion<I, Optional<?>>) (in,
memberOf, session, args) -> {
+ private <I, O> Conversion<I, O> findOptionalConversion() {
+ return (Conversion<I, O>) (Conversion<Object, Optional<?>>)
(in, memberOf, session, args) -> {
if (in instanceof Optional<?> opt) {
if (args.length == 0)
return opt;
- return opt.map(x -> to(x, args[0]));
+ return opt.map(x -> to(x, memberOf, session,
args[0]));
}
if (args.length == 0)
return Optional.of(in);
- return Optional.of(to(in, args[0]));
+ return Optional.of(to(in, memberOf, session, args[0]));
};
}
@@ -349,7 +361,8 @@ 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, memberOf, session, args) -> {
- var elemType = args.length > 0 ? args[0] : null;
+ // Treat Object.class as "no element type"
since it means untyped/raw and needs no element conversion.
+ var elemType = args.length > 0 && args[0] !=
Object.class ? args[0] : null;
if (elemType == null && !inType.isArray() &&
outType.isAssignableFrom(inType))
return (O) in;
var result = newCollection(outType);
@@ -388,8 +401,9 @@ 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, memberOf, session, args) -> {
- var keyType = args.length > 0 ? args[0] : null;
- var valType = args.length > 1 ? args[1] : null;
+ // Treat Object.class as "no key/value type"
since it means untyped/raw and needs no element conversion.
+ var keyType = args.length > 0 && args[0] !=
Object.class ? args[0] : null;
+ var valType = args.length > 1 && args[1] !=
Object.class ? args[1] : null;
if (keyType == null &&
outType.isAssignableFrom(inType))
return (O) in;
var result = newMap(outType);
@@ -425,7 +439,10 @@ public class BasicConverter extends CachingConverter {
// Collections use runtime element types so we skip the
pre-check there too.
if (inType.isArray()) {
var inComponentType = inType.getComponentType();
- if (inComponentType != componentType
+ // Primitive component types are auto-boxed by
Array.get(), so skip the canConvert check.
+ // Otherwise verify that element-level
conversion is possible before returning a lambda.
+ if (!inComponentType.isPrimitive()
+ && inComponentType !=
componentType
&& inComponentType !=
Object.class
&& !canConvert(inComponentType,
componentType))
return null;
@@ -436,13 +453,13 @@ public class BasicConverter extends CachingConverter {
var arr =
Array.newInstance(componentType, list.size());
var i = 0;
for (var elem : list)
- Array.set(arr, i++, to(elem,
componentType));
+ Array.set(arr, i++, to(elem,
memberOf, session, componentType));
return (O) arr;
}
var len = Array.getLength(in);
var arr = Array.newInstance(componentType, len);
for (var i = 0; i < len; i++)
- Array.set(arr, i, to(Array.get(in, i),
componentType));
+ Array.set(arr, i, to(Array.get(in, i),
memberOf, session, componentType));
return (O) arr;
};
}
@@ -531,6 +548,9 @@ public class BasicConverter extends CachingConverter {
&& m.getParameterCount() == 1
&& m.getParameterTypes().get(0).isAssignableFrom(inType)
&& m.hasReturnTypeParent(outType)
+ // Exclude generic Object-param factory methods (e.g.
List.of(E)) when input is not Object,
+ // since type erasure makes E appear as Object at
runtime, causing false matches.
+ && (inType == Object.class ||
m.getParameterTypes().get(0).inner() != Object.class)
);
}
@@ -595,11 +615,11 @@ public class BasicConverter extends CachingConverter {
var opt = info(type).getPublicConstructor(c ->
c.getParameterCount() == 0);
if (opt.isPresent()) {
var ctor = opt.get();
- try {
- return ctor.newInstance();
- } catch (@SuppressWarnings("unused") Exception e) { // HTT:
constructor would have to throw to reach here
- return defaultSupplier.get(); // HTT
- }
+ try {
+ return ctor.newInstance();
+ } catch (@SuppressWarnings("unused") Exception e) { //
HTT: constructor would have to throw to reach here
+ return defaultSupplier.get(); // HTT
+ }
}
return defaultSupplier.get();
}
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 1b59c4ca11..130652181a 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> <I,O> Conversion<I,O>
findConversion(Class<I> inType, Class<O> outType) {
* <jk>if</jk> (inType == String.<jk>class</jk> &&
outType == Integer.<jk>class</jk>)
- * <jk>return</jk> (Conversion<I,O>)
(Conversion<String,Integer>) (<jv>s</jv>, <jv>memberOf</jv>,
<jv>args</jv>) -> Integer.<jsm>valueOf</jsm>(<jv>s</jv>);
+ * <jk>return</jk> (Conversion<I,O>)
(Conversion<String,Integer>) (<jv>s</jv>, <jv>memberOf</jv>,
<jv>session</jv>, <jv>args</jv>) -> Integer.<jsm>valueOf</jsm>(<jv>s</jv>);
* <jk>return null</jk>;
* }
* }
@@ -233,7 +233,7 @@ public abstract class CachingConverter implements Converter
{
@SuppressWarnings("unchecked")
public <T> T to(Object o, Object memberOf, ConverterSession session,
Class<T> type) {
if (o == null)
- return type.isPrimitive() ? primitiveDefault(type) :
null;
+ return nullDefault(type);
var inType = o.getClass();
if (inType == type)
return (T) o;
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConfigurableConverter.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConfigurableConverter.java
index 152bf0ab6d..8b7a4b52e1 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConfigurableConverter.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConfigurableConverter.java
@@ -68,7 +68,7 @@ public class ConfigurableConverter extends BasicConverter {
*/
public boolean hasCustomConversion(Class<?> inType, Class<?> outType) {
for (var finder : finders)
- if (finder.find(inType, outType) != null)
+ if (finder.findConversion(inType, outType) != null)
return true;
return false;
}
@@ -79,7 +79,7 @@ public class ConfigurableConverter extends BasicConverter {
})
protected <I, O> Conversion<I, O> findConversion(Class<I> inType,
Class<O> outType) {
for (var finder : finders) {
- var fn = (Conversion<I, O>) finder.find(inType,
outType);
+ var fn = (Conversion<I, O>)
finder.findConversion(inType, outType);
if (fn != null)
return fn;
}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConversionFinder.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConversionFinder.java
index 642d642c75..111367b16a 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConversionFinder.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConversionFinder.java
@@ -20,8 +20,7 @@ package org.apache.juneau.commons.conversion;
* A pluggable strategy for discovering {@link Conversion} functions between
type pairs.
*
* <p>
- * Implementations are registered on a {@link ConfigurableConverter} via
- * {@link ConfigurableConverter#addFinders(ConversionFinder...)} and are
consulted in order before
+ * Implementations are passed to the {@link ConfigurableConverter} constructor
and are consulted in order before
* the built-in {@link BasicConverter} reflection logic.
*
* <p>
@@ -55,5 +54,5 @@ public interface ConversionFinder {
* @param outType The output type class.
* @return A {@link Conversion} function, or <jk>null</jk> if no
conversion is available.
*/
- Conversion<?,?> find(Class<?> inType, Class<?> outType);
+ Conversion<?,?> findConversion(Class<?> inType, Class<?> outType);
}
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 0a0d958340..bb66211758 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
@@ -42,14 +42,16 @@ public interface Converter {
* Converts the specified object to the specified type.
*
* <p>
- * Returns <jk>null</jk> only when the input object is <jk>null</jk>.
+ * When the input is <jk>null</jk>, returns the JVM default for
primitive types (e.g. {@code 0} for
+ * {@code int.class}, {@code false} for {@code boolean.class}), or
<jk>null</jk> for all other types.
* Throws {@link InvalidConversionException} when no conversion path
exists.
* Use {@link #canConvert(Class, Class)} to pre-check if uncertain.
*
* @param o The object to convert.
* @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>.
+ * @return The converted object, the primitive zero-value if input is
<jk>null</jk> and type is primitive,
+ * or <jk>null</jk> if the input is <jk>null</jk> and type is
non-primitive.
* @throws InvalidConversionException If no conversion path exists from
the input type to the target type.
*/
<T> T to(Object o, Class<T> type);
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConverterSession.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConverterSession.java
index d9884ddd3a..1c303bffef 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConverterSession.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/ConverterSession.java
@@ -32,14 +32,14 @@ import java.util.*;
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
- * <jc>// A conversion that uses the session's TimeZone.</jc>
- * BeanContext <jv>bc</jv> = BeanContext
- * .<jsm>create</jsm>()
- * .addConverter(String.<jk>class</jk>, Calendar.<jk>class</jk>,
(<jv>in</jv>, <jv>memberOf</jv>, <jv>session</jv>, <jv>args</jv>) -> {
- * var <jv>tz</jv> =
<jv>session</jv>.get(TimeZone.<jk>class</jk>).orElse(TimeZone.<jsm>getDefault</jsm>());
- * <jc>// ... parse string using tz ...</jc>
- * })
- * .build();
+ * <jc>// A Conversion lambda that reads TimeZone from the session.</jc>
+ * Conversion<String, Calendar> <jv>conv</jv> = (<jv>in</jv>,
<jv>memberOf</jv>, <jv>session</jv>, <jv>args</jv>) -> {
+ * var <jv>tz</jv> = <jv>session</jv> != <jk>null</jk>
+ * ?
<jv>session</jv>.get(TimeZone.<jk>class</jk>).orElse(TimeZone.<jsm>getDefault</jsm>())
+ * : TimeZone.<jsm>getDefault</jsm>();
+ * <jc>// ... parse in using tz ...</jc>
+ * <jk>return null</jk>;
+ * };
* </p>
*
* <h5 class='section'>See Also:</h5><ul>
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ClassInfo.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ClassInfo.java
index 799bea8512..544db29f88 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ClassInfo.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/ClassInfo.java
@@ -83,9 +83,6 @@ public class ClassInfo extends ElementInfo implements
Annotatable, Type, Compara
private static final Cache<Class,ClassInfo> CACHE =
Cache.of(Class.class, ClassInfo.class).build();
- /** Reusable cached ClassInfo instance for Object.class. */
- public static final ClassInfo OBJECT = ClassInfo.of(Object.class);
-
private static final Map<Class<?>,Class<?>> pmap1 = new HashMap<>();
private static final Map<Class<?>,Class<?>> pmap2 = new HashMap<>();
@@ -239,7 +236,7 @@ public class ClassInfo extends ElementInfo implements
Annotatable, Type, Compara
assertArg(inner != null || innerType != null, "At least one of
inner or innerType must be specified.");
this.innerType = innerType;
this.inner = inner;
- this.isParameterizedType = innerType != null && (innerType
instanceof ParameterizedType);
+ this.isParameterizedType = innerType instanceof
ParameterizedType;
this.dimensions = memoize(this::findDimensions);
this.componentType = memoize(this::findComponentType);
this.packageInfo = memoize(() ->
opt(inner).map(Class::getPackage).filter(p -> p !=
null).map(PackageInfo::of).orElse(null)); // PackageInfo may be null for
primitive types and arrays.
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
index 022493917a..9b7a4f10aa 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
@@ -21,6 +21,7 @@ import static org.apache.juneau.commons.reflect.Visibility.*;
import static org.apache.juneau.commons.utils.AssertionUtils.*;
import static org.apache.juneau.commons.utils.ClassUtils.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.commons.utils.ThrowableUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
@@ -32,6 +33,7 @@ import java.util.*;
import java.util.stream.*;
import org.apache.juneau.annotation.*;
+import org.apache.juneau.collections.*;
import org.apache.juneau.commons.collections.*;
import org.apache.juneau.commons.conversion.*;
import org.apache.juneau.commons.function.*;
@@ -42,6 +44,7 @@ import org.apache.juneau.json5.*;
import org.apache.juneau.marshaller.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.swap.*;
+import org.apache.juneau.utils.*;
/**
* Bean context.
@@ -3817,38 +3820,56 @@ public class BeanContext extends Context implements
ConversionFinder {
public final ConfigurableConverter getConverter() { return converter; }
/**
- * Implements {@link ConversionFinder} by searching the registered
{@link ObjectSwap} list for a swap
- * that can convert between the given type pair.
+ * Implements {@link ConversionFinder} to provide all
BeanContext-specific type conversions that cannot
+ * be expressed in {@link BasicConverter} (which has no dependency on
{@code juneau-marshall}).
*
* <p>
- * For each registered swap:
- * <ul>
- * <li>If the swap class is assignable from {@code inType} and the
normal class is assignable from {@code outType},
- * returns a {@link Conversion} that calls {@link
ObjectSwap#unswap} using the session.
- * <li>If the normal class is assignable from {@code inType} and
the swap class is assignable from {@code outType},
- * returns a {@link Conversion} that calls {@link
ObjectSwap#swap} using the session.
- * </ul>
+ * Conversions are resolved in this priority order:
+ * <ol>
+ * <li>ObjectSwap-based conversions (highest priority)
+ * <li>Iso8601 date/time formatting and parsing
+ * <li>Epoch-millis to date/time
+ * <li>Map/Bean/Collection/Array to String via serializer
+ * <li>String/CharSequence to Array, Map, Collection via JSON
parsing
+ * <li>Map to Bean and String to Bean via BeanMap
+ * <li>Calendar/Date specialized copies
+ * <li>InputStream and Reader construction
+ * </ol>
*
* @param inType The input type class.
* @param outType The output type class.
- * @return A {@link Conversion} backed by a matching swap, or {@code
null} if no swap applies.
+ * @return A matching {@link Conversion}, or {@code null} if none
applies.
*/
@Override
@SuppressWarnings({
- "unchecked" // Type erasure requires unchecked casts for
ObjectSwap generic types
+ "java:S3776" // Cognitive complexity acceptable for
comprehensive conversion dispatch
})
- public Conversion<?,?> find(Class<?> inType, Class<?> outType) {
+ public Conversion<?,?> findConversion(Class<?> inType, Class<?>
outType) {
+ var toMeta = getClassMeta(outType);
+ var fromMeta = getClassMeta(inType);
+
+ // --- ObjectSwap conversions (must come first) ---
for (var swap : objectSwaps) {
var nc = swap.getNormalClass().inner();
var fc = swap.getSwapClass().inner();
- // Unswap: input is swap class, output is normal class
- if (nc.isAssignableFrom(outType) &&
fc.isAssignableFrom(inType)) {
- var to = getClassMeta(outType);
+ // Unswap: input is swap class (or convertible to it),
output is normal class
+ if (nc.isAssignableFrom(outType) &&
(fc.isAssignableFrom(inType) || Map.class.isAssignableFrom(inType) ||
Number.class.isAssignableFrom(inType))) {
return (in, memberOf, session, args) -> {
try {
- var bs = session instanceof
BeanSession bs2 ? bs2 : null;
- var resolvedSwap = bs != null ?
to.getSwap(bs) : null;
- return
((ObjectSwap<Object,Object>) (resolvedSwap != null ? resolvedSwap :
swap)).unswap(bs, in, to);
+ var bs = beanSession(session);
+ var resolvedSwap = bs != null ?
toMeta.getSwap(bs) : null;
+ var activeSwap =
(ObjectSwap<Object,Object>) (resolvedSwap != null ? resolvedSwap : swap);
+ var swapClass =
activeSwap.getSwapClass().inner();
+ // Direct match
+ if (swapClass.isInstance(in))
+ return
activeSwap.unswap(bs, in, toMeta);
+ // Intermediate Map conversion
+ if
(Map.class.isAssignableFrom(swapClass) && in instanceof Map)
+ return
activeSwap.unswap(bs, converter.to(in, memberOf, bs, swapClass), toMeta);
+ // Intermediate Number
conversion
+ if
(Number.class.isAssignableFrom(swapClass) && in instanceof Number)
+ return
activeSwap.unswap(bs, converter.to(in, memberOf, bs, swapClass), toMeta);
+ return null;
} catch (Exception e) {
throw rex(e);
}
@@ -3858,7 +3879,7 @@ public class BeanContext extends Context implements
ConversionFinder {
if (nc.isAssignableFrom(inType) &&
fc.isAssignableFrom(outType)) {
return (in, memberOf, session, args) -> {
try {
- var bs = session instanceof
BeanSession bs2 ? bs2 : null;
+ var bs = beanSession(session);
return
((ObjectSwap<Object,Object>) swap).swap(bs, in);
} catch (Exception e) {
throw rex(e);
@@ -3866,9 +3887,390 @@ public class BeanContext extends Context implements
ConversionFinder {
};
}
}
+
+ // --- Inline/auto-detected swaps from ClassMeta (e.g.
AutoNumberSwap, AutoObjectSwap) ---
+ // These are swaps found on the class itself (via swap()
methods, toXxx() patterns, etc.)
+ // and not registered in the objectSwaps list.
+ var toSwap = toMeta.getSwap(null);
+ if (toSwap != null) {
+ // Use boxed version of swap class to handle primitive
swap types (e.g. toInt() → int.class → Integer.class)
+ var fcBoxed =
info(toSwap.getSwapClass().inner()).getWrapperIfPrimitive().inner();
+ if (fcBoxed.isAssignableFrom(inType) ||
Map.class.isAssignableFrom(inType) || Number.class.isAssignableFrom(inType)) {
+ return (in, memberOf, session, args) -> {
+ try {
+ var bs = beanSession(session);
+ var activeSwap =
(ObjectSwap<Object,Object>) toMeta.getSwap(bs);
+ if (activeSwap == null) return
null;
+ // Use boxed swap class to
handle primitive return types (e.g. int.class → Integer.class)
+ var swapClass =
info(activeSwap.getSwapClass().inner()).getWrapperIfPrimitive().inner();
+ if (swapClass.isInstance(in))
+ return
activeSwap.unswap(bs, in, toMeta);
+ if
(Map.class.isAssignableFrom(swapClass) && in instanceof Map)
+ return
activeSwap.unswap(bs, converter.to(in, memberOf, bs, swapClass), toMeta);
+ if
(Number.class.isAssignableFrom(swapClass) && in instanceof Number)
+ return
activeSwap.unswap(bs, converter.to(in, memberOf, bs, swapClass), toMeta);
+ return null;
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+ }
+ var fromSwap = fromMeta.getSwap(null);
+ if (fromSwap != null) {
+ var fcBoxed =
info(fromSwap.getSwapClass().inner()).getWrapperIfPrimitive().inner();
+ if (fcBoxed.isAssignableFrom(outType) || outType ==
Object.class) {
+ return (in, memberOf, session, args) -> {
+ try {
+ var bs = beanSession(session);
+ var activeSwap =
(ObjectSwap<Object,Object>) fromMeta.getSwap(bs);
+ if (activeSwap == null) return
null;
+ return activeSwap.swap(bs, in);
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+ }
+
+ // --- Date/time → CharSequence via Iso8601Utils (Gap 5) ---
+ if (CharSequence.class.isAssignableFrom(outType) &&
(fromMeta.isDateOrCalendarOrTemporal() || fromMeta.isDuration()))
+ return (in, memberOf, session, args) ->
Iso8601Utils.format(in, fromMeta, sessionTimeZone(session));
+
+ // --- CharSequence → date/time via Iso8601Utils (Gap 6) ---
+ if (CharSequence.class.isAssignableFrom(inType) &&
(toMeta.isDateOrCalendarOrTemporal() || toMeta.isDuration()))
+ return (in, memberOf, session, args) ->
Iso8601Utils.parse(in.toString(), toMeta, sessionTimeZone(session));
+
+ // --- Number → date/time via epoch millis (Gap 7) ---
+ if (Number.class.isAssignableFrom(inType) &&
toMeta.isDateOrCalendarOrTemporal())
+ return (in, memberOf, session, args) ->
Iso8601Utils.fromEpochMillis(((Number)in).longValue(), toMeta,
sessionTimeZone(session));
+
+ // --- Map/Bean/Collection/Array/Optional → String via
serializer (Gap 14) ---
+ if (outType == String.class) {
+ // byte[] → String must come before
isCollectionOrArrayOrOptional since byte[] is an array type
+ if (byte[].class == inType)
+ return (in, memberOf, session, args) -> new
String((byte[]) in, java.nio.charset.StandardCharsets.UTF_8);
+ if (fromMeta.isMapOrBean() ||
fromMeta.isCollectionOrArrayOrOptional()) {
+ return (in, memberOf, session, args) -> {
+ try {
+ var ws =
getBeanToStringSerializer();
+ return ws != null ?
ws.serialize(in) : in.toString();
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+ if (Class.class == inType)
+ return (in, memberOf, session, args) ->
((Class<?>) in).getName();
+ }
+
+ // --- CharSequence → Array via JSON parsing (Gap 10) ---
+ // Excludes byte[] since that is handled by
BasicConverter.findSpecialConversion (getBytes(UTF_8)).
+ if (CharSequence.class.isAssignableFrom(inType) &&
outType.isArray() && outType != byte[].class) {
+ return (in, memberOf, session, args) -> {
+ try {
+ var bs = beanSessionOrDefault(session);
+ var str = in.toString();
+ JsonList list;
+ if (startsWith(str, '['))
+ list =
JsonList.ofJson(str).setBeanSession(bs);
+ else
+ list = new JsonList((Object[])
splita(str)).setBeanSession(bs);
+ return bs.convertToType(list, outType);
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+
+ // --- CharSequence → Map via JSON parsing (Gap 11) ---
+ if (CharSequence.class.isAssignableFrom(inType) &&
Map.class.isAssignableFrom(outType) &&
!toMeta.canCreateNewInstanceFromString(null)) {
+ return (in, memberOf, session, args) -> {
+ try {
+ var bs = beanSessionOrDefault(session);
+ var m = JsonMap.ofJson(in.toString());
+ m.setBeanSession(bs);
+ return bs.convertToType(m, outType);
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+
+ // --- Collection/Array → Collection with session-aware element
conversion ---
+ // When the target is an abstract collection type (List,
Collection), uses JsonList instead of ArrayList
+ // to preserve the session-aware default behavior from the
original convertToCollectionType.
+ if ((Collection.class.isAssignableFrom(inType) ||
inType.isArray()) && Collection.class.isAssignableFrom(outType)) {
+ return (in, memberOf, session, args) -> {
+ // Treat Object.class as "no element type"
(untyped/raw, needs no element conversion).
+ var elemType = args.length > 0 && args[0] !=
Object.class ? (Class<?>) args[0] : null;
+ if (elemType == null && !inType.isArray() &&
outType.isAssignableFrom(in.getClass()))
+ return in;
+ var bs = beanSessionOrDefault(session);
+ Collection<Object> result;
+ if (outType.isAssignableFrom(JsonList.class)) {
+ result = new JsonList(bs); // Use
JsonList for abstract types (List, Collection, Iterable)
+ } else {
+ result = newCollection(outType);
+ }
+ if (in instanceof Collection<?> col2) {
+ for (var elem : col2)
+ result.add(elemType != null ?
converter.to(elem, memberOf, session, elemType) : elem);
+ } else {
+ var len = Array.getLength(in);
+ for (var i = 0; i < len; i++) {
+ var elem = Array.get(in, i);
+ result.add(elemType != null ?
converter.to(elem, memberOf, session, elemType) : elem);
+ }
+ }
+ return result;
+ };
+ }
+
+ // --- Map → Collection (wraps map as single element,
preserving old convertToCollectionType behavior) ---
+ if (Map.class.isAssignableFrom(inType) &&
Collection.class.isAssignableFrom(outType)) {
+ return (in, memberOf, session, args) -> {
+ var bs = beanSessionOrDefault(session);
+ Collection<Object> result;
+ if (outType.isAssignableFrom(JsonList.class)) {
+ result = new JsonList(bs);
+ } else {
+ result = newCollection(outType);
+ }
+ var elemType = args.length > 0 && args[0] !=
Object.class ? (Class<?>) args[0] : null;
+ result.add(elemType != null ? converter.to(in,
memberOf, session, elemType) : in);
+ return result;
+ };
+ }
+
+ // --- CharSequence → Collection via JSON parsing (Gap 12) ---
+ if (CharSequence.class.isAssignableFrom(inType) &&
Collection.class.isAssignableFrom(outType)) {
+ return (in, memberOf, session, args) -> {
+ try {
+ var bs = beanSessionOrDefault(session);
+ var str = in.toString();
+ if (!isProbablyJsonArray(str, false))
+ throw rex("Cannot convert
string to {0}: {1}", outType.getName(), str);
+ var elemType = args.length > 0 ?
args[0] : null;
+ var l2 =
JsonList.ofJson(str).setBeanSession(bs);
+ var result = (Collection<Object>)
newCollection(outType);
+ l2.forEach(x -> result.add(elemType !=
null && x != null ? converter.to(x, elemType) : x));
+ return result;
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+
+ // --- Map → Bean via BeanMap/BuilderSwap/BeanRegistry (Gap 17)
---
+ if (Map.class.isAssignableFrom(inType) && toMeta.isBean()) {
+ return (in, memberOf, session, args) -> {
+ try {
+ var bs = beanSessionOrDefault(session);
+ var m2 = (Map<?,?>) in;
+ var builder =
(BuilderSwap<Object,Object>) toMeta.getBuilderSwap(bs);
+ if (m2 instanceof JsonMap jm && builder
== null) {
+ var typeName =
jm.getString(bs.getBeanTypePropertyName(toMeta));
+ if (nn(typeName)) {
+ var cm =
toMeta.getBeanRegistry().getClassMeta(typeName);
+ if (nn(cm) &&
toMeta.isAssignableFrom(cm.inner()))
+ return
jm.cast(cm);
+ }
+ }
+ if (nn(builder)) {
+ var created =
builder.create(bs, toMeta);
+ if (created != null) {
+ var bm =
bs.toBeanMap(created);
+ bm.load(m2);
+ return
builder.build(bs, bm.getBean(), toMeta);
+ }
+ }
+ return
bs.newBeanMap(toMeta.inner()).load(m2).getBean();
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+
+ // --- CharSequence → URL (URL(String) is deprecated in Java
20+, use URI.create().toURL() instead) ---
+ if (CharSequence.class.isAssignableFrom(inType) && outType ==
java.net.URL.class)
+ return (in, memberOf, session, args) -> {
+ try {
+ return new
java.net.URI(in.toString()).toURL();
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+
+ // --- CharSequence → Bean via BeanMap (Gap 21) ---
+ if (CharSequence.class.isAssignableFrom(inType) &&
toMeta.isBean()) {
+ return (in, memberOf, session, args) -> {
+ try {
+ var bs = beanSessionOrDefault(session);
+ return
bs.newBeanMap(toMeta.inner()).load(in.toString()).getBean();
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+
+ // --- Calendar/Date specialized conversions (Gap 19) ---
+ if (toMeta.isCalendar()) {
+ if (Calendar.class.isAssignableFrom(inType))
+ return (in, memberOf, session, args) -> {
+ var c = (Calendar) in;
+ var c2 = new
GregorianCalendar(c.getTimeZone());
+ c2.setTime(c.getTime());
+ return c2;
+ };
+ if (Date.class.isAssignableFrom(inType))
+ return (in, memberOf, session, args) -> {
+ var c2 = new
GregorianCalendar(TimeZone.getDefault());
+ c2.setTime((Date) in);
+ return c2;
+ };
+ if
(java.time.temporal.Temporal.class.isAssignableFrom(inType))
+ return (in, memberOf, session, args) -> {
+ var temporal =
(java.time.temporal.Temporal) in;
+ java.time.Instant instant;
+ try {
+ instant =
java.time.Instant.from(temporal);
+ } catch (java.time.DateTimeException e)
{
+ // LocalDateTime lacks offset
info - interpret using session/system timezone
+ var tz =
sessionTimeZone(session);
+ var zoneId = tz != null ?
tz.toZoneId() : java.time.ZoneId.systemDefault();
+ instant =
java.time.LocalDateTime.from(temporal).atZone(zoneId).toInstant();
+ }
+ return
Iso8601Utils.fromEpochMillis(instant.toEpochMilli(), toMeta,
sessionTimeZone(session));
+ };
+ }
+ if (toMeta.isDate() && outType == java.util.Date.class) {
+ if (Calendar.class.isAssignableFrom(inType))
+ return (in, memberOf, session, args) ->
((Calendar) in).getTime();
+ if
(java.time.temporal.Temporal.class.isAssignableFrom(inType))
+ return (in, memberOf, session, args) -> {
+ var temporal =
(java.time.temporal.Temporal) in;
+ java.time.Instant instant;
+ try {
+ instant =
java.time.Instant.from(temporal);
+ } catch (java.time.DateTimeException e)
{
+ // LocalDateTime lacks offset
info - interpret using session/system timezone
+ var tz =
sessionTimeZone(session);
+ var zoneId = tz != null ?
tz.toZoneId() : java.time.ZoneId.systemDefault();
+ instant =
java.time.LocalDateTime.from(temporal).atZone(zoneId).toInstant();
+ }
+ return java.util.Date.from(instant);
+ };
+ }
+
+ // --- byte[]/CharSequence → InputStream (Gap 18) ---
+ if (InputStream.class.isAssignableFrom(outType)) {
+ if (byte[].class == inType)
+ return (in, memberOf, session, args) -> { var b
= (byte[]) in; return new ByteArrayInputStream(b, 0, b.length); };
+ if (CharSequence.class.isAssignableFrom(inType))
+ return (in, memberOf, session, args) -> { var b
= in.toString().getBytes(); return new ByteArrayInputStream(b, 0, b.length); };
+ }
+
+ // --- byte[]/CharSequence → Reader (Gap 18) ---
+ if (Reader.class.isAssignableFrom(outType)) {
+ if (byte[].class == inType)
+ return (in, memberOf, session, args) -> new
StringReader(new String((byte[]) in));
+ if (CharSequence.class.isAssignableFrom(inType))
+ return (in, memberOf, session, args) -> new
StringReader(in.toString());
+ }
+
+ // --- CharSequence → Enum using ClassMeta (respects
ignoreUnknownEnumValues) (Gap 13) ---
+ if (CharSequence.class.isAssignableFrom(inType) &&
outType.isEnum()) {
+ return (in, memberOf, session, args) -> {
+ try {
+ return
toMeta.newInstanceFromString(memberOf, in.toString());
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+
+ // --- CharSequence → CharSequence subtype via ClassMeta (Gap
15) ---
+ if (CharSequence.class.isAssignableFrom(inType) &&
toMeta.isCharSequence() && outType != String.class &&
toMeta.canCreateNewInstanceFromString(null)) {
+ return (in, memberOf, session, args) -> {
+ try {
+ return
toMeta.newInstanceFromString(memberOf, in.toString());
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+
+ // --- Array → CharSequence subtype via serializer (Gap 15) ---
+ if (inType.isArray() && toMeta.isCharSequence() && outType !=
String.class && toMeta.canCreateNewInstanceFromString(null)) {
+ return (in, memberOf, session, args) -> {
+ try {
+ var ws = getBeanToStringSerializer();
+ var str = ws != null ? ws.serialize(in)
: in.toString();
+ return
toMeta.newInstanceFromString(memberOf, str);
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+
+ // --- Enum → T via toString() (allows Enum→Boolean,
Enum→Number, etc.) ---
+ // Excludes Enum→Enum (handled by BasicConverter) and
Enum→String (handled by BasicConverter/CharSequence path).
+ if (inType.isEnum() && !outType.isEnum() &&
!CharSequence.class.isAssignableFrom(outType)) {
+ return (in, memberOf, session, args) ->
converter.to(in.toString(), memberOf, session, outType, args);
+ }
+
+ // --- Object → Boolean via toString() (lenient: any non-"true"
string is false) ---
+ // Excludes CharSequence types (handled by BasicConverter),
primitives (handled by CachingConverter nullDefault),
+ // and types that BasicConverter already handles (e.g. via
toBoolean() instance method).
+ if ((outType == Boolean.class || outType == boolean.class) &&
!CharSequence.class.isAssignableFrom(inType) &&
!Number.class.isAssignableFrom(inType) && inType != boolean.class && inType !=
Boolean.class && !BasicConverter.INSTANCE.canConvert(inType, Boolean.class)) {
+ return (in, memberOf, session, args) ->
converter.to(in.toString(), memberOf, session, Boolean.class);
+ }
+
+ // --- Object → Bean via toString() + BeanMap.load() (fallback
for bean-compatible types) ---
+ // Matches old convertToMemberType fallback: if (to.isBean())
return newBeanMap(to.inner()).load(value.toString()).getBean()
+ // Excludes cases where input is already assignable to output
(handled by BeanSession isInstance shortcut).
+ if (toMeta.isBean() && !Map.class.isAssignableFrom(inType) &&
!CharSequence.class.isAssignableFrom(inType)
+ && !outType.isAssignableFrom(inType)) {
+ return (in, memberOf, session, args) -> {
+ try {
+ var bs = beanSessionOrDefault(session);
+ return
bs.newBeanMap(toMeta.inner()).load(in.toString()).getBean();
+ } catch (Exception e) {
+ throw rex(e);
+ }
+ };
+ }
+
return null;
}
+ private static TimeZone sessionTimeZone(ConverterSession session) {
+ return session == null ? null :
session.get(TimeZone.class).orElse(null);
+ }
+
+ private BeanSession beanSession(ConverterSession session) {
+ return session instanceof BeanSession bs ? bs : null;
+ }
+
+ private BeanSession beanSessionOrDefault(ConverterSession session) {
+ return session instanceof BeanSession bs ? bs : defaultSession;
+ }
+
+ private static Collection<Object> newCollection(Class<?> outType) {
+ if (outType == List.class || outType == Collection.class ||
outType == Iterable.class || outType == AbstractList.class)
+ return new ArrayList<>();
+ if (outType == Set.class || outType == LinkedHashSet.class ||
outType == AbstractSet.class)
+ return new LinkedHashSet<>();
+ if (outType == SortedSet.class || outType == NavigableSet.class
|| outType == TreeSet.class)
+ return new TreeSet<>();
+ try {
+ return (Collection<Object>)
outType.getDeclaredConstructor().newInstance();
+ } catch (Exception e) {
+ return new ArrayList<>();
+ }
+ }
+
/**
* Returns the {@link BeanMeta} class for the specified class.
*
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
index d128b36d72..92268c6abd 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
@@ -18,7 +18,6 @@ package org.apache.juneau;
import static org.apache.juneau.commons.utils.AssertionUtils.*;
import static org.apache.juneau.commons.utils.CollectionUtils.*;
-import static org.apache.juneau.commons.utils.StringUtils.*;
import static org.apache.juneau.commons.utils.ThrowableUtils.*;
import static org.apache.juneau.commons.utils.Utils.*;
@@ -36,10 +35,7 @@ import org.apache.juneau.commons.collections.FluentMap;
import org.apache.juneau.commons.conversion.*;
import org.apache.juneau.commons.lang.*;
import org.apache.juneau.commons.reflect.*;
-import org.apache.juneau.commons.time.*;
-import org.apache.juneau.commons.utils.*;
import org.apache.juneau.swap.*;
-import org.apache.juneau.utils.*;
/**
* Session object that lives for the duration of a single use of {@link
BeanContext}.
@@ -254,16 +250,6 @@ public class BeanSession extends ContextSession implements
ConverterSession {
*/
public static final String NAME_PROPERTY_NAME = "_name";
- private boolean hasMutater(ClassMeta<?> from, ClassMeta<?> to) {
- if (to.hasMutaterFrom(from) || from.hasMutaterTo(to))
- return true;
- var c = ctx.getConverter();
- return c != null && c.hasCustomConversion(from.inner(),
to.inner());
- }
-
- private static final boolean isNullOrEmpty(Object o) {
- return o == null || o.toString().isEmpty() ||
o.toString().equals("null");
- }
private final BeanContext ctx;
private final Locale locale;
private final MediaType mediaType;
@@ -1162,265 +1148,21 @@ public class BeanSession extends ContextSession
implements ConverterSession {
* @throws InvalidDataConversionException If the specified value cannot
be converted to the specified type.
* @return The converted value.
*/
- @SuppressWarnings({
- "null", // Null handling verified by context or framework
- "java:S3776", // Cognitive complexity acceptable for this
specific logic
- "java:S6541", // Single-threaded context; synchronization
unnecessary
- })
protected final <T> T convertToMemberType(Object outer, Object value,
ClassMeta<T> to) throws InvalidDataConversionException {
if (to == null)
to = (ClassMeta<T>)object();
-
try {
- var ctxConverter = ctx.getConverter();
-
- // Handle the case of a null value.
- if (value == null) {
- return ctxConverter.to(value, outer, this,
to.innerType(), to.getParameters());
- }
-
- if (to.isOptional() && (! (value instanceof Optional)))
- return (T)opt(convertToMemberType(outer, value,
to.getElementType()));
-
- var tc = to.inner();
-
- // If no conversion needed, then just return the value.
- // Don't include maps or collections, because child
elements may need conversion.
- if (tc.isInstance(value) && ! ((to.isMap() && !
to.getValueType().is(Object.class)) || ((to.isCollection() || to.isOptional())
&& ! to.getElementType().isObject())))
- return (T)value;
-
- ObjectSwap swap = to.getSwap(this);
- if (nn(swap)) {
- var nc = swap.getNormalClass();
- var fc = swap.getSwapClass();
- if (nc.isAssignableFrom(tc) &&
fc.isAssignableFrom(value.getClass()))
- return (T)swap.unswap(this, value, to);
- if (nc.isAssignableFrom(tc) &&
Map.class.isAssignableFrom(fc.inner()) && value instanceof Map) {
- value = convertToMemberType(null,
value, fc.inner());
- return (T)swap.unswap(this, value, to);
- }
- var fcm = getClassMeta(fc);
- if (fcm.isNumber() && value instanceof Number
value2) {
- value = convertToMemberType(null,
value2, fc.inner());
- return (T)swap.unswap(this, value, to);
- }
- }
-
- var from = getClassMetaForObject(value);
- swap = from.getSwap(this);
- if (nn(swap)) {
- var nc = swap.getNormalClass();
- var fc = swap.getSwapClass();
- if (nc.isAssignableFrom(from.inner()) &&
fc.isAssignableFrom(tc))
- return (T)swap.swap(this, value);
- }
-
- if (to.isCharSequence() &&
(from.isDateOrCalendarOrTemporal() || from.isDuration()))
- return (T) Iso8601Utils.format(value, from,
getTimeZone());
-
- if (ctxConverter.hasCustomConversion(from.inner(), tc))
- return ctxConverter.to(value, outer, this, tc);
-
- if (to.isCharSequence() &&
(from.isDateOrCalendarOrTemporal() || from.isDuration()))
- return (T) Iso8601Utils.format(value, from,
getTimeZone());
-
- if (to.isDateOrCalendarOrTemporal() && value instanceof
CharSequence)
- return (T) Iso8601Utils.parse(value.toString(),
to, getTimeZone());
-
- if (to.isDuration() && value instanceof CharSequence)
- return (T) Iso8601Utils.parse(value.toString(),
to, getTimeZone());
-
- if (to.isDateOrCalendarOrTemporal() && value instanceof
Number)
- return (T)
Iso8601Utils.fromEpochMillis(((Number)value).longValue(), to, getTimeZone());
-
- if (to.isPrimitive()) {
- return ctxConverter.to(value, outer, this,
to.innerType(), to.getParameters());
- }
-
- if (to.isNumber()) {
- if (from.isNumber()) {
- return ctxConverter.to(value, outer,
this, to.innerType(), to.getParameters());
- } else if (from.isBoolean()) {
- return ctxConverter.to(value, outer,
this, to.innerType(), to.getParameters());
- } else if (isNullOrEmpty(value)) {
- return null;
- } else if (! hasMutater(from, to)) {
- return ctxConverter.to(value, outer,
this, to.innerType(), to.getParameters());
- }
- }
-
- if (to.isChar()) {
- return ctxConverter.to(value, outer, this,
to.innerType(), to.getParameters());
- }
-
- if (to.isByteArray()) {
- if (from.isInputStream() || from.isReader())
- return ctxConverter.to(value, outer,
this, to.innerType(), to.getParameters());
- if (to.hasMutaterFrom(from))
- return to.mutateFrom(value);
- if (from.hasMutaterTo(to))
- return from.mutateTo(value, to);
- return ctxConverter.to(value, outer, this,
to.innerType(), to.getParameters());
- }
-
- // Handle setting of array properties
- if (to.isArray()) {
- if (from.isCollection())
- return (T)toArray(to,
(Collection)value);
- else if (from.isArray()) {
- // Use reflection to build the list so
primitive arrays (e.g. boolean[]) are handled correctly.
- // Array.get() auto-boxes primitives,
whereas casting to Object[] fails for primitive arrays.
- var len = Array.getLength(value);
- var list = new ArrayList<>(len);
- for (var i = 0; i < len; i++)
- list.add(Array.get(value, i));
- return (T)toArray(to, list);
- }
- else if (startsWith(value.toString(), '['))
- return (T)toArray(to,
JsonList.ofJson(value.toString()).setBeanSession(this));
- else if (to.hasMutaterFrom(from))
- return to.mutateFrom(value);
- else if (from.hasMutaterTo(to))
- return from.mutateTo(value, to);
- else
- return (T)toArray(to, new
JsonList((Object[])StringUtils.splita(value.toString())).setBeanSession(this));
- }
-
- // Target type is some sort of Map that needs to be
converted.
- if (to.isMap()) {
- return convertToMapType(outer, value, from, to);
- }
-
- // Target type is some sort of Collection
- if (to.isCollection()) {
- return convertToCollectionType(outer, value,
from, to);
- }
-
- if (to.isEnum()) {
- return to.newInstanceFromString(outer,
value.toString());
- }
-
- if (to.isString()) {
- if (from.isByteArray()) {
- return (T)new String((byte[])value);
- } else if (from.isMapOrBean() ||
from.isCollectionOrArrayOrOptional()) {
- var ws =
ctx.getBeanToStringSerializer();
- if (nn(ws))
- return (T)ws.serialize(value);
- } else if (from.is(Class.class)) {
- return (T)((Class<?>)value).getName();
- }
- return (T)value.toString();
- }
-
- if (to.isCharSequence()) {
- var c = value.getClass();
- if (c.isArray()) {
- if (c.getComponentType().isPrimitive())
{
- var l = new JsonList(this);
- var size =
Array.getLength(value);
- for (var i = 0; i < size; i++)
- l.add(Array.get(value,
i));
- value = l;
- }
- value = new
JsonList((Object[])value).setBeanSession(this);
- }
-
- return to.newInstanceFromString(outer,
value.toString());
- }
-
- if (to.isBoolean()) {
- if (from.isNumber())
- return
(T)(Boolean.valueOf(((Number)value).intValue() != 0));
- if (isNullOrEmpty(value))
- return null;
- if (! hasMutater(from, to))
- return
(T)Boolean.valueOf(value.toString());
- }
-
- // It's a bean being initialized with a Map
- if (to.isBean() && value instanceof Map value2) {
- var builder =
(BuilderSwap<T,Object>)to.getBuilderSwap(this);
-
- if (value2 instanceof JsonMap m2 && builder ==
null) {
- var typeName =
m2.getString(getBeanTypePropertyName(to));
- if (nn(typeName)) {
- var cm =
to.getBeanRegistry().getClassMeta(typeName);
- if (nn(cm) &&
to.isAssignableFrom(cm.inner()))
- return (T)m2.cast(cm);
- }
- }
- if (nn(builder)) {
- var created = builder.create(this, to);
- if (created != null) {
- var m = toBeanMap(created);
- m.load(value2);
- return builder.build(this,
m.getBean(), to);
- }
- }
- return newBeanMap(tc).load(value2).getBean();
- }
-
- if (to.isInputStream()) {
- if (from.isByteArray()) {
- var b = (byte[])value;
- return (T)new ByteArrayInputStream(b,
0, b.length);
- }
- var b = value.toString().getBytes();
- return (T)new ByteArrayInputStream(b, 0,
b.length);
- }
-
- if (to.isReader()) {
- if (from.isByteArray()) {
- var b = (byte[])value;
- return (T)new StringReader(new
String(b));
- }
- return (T)new StringReader(value.toString());
- }
-
- if (to.isCalendar()) {
- if (from.isCalendar()) {
- var c = (Calendar)value;
- if (value instanceof GregorianCalendar)
{
- var c2 = new
GregorianCalendar(c.getTimeZone());
- c2.setTime(c.getTime());
- return (T)c2;
- }
- }
- if (from.isDate()) {
- var d = (Date)value;
- if (value instanceof GregorianCalendar)
{
- var c2 = new
GregorianCalendar(TimeZone.getDefault());
- c2.setTime(d);
- return (T)c2;
- }
- }
- return
(T)GregorianCalendar.from(GranularZonedDateTime.of(value.toString()).getZonedDateTime());
- }
-
- if (to.isDate() && to.inner() == Date.class) {
- if (from.isCalendar())
- return (T)((Calendar)value).getTime();
- return
(T)GregorianCalendar.from(GranularZonedDateTime.of(value.toString()).getZonedDateTime()).getTime();
- }
-
- if (to.hasMutaterFrom(from))
- return to.mutateFrom(value);
-
- if (from.hasMutaterTo(to))
- return from.mutateTo(value, to);
-
- if (to.isBean())
- return
newBeanMap(to.inner()).load(value.toString()).getBean();
-
- if (to.canCreateNewInstanceFromString(outer))
- return to.newInstanceFromString(outer,
value.toString());
-
+ // If the value is already an instance of the target
type and no element/value conversion is needed, return as-is.
+ // Skip this shortcut for typed maps/collections whose
elements may need conversion.
+ if (value != null && to.inner().isInstance(value)
+ && !((to.isMap() &&
!to.getValueType().is(Object.class)) || ((to.isCollection() || to.isOptional())
&& !to.getElementType().isObject())))
+ return (T) value;
+ return ctx.getConverter().to(value, outer, this,
to.innerType(), to.getParameters());
+ } catch (InvalidDataConversionException e) {
+ throw e;
} catch (Exception e) {
throw new InvalidDataConversionException(value, to, e);
}
-
- throw new InvalidDataConversionException(value, to, null);
}
/**
@@ -1506,84 +1248,4 @@ public class BeanSession extends ContextSession
implements ConverterSession {
return array;
}
- @SuppressWarnings({
- "java:S3776" // Cognitive complexity is acceptable for this
type conversion method
- })
- private <T> T convertToMapType(Object outer, Object value, ClassMeta<?>
from, ClassMeta<T> to) {
- try {
- if (from.isMap()) {
- var m = to.canCreateNewInstance(outer) ?
(Map)to.newInstance(outer) : newGenericMap(to);
- var keyType = to.getKeyType();
- var valueType = to.getValueType();
- ((Map<?,?>)value).forEach((k, v) -> {
- var k2 = k;
- if (k == null) {
- k2 = null; // Preserve null
keys (e.g. when null is used as a map key in source)
- } else if (! keyType.isObject()) {
- if (keyType.isString() &&
k.getClass() != Class.class)
- k2 = k.toString();
- else
- k2 =
convertToMemberType(m, k, keyType);
- }
- var v2 = v;
- if (! valueType.isObject())
- v2 = convertToMemberType(m, v,
valueType);
- m.put(k2, v2);
- });
- return (T)m;
- } else if (! to.canCreateNewInstanceFromString(outer)) {
- var m = JsonMap.ofJson(value.toString());
- m.setBeanSession(this);
- return convertToMemberType(outer, m, to);
- }
- return null;
- } catch (Exception e) {
- throw new
InvalidDataConversionException(value.getClass(), to, e);
- }
- }
-
- @SuppressWarnings({
- "java:S3776" // Cognitive complexity is acceptable for this
type conversion method
- })
- private <T> T convertToCollectionType(Object outer, Object value,
ClassMeta<?> from, ClassMeta<T> to) {
- try {
- Collection l;
- if (to.canCreateNewInstance(outer)) {
- l = (Collection)to.newInstance(outer);
- } else if (to.isSet()) {
- l = set();
- } else {
- l = new JsonList(this);
- }
- var elementType = to.getElementType();
-
- if (from.isArray()) {
- for (var i = 0; i < Array.getLength(value);
i++) {
- var o = Array.get(value, i);
- l.add(elementType.isObject() ? o :
convertToMemberType(l, o, elementType));
- }
- } else if (from.isCollection())
- ((Collection)value).forEach(x ->
l.add(elementType.isObject() ? x : convertToMemberType(l, x, elementType)));
- else if (from.isMap())
- l.add(elementType.isObject() ? value :
convertToMemberType(l, value, elementType));
- else if (isNullOrEmpty(value))
- return null;
- else if (from.isString()) {
- var s = value.toString();
- if (isProbablyJsonArray(s, false)) {
- var l2 = JsonList.ofJson(s);
- l2.setBeanSession(this);
- l2.forEach(x ->
l.add(elementType.isObject() ? x : convertToMemberType(l, x, elementType)));
- } else {
- throw new
InvalidDataConversionException(value.getClass(), to, null);
- }
- } else
- throw new
InvalidDataConversionException(value.getClass(), to, null);
- return (T)l;
- } catch (InvalidDataConversionException e) {
- throw e;
- } catch (Exception e) {
- throw new
InvalidDataConversionException(value.getClass(), to, e);
- }
- }
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cbor/CborOutputStream.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cbor/CborOutputStream.java
index 3523f45d13..30bf779a26 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cbor/CborOutputStream.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cbor/CborOutputStream.java
@@ -64,6 +64,15 @@ public class CborOutputStream extends OutputStream {
}
}
+ @Override /* Overridden from OutputStream */
+ public void write(byte[] b, int off, int len) {
+ try {
+ os.write(b, off, len);
+ } catch (IOException e) {
+ throw new SerializeException(e);
+ }
+ }
+
/**
* Writes a single byte to the stream.
*
@@ -278,10 +287,9 @@ public class CborOutputStream extends OutputStream {
}
@SuppressWarnings({
- "java:S1172", // Parameter out unused; loop counter advances
for surrogate pairs
- "java:S127" // For-loop counter modification acceptable in
this algorithm
+ "java:S127" // For-loop counter modification acceptable in
this algorithm
})
- private int writeUtf8To(CharSequence in, OutputStream out) {
+ private int writeUtf8To(CharSequence in) {
var count = 0;
for (int i = 0, len = in.length(); i < len; i++) {
var c = (in.charAt(i) & 0xFFFF);
@@ -319,7 +327,7 @@ public class CborOutputStream extends OutputStream {
CborOutputStream appendString(CharSequence s) {
int length = getUtf8ByteLength(s);
writeHead(3, length);
- int length2 = writeUtf8To(s, os);
+ int length2 = writeUtf8To(s);
if (length != length2)
throw new SerializeException("Unexpected length.
Expected={0}, Actual={1}", length, length2);
return this;
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/swap/ObjectSwap.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/swap/ObjectSwap.java
index d94b7cf06f..fb7083829b 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/swap/ObjectSwap.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/swap/ObjectSwap.java
@@ -277,7 +277,7 @@ public abstract class ObjectSwap<T,S> {
if (forMediaTypes == null)
return 1;
int i = 0;
- var mt = session.getMediaType();
+ var mt = session != null ? session.getMediaType() : null;
if (mt == null)
return 0;
if (nn(forMediaTypes))
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/toml/TomlSerializerSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/toml/TomlSerializerSession.java
index 08ff2e4cd5..5257b260b5 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/toml/TomlSerializerSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/toml/TomlSerializerSession.java
@@ -249,6 +249,10 @@ public class TomlSerializerSession extends
WriterSerializerSession {
w.quotedKey(key);
}
+ @SuppressWarnings({
+ "unused", // pMeta accepted for API consistency; may be
used by future format-specific logic
+ "java:S1172" // Same as above
+ })
private void writeValue(TomlWriter w, Object value, ClassMeta<?> aType,
BeanPropertyMeta pMeta) throws SerializeException {
if (value == null) {
w.stringValue(ctx.getNullValue());
@@ -320,6 +324,10 @@ public class TomlSerializerSession extends
WriterSerializerSession {
writeValue(w, value, aType, null);
}
+ @SuppressWarnings({
+ "unused", // path and type reserved for future
section-header and type-aware serialization
+ "java:S1172" // Same as above
+ })
private void serializeMapAsTable(TomlWriter w, String path, Map<?,?>
map, ClassMeta<?> type) throws SerializeException {
Predicate<Object> checkNull = x -> isKeepNullProperties() ||
nn(x);
forEachEntry(map, e -> {
@@ -347,6 +355,10 @@ public class TomlSerializerSession extends
WriterSerializerSession {
w.arrayEnd();
}
+ @SuppressWarnings({
+ "unused", // pMeta accepted for API consistency with
callers that track property context
+ "java:S1172" // Same as above
+ })
private boolean isSimpleOrInlineTable(ClassMeta<?> aType, Object value,
BeanPropertyMeta pMeta) throws SerializeException {
if (aType.isBean()) {
BeanMap<?> bm = toBeanMap(value);
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/Iso8601Utils.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/Iso8601Utils.java
index 3cd6065d02..c03bcf3a5b 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/Iso8601Utils.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/Iso8601Utils.java
@@ -366,6 +366,10 @@ public final class Iso8601Utils {
var cal =
GregorianCalendar.from(instant.atZone(zoneId));
return (T) cal;
}
+ if (XMLGregorianCalendar.class.isAssignableFrom(tc)) {
+ var gc = GregorianCalendar.from(instant.atZone(zoneId));
+ return (T) datatypeFactory.newXMLGregorianCalendar(gc);
+ }
return null;
}
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringExpressionMatcher.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringExpressionMatcher.java
index 317d4a387d..dcd6be9025 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringExpressionMatcher.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/StringExpressionMatcher.java
@@ -103,6 +103,10 @@ public class StringExpressionMatcher {
}
abstract static class Exp {
+ @SuppressWarnings({
+ "unused", // set unused in base no-op; subclasses
that override do use it
+ "java:S1172" // Same as above
+ })
void appendTokens(Set<String> set) {}
abstract boolean matches(String input);
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java
index ad26c44aa0..c1b9542b07 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlBeanPropertyMeta.java
@@ -102,7 +102,9 @@ public class XmlBeanPropertyMeta extends
ExtendedBeanPropertyMeta {
public XmlFormat getXmlFormat() { return xmlFormat; }
@SuppressWarnings({
- "java:S3776" // Cognitive complexity acceptable for XML format
detection from annotations
+ "unused", // mp unused here; accepted to match
AnnotationProvider-based caller pattern
+ "java:S1172", // Same as above
+ "java:S3776" // Cognitive complexity acceptable for XML
format detection from annotations
})
private void findXmlInfo(Xml xml, AnnotationProvider mp) {
if (xml == null)
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlParserSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlParserSession.java
index 466d36082d..06404b7f7b 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlParserSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlParserSession.java
@@ -477,7 +477,8 @@ public class YamlParserSession extends ReaderParserSession {
}
@SuppressWarnings({
- "java:S1172" // eType, pMeta kept for API consistency with
callers
+ "unused", // eType, pMeta kept for API consistency with
callers
+ "java:S1172" // Same as above
})
private Object convertToType(String s, ClassMeta<?> sType, ClassMeta<?>
eType, Object outer, BeanPropertyMeta pMeta) throws ParseException {
if (sType.isObject()) {
@@ -1197,7 +1198,9 @@ public class YamlParserSession extends
ReaderParserSession {
// Plain scalar (unquoted)
// ==========================================
@SuppressWarnings({
- "java:S3776" // Cognitive complexity acceptable for plain
scalar parsing
+ "unused", // indent accepted for context but not currently
used in parsing logic
+ "java:S1172", // Same as above
+ "java:S3776" // Cognitive complexity acceptable for plain
scalar parsing
})
private static String parsePlainScalar(ParserReader r, int indent)
throws IOException {
var sb = new StringBuilder();