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 3e96bb1493 FiltereMap/MapBuilder improvements
3e96bb1493 is described below

commit 3e96bb14931ca99f1efa02e0d3bdc76cee4b2872
Author: James Bognar <[email protected]>
AuthorDate: Sun Dec 14 13:31:50 2025 -0500

    FiltereMap/MapBuilder improvements
---
 .../juneau/commons/collections/FilteredMap.java    | 148 ++++++++--
 .../juneau/commons/collections/MapBuilder.java     | 318 +++++++++++++++------
 .../juneau/commons/utils/CollectionUtils.java      |   4 +-
 .../org/apache/juneau/internal/ConverterUtils.java |   5 +-
 .../juneau/commons/collections/BidiMap_Test.java   |   1 -
 .../commons/collections/FilteredMap_Test.java      |  29 +-
 .../juneau/commons/collections/Flag_Test.java      |   1 -
 .../commons/collections/KeywordSet_Test.java       |   1 -
 .../commons/collections/MapBuilder_Test.java       | 305 ++++++++++++--------
 .../juneau/commons/collections/MultiList_Test.java |   2 -
 .../juneau/commons/collections/MultiMap_Test.java  |   1 -
 .../juneau/commons/collections/MultiSet_Test.java  |   1 -
 .../commons/collections/ReversedList_Test.java     |   1 -
 .../juneau/commons/collections/SimpleMap_Test.java |   1 -
 14 files changed, 570 insertions(+), 248 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredMap.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredMap.java
index 026747f1e6..f221e608b9 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredMap.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredMap.java
@@ -34,7 +34,11 @@ import java.util.function.*;
  * <h5 class='section'>Features:</h5>
  * <ul class='spaced-list'>
  *     <li><b>Flexible Filtering:</b> Use any {@link BiPredicate} to filter 
