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 3a28337922 New BasicConverter class
3a28337922 is described below
commit 3a28337922f00d628abdbdff2b2a58d3bd1711f7
Author: James Bognar <[email protected]>
AuthorDate: Thu Apr 2 06:51:55 2026 -0700
New BasicConverter class
---
.gitignore | 3 +-
AGENTS.md | 41 +-
.../juneau/commons/concurrent/SimpleLock.java | 1 +
.../commons/concurrent/SimpleReadWriteLock.java | 6 +
.../juneau/commons/conversion/BasicConverter.java | 513 ++++++++++++++
.../commons/conversion/CachingConverter.java | 165 +++++
.../juneau/commons/conversion/Conversion.java | 47 ++
.../juneau/commons/conversion/Converter.java | 60 +-
.../conversion/InvalidConversionException.java | 53 ++
.../juneau/commons/io/FileReaderBuilder.java | 3 +
.../juneau/commons/io/FileWriterBuilder.java | 3 +
.../juneau/commons/io/NoCloseOutputStream.java | 1 +
.../apache/juneau/commons/io/NoCloseWriter.java | 1 +
.../juneau/commons/io/PathReaderBuilder.java | 3 +
.../juneau/commons/io/ReaderInputStream.java | 4 +-
.../org/apache/juneau/commons/logging/Logger.java | 3 +
.../apache/juneau/commons/reflect/ClassInfo.java | 4 +-
.../org/apache/juneau/commons/utils/IoUtils.java | 5 +
.../apache/juneau/config/internal/ConfigMap.java | 5 +-
.../src/main/java/org/apache/juneau/ClassMeta.java | 1 +
.../juneau/httppart/SimplePartParserSession.java | 4 +-
.../juneau/httppart/SimplePartSerializer.java | 2 +-
.../httppart/SimplePartSerializerSession.java | 4 +-
.../org/apache/juneau/internal/ConverterUtils.java | 8 +-
.../java/org/apache/juneau/reflect/Mutaters.java | 2 +
.../apache/juneau/uon/UonSerializerSession.java | 6 +-
...ricConverter.java => BeanContextConverter.java} | 33 +-
.../src/main/resources/META-INF/persistence.xml | 36 -
.../org/apache/juneau/rest/remote/RrpcServlet.java | 4 +-
.../apache/juneau/bson/BsonOutputStream_Test.java | 3 +
.../juneau/commons/collections/Lists_Test.java | 32 +-
.../juneau/commons/collections/Sets_Test.java | 32 +-
.../commons/conversion/BasicConverter_Test.java | 765 +++++++++++++++++++++
.../commons/conversion/CachingConverter_Test.java | 108 +++
.../juneau/commons/conversion/Converter_Test.java | 34 +-
.../juneau/commons/io/CharSequenceReader_Test.java | 3 +
.../commons/io/NoCloseOutputStream_Test.java | 3 +
.../juneau/commons/io/NoCloseWriter_Test.java | 3 +
.../juneau/commons/io/ReaderInputStream_Test.java | 3 +
.../commons/io/StringBuilderWriter_Test.java | 3 +
.../apache/juneau/hjson/HjsonTokenizer_Test.java | 3 +
.../apache/juneau/html/SimpleHtmlWriter_Test.java | 3 +
.../apache/juneau/uon/UonParserReader_Test.java | 3 +-
.../java/org/apache/juneau/utils/MutatersTest.java | 2 +-
scripts/coverage.py | 284 ++++++++
45 files changed, 2198 insertions(+), 107 deletions(-)
diff --git a/.gitignore b/.gitignore
index d2fb04dec0..4210a16677 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
**/.classpath
**/.project
**/.settings/
+/.metadata/
/bin/
derby.log
@@ -13,7 +14,7 @@ cursor-*
**/dependency-reduced-pom.xml
# Note: Maven site generation creates target/site/ (already ignored by
**/target/ above)
-/juneau.code-workspace
+**/*.code-workspace
/create-mvn-site.log
/coverage/
/coverage.csv
diff --git a/AGENTS.md b/AGENTS.md
index fbdb6192f5..373a11d0eb 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -402,7 +402,46 @@ A reusable Python script is available at
`scripts/build-and-test.py` for common
- When you need to verify both build and tests pass
- During iterative development to quickly test changes
-### 5.1. Command Execution Best Practices
+### 5.1. Coverage Script
+
+A reusable Python script is available at `scripts/coverage.py` for checking
JaCoCo test coverage on any source file, package, or module.
+
+**When asked for current test coverage** on a class, package, or module,
always use this script rather than manually parsing JaCoCo output.
+
+**Usage:**
+```bash
+# Coverage for an entire package
+./scripts/coverage.py
juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/
+
+# Coverage for a single file
+./scripts/coverage.py
juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/BasicConverter.java
+
+# Show only lines with missed branches (hide instruction-only misses)
+./scripts/coverage.py path/to/file.java --branches
+
+# Re-run tests first to refresh coverage data, then report
+./scripts/coverage.py path/to/folder/ --run
+```
+
+**What it does:**
+- Detects the Maven module automatically from the path
+- Generates a JaCoCo report using the existing
`juneau-utest/target/jacoco.exec`
+- Displays branch and instruction coverage percentages with a progress bar
+- Lists every uncovered line with the number of missed branches/instructions
+
+**How coverage data works:**
+- The `.exec` file is produced by `juneau-utest` during a test run and covers
all loaded classes, including those from other modules (e.g. `juneau-commons`)
+- Use `--run` when tests have changed since the last run; omit it to reuse
existing data for speed
+- Report generation takes ~2 seconds; test execution adds ~10-15 seconds
+
+**Trigger phrases:** When the user says any of the following, run this script:
+- "what's the coverage on ..."
+- "show coverage for ..."
+- "current test coverage"
+- "check coverage"
+- "coverage report"
+
+### 5.3. Command Execution Best Practices
**Adding Timeouts to Commands:**
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/concurrent/SimpleLock.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/concurrent/SimpleLock.java
index be2a88f435..d5e784667d 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/concurrent/SimpleLock.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/concurrent/SimpleLock.java
@@ -100,6 +100,7 @@ public class SimpleLock implements AutoCloseable {
* <li class='note'>This instance wraps a <jk>null</jk> lock, so
no actual locking occurs.
* </ul>
*/
+ @SuppressWarnings("resource") // Intentional singleton; wraps a null
lock so close() is a no-op
public static final SimpleLock NO_OP = new SimpleLock(null);
private final Lock lock;
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/concurrent/SimpleReadWriteLock.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/concurrent/SimpleReadWriteLock.java
index e5dbf42aaf..ac9d21e432 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/concurrent/SimpleReadWriteLock.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/concurrent/SimpleReadWriteLock.java
@@ -169,6 +169,9 @@ public class SimpleReadWriteLock extends
ReentrantReadWriteLock {
*
* @return A new {@link SimpleLock} that holds the read lock. The lock
is automatically acquired.
*/
+ @SuppressWarnings({
+ "resource" // Caller takes ownership of the returned lock via
try-with-resources
+ })
public SimpleLock read() {
return new SimpleLock(readLock());
}
@@ -202,6 +205,9 @@ public class SimpleReadWriteLock extends
ReentrantReadWriteLock {
*
* @return A new {@link SimpleLock} that holds the write lock. The lock
is automatically acquired.
*/
+ @SuppressWarnings({
+ "resource" // Caller takes ownership of the returned lock via
try-with-resources
+ })
public SimpleLock write() {
return new SimpleLock(writeLock());
}
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
new file mode 100644
index 0000000000..822b43ffe6
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/BasicConverter.java
@@ -0,0 +1,513 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.conversion;
+
+import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
+import static org.apache.juneau.commons.utils.StringUtils.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+
+import org.apache.juneau.commons.reflect.*;
+
+/**
+ * A concrete {@link CachingConverter} that supports common type conversions
without
+ * requiring a {@code BeanContext} or {@code BeanSession}.
+ *
+ * <p>
+ * The following conversions are supported:
+ *
+ * <table class='styled'>
+ * <tr><th>Input type</th><th>Output type</th><th>Notes</th></tr>
+ * <tr>
+ * <td>Any type</td>
+ * <td>Same or supertype</td>
+ * <td>Identity / widening (no-op cast)</td>
+ * </tr>
+ * <tr>
+ * <td>{@link Number}</td>
+ * <td>
+ * {@link Integer}, {@link Long}, {@link Short}, {@link
Float}, {@link Double}, {@link Byte},
+ * {@link AtomicInteger}, {@link AtomicLong}, and
primitive equivalents
+ * </td>
+ * <td>Narrowing/widening numeric conversion</td>
+ * </tr>
+ * <tr>
+ * <td>{@link Boolean}</td>
+ * <td>{@link Number} types</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>
+ * </tr>
+ * <tr>
+ * <td>{@link Number}</td>
+ * <td>{@link Boolean}</td>
+ * <td><c>intValue() != 0</c></td>
+ * </tr>
+ * <tr>
+ * <td>{@link CharSequence}</td>
+ * <td>{@link Boolean}</td>
+ * <td>{@link Boolean#valueOf(String)}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link CharSequence} (length 1)</td>
+ * <td>{@link Character}</td>
+ * <td><c>charAt(0)</c></td>
+ * </tr>
+ * <tr>
+ * <td>{@link Number}</td>
+ * <td>{@link Character}</td>
+ * <td><c>(char) intValue()</c></td>
+ * </tr>
+ * <tr>
+ * <td>Any</td>
+ * <td>{@link String}</td>
+ * <td>{@link Object#toString()}, with array support via {@link
Arrays#toString(Object[])}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link CharSequence}</td>
+ * <td>Any {@link Enum}</td>
+ * <td>{@link Enum#valueOf(Class, String)}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link Collection} or array</td>
+ * <td>{@link Collection} subtype</td>
+ * <td>Copies elements; element type from <c>args[0]</c></td>
+ * </tr>
+ * <tr>
+ * <td>{@link Map}</td>
+ * <td>{@link Map} subtype</td>
+ * <td>Copies entries; key type from <c>args[0]</c>, value type
from <c>args[1]</c></td>
+ * </tr>
+ * <tr>
+ * <td>{@link Collection} or array</td>
+ * <td>Array type</td>
+ * <td>Element type from <c>outType.getComponentType()</c></td>
+ * </tr>
+ * <tr>
+ * <td>{@link String}</td>
+ * <td>{@link TimeZone}</td>
+ * <td>{@link TimeZone#getTimeZone(String)}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link TimeZone}</td>
+ * <td>{@link String}</td>
+ * <td>{@link TimeZone#getID()}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link String}</td>
+ * <td>{@link Locale}</td>
+ * <td>{@link Locale#forLanguageTag(String)} (underscores
converted to hyphens)</td>
+ * </tr>
+ * <tr>
+ * <td>Any</td>
+ * <td>Any with <c>static T
fromString/valueOf/of/from/parse/create/forName/fromValue/builder(X)</c>
+ * or dynamic <c>fromX/forX/parseX</c> where X is the
input class name</td>
+ * <td>Reflection-based static factory lookup</td>
+ * </tr>
+ * <tr>
+ * <td>Any</td>
+ * <td>Any with <c>public T(X)</c> constructor</td>
+ * <td>Reflection-based constructor lookup</td>
+ * </tr>
+ * <tr>
+ * <td>Any with <c>toX()</c> instance method</td>
+ * <td>Any type X</td>
+ * <td>Instance method where name matches <c>to</c> + output class
name (e.g. <c>toInteger()</c>)</td>
+ * </tr>
+ * </table>
+ *
+ * <h5 class='section'>Thread Safety:</h5>
+ * <p>
+ * This class is thread-safe. The singleton instance can be safely shared
across multiple threads.
+ */
+@SuppressWarnings({
+ "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
+})
+public class BasicConverter extends CachingConverter {
+
+ /**
+ * Singleton instance.
+ */
+ public static final BasicConverter INSTANCE = new BasicConverter();
+
+ private static final Set<String> FACTORY_METHOD_NAMES = Set.of(
+ "fromString", "valueOf", "of", "from", "parse", "create",
"forName", "fromValue", "builder"
+ );
+
+ private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER =
Map.of(
+ boolean.class, Boolean.class,
+ byte.class, Byte.class,
+ char.class, Character.class,
+ double.class, Double.class,
+ float.class, Float.class,
+ int.class, Integer.class,
+ long.class, Long.class,
+ short.class, Short.class
+ );
+
+ /**
+ * Constructor.
+ */
+ protected BasicConverter() {}
+
+ @Override
+ protected <I, O> Conversion<I, O> findConversion(Class<I> inType,
Class<O> outType) {
+ var out = outType.isPrimitive() ? (Class<O>)
PRIMITIVE_TO_WRAPPER.get(outType) : outType;
+
+ Conversion<I, O> c;
+
+ 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;
+
+ if (Number.class.isAssignableFrom(out) && (c =
findNumberConversion(inType, out)) != null) return c;
+
+ if (out == Boolean.class && (c = findBooleanConversion(inType))
!= null) return c;
+
+ if (out == Character.class && (c =
findCharacterConversion(inType)) != null) return c;
+
+ if (out == String.class)
+ return (Conversion<I, O>)
findToStringConversion(inType);
+
+ if (out.isEnum() && (c = findEnumConversion(inType, out)) !=
null) return c;
+
+ if (Collection.class.isAssignableFrom(out) && (c =
findCollectionConversion(inType, out)) != null) return c;
+
+ if (Map.class.isAssignableFrom(out) && (c =
findMapConversion(inType, out)) != null) return c;
+
+ if (out.isArray() && (c = findArrayConversion(inType, out)) !=
null) return c;
+
+ if ((c = findToXMethod(inType, out)) != null) return c;
+
+ if ((c = findStaticFactory(inType, out)) != null) return c;
+
+ if ((c = findConstructorConversion(inType, out)) != null)
return c;
+
+ return null;
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Number conversions
+
//-----------------------------------------------------------------------------------------------------------------
+
+ private <I, O> Conversion<I, O> findNumberConversion(Class<I> inType,
Class<O> outType) {
+ if (Number.class.isAssignableFrom(inType))
+ return findNumberFromNumber(outType);
+ if (inType == Boolean.class)
+ return (Conversion<I, O>)
findNumberFromBoolean(outType);
+ if (CharSequence.class.isAssignableFrom(inType))
+ return (Conversion<I, O>) findNumberFromString(outType);
+ return null;
+ }
+
+ 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());
+ 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);
+ return null;
+ }
+
+ private <O> Conversion<CharSequence, O> findNumberFromString(Class<O>
outType) {
+ return (in, args) -> (O) parseNumber(in.toString(), (Class<?
extends Number>) outType);
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Boolean conversions
+
//-----------------------------------------------------------------------------------------------------------------
+
+ 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 null;
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Character conversions
+
//-----------------------------------------------------------------------------------------------------------------
+
+ private <I, O> Conversion<I, O> findCharacterConversion(Class<I>
inType) {
+ if (CharSequence.class.isAssignableFrom(inType))
+ return (in, 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 null;
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // String conversions
+
//-----------------------------------------------------------------------------------------------------------------
+
+ 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);
+ }
+ return (in, args) -> in.toString();
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Enum conversions
+
//-----------------------------------------------------------------------------------------------------------------
+
+ 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 null;
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Collection conversions
+
//-----------------------------------------------------------------------------------------------------------------
+
+ private <I, O> Conversion<I, O> findCollectionConversion(Class<I>
inType, Class<O> outType) {
+ if (Collection.class.isAssignableFrom(inType) ||
inType.isArray()) {
+ return (in, args) -> {
+ var elemType = args.length > 0 ? args[0] : null;
+ if (elemType == null && !inType.isArray() &&
outType.isAssignableFrom(inType))
+ return (O) in;
+ var result = newCollection(outType);
+ if (Collection.class.isAssignableFrom(inType)) {
+ for (var elem : (Collection<?>) in)
+ result.add(elemType != null ?
to(elem, 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 ?
to(elem, elemType) : elem);
+ }
+ }
+ return (O) result;
+ };
+ }
+ return null;
+ }
+
+ private Collection<Object> newCollection(Class<?> outType) {
+ if (outType == List.class || outType == Collection.class ||
outType == Iterable.class /* HTT: Iterable is a supertype of Collection so
findCollectionConversion never passes Iterable as outType */ || 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<>();
+ if (outType == Queue.class || outType == Deque.class || outType
== LinkedList.class)
+ return new LinkedList<>();
+ return newInstanceOrDefault(outType, ArrayList::new);
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Map conversions
+
//-----------------------------------------------------------------------------------------------------------------
+
+ private <I, O> Conversion<I, O> findMapConversion(Class<I> inType,
Class<O> outType) {
+ if (Map.class.isAssignableFrom(inType)) {
+ return (in, args) -> {
+ var keyType = args.length > 0 ? args[0] : null;
+ var valType = args.length > 1 ? args[1] : null;
+ if (keyType == null &&
outType.isAssignableFrom(inType))
+ return (O) in;
+ var result = newMap(outType);
+ ((Map<?, ?>) in).forEach((k, v) -> result.put(
+ keyType != null ? to(k, keyType) : k,
+ valType != null ? to(v, valType) : v
+ ));
+ return (O) result;
+ };
+ }
+ return null;
+ }
+
+ private Map<Object, Object> newMap(Class<?> outType) {
+ if (outType == Map.class || outType == LinkedHashMap.class ||
outType == AbstractMap.class)
+ return new LinkedHashMap<>();
+ if (outType == SortedMap.class || outType == NavigableMap.class
|| outType == TreeMap.class)
+ return new TreeMap<>();
+ if (outType == HashMap.class)
+ return new HashMap<>();
+ return newInstanceOrDefault(outType, LinkedHashMap::new);
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Array conversions
+
//-----------------------------------------------------------------------------------------------------------------
+
+ 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) -> {
+ if (Collection.class.isAssignableFrom(inType)) {
+ var list = (Collection<?>) in;
+ var arr =
Array.newInstance(componentType, list.size());
+ var i = 0;
+ for (var elem : list)
+ Array.set(arr, i++, to(elem,
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));
+ return (O) arr;
+ };
+ }
+ return null;
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Special-case conversions
+
//-----------------------------------------------------------------------------------------------------------------
+
+ 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);
+ if (TimeZone.class.isAssignableFrom(inType) && outType ==
String.class)
+ return (Conversion<I, O>) (Conversion<TimeZone,
String>) (in, args) -> in.getID();
+ if (inType == String.class && outType == Locale.class)
+ return (Conversion<I, O>) (Conversion<String, Locale>)
(in, args) -> Locale.forLanguageTag(in.replace('_', '-'));
+ if (CharSequence.class.isAssignableFrom(inType) && outType ==
Boolean.class)
+ return (in, args) -> {
+ var s = in.toString();
+ if (s.isEmpty() || "null".equals(s))
+ return null;
+ return (O) Boolean.valueOf(s);
+ };
+ return null;
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Reflection: static factory methods
+
//-----------------------------------------------------------------------------------------------------------------
+
+ private <I, O> Conversion<I, O> findStaticFactory(Class<I> inType,
Class<O> outType) {
+ var ci = info(outType);
+
+ for (var name : FACTORY_METHOD_NAMES) {
+ var opt = findStaticMethod(ci, name, inType, outType);
+ if (opt.isPresent())
+ return (in, 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 null;
+ }
+
+ private Optional<MethodInfo> findStaticMethod(ClassInfo ci, String
name, Class<?> inType, Class<?> outType) {
+ return ci.getPublicMethod(m ->
+ m.isStatic()
+ && m.isNotDeprecated()
+ && m.hasName(name)
+ && m.getParameterCount() == 1
+ && m.getParameterTypes().get(0).isAssignableFrom(inType)
+ && m.hasReturnTypeParent(outType)
+ );
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Reflection: public constructors
+
//-----------------------------------------------------------------------------------------------------------------
+
+ private <I, O> Conversion<I, O> findConstructorConversion(Class<I>
inType, Class<O> outType) {
+ var opt = info(outType).getPublicConstructor(c ->
+ c.getParameterCount() == 1
+ && c.isNotDeprecated()
+ && c.getParameterTypes().get(0).isAssignableFrom(inType)
+ );
+ if (opt.isPresent()) {
+ var ctor = opt.get();
+ return (in, args) -> ctor.newInstance(in);
+ }
+ return null;
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Reflection: toX() instance methods
+
//-----------------------------------------------------------------------------------------------------------------
+
+ private <I, O> Conversion<I, O> findToXMethod(Class<I> inType, Class<O>
outType) {
+ var methodName = "to" + outType.getSimpleName();
+ var opt = info(inType).getPublicMethod(m ->
+ m.isNotStatic()
+ && m.isNotDeprecated()
+ && m.getParameterCount() == 0
+ && m.hasName(methodName)
+ && m.hasReturnTypeParent(outType)
+ );
+ if (opt.isPresent()) {
+ var method = opt.get();
+ return (in, args) -> method.invoke(in);
+ }
+ return null;
+ }
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // Helpers
+
//-----------------------------------------------------------------------------------------------------------------
+
+ private <T> T newInstanceOrDefault(Class<?> type,
java.util.function.Supplier<T> defaultSupplier) {
+ 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) {
+ 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
new file mode 100644
index 0000000000..6f9a94ee38
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/CachingConverter.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.conversion;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.stream.*;
+
+/**
+ * An abstract {@link Converter} implementation that caches previously
determined type conversions.
+ *
+ * <p>
+ * Conversion functions are discovered lazily on the first call for a given
input/output type pair and
+ * stored in a two-level {@link ConcurrentHashMap} for subsequent fast lookup.
+ *
+ * <p>
+ * Subclasses implement {@link #findConversion(Class, Class)} to provide the
actual conversion logic.
+ * The result is cached so that reflection or other expensive discovery
happens only once per type pair.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>public class</jk> MyConverter <jk>extends</jk> CachingConverter {
+ *
+ * <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>args</jv>) ->
Integer.<jsm>valueOf</jsm>(<jv>s</jv>);
+ * <jk>return null</jk>;
+ * }
+ * }
+ * </p>
+ *
+ * <h5 class='section'>Thread Safety:</h5>
+ * <p>
+ * This class is thread-safe. The cache uses {@link ConcurrentHashMap} for
safe concurrent access.
+ * </p>
+ */
+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;
+
+ // Two-level cache: input type -> output type -> conversion function
(or NO_CONVERSION sentinel).
+ private final Map<Class<?>, Map<Class<?>, Conversion<?,?>>> conversions
= new ConcurrentHashMap<>();
+
+ /**
+ * Finds a conversion function from the specified input type to the
specified output type.
+ *
+ * <p>
+ * This method is called lazily the first time a conversion between a
given type pair is requested.
+ * The result is cached so subsequent calls for the same type pair
bypass this method entirely.
+ * When this method returns <jk>null</jk>, a sentinel is stored so that
unconvertable type pairs
+ * are also only evaluated once.
+ *
+ * <p>
+ * Implementations should return <jk>null</jk> if no conversion is
possible for the given types,
+ * which will cause {@link #to(Object, Class)} and {@link #to(Object,
Type, Type...)} to throw
+ * {@link InvalidConversionException} for that type pair.
+ *
+ * @param <I> The input type.
+ * @param <O> The output type.
+ * @param inType The runtime class of the input object.
+ * @param outType The target output class.
+ * @return A {@link Conversion} function, or <jk>null</jk> if no
conversion is available.
+ */
+ protected abstract <I, O> Conversion<I, O> findConversion(Class<I>
inType, Class<O> outType);
+
+ @SuppressWarnings("unchecked")
+ private <I, O> Conversion<I, O> lookupConversion(Class<I> inType,
Class<O> outType) {
+ var fn = conversions
+ .computeIfAbsent(inType, k -> new ConcurrentHashMap<>())
+ .computeIfAbsent(outType, k -> {
+ var found = findConversion(inType, outType);
+ return found != null ? found : NO_CONVERSION;
+ });
+ return fn == NO_CONVERSION ? null : (Conversion<I, O>) fn;
+ }
+
+ @Override
+ public boolean canConvert(Class<?> inType, Class<?> outType) {
+ if (inType == outType)
+ return true;
+ return lookupConversion(inType, outType) != null;
+ }
+
+ /**
+ * Converts the specified object to the specified type.
+ *
+ * <p>
+ * On the first call for a given input/output type pair, {@link
#findConversion(Class, Class)} is
+ * invoked and the result is cached. Subsequent calls use the cached
function directly.
+ *
+ * @param o The object to convert. Can be <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, 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);
+ }
+
+ /**
+ * Converts the specified object to the specified parameterized type.
+ *
+ * <p>
+ * The raw class is extracted from {@code mainType} (supporting both
{@link Class} and
+ * {@link ParameterizedType} values). Type arguments are extracted from
{@code args} the same way
+ * and passed through to the cached {@link Conversion} function at call
time.
+ *
+ * <p>
+ * The cache is keyed on the raw input and output classes only. The
{@code args} are not part of
+ * the cache key — a single cached {@link Conversion} handles all
parameterizations of a given
+ * output type.
+ *
+ * @param o The object to convert. Can be <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, 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, 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
new file mode 100644
index 0000000000..f758fd8e0b
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Conversion.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.conversion;
+
+import java.util.*;
+
+/**
+ * Functional interface representing a single type conversion used by {@link
CachingConverter}.
+ *
+ * <p>
+ * The {@code args} parameter carries any type arguments needed for
parameterized output types.
+ * For example, when converting to a {@link List}, {@code args[0]} would be
the element type.
+ * For a {@link Map}, {@code args[0]} is the key type and {@code args[1]} is
the value type.
+ *
+ * <p>
+ * A single cached {@code Conversion} function can serve all parameterizations
of the same raw output
+ * type by accepting different {@code args} values at runtime.
+ *
+ * @param <I> The input type.
+ * @param <O> The output type.
+ */
+@FunctionalInterface
+public interface Conversion<I, O> {
+
+ /**
+ * Converts the input object to the output type.
+ *
+ * @param in The input object.
+ * @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);
+}
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 0bcf6da49c..f67b158512 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
@@ -16,18 +16,70 @@
*/
package org.apache.juneau.commons.conversion;
+import java.lang.reflect.*;
+
/**
- * Temporary interface. To be replaced with Mutator once that's part of the
common module.
+ * Interface for converting objects between types.
+ *
+ * <p>
+ * Use {@link BasicConverter#INSTANCE} for a default singleton implementation
that supports
+ * a wide range of common type conversions without requiring a {@code
BeanContext} or {@code BeanSession}.
*/
public interface Converter {
+ /**
+ * Returns <jk>true</jk> if this converter can convert from the
specified input type to the specified output type.
+ *
+ * @param inType The input type.
+ * @param outType The output type.
+ * @return <jk>true</jk> if a conversion path exists.
+ */
+ default boolean canConvert(Class<?> inType, Class<?> outType) {
+ return true;
+ }
+
/**
* Converts the specified object to the specified type.
*
- * @param <T> The type to convert to.
+ * <p>
+ * Returns <jk>null</jk> only when the input object is <jk>null</jk>.
+ * 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>.
+ * @throws InvalidConversionException If no conversion path exists from
the input type to the target type.
+ */
+ <T> T to(Object o, Class<T> type);
+
+ /**
+ * Converts the specified object to the specified parameterized type.
+ *
+ * <p>
+ * This method allows conversion to complex parameterized types such as
collections and maps.
+ * The <c>args</c> parameter specifies the type parameters of the main
type.
+ *
+ * <p>
+ * Returns <jk>null</jk> only when the input object is <jk>null</jk>.
+ * Throws {@link InvalidConversionException} when no conversion path
exists.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Convert to List<String></jc>
+ * List<String> <jv>list</jv> =
<jv>converter</jv>.to(<jv>o</jv>, List.<jk>class</jk>, String.<jk>class</jk>);
+ *
+ * <jc>// Convert to Map<String,Integer></jc>
+ * Map<String,Integer> <jv>map</jv> =
<jv>converter</jv>.to(<jv>o</jv>, Map.<jk>class</jk>, String.<jk>class</jk>,
Integer.<jk>class</jk>);
+ * </p>
+ *
* @param o The object to convert.
- * @return The converted object, or <jk>null</jk> if the conversion is
not possible.
+ * @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.
*/
- <T> T convertTo(Class<T> type, Object o);
+ <T> T to(Object o, Type mainType, Type...args);
}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/InvalidConversionException.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/InvalidConversionException.java
new file mode 100644
index 0000000000..ef6ba8af60
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/InvalidConversionException.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.conversion;
+
+/**
+ * Thrown by {@link Converter#to(Object, Class)} when no conversion path
exists between two types.
+ *
+ * <p>
+ * This is an unchecked exception. Callers that are uncertain whether a
conversion is possible should
+ * call {@link Converter#canConvert(Class, Class)} first, or catch this
exception and handle accordingly.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>try</jk> {
+ * Integer <jv>x</jv> =
BasicConverter.<jsf>INSTANCE</jsf>.to(<js>"hello"</js>, Integer.<jk>class</jk>);
+ * } <jk>catch</jk> (InvalidConversionException <jv>e</jv>) {
+ * <jc>// Handle unsupported conversion</jc>
+ * }
+ *
+ * <jc>// Or check first:</jc>
+ * <jk>if</jk>
(BasicConverter.<jsf>INSTANCE</jsf>.canConvert(String.<jk>class</jk>,
Integer.<jk>class</jk>)) {
+ * Integer <jv>x</jv> =
BasicConverter.<jsf>INSTANCE</jsf>.to(<js>"42"</js>, Integer.<jk>class</jk>);
+ * }
+ * </p>
+ */
+public class InvalidConversionException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructor.
+ *
+ * @param inType The runtime type of the input object.
+ * @param outType The target output type.
+ */
+ public InvalidConversionException(Class<?> inType, Class<?> outType) {
+ super("Cannot convert " + inType.getName() + " to " +
outType.getName());
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/FileReaderBuilder.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/FileReaderBuilder.java
index 6a92dad9e7..a8948d02ea 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/FileReaderBuilder.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/FileReaderBuilder.java
@@ -177,6 +177,9 @@ public class FileReaderBuilder {
* @return A new {@link Reader} for reading from the file.
* @throws FileNotFoundException If the file could not be found and
{@link #allowNoFile()} was not called.
*/
+ @SuppressWarnings({
+ "resource" // Caller takes ownership of the returned Reader
+ })
public Reader build() throws FileNotFoundException {
if (allowNoFile && (file == null || ! file.exists()))
return new StringReader("");
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/FileWriterBuilder.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/FileWriterBuilder.java
index 8f719412a5..0c904a5935 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/FileWriterBuilder.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/FileWriterBuilder.java
@@ -217,6 +217,9 @@ public class FileWriterBuilder {
* @return A new {@link Writer} for writing to the file.
* @throws FileNotFoundException If the file could not be created or
opened for writing.
*/
+ @SuppressWarnings({
+ "resource" // Caller takes ownership of the returned Writer
+ })
public Writer build() throws FileNotFoundException {
assertArgNotNull(ARG_file, file);
var os = (OutputStream)new FileOutputStream(file, append);
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/NoCloseOutputStream.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/NoCloseOutputStream.java
index 073a2d727d..fe0d8251ca 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/NoCloseOutputStream.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/NoCloseOutputStream.java
@@ -79,6 +79,7 @@ public class NoCloseOutputStream extends OutputStream {
private static final String ARG_b = "b";
private static final String ARG_os = "os";
+ @SuppressWarnings("resource") // Intentionally not owned; this wrapper
deliberately does not close the underlying stream
private final OutputStream os;
/**
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/NoCloseWriter.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/NoCloseWriter.java
index 290053541b..ce1bae5a0d 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/NoCloseWriter.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/NoCloseWriter.java
@@ -80,6 +80,7 @@ public class NoCloseWriter extends Writer {
private static final String ARG_str = "str";
private static final String ARG_w = "w";
+ @SuppressWarnings("resource") // Intentionally not owned; this wrapper
deliberately does not close the underlying writer
private final Writer w;
/**
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/PathReaderBuilder.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/PathReaderBuilder.java
index f82fe30638..37d453bb08 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/PathReaderBuilder.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/PathReaderBuilder.java
@@ -190,6 +190,9 @@ public class PathReaderBuilder {
* @throws NoSuchFileException If the path does not exist and {@link
#allowNoFile()} was not called.
* @throws IOException If an I/O error occurs opening the path.
*/
+ @SuppressWarnings({
+ "resource" // Caller takes ownership of the returned Reader
+ })
public Reader build() throws IOException {
if (! allowNoFile && path == null) {
throw new IllegalStateException("No path");
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/ReaderInputStream.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/ReaderInputStream.java
index 3df8624d14..bfa4aceec5 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/ReaderInputStream.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/io/ReaderInputStream.java
@@ -84,6 +84,7 @@ public class ReaderInputStream extends InputStream {
private static final String ARG_encoder = "encoder";
private static final String ARG_reader = "reader";
+ @SuppressWarnings("resource") // Intentionally not owned; caller
retains responsibility for closing the underlying Reader
private final Reader reader;
private final CharsetEncoder encoder;
@@ -121,9 +122,6 @@ public class ReaderInputStream extends InputStream {
* @param charset the charset encoding. Must not be <jk>null</jk>.
* @param bufferSize the size of the input buffer in number of
characters. Must be positive.
*/
- @SuppressWarnings({
- "resource" // Reader resource managed by caller
- })
public ReaderInputStream(Reader reader, Charset charset, int
bufferSize) {
// @formatter:off
this(assertArgNotNull(ARG_reader, reader),
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/logging/Logger.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/logging/Logger.java
index 2d528a1d61..371b9da43c 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/logging/Logger.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/logging/Logger.java
@@ -145,6 +145,9 @@ public class Logger extends java.util.logging.Logger {
*
* @return A LogRecordCapture instance.
*/
+ @SuppressWarnings({
+ "resource" // Caller takes ownership of the returned
LogRecordCapture
+ })
public LogRecordCapture captureEvents() {
return new LogRecordCapture(this);
}
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 8c8fa2ef7a..799bea8512 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
@@ -306,7 +306,6 @@ public class ClassInfo extends ElementInfo implements
Annotatable, Type, Compara
* The same StringBuilder for method chaining.
*/
@SuppressWarnings({
- "null", // Null analysis handled by runtime checks
"java:S3776", // Cognitive complexity acceptable for name
formatting logic
"java:S6541" // Synchronization not needed for local
StringBuilder operations
})
@@ -1781,6 +1780,9 @@ public class ClassInfo extends ElementInfo implements
Annotatable, Type, Compara
* @param name The resource name.
* @return An input stream for reading the resource, or <jk>null</jk>
if the resource could not be found.
*/
+ @SuppressWarnings({
+ "resource" // Caller takes ownership of the returned InputStream
+ })
public java.io.InputStream getResourceAsStream(String name) {
return inner == null ? null : inner.getResourceAsStream(name);
}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
index 2a08d30c56..cbb45173dc 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/IoUtils.java
@@ -38,6 +38,7 @@ public class IoUtils {
public static final Charset UTF8 = StandardCharsets.UTF_8;
/** Reusable empty input stream. */
+ @SuppressWarnings("resource") // Intentional singleton; read() always
returns -1 and close() is a no-op
public static final InputStream EMPTY_INPUT_STREAM = new InputStream() {
@Override
public int read() {
@@ -82,6 +83,7 @@ public class IoUtils {
}
/** Reusable empty reader. */
+ @SuppressWarnings("resource") // Intentional singleton; read() always
returns -1 and close() is a no-op
public static final Reader EMPTY_READER = new Reader() {
@Override
public void close() throws IOException { /* no-op */ }
@@ -1042,6 +1044,9 @@ public class IoUtils {
* The reader wrapped in a {@link BufferedReader}, or the original
{@link Reader} if it's already a buffered
* reader.
*/
+ @SuppressWarnings({
+ "resource" // Caller takes ownership of the returned Reader
+ })
public static Reader toBufferedReader(Reader r) {
if (r == null || r instanceof BufferedReader || r instanceof
StringReader)
return r;
diff --git
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
index c0daa4d589..cf768919eb 100644
---
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
+++
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/internal/ConfigMap.java
@@ -482,9 +482,6 @@ public class ConfigMap implements ConfigStoreListener {
return this;
}
- @SuppressWarnings({
- "null" // Null analysis not applicable to this config listener
method
- })
@Override /* Overridden from ConfigStoreListener */
public void onChange(String newContents) {
ConfigEvents changes2 = null;
@@ -499,7 +496,7 @@ public class ConfigMap implements ConfigStoreListener {
} catch (IOException e) {
throw toRex(e);
}
- if (nn(changes2) && ! changes2.isEmpty())
+ if (changes2 != null && ! changes2.isEmpty())
signal(changes2);
}
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
index 0750b2a9be..41ddb2b486 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
@@ -68,6 +68,7 @@ import org.apache.juneau.swap.*;
*/
@Bean(properties =
"innerClass,elementType,keyType,valueType,notABeanReason,initException,beanMeta")
@SuppressWarnings({
+ "deprecation", // Mutaters is deprecated but still used here pending
full migration to Converter.INSTANCE
"java:S1200", // Class has 23 dependencies, acceptable for this core
reflection metadata class
"java:S1452" // Wildcard required - ClassMeta<?>, ObjectSwap<T,?>,
Mutater<T,?>, etc. for element/component types
})
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartParserSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartParserSession.java
index c4b2cdf204..b4c0e5e6d7 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartParserSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartParserSession.java
@@ -18,7 +18,7 @@ package org.apache.juneau.httppart;
import org.apache.juneau.*;
import org.apache.juneau.parser.*;
-import org.apache.juneau.reflect.*;
+import org.apache.juneau.commons.conversion.BasicConverter;
/**
* Session object that lives for the duration of a single use of {@link
SimplePartParser}.
@@ -35,6 +35,6 @@ public class SimplePartParserSession extends
BaseHttpPartParserSession {
@Override /* Overridden from HttpPartParserSession */
public <T> T parse(HttpPartType partType, HttpPartSchema schema, String
in, ClassMeta<T> toType) throws ParseException, SchemaValidationException {
- return Mutaters.fromString(toType.inner(), in);
+ return BasicConverter.INSTANCE.to(in, toType.inner());
}
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartSerializer.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartSerializer.java
index 101cb53f18..759acc0265 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartSerializer.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartSerializer.java
@@ -25,7 +25,7 @@ import org.apache.juneau.commons.collections.*;
* An implementation of {@link HttpPartSerializer} that simply serializes
everything using {@link Object#toString()}.
*
* <p>
- * More precisely, uses the {@link
org.apache.juneau.reflect.Mutaters#toString(Object)} method to stringify
objects.
+ * More precisely, uses {@link
org.apache.juneau.commons.conversion.Converter#INSTANCE} to stringify objects.
*
* <h5 class='section'>Notes:</h5><ul>
* <li class='note'>This class is thread safe and reusable.
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartSerializerSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartSerializerSession.java
index e50c4191a9..602a9aaa09 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartSerializerSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/SimplePartSerializerSession.java
@@ -16,7 +16,7 @@
*/
package org.apache.juneau.httppart;
-import org.apache.juneau.reflect.*;
+import org.apache.juneau.commons.conversion.BasicConverter;
/**
* Session object that lives for the duration of a single use of {@link
SimplePartSerializer}.
@@ -33,6 +33,6 @@ import org.apache.juneau.reflect.*;
public class SimplePartSerializerSession extends BaseHttpPartSerializerSession
{
@Override /* Overridden from PartSerializer */
public String serialize(HttpPartType type, HttpPartSchema schema,
Object value) {
- return Mutaters.toString(value);
+ return BasicConverter.INSTANCE.to(value, String.class);
}
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ConverterUtils.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ConverterUtils.java
index 921633ff63..72f4189cb9 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ConverterUtils.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ConverterUtils.java
@@ -238,7 +238,7 @@ public class ConverterUtils {
* @return A new {@link Lists} containing the converted elements.
*/
public static <T> Lists<T> toListBuilder(Object value, Class<T> type) {
- return listb(type).elementFunction(o ->
GenericConverter.INSTANCE.convertTo(type, o)).addAny(value);
+ return listb(type).elementFunction(o ->
BeanContextConverter.INSTANCE.to(o, type)).addAny(value);
}
/**
@@ -261,8 +261,8 @@ public class ConverterUtils {
*/
public static <K,V> Maps<K,V> toMapBuilder(Object value, Class<K>
keyType, Class<V> valueType) {
return mapb(keyType, valueType)
- .keyFunction(o ->
GenericConverter.INSTANCE.convertTo(keyType, o))
- .valueFunction(o ->
GenericConverter.INSTANCE.convertTo(valueType, o))
+ .keyFunction(o -> BeanContextConverter.INSTANCE.to(o,
keyType))
+ .valueFunction(o -> BeanContextConverter.INSTANCE.to(o,
valueType))
.addAny(value);
}
@@ -287,6 +287,6 @@ public class ConverterUtils {
* @return A new {@link Sets} containing the converted elements.
*/
public static <T> Sets<T> toSetBuilder(Object value, Class<T> type) {
- return setb(type).elementFunction(o ->
GenericConverter.INSTANCE.convertTo(type, o)).addAny(value);
+ return setb(type).elementFunction(o ->
BeanContextConverter.INSTANCE.to(o, type)).addAny(value);
}
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/Mutaters.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/Mutaters.java
index defaaaa2cb..43a6460a29 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/Mutaters.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/reflect/Mutaters.java
@@ -29,7 +29,9 @@ import org.apache.juneau.commons.reflect.*;
/**
* Cache of object that convert POJOs to and from common types such as
strings, readers, and input streams.
*
+ * @deprecated Use {@link
org.apache.juneau.commons.conversion.Converter#INSTANCE} instead.
*/
+@Deprecated
public class Mutaters {
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
index dac79b4d4f..d8d6b879e3 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
@@ -30,7 +30,7 @@ import java.util.function.*;
import org.apache.juneau.*;
import org.apache.juneau.commons.lang.*;
import org.apache.juneau.httppart.*;
-import org.apache.juneau.reflect.*;
+import org.apache.juneau.commons.conversion.BasicConverter;
import org.apache.juneau.serializer.*;
import org.apache.juneau.svl.*;
import org.apache.juneau.utils.*;
@@ -221,9 +221,9 @@ public class UonSerializerSession extends
WriterSerializerSession implements Htt
var cm = getClassMetaForObject(value);
if (nn(cm) && (schema == null || schema.getType() ==
HttpPartDataType.NO_TYPE)) {
if (cm.isNumber() || cm.isBoolean())
- return Mutaters.toString(value);
+ return
BasicConverter.INSTANCE.to(value, String.class);
if (cm.isString()) {
- var s = Mutaters.toString(value);
+ var s =
BasicConverter.INSTANCE.to(value, String.class);
if (s.isEmpty() || !
UonUtils.needsQuotes(s))
return s;
}
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/GenericConverter.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/BeanContextConverter.java
similarity index 76%
rename from
juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/GenericConverter.java
rename to
juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/BeanContextConverter.java
index 1861c14b8b..c6c5385dcb 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/GenericConverter.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/BeanContextConverter.java
@@ -16,6 +16,8 @@
*/
package org.apache.juneau.utils;
+import java.lang.reflect.*;
+
import org.apache.juneau.*;
import org.apache.juneau.commons.conversion.*;
@@ -54,7 +56,7 @@ import org.apache.juneau.commons.conversion.*;
"java:S6541", // Stateless converter, singleton for convenience
"java:S6542" // Singleton required for stateless Converter;
thread-safe shared instance
})
-public class GenericConverter implements Converter {
+public class BeanContextConverter implements Converter {
/**
* Singleton instance of the generic converter.
@@ -62,12 +64,12 @@ public class GenericConverter implements Converter {
* <p>
* This instance can be safely shared across multiple threads and
reused for all conversion operations.
*/
- public static final GenericConverter INSTANCE = new GenericConverter();
+ public static final BeanContextConverter INSTANCE = new
BeanContextConverter();
/**
* Constructor.
*/
- private GenericConverter() {}
+ private BeanContextConverter() {}
/**
* Converts the specified object to the specified type.
@@ -87,15 +89,34 @@ public class GenericConverter implements Converter {
* <li>Object swap conversions
* <li>And many more...
* </ul>
+ * @param o The object to convert.
+ * @param type The target class type.
*
* @param <T> The target type to convert to.
- * @param type The target class type.
- * @param o The object to convert.
* @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 convertTo(Class<T> type, Object o) {
+ public <T> T to(Object o, Class<T> type) {
return BeanContext.DEFAULT_SESSION.convertToType(o, type);
}
+
+ /**
+ * Converts the specified object to the specified parameterized type.
+ *
+ * <p>
+ * This method delegates to the default {@link BeanContext} session for
the actual conversion logic,
+ * supporting complex parameterized types such as collections and maps.
+ *
+ * @param o The object to convert.
+ * @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.
+ */
+ @Override
+ public <T> T to(Object o, Type mainType, Type... args) {
+ return BeanContext.DEFAULT_SESSION.convertToType(o, mainType,
args);
+ }
}
diff --git
a/juneau-examples/juneau-examples-rest/src/main/resources/META-INF/persistence.xml
b/juneau-examples/juneau-examples-rest/src/main/resources/META-INF/persistence.xml
deleted file mode 100644
index 939abb3f01..0000000000
---
a/juneau-examples/juneau-examples-rest/src/main/resources/META-INF/persistence.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<persistence
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
- version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">
- <persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
- <class>org.apache.juneau.examples.rest.petstore.dto.Pet</class>
-
<class>org.apache.juneau.examples.rest.petstore.dto.Order</class>
- <class>org.apache.juneau.examples.rest.petstore.dto.User</class>
- <properties>
- <property name="javax.persistence.jdbc.driver"
value="org.apache.derby.jdbc.EmbeddedDriver" />
- <property name="javax.persistence.jdbc.url"
value="jdbc:derby:target/derby/testDB;create=true" />
- <property name="javax.persistence.jdbc.user" value="" />
- <property name="javax.persistence.jdbc.password"
value="" />
- <property name="hibernate.dialect"
value="org.hibernate.dialect.DerbyDialect" />
- <property name="hibernate.hbm2ddl.auto"
value="create-drop" />
- <property name="show_sql" value="true" />
- <property
name="hibernate.temp.use_jdbc_metadata_defaults" value="false" />
- </properties>
- </persistence-unit>
-</persistence>
\ No newline at end of file
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remote/RrpcServlet.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remote/RrpcServlet.java
index 4e1e72806a..03c2b822f7 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remote/RrpcServlet.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/remote/RrpcServlet.java
@@ -37,7 +37,7 @@ import org.apache.juneau.http.header.*;
import org.apache.juneau.http.remote.*;
import org.apache.juneau.http.response.*;
import org.apache.juneau.parser.*;
-import org.apache.juneau.reflect.*;
+import org.apache.juneau.commons.conversion.BasicConverter;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.servlet.*;
@@ -185,7 +185,7 @@ public abstract class RrpcServlet extends BasicRestServlet {
} else {
t.child(tr(th("Index"), th("Type"), th("Value")));
for (var i = 0; i < types.length; i++) {
- String type = Mutaters.toString(types[i]);
+ String type =
BasicConverter.INSTANCE.to(types[i], String.class);
t.child(tr(td(i), td(type),
td(input().name(String.valueOf(i)).type("text"))));
}
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bson/BsonOutputStream_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bson/BsonOutputStream_Test.java
index 36f4ba37f0..a80eb51368 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/bson/BsonOutputStream_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/bson/BsonOutputStream_Test.java
@@ -28,6 +28,9 @@ import org.junit.jupiter.api.*;
/**
* Tests for {@link BsonOutputStream}.
*/
+@SuppressWarnings({
+ "resource" // BsonOutputStream is intentionally not closed in unit tests
+})
class BsonOutputStream_Test extends TestBase {
@Test
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Lists_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Lists_Test.java
index f0d67673f0..c3cdcc69fc 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Lists_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Lists_Test.java
@@ -346,16 +346,19 @@ class Lists_Test extends TestBase {
void k02_elementFunction_withConverter() {
var converter = new
org.apache.juneau.commons.conversion.Converter() {
@Override
- public <T> T convertTo(Class<T> type, Object o) {
- if (type == Integer.class && o instanceof
String) {
+ public <T> T to(Object o, Class<T> type) {
+ if (type == Integer.class && o instanceof
String)
return
type.cast(Integer.parseInt((String)o));
- }
+ return null;
+ }
+ @Override
+ public <T> T to(Object o, java.lang.reflect.Type
mainType, java.lang.reflect.Type...args) {
return null;
}
};
var list = Lists.create(Integer.class)
- .elementFunction(o ->
converter.convertTo(Integer.class, o))
+ .elementFunction(o -> converter.to(o, Integer.class))
.addAny("1", "2", "3")
.build();
@@ -366,26 +369,33 @@ class Lists_Test extends TestBase {
void k03_elementFunction_multipleConverters() {
var converter1 = new
org.apache.juneau.commons.conversion.Converter() {
@Override
- public <T> T convertTo(Class<T> type, Object o) {
- return null; // Doesn't handle this
+ public <T> T to(Object o, Class<T> type) {
+ return null;
+ }
+ @Override
+ public <T> T to(Object o, java.lang.reflect.Type
mainType, java.lang.reflect.Type...args) {
+ return null;
}
};
var converter2 = new
org.apache.juneau.commons.conversion.Converter() {
@Override
- public <T> T convertTo(Class<T> type, Object o) {
- if (type == Integer.class && o instanceof
String) {
+ public <T> T to(Object o, Class<T> type) {
+ if (type == Integer.class && o instanceof
String)
return
type.cast(Integer.parseInt((String)o));
- }
+ return null;
+ }
+ @Override
+ public <T> T to(Object o, java.lang.reflect.Type
mainType, java.lang.reflect.Type...args) {
return null;
}
};
var list = Lists.create(Integer.class)
.elementFunction(o -> {
- Integer result =
converter1.convertTo(Integer.class, o);
+ Integer result = converter1.to(o,
Integer.class);
if (result != null) return result;
- return converter2.convertTo(Integer.class, o);
+ return converter2.to(o, Integer.class);
})
.addAny("1", "2")
.build();
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Sets_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Sets_Test.java
index 957c138a0f..a33dff4c06 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Sets_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Sets_Test.java
@@ -371,16 +371,19 @@ class Sets_Test extends TestBase {
void l02_elementFunction_withConverter() {
var converter = new
org.apache.juneau.commons.conversion.Converter() {
@Override
- public <T> T convertTo(Class<T> type, Object o) {
- if (type == Integer.class && o instanceof
String) {
+ public <T> T to(Object o, Class<T> type) {
+ if (type == Integer.class && o instanceof
String)
return
type.cast(Integer.parseInt((String)o));
- }
+ return null;
+ }
+ @Override
+ public <T> T to(Object o, java.lang.reflect.Type
mainType, java.lang.reflect.Type...args) {
return null;
}
};
var set = Sets.create(Integer.class)
- .elementFunction(o ->
converter.convertTo(Integer.class, o))
+ .elementFunction(o -> converter.to(o, Integer.class))
.addAny("1", "2", "3")
.build();
@@ -391,26 +394,33 @@ class Sets_Test extends TestBase {
void l03_elementFunction_multipleConverters() {
var converter1 = new
org.apache.juneau.commons.conversion.Converter() {
@Override
- public <T> T convertTo(Class<T> type, Object o) {
- return null; // Doesn't handle this
+ public <T> T to(Object o, Class<T> type) {
+ return null;
+ }
+ @Override
+ public <T> T to(Object o, java.lang.reflect.Type
mainType, java.lang.reflect.Type...args) {
+ return null;
}
};
var converter2 = new
org.apache.juneau.commons.conversion.Converter() {
@Override
- public <T> T convertTo(Class<T> type, Object o) {
- if (type == Integer.class && o instanceof
String) {
+ public <T> T to(Object o, Class<T> type) {
+ if (type == Integer.class && o instanceof
String)
return
type.cast(Integer.parseInt((String)o));
- }
+ return null;
+ }
+ @Override
+ public <T> T to(Object o, java.lang.reflect.Type
mainType, java.lang.reflect.Type...args) {
return null;
}
};
var set = Sets.create(Integer.class)
.elementFunction(o -> {
- Integer result =
converter1.convertTo(Integer.class, o);
+ Integer result = converter1.to(o,
Integer.class);
if (result != null) return result;
- return converter2.convertTo(Integer.class, o);
+ return converter2.to(o, Integer.class);
})
.addAny("1", "2")
.build();
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
new file mode 100644
index 0000000000..be46a4a37c
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/BasicConverter_Test.java
@@ -0,0 +1,765 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.conversion;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+@SuppressWarnings({
+ "unused" // Test helper classes have fields read only via assertions
+})
+
+class BasicConverter_Test extends TestBase {
+
+ private static final BasicConverter C = BasicConverter.INSTANCE;
+
+
//====================================================================================================
+ // a - Number conversions
+
//====================================================================================================
+
+ @Test void a01_numberToNumber() {
+ assertEquals(42, C.to(42L, Integer.class));
+ assertEquals(42L, C.to(42, Long.class));
+ assertEquals((short) 42, C.to(42, Short.class));
+ assertEquals(42f, C.to(42, Float.class));
+ assertEquals(42d, C.to(42, Double.class));
+ assertEquals((byte) 42, C.to(42, Byte.class));
+ assertEquals(42, C.to(42L, AtomicInteger.class).get());
+ assertEquals(42L, C.to(42, AtomicLong.class).get());
+ }
+
+ @Test void a02_booleanToNumber() {
+ assertEquals(1, C.to(true, Integer.class));
+ assertEquals(0, C.to(false, Integer.class));
+ assertEquals(1L, C.to(true, Long.class));
+ assertEquals(0L, C.to(false, Long.class));
+ assertEquals((short) 1, C.to(true, Short.class));
+ assertEquals((short) 0, C.to(false, Short.class));
+ assertEquals(1f, C.to(true, Float.class));
+ assertEquals(0f, C.to(false, Float.class));
+ assertEquals(1d, C.to(true, Double.class));
+ assertEquals(0d, C.to(false, Double.class));
+ assertEquals((byte) 1, C.to(true, Byte.class));
+ assertEquals((byte) 0, C.to(false, Byte.class));
+ assertEquals(1, C.to(true, AtomicInteger.class).get());
+ assertEquals(0, C.to(false, AtomicInteger.class).get());
+ assertEquals(1L, C.to(true, AtomicLong.class).get());
+ assertEquals(0L, C.to(false, AtomicLong.class).get());
+ }
+
+ @Test void a03_stringToNumber() {
+ assertEquals(42, C.to("42", Integer.class));
+ assertEquals(42L, C.to("42", Long.class));
+ assertEquals((short) 42, C.to("42", Short.class));
+ assertEquals(42f, C.to("42", Float.class));
+ assertEquals(42d, C.to("42", Double.class));
+ assertEquals((byte) 42, C.to("42", Byte.class));
+ }
+
+ @Test void a04_primitiveTargets() {
+ assertEquals(42, C.to("42", int.class));
+ assertEquals(42L, C.to("42", long.class));
+ assertEquals((short) 42, C.to("42", short.class));
+ assertEquals(42f, C.to("42", float.class));
+ assertEquals(42d, C.to("42", double.class));
+ assertEquals((byte) 42, C.to("42", byte.class));
+ }
+
+ @Test void a05_numberAndBooleanToUnsupportedNumericType() {
+ // Number → BigDecimal: hits findNumberFromNumber line 234
false branch (not AtomicLong), returns null
+ assertThrows(InvalidConversionException.class, () -> C.to(42,
java.math.BigDecimal.class));
+ // Boolean → BigDecimal: hits findNumberFromBoolean line 246
false branch (not AtomicLong), returns null
+ assertThrows(InvalidConversionException.class, () -> C.to(true,
java.math.BigDecimal.class));
+ }
+
+
//====================================================================================================
+ // b - Boolean conversions
+
//====================================================================================================
+
+ @Test void b01_numberToBoolean() {
+ assertEquals(true, C.to(1, Boolean.class));
+ assertEquals(false, C.to(0, Boolean.class));
+ assertEquals(true, C.to(-1, Boolean.class));
+ assertEquals(true, C.to(42, Boolean.class));
+ }
+
+ @Test void b02_stringToBoolean() {
+ assertEquals(true, C.to("true", Boolean.class));
+ assertEquals(false, C.to("false", Boolean.class));
+ assertEquals(false, C.to("xyz", Boolean.class));
+ assertNull(C.to("", Boolean.class));
+ assertNull(C.to("null", Boolean.class));
+ }
+
+ @Test void b03_primitiveBoolean() {
+ assertEquals(true, C.to("true", boolean.class));
+ assertEquals(false, C.to(0, boolean.class));
+ }
+
+ @Test void b04_unconvertibleToBoolean() {
+ assertThrows(InvalidConversionException.class, () -> C.to(new
Object(), Boolean.class));
+ }
+
+
//====================================================================================================
+ // c - Character conversions
+
//====================================================================================================
+
+ @Test void c01_stringToChar() {
+ assertEquals('A', C.to("A", Character.class));
+ assertNull(C.to("AB", Character.class));
+ }
+
+ @Test void c02_numberToChar() {
+ assertEquals('A', C.to(65, Character.class));
+ }
+
+ @Test void c03_primitiveChar() {
+ assertEquals('X', C.to("X", char.class));
+ }
+
+ @Test void c04_unconvertibleToCharacter() {
+ assertThrows(InvalidConversionException.class, () -> C.to(true,
Character.class));
+ }
+
+
//====================================================================================================
+ // d - String conversions
+
//====================================================================================================
+
+ @Test void d01_objectToString() {
+ assertEquals("42", C.to(42, String.class));
+ assertEquals("true", C.to(true, String.class));
+ assertEquals("3.14", C.to(3.14, String.class));
+ }
+
+ @Test void d02_arrayToString() {
+ assertEquals("[1, 2, 3]", C.to(new int[]{1, 2, 3},
String.class));
+ assertEquals("[1, 2, 3]", C.to(new long[]{1L, 2L, 3L},
String.class));
+ assertEquals("[1.0, 2.0]", C.to(new double[]{1.0, 2.0},
String.class));
+ assertEquals("[1.0, 2.0]", C.to(new float[]{1.0f, 2.0f},
String.class));
+ assertEquals("[true, false]", C.to(new boolean[]{true, false},
String.class));
+ assertEquals("[1, 2, 3]", C.to(new byte[]{1, 2, 3},
String.class));
+ assertEquals("[1, 2, 3]", C.to(new short[]{1, 2, 3},
String.class));
+ assertEquals("[a, b, c]", C.to(new char[]{'a', 'b', 'c'},
String.class));
+ assertEquals("[a, b, c]", C.to(new String[]{"a", "b", "c"},
String.class));
+ }
+
+ @Test void d03_collectionToString() {
+ assertEquals("[a, b]", C.to(List.of("a", "b"), String.class));
+ }
+
+
//====================================================================================================
+ // e - Enum conversions
+
//====================================================================================================
+
+ enum E01_TestEnum { FOO, BAR, BAZ }
+
+ @Test void e01_stringToEnum() {
+ assertEquals(E01_TestEnum.FOO, C.to("FOO", E01_TestEnum.class));
+ assertEquals(E01_TestEnum.BAR, C.to("BAR", E01_TestEnum.class));
+ assertEquals(E01_TestEnum.BAZ, C.to("BAZ", E01_TestEnum.class));
+ }
+
+ @Test void e02_unconvertibleToEnum() {
+ assertThrows(InvalidConversionException.class, () -> C.to(42,
E01_TestEnum.class));
+ }
+
+
//====================================================================================================
+ // f - Collection conversions
+
//====================================================================================================
+
+ @Test void f01_listToList() {
+ var a = C.to(List.of("a", "b", "c"), List.class);
+ assertNotNull(a);
+ assertEquals(3, a.size());
+ assertEquals("a", a.get(0));
+ }
+
+ @Test void f02_listToListTyped() {
+ var a = C.to(List.of("1", "2", "3"), List.class, Integer.class);
+ assertNotNull(a);
+ assertEquals(3, ((List<?>) a).size());
+ assertEquals(1, ((List<?>) a).get(0));
+ assertInstanceOf(Integer.class, ((List<?>) a).get(0));
+ }
+
+ @Test void f03_arrayToList() {
+ var a = C.to(new String[]{"x", "y"}, List.class);
+ assertNotNull(a);
+ assertEquals(2, ((List<?>) a).size());
+ assertEquals("x", ((List<?>) a).get(0));
+ }
+
+ @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);
+ assertNotNull(a);
+ assertEquals(List.of(1, 2, 3), a);
+ }
+
+ @Test void f04_listToSet() {
+ var a = C.to(List.of("a", "b", "a"), Set.class);
+ assertNotNull(a);
+ assertInstanceOf(Set.class, a);
+ assertEquals(2, ((Set<?>) a).size());
+ }
+
+ @Test void f05_listToSortedSet() {
+ var a = C.to(List.of("c", "a", "b"), SortedSet.class);
+ assertNotNull(a);
+ assertInstanceOf(SortedSet.class, a);
+ assertEquals("a", ((SortedSet<?>) a).first());
+ }
+
+ @Test void f06_primitiveArrayToList() {
+ var a = C.to(new int[]{1, 2, 3}, List.class);
+ assertNotNull(a);
+ assertEquals(3, ((List<?>) a).size());
+ assertEquals(1, ((List<?>) a).get(0));
+ }
+
+ @Test void f07_collectionTargetVariants() {
+ var src = List.of("a", "b");
+ assertInstanceOf(Collection.class, C.to(src, Collection.class));
+ assertInstanceOf(List.class, C.to(src, Iterable.class));
+ assertInstanceOf(List.class, C.to(src, AbstractList.class));
+ assertInstanceOf(LinkedHashSet.class, C.to(src,
LinkedHashSet.class));
+ assertInstanceOf(Set.class, C.to(src, AbstractSet.class));
+ assertInstanceOf(NavigableSet.class, C.to(src,
NavigableSet.class));
+ assertInstanceOf(TreeSet.class, C.to(src, TreeSet.class));
+ }
+
+ @Test void f09_listToQueue() {
+ var a = C.to(List.of("a", "b"), Queue.class);
+ assertNotNull(a);
+ assertInstanceOf(Queue.class, a);
+ assertEquals(2, ((Queue<?>) a).size());
+ }
+
+ @Test void f10_listToConcreteCollection() {
+ var a = C.to(List.of("a", "b"), ArrayDeque.class);
+ assertNotNull(a);
+ assertInstanceOf(ArrayDeque.class, a);
+ assertEquals(2, ((ArrayDeque<?>) a).size());
+ }
+
+ public static class F11_NoDefaultCtorCollection extends
ArrayList<Object> {
+ private static final long serialVersionUID = 1L;
+ public F11_NoDefaultCtorCollection(int initialCapacity) {
super(initialCapacity); }
+ }
+
+ @Test void f11_listToCollectionWithNoDefaultCtor() {
+ var a = (Collection<?>) C.to(List.of("a", "b"),
F11_NoDefaultCtorCollection.class);
+ assertNotNull(a);
+ assertInstanceOf(ArrayList.class, a);
+ assertEquals(2, a.size());
+ }
+
+ @Test void f12_arrayToCollectionInterface() {
+ // Array input forces newCollection(Collection.class) — hits
line 336 B=true branch
+ var a = C.to(new String[]{"a", "b"}, Collection.class);
+ assertNotNull(a);
+ assertInstanceOf(Collection.class, a);
+ assertEquals(2, ((Collection<?>) a).size());
+ }
+
+ @Test void f13_arrayToAbstractList() {
+ // Array input forces newCollection(AbstractList.class) — hits
line 336 D=true branch
+ var a = C.to(new String[]{"a", "b"}, AbstractList.class);
+ assertNotNull(a);
+ assertInstanceOf(AbstractList.class, a);
+ assertEquals(2, ((AbstractList<?>) a).size());
+ }
+
+ @Test void f14_listToDeque() {
+ // Hits line 342 B=true branch (Deque.class)
+ var a = C.to(List.of("a", "b"), Deque.class);
+ assertNotNull(a);
+ assertInstanceOf(Deque.class, a);
+ assertEquals(2, ((Deque<?>) a).size());
+ }
+
+ @Test void f15_listToLinkedList() {
+ // Hits line 342 C=true branch (LinkedList.class)
+ var a = C.to(List.of("a", "b"), LinkedList.class);
+ assertNotNull(a);
+ assertInstanceOf(LinkedList.class, a);
+ assertEquals(2, ((LinkedList<?>) a).size());
+ }
+
+
//====================================================================================================
+ // g - Map conversions
+
//====================================================================================================
+
+ @Test void g01_mapToMap() {
+ var a = C.to(Map.of("a", 1, "b", 2), Map.class);
+ assertNotNull(a);
+ assertInstanceOf(Map.class, a);
+ assertEquals(2, ((Map<?, ?>) a).size());
+ }
+
+ @Test void g02_mapToMapTyped() {
+ var a = C.to(Map.of(1, "100", 2, "200"), Map.class,
String.class, Integer.class);
+ assertNotNull(a);
+ var map = (Map<?, ?>) a;
+ assertTrue(map.containsKey("1") || map.containsKey("2"));
+ for (var v : map.values())
+ assertInstanceOf(Integer.class, v);
+ }
+
+ @Test void g03_mapToSortedMap() {
+ var a = C.to(Map.of("c", 3, "a", 1, "b", 2), SortedMap.class);
+ assertNotNull(a);
+ assertInstanceOf(SortedMap.class, a);
+ assertEquals("a", ((SortedMap<?, ?>) a).firstKey());
+ }
+
+ @Test void g04_mapToHashMap() {
+ var a = C.to(Map.of("a", 1), HashMap.class);
+ assertNotNull(a);
+ assertInstanceOf(HashMap.class, a);
+ assertEquals(1, ((HashMap<?, ?>) a).size());
+ }
+
+ @Test void g05_mapToConcreteMap() {
+ var a = C.to(Map.of("a", 1), ConcurrentHashMap.class);
+ assertNotNull(a);
+ assertInstanceOf(ConcurrentHashMap.class, a);
+ assertEquals(1, ((ConcurrentHashMap<?, ?>) a).size());
+ }
+
+ public static class G06_NoDefaultCtorMap extends HashMap<Object,Object>
{
+ private static final long serialVersionUID = 1L;
+ public G06_NoDefaultCtorMap(int initialCapacity) {
super(initialCapacity); }
+ }
+
+ @Test void g06_mapToMapWithNoDefaultCtor() {
+ var a = (Map<?, ?>) C.to(Map.of("a", 1),
G06_NoDefaultCtorMap.class);
+ assertNotNull(a);
+ assertInstanceOf(LinkedHashMap.class, a);
+ assertEquals(1, a.size());
+ }
+
+ @Test void g07_mapToLinkedHashMap() {
+ // Hits line 370 B=true branch (LinkedHashMap.class)
+ var a = (Map<?, ?>) C.to(Map.of("a", 1), LinkedHashMap.class);
+ assertNotNull(a);
+ assertInstanceOf(LinkedHashMap.class, a);
+ assertEquals(1, a.size());
+ }
+
+ @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);
+ assertNotNull(a);
+ assertInstanceOf(AbstractMap.class, a);
+ assertEquals(1, a.size());
+ }
+
+ @Test void g09_mapToNavigableMap() {
+ // Hits line 372 B=true branch (NavigableMap.class)
+ var a = (Map<?, ?>) C.to(Map.of("a", 1), NavigableMap.class);
+ assertNotNull(a);
+ assertInstanceOf(NavigableMap.class, a);
+ assertEquals(1, a.size());
+ }
+
+ @Test void g10_mapToTreeMap() {
+ // Hits line 372 C=true branch (TreeMap.class)
+ var a = (Map<?, ?>) C.to(Map.of("a", 1), TreeMap.class);
+ assertNotNull(a);
+ assertInstanceOf(TreeMap.class, a);
+ assertEquals(1, a.size());
+ }
+
+ @Test void g11_unconvertibleToMap() {
+ assertThrows(InvalidConversionException.class, () ->
C.to("hello", Map.class));
+ }
+
+
//====================================================================================================
+ // h - Array conversions
+
//====================================================================================================
+
+ @Test void h01_listToArray() {
+ var a = C.to(List.of("a", "b", "c"), String[].class);
+ assertNotNull(a);
+ assertArrayEquals(new String[]{"a", "b", "c"}, a);
+ }
+
+ @Test void h02_arrayToTypedArray() {
+ var a = C.to(new Object[]{"1", "2", "3"}, Integer[].class);
+ assertNotNull(a);
+ assertArrayEquals(new Integer[]{1, 2, 3}, a);
+ }
+
+ @Test void h03_listToIntArray() {
+ var a = C.to(List.of(1, 2, 3), int[].class);
+ assertNotNull(a);
+ assertArrayEquals(new int[]{1, 2, 3}, (int[]) a);
+ }
+
+ @Test void h04_unconvertibleToArray() {
+ assertThrows(InvalidConversionException.class, () -> C.to(42,
String[].class));
+ }
+
+
//====================================================================================================
+ // i - Reflection: static factory methods
+
//====================================================================================================
+
+ public static class I01_Target {
+ public final String value;
+ private I01_Target(String v) { value = v; }
+ public static I01_Target valueOf(String s) { return new
I01_Target(s); }
+ }
+
+ @Test void i01_staticValueOf() {
+ var a = C.to("hello", I01_Target.class);
+ assertNotNull(a);
+ assertEquals("hello", a.value);
+ }
+
+ public static class I02_Target {
+ public final String value;
+ private I02_Target(String v) { value = v; }
+ public static I02_Target fromString(String s) { return new
I02_Target(s); }
+ }
+
+ @Test void i02_staticFromString() {
+ var a = C.to("world", I02_Target.class);
+ assertNotNull(a);
+ assertEquals("world", a.value);
+ }
+
+ public static class I03_Target {
+ public final String value;
+ private I03_Target(String v) { value = v; }
+ public static I03_Target of(String s) { return new
I03_Target(s); }
+ }
+
+ @Test void i03_staticOf() {
+ var a = C.to("test", I03_Target.class);
+ assertNotNull(a);
+ assertEquals("test", a.value);
+ }
+
+ public static class I04_Target {
+ public final String value;
+ private I04_Target(String v) { value = v; }
+ public static I04_Target create(String s) { return new
I04_Target(s); }
+ }
+
+ @Test void i04_staticCreate() {
+ var a = C.to("create-test", I04_Target.class);
+ assertNotNull(a);
+ assertEquals("create-test", a.value);
+ }
+
+ public static class I05_Target {
+ public final String value;
+ private I05_Target(String v) { value = v; }
+ public static I05_Target parse(String s) { return new
I05_Target(s); }
+ }
+
+ @Test void i05_staticParse() {
+ var a = C.to("parse-test", I05_Target.class);
+ assertNotNull(a);
+ assertEquals("parse-test", a.value);
+ }
+
+ public static class I06_Target {
+ public final String value;
+ private I06_Target(String v) { value = v; }
+ public static I06_Target from(String s) { return new
I06_Target(s); }
+ }
+
+ @Test void i06_staticFrom() {
+ var a = C.to("from-test", I06_Target.class);
+ assertNotNull(a);
+ assertEquals("from-test", a.value);
+ }
+
+ public static class I07_Target {
+ public final String value;
+ private I07_Target(String v) { value = v; }
+ public static I07_Target forName(String s) { return new
I07_Target(s); }
+ }
+
+ @Test void i07_staticForName() {
+ var a = C.to("forName-test", I07_Target.class);
+ assertNotNull(a);
+ assertEquals("forName-test", a.value);
+ }
+
+ public static class I08_Target {
+ public final String value;
+ private I08_Target(String v) { value = v; }
+ public static I08_Target fromValue(String s) { return new
I08_Target(s); }
+ }
+
+ @Test void i08_staticFromValue() {
+ var a = C.to("fromValue-test", I08_Target.class);
+ assertNotNull(a);
+ assertEquals("fromValue-test", a.value);
+ }
+
+ public static class I09_Target {
+ public final String value;
+ private I09_Target(String v) { value = v; }
+ public static I09_Target builder(String s) { return new
I09_Target(s); }
+ }
+
+ @Test void i09_staticBuilder() {
+ var a = C.to("builder-test", I09_Target.class);
+ assertNotNull(a);
+ assertEquals("builder-test", a.value);
+ }
+
+ public static class I10_Target {
+ public final String value;
+ private I10_Target(String v) { value = v; }
+ public static I10_Target fromString(String s) { return new
I10_Target(s); }
+ }
+
+ @Test void i10_dynamicFromX() {
+ var a = C.to("fromString-test", I10_Target.class);
+ assertNotNull(a);
+ assertEquals("fromString-test", a.value);
+ }
+
+ public static class I11_Input {
+ public final String value;
+ public I11_Input(String v) { value = v; }
+ }
+
+ public static class I11_Target {
+ public final String value;
+ private I11_Target(String v) { value = v; }
+ public static I11_Target fromI11_Input(I11_Input s) { return
new I11_Target(s.value); }
+ }
+
+ @Test void i11_dynamicFromInputClassName() {
+ var a = C.to(new I11_Input("dynamic-from"), I11_Target.class);
+ assertNotNull(a);
+ assertEquals("dynamic-from", a.value);
+ }
+
+ public static class I12_Input {
+ public final String value;
+ public I12_Input(String v) { value = v; }
+ }
+
+ public static class I12_Target {
+ public final String value;
+ private I12_Target(String v) { value = v; }
+ public static I12_Target forI12_Input(I12_Input s) { return new
I12_Target(s.value); }
+ }
+
+ @Test void i12_dynamicForInputClassName() {
+ var a = C.to(new I12_Input("dynamic-for"), I12_Target.class);
+ assertNotNull(a);
+ assertEquals("dynamic-for", a.value);
+ }
+
+ public static class I13_Input {
+ public final String value;
+ public I13_Input(String v) { value = v; }
+ }
+
+ public static class I13_Target {
+ public final String value;
+ private I13_Target(String v) { value = v; }
+ public static I13_Target parseI13_Input(I13_Input s) { return
new I13_Target(s.value); }
+ }
+
+ @Test void i13_dynamicParseInputClassName() {
+ var a = C.to(new I13_Input("dynamic-parse"), I13_Target.class);
+ assertNotNull(a);
+ assertEquals("dynamic-parse", a.value);
+ }
+
+ public static class I14_DecoyStaticMethod {
+ public static String fromString(String s) { return s; } //
returns String, not I14_DecoyStaticMethod
+ }
+
+ @Test void i14_staticMethodWrongReturnType() {
+ assertThrows(InvalidConversionException.class, () ->
C.to("hello", I14_DecoyStaticMethod.class));
+ }
+
+
//====================================================================================================
+ // j - Reflection: public constructors
+
//====================================================================================================
+
+ public static class J01_Target {
+ public final String value;
+ public J01_Target(String v) { value = v; }
+ }
+
+ @Test void j01_constructorFromString() {
+ var a = C.to("ctor-test", J01_Target.class);
+ assertNotNull(a);
+ assertEquals("ctor-test", a.value);
+ }
+
+ public static class J02_Target {
+ public final int value;
+ public J02_Target(Integer v) { value = v; }
+ }
+
+ @Test void j02_constructorFromInteger() {
+ var a = C.to(99, J02_Target.class);
+ assertNotNull(a);
+ assertEquals(99, a.value);
+ }
+
+
//====================================================================================================
+ // k - Reflection: toX() instance methods
+
//====================================================================================================
+
+ public static class K01_Source {
+ private final int value;
+ public K01_Source(int v) { value = v; }
+ public Integer toInteger() { return value; }
+ }
+
+ @Test void k01_toXMethod() {
+ var a = C.to(new K01_Source(42), Integer.class);
+ assertNotNull(a);
+ assertEquals(42, a);
+ }
+
+ public static class K02_Source {
+ private final String value;
+ public K02_Source(String v) { value = v; }
+ public List<String> toList() { return
List.of(value.split(",")); }
+ }
+
+ @Test void k02_toXMethodReturningCollection() {
+ var a = C.to(new K02_Source("a,b,c"), List.class);
+ assertNotNull(a);
+ assertEquals(List.of("a", "b", "c"), a);
+ }
+
+ public static class K03_WrongReturnType {
+ public String toInteger() { return "not-an-integer"; } //
returns String, not Integer
+ }
+
+ @Test void k03_toXMethodWrongReturnType() {
+ // toInteger() exists but returns String — hits line 488 false
branch (hasReturnTypeParent fails)
+ assertThrows(InvalidConversionException.class, () -> C.to(new
K03_WrongReturnType(), Integer.class));
+ }
+
+
//====================================================================================================
+ // l - Special case conversions (TimeZone, Locale, String→Boolean)
+
//====================================================================================================
+
+ @Test void l01_stringToTimeZone() {
+ var a = C.to("GMT", TimeZone.class);
+ assertNotNull(a);
+ assertEquals("GMT", a.getID());
+ }
+
+ @Test void l02_stringToTimeZoneWithOffset() {
+ var a = C.to("America/New_York", TimeZone.class);
+ assertNotNull(a);
+ assertEquals("America/New_York", a.getID());
+ }
+
+ @Test void l03_timeZoneToString() {
+ var a = C.to(TimeZone.getTimeZone("PST"), String.class);
+ assertEquals("PST", a);
+ }
+
+ @Test void l04_timeZoneToNonString() {
+ assertThrows(InvalidConversionException.class, () ->
C.to(TimeZone.getTimeZone("PST"), Integer.class));
+ }
+
+ @Test void l05_stringToLocale() {
+ var a = C.to("en-US", Locale.class);
+ assertNotNull(a);
+ assertEquals("en", a.getLanguage());
+ assertEquals("US", a.getCountry());
+ }
+
+ @Test void l06_stringToLocaleWithUnderscore() {
+ var a = C.to("en_US", Locale.class);
+ assertNotNull(a);
+ assertEquals("en", a.getLanguage());
+ assertEquals("US", a.getCountry());
+ }
+
+ @Test void l07_stringToBooleanEmpty() {
+ assertNull(C.to("", Boolean.class));
+ }
+
+ @Test void l08_stringToBooleanNull() {
+ assertNull(C.to("null", Boolean.class));
+ }
+
+ @Test void l09_stringToBooleanTrue() {
+ assertEquals(true, C.to("true", Boolean.class));
+ }
+
+ @Test void l10_stringToBooleanFalse() {
+ assertEquals(false, C.to("false", Boolean.class));
+ }
+
+
//====================================================================================================
+ // m - Identity / assignability
+
//====================================================================================================
+
+ @Test void m01_identity() {
+ var a = "hello";
+ assertSame(a, C.to(a, String.class));
+ }
+
+ @Test void m02_widening() {
+ var a = new ArrayList<>(List.of("a", "b"));
+ var b = C.to(a, List.class);
+ assertSame(a, b);
+ }
+
+ @Test void m03_numberWidening() {
+ var a = Integer.valueOf(42);
+ var b = C.to(a, Number.class);
+ assertSame(a, b);
+ }
+
+
//====================================================================================================
+ // n - Null handling
+
//====================================================================================================
+
+ @Test void n01_nullInput() {
+ assertNull(C.to(null, String.class));
+ assertNull(C.to(null, Integer.class));
+ assertNull(C.to(null, List.class));
+ assertNull(C.to(null, Map.class));
+ assertNull(C.to(null, int.class));
+ }
+
+
//====================================================================================================
+ // o - No conversion available
+
//====================================================================================================
+
+ @Test void o01_noConversion() {
+ assertThrows(InvalidConversionException.class, () -> C.to(new
Object(), BasicConverter_Test.class));
+ }
+}
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
new file mode 100644
index 0000000000..91caefca46
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/CachingConverter_Test.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.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.*;
+
+class CachingConverter_Test extends TestBase {
+
+ private static final BasicConverter C = BasicConverter.INSTANCE;
+
+
//====================================================================================================
+ // a - canConvert
+
//====================================================================================================
+
+ @Test void a01_canConvertSameType() {
+ assertTrue(C.canConvert(String.class, String.class));
+ }
+
+ @Test void a02_canConvertKnownConversion() {
+ assertTrue(C.canConvert(String.class, Integer.class));
+ }
+
+ @Test void a03_canConvertNoConversionAvailable() {
+ assertFalse(C.canConvert(StringBuilder.class,
java.net.URI.class));
+ }
+
+
//====================================================================================================
+ // b - to(Object, Type, Type...)
+
//====================================================================================================
+
+ @Test void b01_toTypeNullInput() {
+ assertNull(C.to(null, (Type) List.class));
+ }
+
+ // Helper to get a ParameterizedType for List<String> via reflection.
+ @SuppressWarnings("unused")
+ private List<String> listOfStringField;
+ private static final Type LIST_OF_STRING;
+ static {
+ try {
+ LIST_OF_STRING =
CachingConverter_Test.class.getDeclaredField("listOfStringField").getGenericType();
+ } catch (NoSuchFieldException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ @Test void b02_toParameterizedType() {
+ var result = C.to(List.of("a", "b"), LIST_OF_STRING);
+ assertNotNull(result);
+ assertInstanceOf(List.class, result);
+ }
+
+ @Test void b03_toTypeWithPlainClass() {
+ var result = C.to(42, (Type) String.class);
+ assertEquals("42", result);
+ }
+
+ @Test void b04_toTypeNoConversionAvailable() {
+ assertThrows(InvalidConversionException.class, () -> C.to(new
StringBuilder("x"), (Type) java.net.URI.class));
+ }
+
+ @Test void b05_toTypeWithPlainClassArg() {
+ // args contains a plain Class (false branch of line 157
ternary)
+ var result = C.to(List.of("1", "2"), (Type) List.class, (Type)
Integer.class);
+ assertNotNull(result);
+ assertInstanceOf(List.class, result);
+ assertEquals(List.of(1, 2), result);
+ }
+
+ // Helper field used to obtain a ParameterizedType for use as an arg.
+ @SuppressWarnings("unused")
+ private List<String> listArgField;
+ private static final Type LIST_ARG_TYPE;
+ static {
+ try {
+ LIST_ARG_TYPE =
CachingConverter_Test.class.getDeclaredField("listArgField").getGenericType();
+ } catch (NoSuchFieldException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ @Test void b06_toTypeWithParameterizedTypeArg() {
+ // args contains a ParameterizedType (true branch of line 157
ternary); raw type List is extracted
+ var result = C.to(List.of(List.of("a", "b")), (Type)
List.class, LIST_ARG_TYPE);
+ assertNotNull(result);
+ assertInstanceOf(List.class, result);
+ }
+}
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Converter.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/Converter_Test.java
similarity index 51%
copy from
juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Converter.java
copy to
juneau-utest/src/test/java/org/apache/juneau/commons/conversion/Converter_Test.java
index 0bcf6da49c..a592d83fc5 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/Converter.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/conversion/Converter_Test.java
@@ -16,18 +16,26 @@
*/
package org.apache.juneau.commons.conversion;
-/**
- * Temporary interface. To be replaced with Mutator once that's part of the
common module.
- */
-public interface Converter {
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.lang.reflect.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+class Converter_Test extends TestBase {
+
+
//====================================================================================================
+ // a - default canConvert
+
//====================================================================================================
+
+ private static final Converter STUB = new Converter() {
+ @Override public <T> T to(Object o, Class<T> type) { return
type.cast(o); }
+ @Override public <T> T to(Object o, Type mainType, Type...
args) { return null; }
+ };
- /**
- * Converts the specified object to the specified type.
- *
- * @param <T> The type to convert to.
- * @param type The type to convert to.
- * @param o The object to convert.
- * @return The converted object, or <jk>null</jk> if the conversion is
not possible.
- */
- <T> T convertTo(Class<T> type, Object o);
+ @Test void a01_defaultCanConvertAlwaysTrue() {
+ assertTrue(STUB.canConvert(String.class, String.class));
+ assertTrue(STUB.canConvert(String.class, Integer.class));
+ }
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/io/CharSequenceReader_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/io/CharSequenceReader_Test.java
index e29550ecbe..4cd6f026ec 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/io/CharSequenceReader_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/io/CharSequenceReader_Test.java
@@ -21,6 +21,9 @@ import static org.junit.jupiter.api.Assertions.*;
import org.apache.juneau.*;
import org.junit.jupiter.api.*;
+@SuppressWarnings({
+ "resource" // Readers intentionally not closed in unit tests
+})
class CharSequenceReader_Test extends TestBase {
//====================================================================================================
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/io/NoCloseOutputStream_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/io/NoCloseOutputStream_Test.java
index c41d2238dd..540db7ded9 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/io/NoCloseOutputStream_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/io/NoCloseOutputStream_Test.java
@@ -23,6 +23,9 @@ import java.io.*;
import org.apache.juneau.*;
import org.junit.jupiter.api.*;
+@SuppressWarnings({
+ "resource" // NoCloseOutputStream wrappers intentionally not closed in
unit tests
+})
class NoCloseOutputStream_Test extends TestBase {
//====================================================================================================
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/io/NoCloseWriter_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/io/NoCloseWriter_Test.java
index 3c8842ae1c..8505017b4a 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/io/NoCloseWriter_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/io/NoCloseWriter_Test.java
@@ -23,6 +23,9 @@ import java.io.*;
import org.apache.juneau.*;
import org.junit.jupiter.api.*;
+@SuppressWarnings({
+ "resource" // NoCloseWriter wrappers intentionally not closed in unit
tests
+})
class NoCloseWriter_Test extends TestBase {
//====================================================================================================
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/io/ReaderInputStream_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/io/ReaderInputStream_Test.java
index 1d9efb4791..4fa3f9a06d 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/io/ReaderInputStream_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/io/ReaderInputStream_Test.java
@@ -24,6 +24,9 @@ import java.nio.charset.*;
import org.apache.juneau.*;
import org.junit.jupiter.api.*;
+@SuppressWarnings({
+ "resource" // Streams intentionally not closed in unit tests
+})
class ReaderInputStream_Test extends TestBase {
//====================================================================================================
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/io/StringBuilderWriter_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/io/StringBuilderWriter_Test.java
index a60d30e9fb..eee414af67 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/commons/io/StringBuilderWriter_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/io/StringBuilderWriter_Test.java
@@ -23,6 +23,9 @@ import java.io.*;
import org.apache.juneau.*;
import org.junit.jupiter.api.*;
+@SuppressWarnings({
+ "resource" // Writers intentionally not closed in unit tests
+})
class StringBuilderWriter_Test extends TestBase {
//====================================================================================================
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/hjson/HjsonTokenizer_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/hjson/HjsonTokenizer_Test.java
index 3229e8fd96..73f68b7de9 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/hjson/HjsonTokenizer_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/hjson/HjsonTokenizer_Test.java
@@ -33,6 +33,9 @@ import org.junit.jupiter.params.provider.MethodSource;
/**
* Tests for {@link HjsonTokenizer}.
*/
+@SuppressWarnings({
+ "resource" // ParserPipe intentionally not closed in unit tests
+})
class HjsonTokenizer_Test extends TestBase {
private static HjsonTokenizer tokenizer(String input) {
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/html/SimpleHtmlWriter_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/html/SimpleHtmlWriter_Test.java
index 4e32a8040b..6abefbff9b 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/html/SimpleHtmlWriter_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/html/SimpleHtmlWriter_Test.java
@@ -21,6 +21,9 @@ import org.junit.jupiter.api.*;
/**
* Tests for SimpleHtmlWriter fluent setter overrides.
*/
+@SuppressWarnings({
+ "resource" // Writers intentionally not closed in unit tests
+})
class SimpleHtmlWriter_Test extends TestBase {
@Test void a01_fluentChaining_tagMethods() {
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/uon/UonParserReader_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/uon/UonParserReader_Test.java
index dbaefb5e03..d2ef62cd85 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/uon/UonParserReader_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/uon/UonParserReader_Test.java
@@ -26,7 +26,8 @@ import org.apache.juneau.parser.*;
import org.junit.jupiter.api.*;
@SuppressWarnings({
- "java:S5961" // High assertion count acceptable in comprehensive test
+ "java:S5961", // High assertion count acceptable in comprehensive test
+ "resource" // Readers intentionally not closed in unit tests
})
class UonParserReader_Test extends TestBase {
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/utils/MutatersTest.java
b/juneau-utest/src/test/java/org/apache/juneau/utils/MutatersTest.java
index 093d7a883e..2e8eff62ef 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/utils/MutatersTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/utils/MutatersTest.java
@@ -23,7 +23,7 @@ import org.apache.juneau.*;
import org.junit.jupiter.api.*;
@SuppressWarnings({
- "java:S1172" // Unused parameters in tests are intentional
+ "java:S1172" // Unused parameters in tests are intentional
})
class MutatersTest extends TestBase {
diff --git a/scripts/coverage.py b/scripts/coverage.py
new file mode 100755
index 0000000000..cd7aeb7511
--- /dev/null
+++ b/scripts/coverage.py
@@ -0,0 +1,284 @@
+#!/usr/bin/env python3
+#
***************************************************************************************************************************
+# * Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file
+# * distributed with this work for additional information regarding copyright
ownership. The ASF licenses this file
+# * to you under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance
+# * with the License. You may obtain a copy of the License at
+# *
+# * http://www.apache.org/licenses/LICENSE-2.0
+# *
+# * Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an
+# * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the
+# * specific language governing permissions and limitations under the License.
+#
***************************************************************************************************************************
+"""
+Coverage reporter for Apache Juneau.
+
+Shows JaCoCo branch and instruction coverage for a source file or folder.
+
+Usage:
+ ./scripts/coverage.py <path> [options]
+
+Arguments:
+ path A source file (.java) or source folder to report on.
+ Paths can be absolute or relative to the repo root.
+
+Options:
+ --run, -r Re-run tests before reporting (updates the .exec data).
+ --branches, -b Show only lines with missed branches (default: show all
uncovered).
+ --help, -h Show this help message.
+
+Examples:
+ ./scripts/coverage.py
juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/
+ ./scripts/coverage.py
juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/BasicConverter.java
+ ./scripts/coverage.py
juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/conversion/
--run
+"""
+
+import subprocess
+import sys
+import xml.etree.ElementTree as ET
+from pathlib import Path
+
+REPO_ROOT = Path(__file__).resolve().parent.parent
+UTEST_MODULE = REPO_ROOT / "juneau-utest"
+UTEST_EXEC = UTEST_MODULE / "target" / "jacoco.exec"
+
+SRC_MARKERS = ["src/main/java", "src/test/java"]
+
+
+def die(msg):
+ print(f"ERROR: {msg}", file=sys.stderr)
+ sys.exit(1)
+
+
+def find_maven_module(path: Path) -> Path:
+ """Walk up from path to find the nearest directory containing a pom.xml."""
+ p = path if path.is_dir() else path.parent
+ while p != REPO_ROOT.parent:
+ if (p / "pom.xml").exists() and p != REPO_ROOT:
+ return p
+ p = p.parent
+ return None
+
+
+def path_to_jacoco_package(path: Path) -> tuple[str | None, str | None]:
+ """
+ Given a source path, return (jacoco_package, filename).
+ jacoco_package uses slash separators (e.g.
org/apache/juneau/commons/conversion).
+ filename is the .java filename, or None if path is a directory.
+ """
+ parts = path.parts
+ for marker in SRC_MARKERS:
+ marker_parts = tuple(marker.split("/"))
+ for i in range(len(parts) - len(marker_parts) + 1):
+ if parts[i:i + len(marker_parts)] == marker_parts:
+ remainder = parts[i + len(marker_parts):]
+ if path.is_file():
+ pkg = "/".join(remainder[:-1])
+ fname = remainder[-1]
+ else:
+ pkg = "/".join(remainder).rstrip("/")
+ fname = None
+ return pkg, fname
+ return None, None
+
+
+def run_tests():
+ """Re-run all tests in juneau-utest to refresh the .exec file."""
+ print("Running tests to refresh coverage data...")
+ result = subprocess.run(
+ ["mvn", "test", "-Drat.skip=true", "-q"],
+ cwd=UTEST_MODULE,
+ capture_output=True,
+ text=True
+ )
+ if result.returncode != 0:
+ print(result.stderr[-3000:], file=sys.stderr)
+ die("Tests failed. Fix failures before checking coverage.")
+ print("Tests passed.\n")
+
+
+def generate_report(module: Path) -> Path:
+ """Generate JaCoCo XML report for the given module using the utest exec
file."""
+ xml_path = module / "target" / "site" / "jacoco" / "jacoco.xml"
+ result = subprocess.run(
+ ["mvn", "jacoco:report", f"-Djacoco.dataFile={UTEST_EXEC}", "-q"],
+ cwd=module,
+ capture_output=True,
+ text=True
+ )
+ if result.returncode != 0:
+ print(result.stderr[-2000:], file=sys.stderr)
+ die(f"Failed to generate JaCoCo report for {module}.")
+ return xml_path
+
+
+def bar(covered, total, width=20):
+ """Render a simple ASCII progress bar."""
+ if total == 0:
+ filled = width
+ else:
+ filled = round(covered / total * width)
+ return "[" + "#" * filled + "." * (width - filled) + "]"
+
+
+def pct(covered, total):
+ if total == 0:
+ return "100%"
+ return f"{covered / total * 100:.0f}%"
+
+
+def report(xml_path: Path, pkg_filter: str, file_filter: str | None,
branches_only: bool):
+ """Parse jacoco.xml and print coverage for the matching package/file."""
+ if not xml_path.exists():
+ die(f"JaCoCo report not found at {xml_path}. Run with --run to
generate it.")
+
+ tree = ET.parse(xml_path)
+ root = tree.getroot()
+
+ matched_packages = []
+ for pkg in root.findall("package"):
+ name = pkg.get("name", "")
+ if pkg_filter and not (name == pkg_filter or
name.startswith(pkg_filter + "/")):
+ continue
+ matched_packages.append(pkg)
+
+ if not matched_packages:
+ die(f"No JaCoCo data found for package '{pkg_filter}'.\n"
+ f"Make sure the module is built and the exec file is up to date.")
+
+ # Collect per-file data
+ files_data = [] # list of (pkg_name, fname, lines_with_issues)
+ total_mb = total_cb = total_mi = total_ci = 0
+
+ for pkg in matched_packages:
+ pkg_name = pkg.get("name", "")
+ for sf in pkg.findall("sourcefile"):
+ fname = sf.get("name", "")
+ if file_filter and fname != file_filter:
+ continue
+
+ # Aggregate counters from <counter> elements
+ mb = cb = mi = ci = 0
+ for ctr in sf.findall("counter"):
+ t = ctr.get("type")
+ m = int(ctr.get("missed", 0))
+ c = int(ctr.get("covered", 0))
+ if t == "BRANCH":
+ mb, cb = m, c
+ elif t == "INSTRUCTION":
+ mi, ci = m, c
+
+ total_mb += mb
+ total_cb += cb
+ total_mi += mi
+ total_ci += ci
+
+ # Collect uncovered lines
+ uncovered = []
+ for line in sf.findall("line"):
+ ln = int(line.get("nr", 0))
+ lmb = int(line.get("mb", 0))
+ lmi = int(line.get("mi", 0))
+ if branches_only and lmb > 0:
+ lcb = int(line.get("cb", 0))
+ uncovered.append((ln, lmb, lmb + lcb, lmi))
+ elif not branches_only and (lmb > 0 or lmi > 0):
+ lcb = int(line.get("cb", 0))
+ uncovered.append((ln, lmb, lmb + lcb, lmi))
+
+ files_data.append((pkg_name, fname, mb, cb, mi, ci, uncovered))
+
+ if not files_data:
+ print(f"No data found for the specified path.")
+ return
+
+ # Print per-file results
+ for pkg_name, fname, mb, cb, mi, ci, uncovered in sorted(files_data):
+ branch_total = mb + cb
+ instr_total = mi + ci
+ branch_pct = pct(cb, branch_total)
+ instr_pct = pct(ci, instr_total)
+ print(f"\n{'='*70}")
+ print(f" {pkg_name.replace('/', '.')}.{fname.removesuffix('.java')}")
+ print(f"{'='*70}")
+ print(f" Branches: {bar(cb, branch_total)} {branch_pct:>4}
({cb}/{branch_total} covered, {mb} missed)")
+ print(f" Instructions: {bar(ci, instr_total)} {instr_pct:>4}
({ci}/{instr_total} covered, {mi} missed)")
+ if uncovered:
+ print(f"\n Uncovered lines:")
+ for ln, lmb, ltotal, lmi in sorted(uncovered):
+ parts = []
+ if lmb > 0:
+ parts.append(f"{lmb}/{ltotal} branches missed")
+ if lmi > 0:
+ parts.append(f"{lmi} instructions missed")
+ print(f" line {ln:4d}: {', '.join(parts)}")
+ else:
+ print(f"\n All lines covered!")
+
+ # Print summary if multiple files
+ if len(files_data) > 1:
+ branch_total = total_mb + total_cb
+ instr_total = total_mi + total_ci
+ print(f"\n{'='*70}")
+ print(f" TOTAL SUMMARY")
+ print(f"{'='*70}")
+ print(f" Branches: {bar(total_cb, branch_total)} {pct(total_cb,
branch_total):>4} ({total_cb}/{branch_total} covered, {total_mb} missed)")
+ print(f" Instructions: {bar(total_ci, instr_total)} {pct(total_ci,
instr_total):>4} ({total_ci}/{instr_total} covered, {total_mi} missed)")
+ print()
+
+
+def main():
+ args = sys.argv[1:]
+ if not args or "--help" in args or "-h" in args:
+ print(__doc__)
+ return 0
+
+ path_arg = None
+ do_run = False
+ branches_only = False
+
+ for arg in args:
+ if arg in ("--run", "-r"):
+ do_run = True
+ elif arg in ("--branches", "-b"):
+ branches_only = True
+ elif arg.startswith("-"):
+ die(f"Unknown option: {arg}")
+ else:
+ path_arg = arg
+
+ if not path_arg:
+ die("No path specified.")
+
+ path = Path(path_arg)
+ if not path.is_absolute():
+ path = REPO_ROOT / path
+ path = path.resolve()
+
+ if not path.exists():
+ die(f"Path does not exist: {path}")
+
+ module = find_maven_module(path)
+ if not module:
+ die(f"Could not determine Maven module for path: {path}")
+
+ pkg_filter, file_filter = path_to_jacoco_package(path)
+ if pkg_filter is None:
+ die(f"Path does not appear to be under src/main/java or src/test/java:
{path}")
+
+ if do_run:
+ run_tests()
+
+ if not UTEST_EXEC.exists():
+ die(f"No exec file found at {UTEST_EXEC}. Run with --run first.")
+
+ print(f"Generating JaCoCo report for module:
{module.relative_to(REPO_ROOT)}")
+ xml_path = generate_report(module)
+
+ report(xml_path, pkg_filter, file_filter, branches_only)
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())