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 32d510f55 Test modernization
32d510f55 is described below

commit 32d510f5585921ee3dd0bbc4adf5ab38252a5984
Author: James Bognar <[email protected]>
AuthorDate: Tue Sep 9 13:51:38 2025 -0400

    Test modernization
---
 .../src/test/java/org/apache/juneau/TestUtils.java |   2 +-
 .../a/rttests/RoundTripTransformBeans_Test.java    |  40 ++--
 .../apache/juneau/junit/BasicBeanConverter.java    | 234 ++++++++-------------
 .../org/apache/juneau/junit/BeanConverter.java     |  66 +++---
 .../java/org/apache/juneau/junit/Listifier.java    |   2 +-
 .../org/apache/juneau/junit/NestedTokenizer.java   |  18 +-
 .../{Listifier.java => PropertyExtractor.java}     |   9 +-
 .../apache/juneau/junit/PropertyExtractors.java    |  94 +++++++++
 .../java/org/apache/juneau/junit/Stringifier.java  |   2 +-
 .../test/java/org/apache/juneau/junit/Swapper.java |   2 +-
 .../{Stringifier.java => ThrowingSupplier.java}    |   9 +-
 .../juneau/junit/{Listifier.java => Utils.java}    |  38 +++-
 12 files changed, 282 insertions(+), 234 deletions(-)

diff --git a/juneau-utest/src/test/java/org/apache/juneau/TestUtils.java 
b/juneau-utest/src/test/java/org/apache/juneau/TestUtils.java
index a94c30586..37fc5131a 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/TestUtils.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/TestUtils.java
@@ -936,7 +936,7 @@ public class TestUtils extends Utils2 {
                var i = name.indexOf("{");
                var pn = i == -1 ? name : name.substring(0, i);
                var spn = i == -1 ? null : splitNestedInner(name);
-               var e = ofNullable(converter.getEntry(o, pn.equals("<NULL>") ? 
null : pn)).orElse(null);
+               var e = ofNullable(converter.getProperty(o, pn.equals("<NULL>") 
? null : pn)).orElse(null);
                if (spn == null || e == null) return converter.stringify(e);
                return spn.stream().map(x -> getEntry(converter, e, 
x)).map(converter::stringify).collect(joining(",","{","}"));
        }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