entries based on key, value, or both
- *     <li><b>Custom Map Types:</b> Works with any map implementation via the 
builder's <c>creator</c> function
+ *     <li><b>Optional Filter:</b> Filter is optional when using the builder - 
defaults to accepting all entries
+ *     <li><b>Multiple Filters:</b> Can combine multiple filters using AND 
logic
+ *     <li><b>Custom Map Types:</b> Works with any map implementation via the 
builder's <c>inner</c> method
+ *     <li><b>Type Conversion:</b> Supports automatic type conversion via 
key/value functions
+ *     <li><b>Convenience Methods:</b> Provides {@link #add(Object, Object)}, 
{@link #addAll(Map)}, {@link #addAny(Object...)}, and {@link 
#addPairs(Object...)} for easy entry addition
  *     <li><b>Transparent Interface:</b> Implements the full {@link Map} 
interface, so it can be used anywhere a map is expected
  *     <li><b>Filter on Add:</b> Filtering happens when entries are added via 
{@link #put(Object, Object)}, {@link #putAll(Map)}, etc.
  * </ul>
@@ -60,7 +64,7 @@ import java.util.function.*;
  *     FilteredMap&lt;String, Integer&gt; <jv>map</jv> = FilteredMap
  *             .<jsm>create</jsm>(String.<jk>class</jk>, 
Integer.<jk>class</jk>)
  *             .filter((k, v) -&gt; v != <jk>null</jk> &amp;&amp; v &gt; 0)
- *             .creator(() -&gt; <jk>new</jk> TreeMap&lt;&gt;())
+ *             .inner(<jk>new</jk> TreeMap&lt;&gt;())
  *             .build();
  *
  *     <jv>map</jv>.put(<js>"a"</js>, 5);   <jc>// Added</jc>
@@ -77,13 +81,13 @@ import java.util.function.*;
  *             being filtered out versus an existing entry being filtered out.
  *     <li>The filter is not applied when reading from the map (e.g., {@link 
#get(Object)}, {@link #containsKey(Object)})
  *     <li>All other map operations behave as expected on the underlying map
- *     <li>The underlying map type is determined by the <c>creator</c> 
function (defaults to {@link LinkedHashMap})
+ *     <li>The underlying map type is determined by the <c>inner</c> method 
(defaults to {@link LinkedHashMap})
  * </ul>
  *
  * <h5 class='section'>Thread Safety:</h5>
  * <p>
  * This class is not thread-safe unless the underlying map is thread-safe. If 
thread safety is required,
- * use a thread-safe map type (e.g., {@link 
java.util.concurrent.ConcurrentHashMap}) via the <c>creator</c> function.
+ * use a thread-safe map type (e.g., {@link 
java.util.concurrent.ConcurrentHashMap}) via the <c>inner</c> method.
  *
  * <h5 class='section'>See Also:</h5>
  * <ul>
@@ -103,7 +107,7 @@ public class FilteredMap<K,V> extends AbstractMap<K,V> {
         *      FilteredMap&lt;String, Integer&gt; <jv>map</jv> = FilteredMap
         *              .<jsm>create</jsm>(String.<jk>class</jk>, 
Integer.<jk>class</jk>)
         *              .filter((k, v) -&gt; v != <jk>null</jk> &amp;&amp; v 
&gt; 0)
-        *              .creator(() -&gt; <jk>new</jk> TreeMap&lt;&gt;())
+        *              .inner(<jk>new</jk> TreeMap&lt;&gt;())
         *              .build();
         * </p>
         *
@@ -111,8 +115,8 @@ public class FilteredMap<K,V> extends AbstractMap<K,V> {
         * @param <V> The value type.
         */
        public static class Builder<K,V> {
-               private BiPredicate<K,V> filter;
-               private Supplier<Map<K,V>> creator = LinkedHashMap::new;
+               private BiPredicate<K,V> filter = (k,v) -> true;
+               private Map<K,V> inner;
                private Class<K> keyType;
                private Class<V> valueType;
                private Function<Object,K> keyFunction;
@@ -125,6 +129,13 @@ public class FilteredMap<K,V> extends AbstractMap<K,V> {
                 * The predicate receives both the key and value of each entry. 
If it returns <jk>true</jk>,
                 * the entry is added to the map. If it returns <jk>false</jk>, 
the entry is silently ignored.
                 *
+                * <p>
+                * This method is optional. If not called, the map will accept 
all entries (defaults to <c>(k,v) -&gt; true</c>).
+                *
+                * <p>
+                * This method can be called multiple times. When called 
multiple times, all filters are combined
+                * using AND logic - an entry must pass all filters to be added 
to the map.
+                *
                 * <h5 class='section'>Example:</h5>
                 * <p class='bjava'>
                 *      <jc>// Filter out null values</jc>
@@ -134,39 +145,53 @@ public class FilteredMap<K,V> extends AbstractMap<K,V> {
                 *      <jc>// Filter based on both key and value</jc>
                 *      Builder&lt;String, Integer&gt; <jv>b2</jv> = 
FilteredMap.<jsm>create</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
                 *              .filter((k, v) -&gt; ! 
k.startsWith(<js>"_"</js>) &amp;&amp; v != <jk>null</jk> &amp;&amp; v &gt; 0);
+                *
+                *      <jc>// Multiple filters combined with AND</jc>
+                *      Builder&lt;String, Integer&gt; <jv>b3</jv> = 
FilteredMap.<jsm>create</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+                *              .filter((k, v) -&gt; v != <jk>null</jk>)        
   <jc>// First filter</jc>
+                *              .filter((k, v) -&gt; v &gt; 0)                  
  <jc>// Second filter (ANDed with first)</jc>
+                *              .filter((k, v) -&gt; ! 
k.startsWith(<js>"_"</js>)); <jc>// Third filter (ANDed with previous)</jc>
                 * </p>
                 *
                 * @param value The filter predicate. Must not be <jk>null</jk>.
                 * @return This object for method chaining.
                 */
                public Builder<K,V> filter(BiPredicate<K,V> value) {
-                       filter = assertArgNotNull("value", value);
+                       BiPredicate<K,V> newFilter = assertArgNotNull("value", 
value);
+                       if (filter == null)
+                               filter = newFilter;
+                       else
+                               filter = filter.and(newFilter);
                        return this;
                }
 
                /**
-                * Sets the supplier that creates the underlying map instance.
+                * Sets the underlying map instance that will store the 
filtered entries.
                 *
                 * <p>
-                * This supplier is called once during {@link #build()} to 
create the underlying map that will
-                * store the filtered entries. The default implementation 
creates a {@link LinkedHashMap}.
+                * If not specified, a new {@link LinkedHashMap} will be 
created during {@link #build()}.
                 *
                 * <h5 class='section'>Example:</h5>
                 * <p class='bjava'>
                 *      <jc>// Use a TreeMap for sorted keys</jc>
                 *      Builder&lt;String, Integer&gt; <jv>b</jv> = 
FilteredMap.<jsm>create</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
-                *              .creator(() -&gt; <jk>new</jk> 
TreeMap&lt;&gt;());
+                *              .inner(<jk>new</jk> TreeMap&lt;&gt;());
                 *
                 *      <jc>// Use a ConcurrentHashMap for thread safety</jc>
                 *      Builder&lt;String, Integer&gt; <jv>b2</jv> = 
FilteredMap.<jsm>create</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
-                *              .creator(() -&gt; <jk>new</jk> 
ConcurrentHashMap&lt;&gt;());
+                *              .inner(<jk>new</jk> 
ConcurrentHashMap&lt;&gt;());
+                *
+                *      <jc>// Use an existing map</jc>
+                *      Map&lt;String, Integer&gt; <jv>existing</jv> = 
<jk>new</jk> LinkedHashMap&lt;&gt;();
+                *      Builder&lt;String, Integer&gt; <jv>b3</jv> = 
FilteredMap.<jsm>create</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+                *              .inner(<jv>existing</jv>);
                 * </p>
                 *
-                * @param value The creator supplier. Must not be <jk>null</jk>.
+                * @param value The underlying map instance. Must not be 
<jk>null</jk>.
                 * @return This object for method chaining.
                 */
-               public Builder<K,V> creator(Supplier<Map<K,V>> value) {
-                       creator = assertArgNotNull("value", value);
+               public Builder<K,V> inner(Map<K,V> value) {
+                       inner = assertArgNotNull("value", value);
                        return this;
                }
 
@@ -255,12 +280,15 @@ public class FilteredMap<K,V> extends AbstractMap<K,V> {
                /**
                 * Builds a new {@link FilteredMap} instance.
                 *
+                * <p>
+                * If {@link #filter(BiPredicate)} was not called, the map will 
accept all entries
+                * (defaults to <c>(k,v) -&gt; true</c>).
+                *
                 * @return A new filtered map instance.
-                * @throws IllegalArgumentException If the filter has not been 
set.
                 */
                public FilteredMap<K,V> build() {
-                       assertArgNotNull("filter", filter);
-                       return new FilteredMap<>(filter, creator.get(), 
keyType, valueType, keyFunction, valueFunction);
+                       Map<K,V> map = inner != null ? inner : new 
LinkedHashMap<>();
+                       return new FilteredMap<>(filter, map, keyType, 
valueType, keyFunction, valueFunction);
                }
        }
 
@@ -324,7 +352,7 @@ public class FilteredMap<K,V> extends AbstractMap<K,V> {
        /**
         * Constructor.
         *
-        * @param filter The filter predicate. Must not be <jk>null</jk>.
+        * @param filter The filter predicate. Can be <jk>null</jk> (if null, 
all entries are accepted).
         * @param map The underlying map. Must not be <jk>null</jk>.
         * @param keyType The key type. Must not be <jk>null</jk> (use 
<c>Object.class</c> to disable type checking).
         * @param valueType The value type. Must not be <jk>null</jk> (use 
<c>Object.class</c> to disable type checking).
@@ -405,7 +433,8 @@ public class FilteredMap<K,V> extends AbstractMap<K,V> {
         *
         * <p>
         * This method provides access to the filter for debugging, inspection, 
or advanced use cases.
-        * The returned predicate is the same instance used internally by this 
map.
+        * The returned predicate is the combined filter used internally by 
this map. If multiple filters
+        * were set via the builder, they are combined using AND logic.
         *
         * <h5 class='section'>Example:</h5>
         * <p class='bjava'>
@@ -478,7 +507,7 @@ public class FilteredMap<K,V> extends AbstractMap<K,V> {
         * Adds an entry to this map with automatic type conversion.
         *
         * <p>
-        * This method converts the key and value using the configured 
converters (if any) and validates
+        * This method converts the key and value using the configured 
key/value functions (if any) and validates
         * the types (if key/value types were specified when creating the map). 
After conversion and validation,
         * the entry is added using the standard {@link #put(Object, Object)} 
method, which applies the filter.
         *
@@ -546,13 +575,86 @@ public class FilteredMap<K,V> extends AbstractMap<K,V> {
         * </p>
         *
         * @param source The map containing entries to add. Can be 
<jk>null</jk> (no-op).
+        * @return This object for method chaining.
         */
-       public void addAll(Map<?,?> source) {
+       public FilteredMap<K,V> addAll(Map<?,?> source) {
                if (source != null) {
                        for (var entry : source.entrySet()) {
                                add(entry.getKey(), entry.getValue());
                        }
                }
+               return this;
+       }
+
+       /**
+        * Adds arbitrary values to this map.
+        *
+        * <p>
+        * Objects can be any of the following:
+        * <ul>
+        *      <li>Maps of key/value types convertible to the key/value types 
of this map.
+        * </ul>
+        *
+        * <p>
+        * Each entry from the maps will be added using {@link #add(Object, 
Object)}, which applies
+        * conversion and filtering.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      FilteredMap&lt;String, Integer&gt; <jv>map</jv> = FilteredMap
+        *              .<jsm>create</jsm>(String.<jk>class</jk>, 
Integer.<jk>class</jk>)
+        *              .filter((k, v) -&gt; v != <jk>null</jk> &amp;&amp; v 
&gt; 0)
+        *              .build();
+        *
+        *      Map&lt;String, Integer&gt; <jv>map1</jv> = Map.of(<js>"a"</js>, 
5, <js>"b"</js>, -1);
+        *      Map&lt;String, Integer&gt; <jv>map2</jv> = Map.of(<js>"c"</js>, 
10);
+        *      <jv>map</jv>.addAny(<jv>map1</jv>, <jv>map2</jv>);  <jc>// Adds 
a=5, c=10 (b=-1 filtered out)</jc>
+        * </p>
+        *
+        * @param values The values to add. Can be <jk>null</jk> or contain 
<jk>null</jk> values (ignored).
+        * @return This object for method chaining.
+        */
+       public FilteredMap<K,V> addAny(Object...values) {
+               if (values != null) {
+                       for (var o : values) {
+                               if (o != null && o instanceof Map<?,?> m) {
+                                       addAll(m);
+                               }
+                       }
+               }
+               return this;
+       }
+
+       /**
+        * Adds a list of key/value pairs to this map.
+        *
+        * <p>
+        * The pairs are processed in order, with each pair consisting of two 
consecutive elements
+        * in the array. Each pair is added using {@link #add(Object, Object)}, 
which applies
+        * conversion and filtering.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      FilteredMap&lt;String, Integer&gt; <jv>map</jv> = FilteredMap
+        *              .<jsm>create</jsm>(String.<jk>class</jk>, 
Integer.<jk>class</jk>)
+        *              .filter((k, v) -&gt; v != <jk>null</jk> &amp;&amp; v 
&gt; 0)
+        *              .build();
+        *
+        *      <jv>map</jv>.addPairs(<js>"a"</js>, 5, <js>"b"</js>, -1, 
<js>"c"</js>, 10);
+        *      <jc>// Adds a=5, c=10 (b=-1 filtered out)</jc>
+        * </p>
+        *
+        * @param pairs The pairs to add. Must have an even number of elements.
+        * @return This object for method chaining.
+        * @throws IllegalArgumentException If an odd number of parameters is 
provided.
+        */
+       public FilteredMap<K,V> addPairs(Object...pairs) {
+               assertArgNotNull("pairs", pairs);
+               if (pairs.length % 2 != 0)
+                       throw illegalArg("Odd number of parameters passed into 
addPairs()");
+               for (var i = 0; i < pairs.length; i += 2)
+                       add(pairs[i], pairs[i + 1]);
+               return this;
        }
 
        private K convertKey(Object key) {
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MapBuilder.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MapBuilder.java
index 0a63eca7b2..f619869d11 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MapBuilder.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MapBuilder.java
@@ -17,14 +17,12 @@
 package org.apache.juneau.commons.collections;
 
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
-import static org.apache.juneau.commons.utils.CollectionUtils.*;
 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.lang.reflect.*;
 import java.util.*;
-
-import org.apache.juneau.commons.utils.*;
+import java.util.function.*;
 
 /**
  * A fluent builder for constructing {@link Map} instances with various 
configuration options.
@@ -36,7 +34,7 @@ import org.apache.juneau.commons.utils.*;
  *
  * <p>
  * Instances of this builder can be created using {@link #create(Class, 
Class)} or the convenience method
- * {@link org.apache.juneau.commons.utils.CollectionUtils#mapb(Class, Class, 
org.apache.juneau.commons.utils.Converter...)}.
+ * {@link org.apache.juneau.commons.utils.CollectionUtils#mapb(Class, Class)}.
  *
  * <h5 class='section'>Features:</h5>
  * <ul class='spaced-list'>
@@ -44,30 +42,30 @@ import org.apache.juneau.commons.utils.*;
  *     <li>Multiple add methods - single entries, pairs, other maps
  *     <li>Arbitrary input support - automatic type conversion with {@link 
#addAny(Object...)}
  *     <li>Pair adding - {@link #addPairs(Object...)} for varargs key-value 
pairs
- *     <li>Filtering support - exclude unwanted values via {@link #filtered()} 
or {@link #filtered(java.util.function.Predicate)}
+ *     <li>Filtering support - exclude unwanted entries via {@link 
#filtered()} or {@link #filtered(BiPredicate)}
  *     <li>Sorting support - natural key order or custom {@link Comparator}
  *     <li>Sparse mode - return <jk>null</jk> for empty maps
  *     <li>Unmodifiable mode - create immutable maps
- *     <li>Custom converters - type conversion via {@link Converter}
+ *     <li>Custom conversion functions - type conversion via {@link 
#keyFunction(Function)} and {@link #valueFunction(Function)}
  * </ul>
  *
  * <h5 class='section'>Examples:</h5>
  * <p class='bjava'>
  *     <jk>import static</jk> 
org.apache.juneau.commons.utils.CollectionUtils.*;
  *
- *     <jc>// Basic usage</jc>
+ *     <jc>// Basic usage - returns Map</jc>
  *     Map&lt;String,Integer&gt; <jv>map</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
  *             .add(<js>"one"</js>, 1)
  *             .add(<js>"two"</js>, 2)
  *             .add(<js>"three"</js>, 3)
  *             .build();
  *
- *     <jc>// Using pairs</jc>
+ *     <jc>// Using pairs - returns Map</jc>
  *     Map&lt;String,String&gt; <jv>props</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, String.<jk>class</jk>)
  *             .addPairs(<js>"host"</js>, <js>"localhost"</js>, 
<js>"port"</js>, <js>"8080"</js>)
  *             .build();
  *
- *     <jc>// With sorting by key</jc>
+ *     <jc>// With sorting by key - returns Map</jc>
  *     Map&lt;String,Integer&gt; <jv>sorted</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
  *             .add(<js>"zebra"</js>, 3)
  *             .add(<js>"apple"</js>, 1)
@@ -75,14 +73,14 @@ import org.apache.juneau.commons.utils.*;
  *             .sorted()
  *             .build();  <jc>// Returns TreeMap with natural key order</jc>
  *
- *     <jc>// Immutable map</jc>
+ *     <jc>// Immutable map - returns Map</jc>
  *     Map&lt;String,String&gt; <jv>config</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, String.<jk>class</jk>)
  *             .add(<js>"env"</js>, <js>"prod"</js>)
  *             .add(<js>"region"</js>, <js>"us-west"</js>)
  *             .unmodifiable()
  *             .build();
  *
- *     <jc>// From multiple sources</jc>
+ *     <jc>// From multiple sources - returns Map</jc>
  *     Map&lt;String,Integer&gt; <jv>existing</jv> = Map.of(<js>"a"</js>, 1, 
<js>"b"</js>, 2);
  *     Map&lt;String,Integer&gt; <jv>combined</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
  *             .addAll(<jv>existing</jv>)
@@ -93,6 +91,18 @@ import org.apache.juneau.commons.utils.*;
  *     Map&lt;String,String&gt; <jv>maybeNull</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, String.<jk>class</jk>)
  *             .sparse()
  *             .build();  <jc>// Returns null, not empty map</jc>
+ *
+ *     <jc>// FluentMap wrapper - use buildFluent()</jc>
+ *     FluentMap&lt;String,Integer&gt; <jv>fluent</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+ *             .add(<js>"one"</js>, 1)
+ *             .buildFluent();
+ *
+ *     <jc>// FilteredMap - use buildFiltered()</jc>
+ *     FilteredMap&lt;String,Integer&gt; <jv>filtered</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+ *             .filtered((k, v) -&gt; v &gt; 0)
+ *             .add(<js>"a"</js>, 5)
+ *             .add(<js>"b"</js>, -1)  <jc>// Filtered out</jc>
+ *             .buildFiltered();
  * </p>
  *
  * <h5 class='section'>Thread Safety:</h5>
@@ -153,11 +163,12 @@ public class MapBuilder<K,V> {
        private boolean unmodifiable = false, sparse = false;
        private Comparator<K> comparator;
 
-       private java.util.function.Predicate<Object> filter;
+       private BiPredicate<K,V> filter;
        private Class<K> keyType;
        private Class<V> valueType;
 
-       private List<Converter> converters;
+       private Function<Object,K> keyFunction;
+       private Function<Object,V> valueFunction;
 
        /**
         * Constructor.
@@ -174,16 +185,13 @@ public class MapBuilder<K,V> {
         * Adds a single entry to this map.
         *
         * <p>
-        * If a filter has been set via {@link 
#filtered(java.util.function.Predicate)} or {@link #filtered()},
-        * the value will only be added if it passes the filter (i.e., the 
filter returns {@code true}).
+        * Note: Filtering is applied at build time, not when adding entries.
         *
         * @param key The map key.
         * @param value The map value.
         * @return This object.
         */
        public MapBuilder<K,V> add(K key, V value) {
-               if (filter != null && ! filter.test(value))
-                       return this;
                if (map == null)
                        map = new LinkedHashMap<>();
                map.put(key, value);
@@ -197,44 +205,49 @@ public class MapBuilder<K,V> {
         * This is a no-op if the value is <jk>null</jk>.
         *
         * <p>
-        * If a filter has been set, each value will be filtered before being 
added.
+        * Note: Filtering is applied at build time, not when adding entries.
         *
         * @param value The map to add to this map.
         * @return This object.
         */
        public MapBuilder<K,V> addAll(Map<K,V> value) {
                if (nn(value)) {
-                       for (Map.Entry<K,V> entry : value.entrySet())
-                               add(entry.getKey(), entry.getValue());
+                       if (map == null)
+                               map = new LinkedHashMap<>();
+                       map.putAll(value);
                }
                return this;
        }
 
        /**
-        * Adds arbitrary values to this list.
+        * Adds arbitrary values to this map.
         *
         * <p>
         * Objects can be any of the following:
         * <ul>
         *      <li>Maps of key/value types convertible to the key/value types 
of this map.
-        *      <li>JSON object strings parsed and convertible to the key/value 
types of this map.
         * </ul>
         *
-        * @param values The values to add.
+        * <p>
+        * Each entry from the maps will be added using {@link #add(Object, 
Object)}, which applies
+        * key/value function conversion if configured. Non-Map objects will 
cause a {@link RuntimeException} to be thrown.
+        *
+        * @param values The values to add. Can contain <jk>null</jk> values 
(ignored).
         * @return This object.
+        * @throws RuntimeException If a non-Map object is provided.
         */
        @SuppressWarnings("unchecked")
        public MapBuilder<K,V> addAny(Object...values) {
                for (var o : values) {
                        if (nn(o)) {
                                if (o instanceof Map o2) {
-                                       o2.forEach((k, v) -> 
add(toType(keyType, k), toType(valueType, v)));
+                                       o2.forEach((k, v) -> {
+                                               K key = convertKey(k);
+                                               V value = convertValue(v);
+                                               add(key, value);
+                                       });
                                } else {
-                                       var m = converters.stream().map(x -> 
x.convertTo(Map.class, o)).filter(Utils::nn).findFirst().orElse(null);
-                                       if (nn(m))
-                                               addAny(m);
-                                       else
-                                               throw rex("Object of type {0} 
could not be converted to type {1}", cn(o), "Map");
+                                       throw rex("Object of type {0} could not 
be converted to type {1}", cn(o), "Map");
                                }
                        }
                }
@@ -251,7 +264,7 @@ public class MapBuilder<K,V> {
        public MapBuilder<K,V> addPairs(Object...pairs) {
                assertArgNotNull("pairs", pairs);
                if (pairs.length % 2 != 0)
-                       throw illegalArg("Odd number of parameters passed into 
AMap.ofPairs()");
+                       throw illegalArg("Odd number of parameters passed into 
MapBuilder.addPairs(...)");
                for (var i = 0; i < pairs.length; i += 2)
                        add((K)pairs[i], (V)pairs[i + 1]);
                return this;
@@ -260,48 +273,155 @@ public class MapBuilder<K,V> {
        /**
         * Builds the map.
         *
-        * @return A map conforming to the settings on this builder.
-        */
-       /**
-        * Builds the map.
+        * <p>
+        * Applies filtering, sorting, unmodifiable, and sparse options.
         *
         * <p>
-        * Applies sorting/unmodifiable/sparse options.
+        * If filtering is applied, the result is wrapped in a {@link 
FilteredMap}. If sorting is applied,
+        * a {@link TreeMap} is used as the underlying map; otherwise a {@link 
LinkedHashMap} is used.
         *
-        * @return The built map or {@code null} if {@link #sparse()} is set 
and the map is empty.
+        * @return The built map, or {@code null} if {@link #sparse()} is set 
and the map is empty.
         */
        public Map<K,V> build() {
                if (sparse) {
                        if (nn(map) && map.isEmpty())
-                               map = null;
+                               return null;
                } else {
                        if (map == null)
                                map = new LinkedHashMap<>();
                }
-               if (nn(map)) {
-                       if (nn(comparator)) {
+
+               Map<K,V> result = map;
+
+               if (nn(result)) {
+                       // Apply filtering if specified
+                       if (nn(filter)) {
+                               Map<K,V> innerMap = nn(comparator)
+                                       ? new TreeMap<>(comparator)
+                                       : new LinkedHashMap<>();
+
+                               var filteredMap = 
FilteredMap.<K,V>create(keyType, valueType)
+                                       .filter(filter)
+                                       .inner(innerMap)
+                                       .build();
+
+                               // Add all entries to the filtered map
+                               result.forEach(filteredMap::put);
+                               result = filteredMap;
+                       } else if (nn(comparator)) {
+                               // Apply sorting if no filter
                                var m2 = new TreeMap<K,V>(comparator);
-                               m2.putAll(map);
-                               map = m2;
+                               m2.putAll(result);
+                               result = m2;
                        }
+
+                       // Apply unmodifiable if specified
                        if (unmodifiable)
-                               map = Collections.unmodifiableMap(map);
+                               result = Collections.unmodifiableMap(result);
                }
-               return map;
+
+               return result;
+       }
+
+       /**
+        * Builds the map and wraps it in a {@link FluentMap}.
+        *
+        * <p>
+        * This is a convenience method that calls {@link #build()} and wraps 
the result in a {@link FluentMap}.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jk>import static</jk> 
org.apache.juneau.commons.utils.CollectionUtils.*;
+        *
+        *      FluentMap&lt;String,Integer&gt; <jv>map</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+        *              .add(<js>"one"</js>, 1)
+        *              .add(<js>"two"</js>, 2)
+        *              .buildFluent();
+        * </p>
+        *
+        * @return The built map wrapped in a {@link FluentMap}, or {@code 
null} if {@link #sparse()} is set and the map is empty.
+        */
+       public FluentMap<K,V> buildFluent() {
+               Map<K,V> result = build();
+               return result == null ? null : new FluentMap<>(result);
        }
 
        /**
-        * Registers value converters that can adapt incoming values in {@link 
#addAny(Object...)}.
+        * Builds the map as a {@link FilteredMap}.
+        *
+        * <p>
+        * If sorting is applied, a {@link TreeMap} is used as the underlying 
map; otherwise a {@link LinkedHashMap} is used.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jk>import static</jk> 
org.apache.juneau.commons.utils.CollectionUtils.*;
         *
-        * @param values Converters to register. Ignored if {@code null}.
+        *      FilteredMap&lt;String,Integer&gt; <jv>map</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+        *              .filtered((k, v) -&gt; v != <jk>null</jk> &amp;&amp; v 
&gt; 0)
+        *              .add(<js>"a"</js>, 5)
+        *              .add(<js>"b"</js>, -1)  <jc>// Will be filtered out</jc>
+        *              .buildFiltered();
+        * </p>
+        *
+        * <p>
+        * Note: If {@link #unmodifiable()} is set, the returned map will be 
wrapped in an unmodifiable view,
+        * which may cause issues if the FilteredMap tries to modify it 
internally. It's recommended to avoid
+        * using {@link #unmodifiable()} when calling this method.
+        *
+        * @return The built map as a {@link FilteredMap}, or {@code null} if 
{@link #sparse()} is set and the map is empty.
+        */
+       public FilteredMap<K,V> buildFiltered() {
+               var m = build();
+               if (m == null)  // sparse mode and empty
+                       return null;
+               if (m instanceof FilteredMap<K,V> m2)
+                       return m2;
+               // Note that if unmodifiable is true, 'm' will be unmodifiable 
and will cause an error if you try
+               // to insert a value from within FilteredMap.
+               return FilteredMap.create(keyType, valueType).inner(m).build();
+       }
+
+       /**
+        * Sets the key conversion function for converting keys in {@link 
#addAny(Object...)}.
+        *
+        * <p>
+        * The function is applied to each key when adding entries from maps in 
{@link #addAny(Object...)}.
+        *
+        * @param keyFunction The function to convert keys. Must not be 
<jk>null</jk>.
         * @return This object.
         */
-       public MapBuilder<K,V> converters(Converter...values) {
-               if (values.length == 0)
-                       return this;
-               if (converters == null)
-                       converters = list();
-               converters.addAll(l(values));
+       public MapBuilder<K,V> keyFunction(Function<Object,K> keyFunction) {
+               this.keyFunction = assertArgNotNull("keyFunction", keyFunction);
+               return this;
+       }
+
+       /**
+        * Sets the value conversion function for converting values in {@link 
#addAny(Object...)}.
+        *
+        * <p>
+        * The function is applied to each value when adding entries from maps 
in {@link #addAny(Object...)}.
+        *
+        * @param valueFunction The function to convert values. Must not be 
<jk>null</jk>.
+        * @return This object.
+        */
+       public MapBuilder<K,V> valueFunction(Function<Object,V> valueFunction) {
+               this.valueFunction = assertArgNotNull("valueFunction", 
valueFunction);
+               return this;
+       }
+
+       /**
+        * Sets both key and value conversion functions.
+        *
+        * <p>
+        * Convenience method for setting both functions at once.
+        *
+        * @param keyFunction The function to convert keys. Must not be 
<jk>null</jk>.
+        * @param valueFunction The function to convert values. Must not be 
<jk>null</jk>.
+        * @return This object.
+        */
+       public MapBuilder<K,V> functions(Function<Object,K> keyFunction, 
Function<Object,V> valueFunction) {
+               this.keyFunction = assertArgNotNull("keyFunction", keyFunction);
+               this.valueFunction = assertArgNotNull("valueFunction", 
valueFunction);
                return this;
        }
 
@@ -334,12 +454,12 @@ public class MapBuilder<K,V> {
         * <p class='bjava'>
         *      <jk>import static</jk> 
org.apache.juneau.commons.utils.CollectionUtils.*;
         *
-        *      Map&lt;String,Object&gt; <jv>map</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, Object.<jk>class</jk>)
+        *      FluentMap&lt;String,Object&gt; <jv>map</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, Object.<jk>class</jk>)
         *              .filtered()
         *              .add(<js>"name"</js>, <js>"John"</js>)
-        *              .add(<js>"age"</js>, -1)              <jc>// Not 
added</jc>
-        *              .add(<js>"enabled"</js>, <jk>false</jk>)   <jc>// Not 
added</jc>
-        *              .add(<js>"tags"</js>, <jk>new</jk> String[0]) <jc>// 
Not added</jc>
+        *              .add(<js>"age"</js>, -1)              <jc>// Filtered 
out at build time</jc>
+        *              .add(<js>"enabled"</js>, <jk>false</jk>)   <jc>// 
Filtered out at build time</jc>
+        *              .add(<js>"tags"</js>, <jk>new</jk> String[0]) <jc>// 
Filtered out at build time</jc>
         *              .build();
         * </p>
         *
@@ -347,22 +467,30 @@ public class MapBuilder<K,V> {
         */
        public MapBuilder<K,V> filtered() {
                // @formatter:off
-               return filtered(x -> ! (
-                       x == null
-                       || (x instanceof Boolean x2 && x2.equals(false))
-                       || (x instanceof Number x3 && x3.intValue() == -1)
-                       || (isArray(x) && Array.getLength(x) == 0)
-                       || (x instanceof Map x2 && x2.isEmpty())
-                       || (x instanceof Collection x3 && x3.isEmpty())
+               return filtered((k, v) -> ! (
+                       v == null
+                       || (v instanceof Boolean v2 && v2.equals(false))
+                       || (v instanceof Number v3 && v3.intValue() == -1)
+                       || (isArray(v) && Array.getLength(v) == 0)
+                       || (v instanceof Map v2 && v2.isEmpty())
+                       || (v instanceof Collection v3 && v3.isEmpty())
                        ));
                // @formatter:on
        }
 
        /**
-        * Applies a filter predicate to values added via {@link #add(Object, 
Object)}.
+        * Applies a filter predicate to entries when building the map.
         *
         * <p>
-        * Values where the predicate returns {@code true} will be kept; values 
where it returns {@code false} will not be added.
+        * The filter receives both the key and value of each entry. Entries 
where the predicate returns
+        * {@code true} will be kept; entries where it returns {@code false} 
will be filtered out.
+        *
+        * <p>
+        * This method can be called multiple times. When called multiple 
times, all filters are combined
+        * using AND logic - an entry must pass all filters to be kept in the 
map.
+        *
+        * <p>
+        * Note: Filtering is applied at build time, not when adding entries.
         *
         * <h5 class='section'>Example:</h5>
         * <p class='bjava'>
@@ -370,18 +498,32 @@ public class MapBuilder<K,V> {
         *
         *      <jc>// Keep only non-null, non-empty string values</jc>
         *      Map&lt;String,String&gt; <jv>map</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, String.<jk>class</jk>)
-        *              .filtered(<jv>x</jv> -&gt; <jv>x</jv> != <jk>null</jk> 
&amp;&amp; !<jv>x</jv>.equals(<js>""</js>))
+        *              .filtered((k, v) -&gt; v != <jk>null</jk> &amp;&amp; 
!v.equals(<js>""</js>))
         *              .add(<js>"a"</js>, <js>"foo"</js>)
-        *              .add(<js>"b"</js>, <jk>null</jk>)     <jc>// Not 
added</jc>
-        *              .add(<js>"c"</js>, <js>""</js>)       <jc>// Not 
added</jc>
+        *              .add(<js>"b"</js>, <jk>null</jk>)     <jc>// Filtered 
out at build time</jc>
+        *              .add(<js>"c"</js>, <js>""</js>)       <jc>// Filtered 
out at build time</jc>
+        *              .build();
+        *
+        *      <jc>// Multiple filters combined with AND</jc>
+        *      Map&lt;String,Integer&gt; <jv>map2</jv> = 
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+        *              .filtered((k, v) -&gt; v != <jk>null</jk>)           
<jc>// First filter</jc>
+        *              .filtered((k, v) -&gt; v &gt; 0)                    
<jc>// Second filter (ANDed with first)</jc>
+        *              .filtered((k, v) -&gt; ! k.startsWith(<js>"_"</js>)) 
<jc>// Third filter (ANDed with previous)</jc>
+        *              .add(<js>"a"</js>, 5)
+        *              .add(<js>"_b"</js>, 10)  <jc>// Filtered out (starts 
with "_")</jc>
+        *              .add(<js>"c"</js>, -1)   <jc>// Filtered out (not &gt; 
0)</jc>
         *              .build();
         * </p>
         *
         * @param filter The filter predicate. Must not be <jk>null</jk>.
         * @return This object.
         */
-       public MapBuilder<K,V> filtered(java.util.function.Predicate<Object> 
filter) {
-               this.filter = assertArgNotNull("filter", filter);
+       public MapBuilder<K,V> filtered(BiPredicate<K,V> filter) {
+               BiPredicate<K,V> newFilter = assertArgNotNull("filter", filter);
+               if (this.filter == null)
+                       this.filter = newFilter;
+               else
+                       this.filter = this.filter.and(newFilter);
                return this;
        }
 
@@ -444,22 +586,32 @@ public class MapBuilder<K,V> {
        }
 
        /**
-        * Converts an object to the specified type.
+        * Converts a key object to the key type.
         *
-        * @param <T> The type to convert to.
-        * @param c The type to convert to.
         * @param o The object to convert.
-        * @return The converted object.
-        * @throws RuntimeException If the object cannot be converted to the 
specified type.
+        * @return The converted key.
         */
-       private <T> T toType(Class<T> c, Object o) {
-               if (c.isInstance(o))
-                       return c.cast(o);
-               if (nn(converters)) {
-                       var e = converters.stream().map(x -> x.convertTo(c, 
o)).filter(Utils::nn).findFirst().orElse(null);
-                       if (nn(e))
-                               return e;
-               }
-               throw rex("Object of type {0} could not be converted to type 
{1}", cn(o), cn(c));
+       @SuppressWarnings("unchecked")
+       private K convertKey(Object o) {
+               if (keyType.isInstance(o))
+                       return (K)o;
+               if (nn(keyFunction))
+                       return keyFunction.apply(o);
+               throw rex("Object of type {0} could not be converted to key 
type {1}", cn(o), cn(keyType));
+       }
+
+       /**
+        * Converts a value object to the value type.
+        *
+        * @param o The object to convert.
+        * @return The converted value.
+        */
+       @SuppressWarnings("unchecked")
+       private V convertValue(Object o) {
+               if (valueType.isInstance(o))
+                       return (V)o;
+               if (nn(valueFunction))
+                       return valueFunction.apply(o);
+               throw rex("Object of type {0} could not be converted to value 
type {1}", cn(o), cn(valueType));
        }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
index 6bd5a35391..5e94677692 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
@@ -1567,8 +1567,8 @@ public class CollectionUtils {
         * @param converters Optional converters to use for converting values.
         * @return A new map builder.
         */
-       public static <K,V> MapBuilder<K,V> mapb(Class<K> keyType, Class<V> 
valueType, Converter...converters) {
-               return MapBuilder.create(keyType, 
valueType).converters(converters);
+       public static <K,V> MapBuilder<K,V> mapb(Class<K> keyType, Class<V> 
valueType) {
+               return MapBuilder.create(keyType, valueType);
        }
 
        /**
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 11f018c1d7..88c1d04631 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
@@ -255,7 +255,10 @@ public class ConverterUtils {
         * @return A new {@link MapBuilder} containing the converted entries.
         */
        public static <K,V> MapBuilder<K,V> toMapBuilder(Object value, Class<K> 
keyType, Class<V> valueType) {
-               return mapb(keyType, valueType, 
GenericConverter.INSTANCE).addAny(value);
+               return mapb(keyType, valueType)
+                       .keyFunction(o -> 
GenericConverter.INSTANCE.convertTo(keyType, o))
+                       .valueFunction(o -> 
GenericConverter.INSTANCE.convertTo(valueType, o))
+                       .addAny(value);
        }
 
        /**
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/BidiMap_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/BidiMap_Test.java
index dade9ee646..825ff9d9aa 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/BidiMap_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/BidiMap_Test.java
@@ -650,7 +650,6 @@ class BidiMap_Test extends TestBase {
                        .add("one", 1)
                        .build();
 
-               assertFalse(map.equals("not a map"));
                assertFalse(map.equals(null));
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FilteredMap_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FilteredMap_Test.java
index 34a2e3df41..5b700bff7a 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FilteredMap_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FilteredMap_Test.java
@@ -164,7 +164,7 @@ class FilteredMap_Test extends TestBase {
                var map = FilteredMap
                        .create(String.class, Integer.class)
                        .filter((k, v) -> v != null && v > 0)
-                       .creator(() -> new TreeMap<>())
+                       .inner(new TreeMap<>())
                        .build();
 
                map.put("zebra", 3);
@@ -185,7 +185,7 @@ class FilteredMap_Test extends TestBase {
                var map = FilteredMap
                        .create(String.class, String.class)
                        .filter((k, v) -> v != null)
-                       .creator(() -> new ConcurrentHashMap<>())
+                       .inner(new ConcurrentHashMap<>())
                        .build();
 
                map.put("key1", "value1");
@@ -420,10 +420,18 @@ class FilteredMap_Test extends TestBase {
        
//====================================================================================================
 
        @Test
-       void i01_builder_noFilter_throwsException() {
-               assertThrowsWithMessage(IllegalArgumentException.class, 
"filter", () -> {
-                       FilteredMap.create(String.class, String.class).build();
-               });
+       void i01_builder_noFilter_acceptsAllEntries() {
+               // Filter is optional - defaults to (k,v) -> true (accepts all 
entries)
+               var map = FilteredMap.create(String.class, String.class)
+                       .build();
+
+               assertNotNull(map);
+               map.put("key1", "value1");
+               map.put("key2", null);  // Should be accepted (no filter)
+               map.put("key3", "value3");
+
+               assertSize(3, map);
+               assertMap(map, "key1=value1", "key2=<null>", "key3=value3");
        }
 
        @Test
@@ -434,11 +442,11 @@ class FilteredMap_Test extends TestBase {
        }
 
        @Test
-       void i03_builder_nullCreator_throwsException() {
+       void i03_builder_nullInner_throwsException() {
                assertThrowsWithMessage(IllegalArgumentException.class, 
"value", () -> {
                        FilteredMap.create(String.class, String.class)
                                .filter((k, v) -> true)
-                               .creator(null);
+                               .inner(null);
                });
        }
 
@@ -907,7 +915,10 @@ class FilteredMap_Test extends TestBase {
 
                var retrievedFilter = map.getFilter();
                assertNotNull(retrievedFilter);
-               assertSame(originalFilter, retrievedFilter);  // Should be the 
same instance
+               // The filter may be combined with the default filter, so test 
behavior instead of instance equality
+               assertTrue(retrievedFilter.test("key1", 5));   // Should accept 
positive values
+               assertFalse(retrievedFilter.test("key2", -1)); // Should reject 
negative values
+               assertFalse(retrievedFilter.test("key3", null)); // Should 
reject null values
        }
 
        @Test
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Flag_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Flag_Test.java
index 82a6fdf144..0061eb2a8d 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Flag_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/Flag_Test.java
@@ -281,7 +281,6 @@ class Flag_Test extends TestBase {
        @Test
        void d06_equals_notAFlag() {
                var flag = Flag.of(true);
-               assertFalse(flag.equals("not a flag"));
                assertFalse(flag.equals(null));
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/KeywordSet_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/KeywordSet_Test.java
index a23f342655..938daf601e 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/KeywordSet_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/KeywordSet_Test.java
@@ -112,7 +112,6 @@ class KeywordSet_Test extends TestBase {
        void w08_equals_notAKeywordSet() {
                var ks = new KeywordSet("apple", "banana");
 
-               assertFalse(ks.equals("not a keyword set"));
                assertFalse(ks.equals(null));
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MapBuilder_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MapBuilder_Test.java
index 01c7d32bf2..f78cb25392 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MapBuilder_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MapBuilder_Test.java
@@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.commons.utils.*;
 import org.junit.jupiter.api.*;
 
 class MapBuilder_Test extends TestBase {
@@ -353,7 +352,7 @@ class MapBuilder_Test extends TestBase {
        @Test
        void i01_filtered_customPredicate() {
                var map = MapBuilder.create(String.class, String.class)
-                       .filtered(x -> x != null && !x.equals(""))
+                       .filtered((k, v) -> v != null && !v.equals(""))
                        .add("a", "foo")
                        .add("b", null)     // Not added
                        .add("c", "")       // Not added
@@ -382,7 +381,7 @@ class MapBuilder_Test extends TestBase {
        @Test
        void i03_filtered_rejectsValue() {
                var map = MapBuilder.create(String.class, Integer.class)
-                       .filtered(x -> x != null && (Integer)x > 0)
+                       .filtered((k, v) -> v != null && v > 0)
                        .add("a", 1)
                        .add("b", -1)  // Not added
                        .add("c", 0)   // Not added
@@ -395,7 +394,7 @@ class MapBuilder_Test extends TestBase {
        @Test
        void i04_add_withFilter() {
                var map = MapBuilder.create(String.class, String.class)
-                       .filtered(x -> x != null && x instanceof String && 
((String)x).length() > 2)
+                       .filtered((k, v) -> v != null && v.length() > 2)
                        .add("a", "foo")   // Added
                        .add("b", "ab")    // Not added (length <= 2)
                        .add("c", "bar")   // Added
@@ -412,7 +411,7 @@ class MapBuilder_Test extends TestBase {
                existing.put("z", "another");
 
                var map = MapBuilder.create(String.class, String.class)
-                       .filtered(x -> x != null && x instanceof String && 
((String)x).length() > 2)
+                       .filtered((k, v) -> v != null && v.length() > 2)
                        .addAll(existing)
                        .build();
 
@@ -420,13 +419,12 @@ class MapBuilder_Test extends TestBase {
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
-       // Converters
+       // Key/Value Functions
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       void j01_converters_emptyArray() {
+       void j01_keyValueFunctions_notSet() {
                var map = MapBuilder.create(String.class, Integer.class)
-                       .converters()  // Empty array
                        .add("a", 1)
                        .build();
 
@@ -434,23 +432,13 @@ class MapBuilder_Test extends TestBase {
        }
 
        @Test
-       void j02_converters_withConverter() {
-               Converter converter = new Converter() {
-                       @Override
-                       public <T> T convertTo(Class<T> type, Object o) {
-                               if (type == Integer.class && o instanceof 
String) {
-                                       return 
type.cast(Integer.parseInt((String)o));
-                               }
-                               return null;
-                       }
-               };
-
+       void j02_keyValueFunctions_withValueFunction() {
                var inputMap = new LinkedHashMap<String,String>();
                inputMap.put("a", "1");
                inputMap.put("b", "2");
 
                var map = MapBuilder.create(String.class, Integer.class)
-                       .converters(converter)
+                       .valueFunction(o -> o instanceof String ? 
Integer.parseInt((String)o) : (Integer)o)
                        .addAny(inputMap)
                        .build();
 
@@ -458,33 +446,35 @@ class MapBuilder_Test extends TestBase {
        }
 
        @Test
-       void j03_converters_multipleConverters() {
-               Converter converter1 = new Converter() {
-                       @Override
-                       public <T> T convertTo(Class<T> type, Object o) {
-                               return null;  // Doesn't handle this
-                       }
-               };
-
-               Converter converter2 = new Converter() {
-                       @Override
-                       public <T> T convertTo(Class<T> type, Object o) {
-                               if (type == Integer.class && o instanceof 
String) {
-                                       return 
type.cast(Integer.parseInt((String)o));
-                               }
-                               return null;
-                       }
-               };
+       void j03_keyValueFunctions_withBothFunctions() {
+               var inputMap = new LinkedHashMap<Integer,String>();
+               inputMap.put(1, "10");
+               inputMap.put(2, "20");
 
-               var inputMap = new LinkedHashMap<String,String>();
-               inputMap.put("a", "1");
+               var map = MapBuilder.create(String.class, Integer.class)
+                       .keyFunction(o -> String.valueOf(o))
+                       .valueFunction(o -> o instanceof String ? 
Integer.parseInt((String)o) : (Integer)o)
+                       .addAny(inputMap)
+                       .build();
+
+               assertMap(map, "1=10", "2=20");
+       }
+
+       @Test
+       void j04_keyValueFunctions_functionsConvenienceMethod() {
+               var inputMap = new LinkedHashMap<Integer,String>();
+               inputMap.put(1, "10");
+               inputMap.put(2, "20");
 
                var map = MapBuilder.create(String.class, Integer.class)
-                       .converters(converter1, converter2)
+                       .functions(
+                               o -> String.valueOf(o),  // key function
+                               o -> o instanceof String ? 
Integer.parseInt((String)o) : (Integer)o  // value function
+                       )
                        .addAny(inputMap)
                        .build();
 
-               assertMap(map, "a=1");
+               assertMap(map, "1=10", "2=20");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -550,25 +540,13 @@ class MapBuilder_Test extends TestBase {
 
        @Test
        void k04_addAny_withTypeConversion() {
-               var converter = new Converter() {
-                       @Override
-                       public <T> T convertTo(Class<T> type, Object o) {
-                               if (type == String.class && o instanceof 
Integer) {
-                                       return type.cast(String.valueOf(o));
-                               }
-                               if (type == Integer.class && o instanceof 
String) {
-                                       return 
type.cast(Integer.parseInt((String)o));
-                               }
-                               return null;
-                       }
-               };
-
                var inputMap = new LinkedHashMap<Integer,String>();
                inputMap.put(1, "10");
                inputMap.put(2, "20");
 
                var map = MapBuilder.create(String.class, Integer.class)
-                       .converters(converter)
+                       .keyFunction(o -> String.valueOf(o))
+                       .valueFunction(o -> o instanceof String ? 
Integer.parseInt((String)o) : (Integer)o)
                        .addAny(inputMap)
                        .build();
 
@@ -576,57 +554,33 @@ class MapBuilder_Test extends TestBase {
        }
 
        @Test
-       void k05_addAny_withConverterToMap() {
-               var converter = new Converter() {
-                       @Override
-                       public <T> T convertTo(Class<T> type, Object o) {
-                               if (type == Map.class && o instanceof String) {
-                                       var m = new 
LinkedHashMap<String,String>();
-                                       // Simple parsing: 
"key1=value1,key2=value2"
-                                       var s = (String)o;
-                                       for (var pair : s.split(",")) {
-                                               var kv = pair.split("=");
-                                               if (kv.length == 2) {
-                                                       m.put(kv[0], kv[1]);
-                                               }
-                                       }
-                                       return type.cast(m);
-                               }
-                               return null;
+       void k05_addAny_withStringToMapConversion() {
+               // This test is no longer applicable since addAny only accepts 
Map instances
+               // If we want to parse strings, we'd need to do it before 
calling addAny
+               var parsedMap = new LinkedHashMap<String,String>();
+               for (var pair : "a=1,b=2".split(",")) {
+                       var kv = pair.split("=");
+                       if (kv.length == 2) {
+                               parsedMap.put(kv[0], kv[1]);
                        }
-               };
+               }
 
                var map = MapBuilder.create(String.class, String.class)
-                       .converters(converter)
-                       .addAny("a=1,b=2")
+                       .addAny(parsedMap)
                        .build();
 
                assertMap(map, "a=1", "b=2");
        }
 
        @Test
-       void k06_addAny_withConverterToMap_recursive() {
-               var converter = new Converter() {
-                       @Override
-                       public <T> T convertTo(Class<T> type, Object o) {
-                               if (type == Map.class && o instanceof String) {
-                                       var m = new 
LinkedHashMap<String,String>();
-                                       var s = (String)o;
-                                       for (var pair : s.split(",")) {
-                                               var kv = pair.split("=");
-                                               if (kv.length == 2) {
-                                                       m.put(kv[0], kv[1]);
-                                               }
-                                       }
-                                       return type.cast(m);
-                               }
-                               return null;
-                       }
-               };
+       void k06_addAny_withMultipleMaps_parsedFromStrings() {
+               var map1 = new LinkedHashMap<String,String>();
+               map1.put("x", "foo");
+               var map2 = new LinkedHashMap<String,String>();
+               map2.put("y", "bar");
 
                var map = MapBuilder.create(String.class, String.class)
-                       .converters(converter)
-                       .addAny("x=foo", "y=bar")
+                       .addAny(map1, map2)
                        .build();
 
                assertMap(map, "x=foo", "y=bar");
@@ -640,9 +594,9 @@ class MapBuilder_Test extends TestBase {
        }
 
        @Test
-       void k08_addAny_conversionFailure() {
-               // When converters is null and we try to add a non-Map, it will 
throw NPE
-               assertThrows(NullPointerException.class, () -> {
+       void k08_addAny_nonMapObject() {
+               // addAny only accepts Map instances, non-Map objects throw 
exception
+               assertThrows(RuntimeException.class, () -> {
                        MapBuilder.create(String.class, Integer.class)
                                .addAny("not-a-map")
                                .build();
@@ -650,39 +604,40 @@ class MapBuilder_Test extends TestBase {
        }
 
        @Test
-       void k09_addAny_converterReturnsNull() {
-               // Converter exists but returns null (can't convert)
-               var converter = new Converter() {
-                       @Override
-                       public <T> T convertTo(Class<T> type, Object o) {
-                               return null;  // Can't convert
-                       }
-               };
+       void k09_addAny_valueFunctionReturnsNull() {
+               // Value function that can't convert should throw exception
+               var inputMap = new LinkedHashMap<String,String>();
+               inputMap.put("a", "not-an-integer");
 
-               // Should throw RuntimeException when converter can't convert 
non-Map object
                assertThrows(RuntimeException.class, () -> {
                        MapBuilder.create(String.class, Integer.class)
-                               .converters(converter)
-                               .addAny("not-a-map")
+                               .valueFunction(o -> {
+                                       if (o instanceof String) {
+                                               try {
+                                                       return 
Integer.parseInt((String)o);
+                                               } catch (NumberFormatException 
e) {
+                                                       throw new 
RuntimeException("Cannot convert", e);
+                                               }
+                                       }
+                                       return (Integer)o;
+                               })
+                               .addAny(inputMap)
                                .build();
                });
        }
 
        @Test
-       void k10_addAny_toType_conversionFailure() {
-               var converter = new Converter() {
-                       @Override
-                       public <T> T convertTo(Class<T> type, Object o) {
-                               return null;  // Can't convert
-                       }
-               };
-
-               var inputMap = new LinkedHashMap<String,String>();
-               inputMap.put("a", "not-an-integer");
+       void k10_addAny_keyFunctionConversionFailure() {
+               var inputMap = new LinkedHashMap<Object,String>();
+               inputMap.put(new Object(), "value");  // Object can't be 
converted to String
 
                assertThrows(RuntimeException.class, () -> {
-                       MapBuilder.create(String.class, Integer.class)
-                               .converters(converter)
+                       MapBuilder.create(String.class, String.class)
+                               .keyFunction(o -> {
+                                       if (o instanceof String)
+                                               return (String)o;
+                                       throw new RuntimeException("Cannot 
convert key");
+                               })
                                .addAny(inputMap)
                                .build();
                });
@@ -742,4 +697,112 @@ class MapBuilder_Test extends TestBase {
                assertNotNull(map);
                assertThrows(UnsupportedOperationException.class, () -> 
map.put("a", 1));
        }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // BuildFluent
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void m01_buildFluent_returnsFluentMap() {
+               var map = MapBuilder.create(String.class, Integer.class)
+                       .add("a", 1)
+                       .add("b", 2)
+                       .buildFluent();
+
+               assertNotNull(map);
+               assertSize(2, map);
+               assertMap(map, "a=1", "b=2");
+       }
+
+       @Test
+       void m02_buildFluent_sparseEmpty() {
+               var map = MapBuilder.create(String.class, Integer.class)
+                       .sparse()
+                       .buildFluent();
+
+               assertNull(map);
+       }
+
+       @Test
+       void m03_buildFluent_withFiltering() {
+               var map = MapBuilder.create(String.class, Integer.class)
+                       .filtered((k, v) -> v != null && v > 0)
+                       .add("a", 1)
+                       .add("b", -1)  // Filtered out
+                       .buildFluent();
+
+               assertNotNull(map);
+               assertSize(1, map);
+               assertMap(map, "a=1");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // BuildFiltered
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void n01_buildFiltered_withFilter() {
+               var map = MapBuilder.create(String.class, Integer.class)
+                       .filtered((k, v) -> v != null && v > 0)
+                       .add("a", 1)
+                       .add("b", -1)  // Filtered out
+                       .add("c", 2)
+                       .buildFiltered();
+
+               assertNotNull(map);
+               assertSize(2, map);
+               assertMap(map, "a=1", "c=2");
+       }
+
+       @Test
+       void n02_buildFiltered_withoutFilter() {
+               // buildFiltered() should work even without explicit filter 
(uses default filter)
+               var map = MapBuilder.create(String.class, Integer.class)
+                       .add("a", 1)
+                       .add("b", 2)
+                       .buildFiltered();
+
+               assertNotNull(map);
+               assertSize(2, map);
+       }
+
+       @Test
+       void n03_buildFiltered_sparseEmpty() {
+               var map = MapBuilder.create(String.class, Integer.class)
+                       .filtered((k, v) -> v != null && v > 0)
+                       .sparse()
+                       .buildFiltered();
+
+               assertNull(map);
+       }
+
+       @Test
+       void n04_buildFiltered_withSorted() {
+               var map = MapBuilder.create(String.class, Integer.class)
+                       .filtered((k, v) -> v != null && v > 0)
+                       .add("c", 3)
+                       .add("a", 1)
+                       .add("b", 2)
+                       .sorted()
+                       .buildFiltered();
+
+               assertNotNull(map);
+               assertList(map.keySet(), "a", "b", "c");
+       }
+
+       @Test
+       void n05_buildFiltered_multipleFilters() {
+               var map = MapBuilder.create(String.class, Integer.class)
+                       .filtered((k, v) -> v != null)
+                       .filtered((k, v) -> v > 0)
+                       .filtered((k, v) -> !k.startsWith("_"))
+                       .add("a", 1)
+                       .add("_b", 2)  // Filtered out (starts with _)
+                       .add("c", -1)  // Filtered out (not > 0)
+                       .add("d", 3)
+                       .buildFiltered();
+
+               assertNotNull(map);
+               assertMap(map, "a=1", "d=3");
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiList_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiList_Test.java
index 4704db7f84..94fee985f3 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiList_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiList_Test.java
@@ -593,8 +593,6 @@ class MultiList_Test extends TestBase {
        void g05_equals_notAList() {
                var l1 = l(a("1", "2"));
                var multiList = new MultiList<>(l1);
-
-               assertFalse(multiList.equals("not a list"));
                assertFalse(multiList.equals(null));
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiMap_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiMap_Test.java
index 9e42df341e..bf379f2263 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiMap_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiMap_Test.java
@@ -488,7 +488,6 @@ class MultiMap_Test extends TestBase {
                var map1 = map("key1", "value1");
                var multiMap = new MultiMap<>(map1);
 
-               assertFalse(multiMap.equals("not a map"));
                assertFalse(multiMap.equals(null));
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiSet_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiSet_Test.java
index 4a2f2255a2..9dae3e02b6 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiSet_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/MultiSet_Test.java
@@ -283,7 +283,6 @@ class MultiSet_Test extends TestBase {
                var l1 = l(a("1", "2"));
                var multiSet = new MultiSet<>(l1);
 
-               assertFalse(multiSet.equals("not a set"));
                assertFalse(multiSet.equals(null));
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/ReversedList_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/ReversedList_Test.java
index 7b83ea7cde..c0a277ff7f 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/ReversedList_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/ReversedList_Test.java
@@ -567,7 +567,6 @@ class ReversedList_Test extends TestBase {
                var original = new ArrayList<>(List.of("a", "b", "c"));
                var reversed = new ReversedList<>(original);
 
-               assertFalse(reversed.equals("not a list"));
                assertFalse(reversed.equals(null));
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/SimpleMap_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/SimpleMap_Test.java
index c0918ca342..fcfd4013f4 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/SimpleMap_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/SimpleMap_Test.java
@@ -386,7 +386,6 @@ class SimpleMap_Test extends TestBase {
                Object[] values = { "value1" };
                SimpleMap<String,Object> map = new SimpleMap<>(keys, values);
 
-               assertFalse(map.equals("not a map"));
                assertFalse(map.equals(null));
        }
 

Reply via email to