index b1538a5cb..5613c3b7e 100755
--- 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeans_Test.java
@@ -541,14 +541,14 @@ class RoundTripTransformBeans_Test extends SimpleTestBase 
{
 
        public static class F1 {
 
-               @Swap(TemporalCalendarSwap.IsoLocalDateTime.class)
+               @Swap(TemporalCalendarSwap.IsoInstant.class)
                private Calendar c;
                public Calendar getC() { return c; }
                public void setC(Calendar v) { c = v; }
 
                public static F1 create() {
                        var x = new F1();
-                       x.setC(parseISO8601Calendar("2018-12-12T05:12:00"));
+                       x.setC(parseISO8601Calendar("2018-12-12T05:12:00Z"));
                        return x;
                }
        }
@@ -562,17 +562,17 @@ class RoundTripTransformBeans_Test extends SimpleTestBase 
{
                var x = F1.create();
 
                var r = s.serialize(x);
-               assertEquals("{c:'2018-12-12T05:12:00'}", r);
+               assertEquals("{c:'2018-12-12T05:12:00Z'}", r);
 
                x = p.parse(r, F1.class);
-               assertBean(x, "c", "2018-12-12T10:12:00Z");
+               assertBean(x, "c", "2018-12-12T05:12:00Z");
 
                t.roundTrip(x, F1.class);
        }
 
-       @Swap(on="Dummy1.c", value=TemporalCalendarSwap.IsoLocalDateTime.class)
-       @Swap(on="F1c.c", value=TemporalCalendarSwap.IsoLocalDateTime.class)
-       @Swap(on="Dummy2.c", value=TemporalCalendarSwap.IsoLocalDateTime.class)
+       @Swap(on="Dummy1.c", value=TemporalCalendarSwap.IsoInstant.class)
+       @Swap(on="F1c.c", value=TemporalCalendarSwap.IsoInstant.class)
+       @Swap(on="Dummy2.c", value=TemporalCalendarSwap.IsoInstant.class)
        private static class F1cConfig {}
 
        public static class F1c {
@@ -583,7 +583,7 @@ class RoundTripTransformBeans_Test extends SimpleTestBase {
 
                public static F1c create() {
                        var x = new F1c();
-                       x.setC(parseISO8601Calendar("2018-12-12T05:12:00"));
+                       x.setC(parseISO8601Calendar("2018-12-12T05:12:00Z"));
                        return x;
                }
        }
@@ -597,16 +597,16 @@ class RoundTripTransformBeans_Test extends SimpleTestBase 
{
                var x = F1c.create();
 
                var r = s.serialize(x);
-               assertEquals("{c:'2018-12-12T05:12:00'}", r);
+               assertEquals("{c:'2018-12-12T05:12:00Z'}", r);
 
                x = p.parse(r, F1c.class);
-               assertSerialized(x, s, "{c:'2018-12-12T05:12:00'}");
+               assertSerialized(x, s, "{c:'2018-12-12T05:12:00Z'}");
 
                t.roundTrip(x, F1c.class);
        }
 
        public static class F2a {
-               @Swap(TemporalCalendarSwap.IsoLocalDateTime.class)
+               @Swap(TemporalCalendarSwap.IsoInstant.class)
                protected Calendar c;
        }
 
@@ -617,7 +617,7 @@ class RoundTripTransformBeans_Test extends SimpleTestBase {
 
                public static F2 create() {
                        var x = new F2();
-                       x.setC(parseISO8601Calendar("2018-12-12T05:12:00"));
+                       x.setC(parseISO8601Calendar("2018-12-12T05:12:00Z"));
                        return x;
                }
        }
@@ -631,17 +631,17 @@ class RoundTripTransformBeans_Test extends SimpleTestBase 
{
                var x = F2.create();
 
                var r = s.serialize(x);
-               assertEquals("{c:'2018-12-12T05:12:00'}", r);
+               assertEquals("{c:'2018-12-12T05:12:00Z'}", r);
 
                x = p.parse(r, F2.class);
-               assertBean(x, "c", "2018-12-12T10:12:00Z");
+               assertBean(x, "c", "2018-12-12T05:12:00Z");
 
                t.roundTrip(x, F2.class);
        }
 
-       @Swap(on="Dummy1.c", value=TemporalCalendarSwap.IsoLocalDateTime.class)
-       @Swap(on="F2ac.c", value=TemporalCalendarSwap.IsoLocalDateTime.class)
-       @Swap(on="Dummy2.c", value=TemporalCalendarSwap.IsoLocalDateTime.class)
+       @Swap(on="Dummy1.c", value=TemporalCalendarSwap.IsoInstant.class)
+       @Swap(on="F2ac.c", value=TemporalCalendarSwap.IsoInstant.class)
+       @Swap(on="Dummy2.c", value=TemporalCalendarSwap.IsoInstant.class)
        private static class F2acConfig {}
 
        public static class F2ac {
@@ -655,7 +655,7 @@ class RoundTripTransformBeans_Test extends SimpleTestBase {
 
                public static F2c create() {
                        var x = new F2c();
-                       x.setC(parseISO8601Calendar("2018-12-12T05:12:00"));
+                       x.setC(parseISO8601Calendar("2018-12-12T05:12:00Z"));
                        return x;
                }
        }
@@ -669,10 +669,10 @@ class RoundTripTransformBeans_Test extends SimpleTestBase 
{
                var x = F2.create();
 
                var r = s.serialize(x);
-               assertEquals("{c:'2018-12-12T05:12:00'}", r);
+               assertEquals("{c:'2018-12-12T05:12:00Z'}", r);
 
                x = p.parse(r, F2.class);
-               assertBean(x, "c", "2018-12-12T10:12:00Z");
+               assertBean(x, "c", "2018-12-12T05:12:00Z");
 
                t.roundTrip(x, F2.class);
        }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/junit/BasicBeanConverter.java 
b/juneau-utest/src/test/java/org/apache/juneau/junit/BasicBeanConverter.java
index 315ee3200..43796bfe0 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/junit/BasicBeanConverter.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/junit/BasicBeanConverter.java
@@ -14,10 +14,10 @@ package org.apache.juneau.junit;
 
 import static java.util.stream.Collectors.*;
 import static java.util.stream.StreamSupport.*;
-import static java.lang.Integer.*;
 import static java.time.format.DateTimeFormatter.*;
 import static java.util.Collections.*;
 import static java.util.Optional.*;
+import static org.apache.juneau.junit.Utils.*;
 
 import java.io.*;
 
@@ -25,14 +25,12 @@ import static java.util.Spliterators.*;
 
 import java.lang.reflect.*;
 import java.nio.file.*;
-import java.text.*;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.function.*;
 import java.util.stream.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.common.internal.*;
 
 /**
  * Default implementation of {@link BeanConverter} for Bean-Centric Test (BCT) 
object conversion.
@@ -191,7 +189,6 @@ import org.apache.juneau.common.internal.*;
  * </ul>
  *
  * @see BeanConverter
- * @see TestUtils
  */
 @SuppressWarnings({"unchecked","rawtypes"})
 public class BasicBeanConverter implements BeanConverter {
@@ -213,6 +210,7 @@ public class BasicBeanConverter implements BeanConverter {
        private final List<StringifierEntry<?>> stringifiers;
        private final List<ListifierEntry<?>> listifiers;
        private final List<SwapperEntry<?>> swappers;
+       private final List<PropertyExtractor> propertyExtractors;
        private final Map<String,Object> settings;
 
        private final ConcurrentHashMap<Class,Optional<Stringifier<?>>> 
stringifierMap = new ConcurrentHashMap<>();
@@ -223,10 +221,12 @@ public class BasicBeanConverter implements BeanConverter {
                stringifiers = new ArrayList<>(b.stringifiers);
                listifiers = new ArrayList<>(b.listifiers);
                swappers = new ArrayList<>(b.swappers);
+               propertyExtractors = new ArrayList<>(b.propertyExtractors);
                settings = new HashMap<>(b.settings);
                Collections.reverse(stringifiers);
                Collections.reverse(listifiers);
                Collections.reverse(swappers);
+               Collections.reverse(propertyExtractors);
        }
 
        /**
@@ -246,29 +246,23 @@ public class BasicBeanConverter implements BeanConverter {
                o = swap(o);
                if (o == null)
                        return getSetting(SETTING_nullValue, null);
-               Class<?> c = o.getClass();
+               var c = o.getClass();
                var stringifier = stringifierMap.computeIfAbsent(c, 
this::findStringifier);
                if (stringifier.isEmpty()) {
-                       if (c.isArray())
-                               return stringify(arrayToList(o));
-                       if (canListify(o)) {
-                               stringifier = of((o2, bs) -> 
bs.stringify(bs.listify(o2)));
-                       } else {
-                               stringifier = of((o2, bs) -> o2.toString());
-                       }
+                       stringifier = of(canListify(o) ? (bc, o2) -> 
bc.stringify(bc.listify(o2)) : (bc, o2) -> o2.toString());
                        stringifierMap.putIfAbsent(c, stringifier);
                }
                var o2 = o;
-               return stringifier.map(x -> (Stringifier)x).map(x -> 
x.apply(o2, this)).map(Object::toString).orElse(null);
+               return stringifier.map(x -> (Stringifier)x).map(x -> 
x.apply(this, o2)).map(Object::toString).orElse(null);
        }
 
        @Override
        public Object swap(Object o) {
                if (o == null) return null;
-               Class<?> c = o.getClass();
+               var c = o.getClass();
                var swapper = swapperMap.computeIfAbsent(c, this::findSwapper);
                if (swapper.isPresent())
-                       return swap(swapper.map(x -> (Swapper)x).map(x -> 
x.apply(o, this)).orElse(null));
+                       return swap(swapper.map(x -> (Swapper)x).map(x -> 
x.apply(this, o)).orElse(null));
                return o;
        }
 
@@ -277,21 +271,22 @@ public class BasicBeanConverter implements BeanConverter {
                o = swap(o);
                if (o == null)
                        return null;
-               Class<?> c = o.getClass();
-               if (c.isArray())
-                       return arrayToList(o);
                if (o instanceof List)
                        return (List<Object>)o;
+               var c = o.getClass();
+               if (c.isArray())
+                       return arrayToList(o);
                var o2 = o;
-               return listifierMap.computeIfAbsent(c, 
this::findListifier).map(x -> (Listifier)x).map(x -> (List<Object>)x.apply(o2, 
this)).orElse(null);
+               return listifierMap.computeIfAbsent(c, 
this::findListifier).map(x -> (Listifier)x).map(x -> 
(List<Object>)x.apply(this, o2)).orElse(null);
        }
 
        @Override
        public boolean canListify(Object o) {
                o = swap(o);
-               if (o == null) return false;
-               Class<?> c = o.getClass();
-               return c.isArray() || listifierMap.computeIfAbsent(c, 
this::findListifier).isPresent();
+               if (o == null)
+                       return false;
+               var c = o.getClass();
+               return o instanceof List || c.isArray() || 
listifierMap.computeIfAbsent(c, this::findListifier).isPresent();
        }
 
        @Override
@@ -300,57 +295,14 @@ public class BasicBeanConverter implements BeanConverter {
        }
 
        @Override
-       public Object getEntry(Object object, String name) {
-               return safe(() -> {
-                       var o = swap(object);
-                       if (o instanceof Map o2) {
-                               if (o2.containsKey(name)) return o2.get(name);
-                               if ("size".equals(name)) return o2.size();
-                       } else if (canListify(o)) {
-                               var l = listify(o);
-                               if (name.matches("-?\\d+"))
-                                       return l.get(parseInt(name));
-                               if (o.getClass().isArray()) {
-                                       if ("length".equals(name)) return 
l.size();
-                                       if ("size".equals(name)) return 
l.size();
-                               } else {
-                                       if ("size".equals(name)) return 
l.size();
-                               }
-                       }
-                       var f = (Field)null;
-                       var c = o.getClass();
-                       var n = Character.toUpperCase(name.charAt(0)) + 
name.substring(1);
-                       var m = Arrays.stream(c.getMethods()).filter(x -> 
x.getName().equals("is"+n) && x.getParameterCount() == 
0).findFirst().orElse(null);
-                       if (m != null) {
-                               m.setAccessible(true);
-                               return m.invoke(o);
-                       }
-                       m = Arrays.stream(c.getMethods()).filter(x -> 
x.getName().equals("get"+n) && x.getParameterCount() == 
0).findFirst().orElse(null);
-                       if (m != null) {
-                               m.setAccessible(true);
-                               return m.invoke(o);
-                       }
-                       m = Arrays.stream(c.getMethods()).filter(x -> 
x.getName().equals("get") && x.getParameterCount() == 1 && 
x.getParameterTypes()[0] == String.class).findFirst().orElse(null);
-                       if (m != null) {
-                               m.setAccessible(true);
-                               return m.invoke(o, name);
-                       }
-                       var c2 = c;
-                       while (f == null && c2 != null) {
-                               f = 
Arrays.stream(c2.getDeclaredFields()).filter(x -> 
x.getName().equals(name)).findFirst().orElse(null);
-                               c2 = c2.getSuperclass();
-                       }
-                       if (f != null) {
-                               f.setAccessible(true);
-                               return f.get(o);
-                       }
-                       m = Arrays.stream(c.getMethods()).filter(x -> 
x.getName().equals(name) && x.getParameterCount() == 
0).findFirst().orElse(null);
-                       if (m != null) {
-                               m.setAccessible(true);
-                               return m.invoke(o);
-                       }
-                       throw new RuntimeException(f("Property {0} not found on 
object of type {1}", name, o.getClass().getSimpleName()));
-               });
+       public Object getProperty(Object object, String name) {
+               var o = swap(object);
+               return propertyExtractors
+                       .stream()
+                       .filter(x -> x.canExtract(this, o, name))
+                       .findFirst()
+                       .orElseThrow(()->new RuntimeException(f("Could not find 
extractor for object of type {0}", o.getClass().getName())))
+                       .extract(this, o, name);
        }
 
        private Optional<Stringifier<?>> findStringifier(Class<?> c) {
@@ -395,6 +347,7 @@ public class BasicBeanConverter implements BeanConverter {
                return findSwapper(c.getSuperclass());
        }
 
+       @Override
        public <T> T getSetting(String key, T def) {
                return (T)settings.getOrDefault(key, def);
        }
@@ -452,6 +405,10 @@ public class BasicBeanConverter implements BeanConverter {
         */
        public static class Builder {
                private Map<String,Object> settings = new HashMap<>();
+               private List<StringifierEntry<?>> stringifiers = new 
ArrayList<>();
+               private List<ListifierEntry<?>> listifiers = new ArrayList<>();
+               private List<SwapperEntry<?>> swappers = new ArrayList<>();
+               private List<PropertyExtractor> propertyExtractors = new 
ArrayList<>();
 
                /**
                 * Adds a configuration setting to the converter.
@@ -462,8 +419,6 @@ public class BasicBeanConverter implements BeanConverter {
                 */
                public Builder addSetting(String key, Object value) { 
settings.put(key, value); return this; }
 
-               private List<StringifierEntry<?>> stringifiers = new 
ArrayList<>();
-
                /**
                 * Registers a custom stringifier for a specific type.
                 *
@@ -477,8 +432,6 @@ public class BasicBeanConverter implements BeanConverter {
                 */
                public <T> Builder addStringifier(Class<T> c, Stringifier<T> s) 
{ stringifiers.add(new StringifierEntry<>(c, s)); return this; }
 
-               private List<ListifierEntry<?>> listifiers = new ArrayList<>();
-
                /**
                 * Registers a custom listifier for a specific type.
                 *
@@ -492,8 +445,6 @@ public class BasicBeanConverter implements BeanConverter {
                 */
                public <T> Builder addListifier(Class<T> c, Listifier<T> l) { 
listifiers.add(new ListifierEntry<>(c, l)); return this; }
 
-               private List<SwapperEntry<?>> swappers = new ArrayList<>();
-
                /**
                 * Registers a custom swapper for a specific type.
                 *
@@ -508,6 +459,8 @@ public class BasicBeanConverter implements BeanConverter {
                 */
                public <T> Builder addSwapper(Class<T> c, Swapper<T> s) { 
swappers.add(new SwapperEntry<>(c, s)); return this; }
 
+               public <T> Builder addPropertyExtractor(PropertyExtractor e) { 
propertyExtractors.add(e); return this; }
+
                /**
                 * Adds default handlers and settings for common Java types.
                 *
@@ -541,30 +494,34 @@ public class BasicBeanConverter implements BeanConverter {
                        addSetting(SETTING_emptyValue, "<empty>");
                        addSetting(SETTING_classNameFormat, "simple");
 
-                       addStringifier(Map.Entry.class, (o, bs) -> 
bs.stringify(o.getKey()) + bs.getSetting(SETTING_mapEntrySeparator, "=") + 
bs.stringify(o.getValue()));
-                       addStringifier(GregorianCalendar.class, (o, bs) ->  
o.toZonedDateTime().format(bs.getSetting(SETTING_calendarFormat, ISO_INSTANT)));
-                       addStringifier(Date.class, (o, bs) ->  
o.toInstant().toString());
-                       addStringifier(InputStream.class, (o, bs) ->  
stringify(o));
-                       addStringifier(byte[].class, (o, bs) ->  stringify(o));
-                       addStringifier(Reader.class, (o, bs) -> stringify(o));
-                       addStringifier(File.class, (o, bs) -> stringify(o));
-                       addStringifier(Enum.class, (o, bs) -> o.name());
-                       addStringifier(Class.class, (o, bs) -> stringify(o, 
bs));
-                       addStringifier(Constructor.class, (o, bs) -> 
stringify(o, bs));
-                       addStringifier(Method.class, (o, bs) -> stringify(o, 
bs));
-                       addStringifier(List.class, (o, bs) -> 
((List<?>)o).stream().map(bs::stringify).collect(joining(bs.getSetting(SETTING_fieldSeparator,
 ","), bs.getSetting(SETTING_collectionPrefix, "["), 
bs.getSetting(SETTING_collectionSuffix, "]"))));
-                       addStringifier(Map.class, (o, bs) -> 
((Map<?,?>)o).entrySet().stream().map(bs::stringify).collect(joining(bs.getSetting(SETTING_fieldSeparator,
 ","), bs.getSetting(SETTING_mapPrefix, "{"), bs.getSetting(SETTING_mapSuffix, 
"}"))));
-
-                       addListifier(Collection.class, (o, bs) -> new 
ArrayList<>(o));
-                       addListifier(Iterable.class, (o, bs) -> 
stream(((Iterable<Object>)o).spliterator(), false).toList());
-                       addListifier(Iterator.class, (o, bs) -> 
stream(spliteratorUnknownSize(o, 0), false).toList());
-                       addListifier(Enumeration.class, (o, bs) -> list(o));
-                       addListifier(Stream.class, (o, bs) -> o.toList());
-                       addListifier(Optional.class, (o, bs) -> o.isEmpty() ? 
emptyList() : singletonList(o.get()));
-                       addListifier(Map.class, (o, bs) -> new 
ArrayList<>(((Map<?,?>)o).entrySet()));
-
-                       addSwapper(Optional.class, (o, bs) -> o.orElse(null));
-                       addSwapper(Supplier.class, (o, bs) -> o.get());
+                       addStringifier(Map.Entry.class, (bc, o) -> 
bc.stringify(o.getKey()) + bc.getSetting(SETTING_mapEntrySeparator, "=") + 
bc.stringify(o.getValue()));
+                       addStringifier(GregorianCalendar.class, (bc, o) ->  
o.toZonedDateTime().format(bc.getSetting(SETTING_calendarFormat, ISO_INSTANT)));
+                       addStringifier(Date.class, (bc, o) ->  
o.toInstant().toString());
+                       addStringifier(InputStream.class, (bc, o) ->  
stringify(o));
+                       addStringifier(byte[].class, (bc, o) ->  stringify(o));
+                       addStringifier(Reader.class, (bc, o) -> stringify(o));
+                       addStringifier(File.class, (bc, o) -> stringify(o));
+                       addStringifier(Enum.class, (bc, o) -> o.name());
+                       addStringifier(Class.class, (bc, o) -> stringify(bc, 
o));
+                       addStringifier(Constructor.class, (bc, o) -> 
stringify(bc, o));
+                       addStringifier(Method.class, (bc, o) -> stringify(bc, 
o));
+                       addStringifier(List.class, (bc, o) -> 
((List<?>)o).stream().map(bc::stringify).collect(joining(bc.getSetting(SETTING_fieldSeparator,
 ","), bc.getSetting(SETTING_collectionPrefix, "["), 
bc.getSetting(SETTING_collectionSuffix, "]"))));
+                       addStringifier(Map.class, (bc, o) -> 
((Map<?,?>)o).entrySet().stream().map(bc::stringify).collect(joining(bc.getSetting(SETTING_fieldSeparator,
 ","), bc.getSetting(SETTING_mapPrefix, "{"), bc.getSetting(SETTING_mapSuffix, 
"}"))));
+
+                       addListifier(Collection.class, (bc, o) -> new 
ArrayList<>(o));
+                       addListifier(Iterable.class, (bc, o) -> 
stream(((Iterable<Object>)o).spliterator(), false).toList());
+                       addListifier(Iterator.class, (bc, o) -> 
stream(spliteratorUnknownSize(o, 0), false).toList());
+                       addListifier(Enumeration.class, (bc, o) -> list(o));
+                       addListifier(Stream.class, (bc, o) -> o.toList());
+                       addListifier(Optional.class, (bc, o) -> o.isEmpty() ? 
emptyList() : singletonList(o.get()));
+                       addListifier(Map.class, (bc, o) -> new 
ArrayList<>(((Map<?,?>)o).entrySet()));
+
+                       addSwapper(Optional.class, (bc, o) -> o.orElse(null));
+                       addSwapper(Supplier.class, (bc, o) -> o.get());
+
+                       addPropertyExtractor(new 
PropertyExtractors.ObjectPropertyExtractor());
+                       addPropertyExtractor(new 
PropertyExtractors.ListPropertyExtractor());
+                       addPropertyExtractor(new 
PropertyExtractors.MapPropertyExtractor());
 
                        return this;
                }
@@ -619,22 +576,22 @@ public class BasicBeanConverter implements BeanConverter {
 
        private static final char[] HEX = "0123456789ABCDEF".toCharArray();
 
-       private static String stringify(byte[] bytes) {
-               var sb = new StringBuilder(bytes.length * 2);
-               for (var element : bytes) {
+       private static String stringify(byte[] o) {
+               var sb = new StringBuilder(o.length * 2);
+               for (var element : o) {
                        var v = element & 0xFF;
                        sb.append(HEX[v >>> 4]).append(HEX[v & 0x0F]);
                }
                return sb.toString();
        }
 
-       private static String stringify(InputStream is) {
+       private static String stringify(InputStream o) {
                return safe(() -> {
-                       try (var is2 = is) {
+                       try (var o2 = o) {
                                var buff = new ByteArrayOutputStream(1024);
                                var nRead = 0;
                                var b = new byte[1024];
-                               while ((nRead = is2.read(b, 0, b.length)) != -1)
+                               while ((nRead = o2.read(b, 0, b.length)) != -1)
                                        buff.write(b, 0, nRead);
                                buff.flush();
                                return stringify(buff.toByteArray());
@@ -642,67 +599,40 @@ public class BasicBeanConverter implements BeanConverter {
                });
        }
 
-       private static String stringify(Reader in) {
+       private static String stringify(Reader o) {
                return safe(() -> {
-                       try (var in2 = in) {
+                       try (var o2 = o) {
                                var sb = new StringBuilder();
                                var buf = new char[1024];
                                var i = 0;
-                               while ((i = in2.read(buf)) != -1)
+                               while ((i = o2.read(buf)) != -1)
                                        sb.append(buf, 0, i);
                                return sb.toString();
                        }
                });
        }
 
-       private static String stringify(File in) {
-               return 
safe(()->stringify(Files.newBufferedReader(in.toPath())));
+       private static String stringify(File o) {
+               return safe(()->stringify(Files.newBufferedReader(o.toPath())));
        }
 
-       private static String stringify(Class<?> in, BasicBeanConverter bs) {
-               return switch(bs.getSetting(SETTING_classNameFormat, "")) {
-                       case "simple" -> in.getSimpleName();
-                       case "canonical" -> in.getCanonicalName();
-                       default -> in.getName();
+       private static String stringify(BeanConverter bc, Class<?> o) {
+               return switch(bc.getSetting(SETTING_classNameFormat, 
"default")) {
+                       case "simple" -> o.getSimpleName();
+                       case "canonical" -> o.getCanonicalName();
+                       default -> o.getName();
                };
        }
 
-       private static String stringify(Constructor o, BasicBeanConverter bs) {
-               return new 
StringBuilder().append(stringify(o.getDeclaringClass(), 
bs)).append('(').append(stringify(o.getParameterTypes(), 
bs)).append(')').toString();
+       private static String stringify(BeanConverter bc, Constructor o) {
+               return new StringBuilder().append(stringify(bc, 
o.getDeclaringClass())).append('(').append(stringify(bc, 
o.getParameterTypes())).append(')').toString();
        }
 
-       private static String stringify(Method o, BasicBeanConverter bs) {
-               return new 
StringBuilder().append(o.getName()).append('(').append(stringify(o.getParameterTypes(),
 bs)).append(')').toString();
-       }
-
-       private static String stringify(Class[] o, BasicBeanConverter bs) {
-               var sb = new StringBuilder();
-               for (int i = 0; i < o.length; i++) {
-                       if (i > 0)
-                               sb.append(',');
-                       sb.append(stringify(o[i], bs));
-               }
-               return sb.toString();
-       }
-
-       private static String f(String msg, Object...args) {
-               return args.length == 0 ? msg : MessageFormat.format(msg, args);
-       }
-
-       private static <T> T safe(ThrowingSupplier<T> s) {
-               try {
-                       return s.get();
-               } catch (RuntimeException e) {
-                       throw e;
-               } catch (Exception e) {
-                       throw new RuntimeException(e);
-               }
+       private static String stringify(BeanConverter bc, Method o) {
+               return new 
StringBuilder().append(o.getName()).append('(').append(stringify(bc, 
o.getParameterTypes())).append(')').toString();
        }
 
-       private static List<Object> arrayToList(Object o) {
-               var l = new ArrayList<>();
-               for (var i = 0; i < Array.getLength(o); i++)
-                       l.add(Array.get(o, i));
-               return l;
+       private static String stringify(BeanConverter bc, Class[] o) {
+               return Arrays.stream(o).map(x -> stringify(bc, 
x)).collect(joining(","));
        }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/junit/BeanConverter.java 
b/juneau-utest/src/test/java/org/apache/juneau/junit/BeanConverter.java
index 1a78bc72e..11845c097 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/junit/BeanConverter.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/junit/BeanConverter.java
@@ -18,12 +18,12 @@ import org.apache.juneau.*;
 
 /**
  * Abstract interface for Bean-Centric Test (BCT) object conversion and 
property access.
- * 
- * <p>This interface defines the core contract for converting objects to 
strings and lists, 
- * and for accessing object properties in a uniform way. It forms the 
foundation of the BCT 
- * testing framework, enabling consistent object introspection and value 
extraction across 
+ *
+ * <p>This interface defines the core contract for converting objects to 
strings and lists,
+ * and for accessing object properties in a uniform way. It forms the 
foundation of the BCT
+ * testing framework, enabling consistent object introspection and value 
extraction across
  * different object types and structures.</p>
- * 
+ *
  * <p>The BeanConverter is designed to handle a wide variety of Java objects 
including:</p>
  * <ul>
  *     <li><b>Primitives and wrappers</b> - Numbers, booleans, characters</li>
@@ -32,24 +32,24 @@ import org.apache.juneau.*;
  *     <li><b>Custom beans and POJOs</b> - JavaBean-style objects with 
getters/setters</li>
  *     <li><b>Special objects</b> - Optional, Supplier, Date/Calendar, File, 
streams</li>
  * </ul>
- * 
+ *
  * <h5 class='section'>Core Conversion Operations:</h5>
  * <dl>
  *     <dt><b>{@link #stringify(Object)}</b></dt>
  *     <dd>Converts any object to its string representation, handling nested 
structures</dd>
- * 
+ *
  *     <dt><b>{@link #listify(Object)}</b></dt>
  *     <dd>Converts collection-like objects (arrays, Collections, Iterables, 
etc.) to List&lt;Object&gt;</dd>
- * 
+ *
  *     <dt><b>{@link #swap(Object)}</b></dt>
  *     <dd>Pre-processes objects before conversion (e.g., unwrapping Optional, 
calling Supplier)</dd>
- * 
- *     <dt><b>{@link #getEntry(Object, String)}</b></dt>
+ *
+ *     <dt><b>{@link #getProperty(Object, String)}</b></dt>
  *     <dd>Accesses object properties using multiple fallback mechanisms</dd>
  * </dl>
- * 
+ *
  * <h5 class='section'>Property Access Strategy:</h5>
- * <p>The {@link #getEntry(Object, String)} method uses a comprehensive 
fallback approach:</p>
+ * <p>The {@link #getProperty(Object, String)} method uses a comprehensive 
fallback approach:</p>
  * <ol>
  *     <li><b>Collection/Array access:</b> Numeric indices for 
arrays/lists</li>
  *     <li><b>Universal size properties:</b> "length" and "size" for arrays, 
collections, and maps</li>
@@ -60,7 +60,7 @@ import org.apache.juneau.*;
  *     <li><b>Public fields:</b> Direct field access via reflection</li>
  *     <li><b>No-arg methods:</b> Method calls with property names</li>
  * </ol>
- * 
+ *
  * <h5 class='section'>Extensibility:</h5>
  * <p>The interface is designed to be implemented by custom converters that 
can:</p>
  * <ul>
@@ -70,7 +70,7 @@ import org.apache.juneau.*;
  *     <li>Override property access mechanisms</li>
  *     <li>Configure formatting and display options</li>
  * </ul>
- * 
+ *
  * <h5 class='section'>Primary Implementation:</h5>
  * <p>The main implementation is {@link BasicBeanConverter}, which 
provides:</p>
  * <ul>
@@ -79,7 +79,7 @@ import org.apache.juneau.*;
  *     <li>Performance optimization through caching</li>
  *     <li>Comprehensive default handling for common Java types</li>
  * </ul>
- * 
+ *
  * <h5 class='section'>Usage in BCT Framework:</h5>
  * <p>This interface is used internally by BCT assertion methods like:</p>
  * <ul>
@@ -89,7 +89,7 @@ import org.apache.juneau.*;
  *     <li>{@link TestUtils#assertList(List, Object...)}</li>
  *     <li>{@link TestUtils#assertBeans(Collection, String, String...)}</li>
  * </ul>
- * 
+ *
  * @see BasicBeanConverter
  * @see TestUtils
  */
@@ -97,7 +97,7 @@ public interface BeanConverter {
 
        /**
         * Converts an object to its string representation.
-        * 
+        *
         * <p>This method applies swapping logic first via {@link 
#swap(Object)}, then determines
         * the appropriate string conversion based on the object's type. The 
conversion handles:</p>
         * <ul>
@@ -108,7 +108,7 @@ public interface BeanConverter {
         *      <li><b>Maps:</b> Converts to brace-delimited key-value 
format</li>
         *      <li><b>Custom objects:</b> Uses registered stringifiers or 
fallback toString()</li>
         * </ul>
-        * 
+        *
         * @param o The object to stringify
         * @return The string representation of the object
         */
@@ -116,7 +116,7 @@ public interface BeanConverter {
 
        /**
         * Converts a collection-like object to a List&lt;Object&gt;.
-        * 
+        *
         * <p>This method applies swapping logic first via {@link 
#swap(Object)}, then converts
         * collection-like objects to lists. Supported types include:</p>
         * <ul>
@@ -129,7 +129,7 @@ public interface BeanConverter {
         *      <li><b>Optional:</b> Returns empty list or single-element 
list</li>
         *      <li><b>Map:</b> Returns list of Map.Entry objects</li>
         * </ul>
-        * 
+        *
         * @param o The object to convert to a list
         * @return A List containing the elements, or null if the object is null
         */
@@ -137,10 +137,10 @@ public interface BeanConverter {
 
        /**
         * Determines if an object can be converted to a list.
-        * 
+        *
         * <p>This method checks if the object (after swapping) is a type that 
can be
         * meaningfully converted to a List&lt;Object&gt; via {@link 
#listify(Object)}.</p>
-        * 
+        *
         * @param o The object to test
         * @return True if the object can be listified, false otherwise
         */
@@ -148,17 +148,17 @@ public interface BeanConverter {
 
        /**
         * Returns the string representation used for null values.
-        * 
+        *
         * <p>This is used by {@link #stringify(Object)} when the object (after 
swapping) is null.
         * The default implementation typically returns "&lt;null&gt;" or 
similar.</p>
-        * 
+        *
         * @return The null value representation
         */
        String nullValue();
 
        /**
         * Pre-processes objects before conversion operations.
-        * 
+        *
         * <p>This method applies object transformations before stringification 
or listification.
         * Common swapping operations include:</p>
         * <ul>
@@ -167,9 +167,9 @@ public interface BeanConverter {
         *      <li><b>Lazy objects:</b> Forces evaluation of deferred 
computations</li>
         *      <li><b>Wrapper types:</b> Extracts wrapped values</li>
         * </ul>
-        * 
+        *
         * <p>The method may be called recursively if swapping produces another 
swappable type.</p>
-        * 
+        *
         * @param o The object to swap
         * @return The swapped object, or the original object if no swapping is 
needed
         */
@@ -177,10 +177,10 @@ public interface BeanConverter {
 
        /**
         * Accesses a named property or field from an object.
-        * 
+        *
         * <p>This is the core property access method used by BCT assertions. 
It employs
         * multiple fallback strategies to extract values from objects:</p>
-        * 
+        *
         * <h5 class='section'>Access Priority Order:</h5>
         * <ol>
         *      <li><b>Map key access:</b> If object is a Map, looks up the 
name as a key</li>
@@ -192,7 +192,7 @@ public interface BeanConverter {
         *      <li><b>Public fields:</b> Direct field access via 
reflection</li>
         *      <li><b>No-arg methods:</b> Methods with the exact property 
name</li>
         * </ol>
-        * 
+        *
         * <h5 class='section'>Special Property Names:</h5>
         * <ul>
         *      <li><b>"length"/"size":</b> Returns size for arrays, 
collections, maps</li>
@@ -200,11 +200,13 @@ public interface BeanConverter {
         *      <li><b>"&lt;NULL&gt;":</b> Accesses null key in maps</li>
         *      <li><b>"-1", "-2", etc.:</b> Negative indexing from end of 
array/list</li>
         * </ul>
-        * 
+        *
         * @param object The object to access properties from
         * @param name The property/field name to access
         * @return The property value
         * @throws RuntimeException if the property cannot be found or accessed
         */
-       Object getEntry(Object object, String name);
+       Object getProperty(Object object, String name);
+
+       <T> T getSetting(String key, T defaultValue);
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/junit/Listifier.java 
b/juneau-utest/src/test/java/org/apache/juneau/junit/Listifier.java
index 92db0fdec..0d96801f1 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/junit/Listifier.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/junit/Listifier.java
@@ -16,4 +16,4 @@ import java.util.*;
 import java.util.function.*;
 
 @FunctionalInterface
-public interface Listifier<T> extends 
BiFunction<T,BasicBeanConverter,List<Object>> {}
+public interface Listifier<T> extends BiFunction<BeanConverter,T,List<Object>> 
{}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/junit/NestedTokenizer.java 
b/juneau-utest/src/test/java/org/apache/juneau/junit/NestedTokenizer.java
index c2d5f1371..62242db6b 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/junit/NestedTokenizer.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/junit/NestedTokenizer.java
@@ -14,10 +14,10 @@ package org.apache.juneau.junit;
 
 import static java.util.Collections.*;
 import static java.util.stream.Collectors.*;
+import static org.apache.juneau.junit.Utils.*;
 import static org.apache.juneau.junit.NestedTokenizer.ParseState.*;
 
 import java.util.*;
-import java.util.function.*;
 
 /**
  * Splits a nested comma-delimited string into a list of Token objects using a 
state machine parser.
@@ -246,20 +246,4 @@ public class NestedTokenizer {
                        return Objects.hash(value, nested);
                }
        }
-
-       
//---------------------------------------------------------------------------------------------
-       // Helper methods.
-       
//---------------------------------------------------------------------------------------------
-
-       private static <T,U> boolean eq(T o1, U o2, BiPredicate<T,U> test) {
-               if (o1 == null) { return o2 == null; }
-               if (o2 == null) { return false; }
-               if (o1 == o2) { return true; }
-               return test.test(o1, o2);
-       }
-
-       @SuppressWarnings("unlikely-arg-type")
-       private static <T,U> boolean eq(T o1, U o2) {
-               return Objects.equals(o1, o2);
-       }
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/junit/Listifier.java 
b/juneau-utest/src/test/java/org/apache/juneau/junit/PropertyExtractor.java
similarity index 90%
copy from juneau-utest/src/test/java/org/apache/juneau/junit/Listifier.java
copy to 
juneau-utest/src/test/java/org/apache/juneau/junit/PropertyExtractor.java
index 92db0fdec..95df43bda 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/junit/Listifier.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/junit/PropertyExtractor.java
@@ -12,8 +12,9 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.junit;
 
-import java.util.*;
-import java.util.function.*;
+public interface PropertyExtractor {
 
-@FunctionalInterface
-public interface Listifier<T> extends 
BiFunction<T,BasicBeanConverter,List<Object>> {}
+       boolean canExtract(BeanConverter converter, Object o, String key);
+
+       Object extract(BeanConverter converter, Object o, String key);
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/junit/PropertyExtractors.java 
b/juneau-utest/src/test/java/org/apache/juneau/junit/PropertyExtractors.java
new file mode 100644
index 000000000..2b56e58af
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/junit/PropertyExtractors.java
@@ -0,0 +1,94 @@
+package org.apache.juneau.junit;
+
+import static java.lang.Integer.*;
+import static org.apache.juneau.junit.Utils.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+public class PropertyExtractors {
+
+       public static class ObjectPropertyExtractor implements 
PropertyExtractor {
+
+               @Override
+               public boolean canExtract(BeanConverter converter, Object o, 
String name) {
+                       return true;
+               }
+
+               @Override
+               public Object extract(BeanConverter converter, Object o, String 
name) {
+                       return
+                               safe(() -> {
+                                       if (o == null)
+                                               return null;
+                                       var f = (Field)null;
+                                       var c = o.getClass();
+                                       var n = 
Character.toUpperCase(name.charAt(0)) + name.substring(1);
+                                       var m = 
Arrays.stream(c.getMethods()).filter(x -> x.getName().equals("is"+n) && 
x.getParameterCount() == 0).findFirst().orElse(null);
+                                       if (m != null) {
+                                               m.setAccessible(true);
+                                               return m.invoke(o);
+                                       }
+                                       m = 
Arrays.stream(c.getMethods()).filter(x -> x.getName().equals("get"+n) && 
x.getParameterCount() == 0).findFirst().orElse(null);
+                                       if (m != null) {
+                                               m.setAccessible(true);
+                                               return m.invoke(o);
+                                       }
+                                       m = 
Arrays.stream(c.getMethods()).filter(x -> x.getName().equals("get") && 
x.getParameterCount() == 1 && x.getParameterTypes()[0] == 
String.class).findFirst().orElse(null);
+                                       if (m != null) {
+                                               m.setAccessible(true);
+                                               return m.invoke(o, name);
+                                       }
+                                       var c2 = c;
+                                       while (f == null && c2 != null) {
+                                               f = 
Arrays.stream(c2.getDeclaredFields()).filter(x -> 
x.getName().equals(name)).findFirst().orElse(null);
+                                               c2 = c2.getSuperclass();
+                                       }
+                                       if (f != null) {
+                                               f.setAccessible(true);
+                                               return f.get(o);
+                                       }
+                                       m = 
Arrays.stream(c.getMethods()).filter(x -> x.getName().equals(name) && 
x.getParameterCount() == 0).findFirst().orElse(null);
+                                       if (m != null) {
+                                               m.setAccessible(true);
+                                               return m.invoke(o);
+                                       }
+                                       throw new RuntimeException(f("Property 
{0} not found on object of type {1}", name, o.getClass().getSimpleName()));
+                               });
+               }
+       }
+
+       public static class ListPropertyExtractor extends 
ObjectPropertyExtractor {
+
+               @Override
+               public boolean canExtract(BeanConverter converter, Object o, 
String name) {
+                       return converter.canListify(o);
+               }
+
+               @Override
+               public Object extract(BeanConverter converter, Object o, String 
name) {
+                       var l = converter.listify(o);
+                       if (name.matches("-?\\d+"))
+                               return l.get(parseInt(name));
+                       if ("length".equals(name)) return l.size();
+                       if ("size".equals(name)) return l.size();
+                       return super.extract(converter, o, name);
+               }
+       }
+
+       public static class MapPropertyExtractor extends 
ObjectPropertyExtractor {
+
+               @Override
+               public boolean canExtract(BeanConverter converter, Object o, 
String name) {
+                       return o instanceof Map;
+               }
+
+               @Override
+               public Object extract(BeanConverter converter, Object o, String 
name) {
+                       var m = (Map<?,?>)o;
+                       if (m.containsKey(name)) return m.get(name);
+                       if ("size".equals(name)) return m.size();
+                       return super.extract(converter, o, name);
+               }
+       }
+}
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/junit/Stringifier.java 
b/juneau-utest/src/test/java/org/apache/juneau/junit/Stringifier.java
index ec7f8fee9..e86ee4b1d 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/junit/Stringifier.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/junit/Stringifier.java
@@ -15,4 +15,4 @@ package org.apache.juneau.junit;
 import java.util.function.*;
 
 @FunctionalInterface
-public interface Stringifier<T> extends 
BiFunction<T,BasicBeanConverter,String> {}
+public interface Stringifier<T> extends BiFunction<BeanConverter,T,String> {}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/junit/Swapper.java 
b/juneau-utest/src/test/java/org/apache/juneau/junit/Swapper.java
index c09a5b880..7d6227b4b 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/junit/Swapper.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/junit/Swapper.java
@@ -15,4 +15,4 @@ package org.apache.juneau.junit;
 import java.util.function.*;
 
 @FunctionalInterface
-public interface Swapper<T> extends BiFunction<T,BasicBeanConverter,Object> {}
+public interface Swapper<T> extends BiFunction<BeanConverter,T,Object> {}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/junit/Stringifier.java 
b/juneau-utest/src/test/java/org/apache/juneau/junit/ThrowingSupplier.java
similarity index 93%
copy from juneau-utest/src/test/java/org/apache/juneau/junit/Stringifier.java
copy to juneau-utest/src/test/java/org/apache/juneau/junit/ThrowingSupplier.java
index ec7f8fee9..a050c8fbc 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/junit/Stringifier.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/junit/ThrowingSupplier.java
@@ -12,7 +12,10 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.junit;
 
-import java.util.function.*;
-
+/**
+ * A supplier that throws an exception.
+ */
 @FunctionalInterface
-public interface Stringifier<T> extends 
BiFunction<T,BasicBeanConverter,String> {}
+public interface ThrowingSupplier<T> {
+       T get() throws Exception;
+}
\ No newline at end of file
diff --git a/juneau-utest/src/test/java/org/apache/juneau/junit/Listifier.java 
b/juneau-utest/src/test/java/org/apache/juneau/junit/Utils.java
similarity index 64%
copy from juneau-utest/src/test/java/org/apache/juneau/junit/Listifier.java
copy to juneau-utest/src/test/java/org/apache/juneau/junit/Utils.java
index 92db0fdec..9b29b735c 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/junit/Listifier.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/junit/Utils.java
@@ -12,8 +12,42 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.junit;
 
+import java.lang.reflect.*;
+import java.text.*;
 import java.util.*;
 import java.util.function.*;
 
-@FunctionalInterface
-public interface Listifier<T> extends 
BiFunction<T,BasicBeanConverter,List<Object>> {}
+public class Utils {
+       public static <T> T safe(ThrowingSupplier<T> s) {
+               try {
+                       return s.get();
+               } catch (RuntimeException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       public static String f(String msg, Object...args) {
+               return args.length == 0 ? msg : MessageFormat.format(msg, args);
+       }
+
+       public static List<Object> arrayToList(Object o) {
+               var l = new ArrayList<>();
+               for (var i = 0; i < Array.getLength(o); i++)
+                       l.add(Array.get(o, i));
+               return l;
+       }
+
+       public static <T,U> boolean eq(T o1, U o2, BiPredicate<T,U> test) {
+               if (o1 == null) { return o2 == null; }
+               if (o2 == null) { return false; }
+               if (o1 == o2) { return true; }
+               return test.test(o1, o2);
+       }
+
+       @SuppressWarnings("unlikely-arg-type")
+       public static <T,U> boolean eq(T o1, U o2) {
+               return Objects.equals(o1, o2);
+       }
+}


Reply via email to