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 27f4857248 FilteredMap/MapBuilder improvements
27f4857248 is described below
commit 27f48572485699737aef21bbb7917b7c33f4cccc
Author: James Bognar <[email protected]>
AuthorDate: Sun Dec 14 15:16:05 2025 -0500
FilteredMap/MapBuilder improvements
---
.../org/apache/juneau/junit/bct/Listifiers.java | 2 +
.../juneau/commons/collections/FilteredList.java | 648 ++++++++++++++
.../juneau/commons/collections/FilteredSet.java | 595 +++++++++++++
.../juneau/commons/collections/ListBuilder.java | 51 +-
.../juneau/commons/collections/MapBuilder.java | 229 ++++-
.../juneau/commons/collections/SetBuilder.java | 51 +-
.../juneau/commons/utils/CollectionUtils.java | 4 +-
.../org/apache/juneau/commons/utils/Utils.java | 12 +
.../apache/juneau/bean/openapi3/Callback_Test.java | 2 +-
.../juneau/bean/openapi3/Components_Test.java | 2 +-
.../apache/juneau/bean/openapi3/OpenApi_Test.java | 2 +-
.../juneau/bean/openapi3/Operation_Test.java | 2 +-
.../juneau/bean/openapi3/Parameter_Test.java | 2 +-
.../commons/collections/FilteredList_Test.java | 968 +++++++++++++++++++++
.../commons/collections/FilteredSet_Test.java | 948 ++++++++++++++++++++
.../commons/collections/MapBuilder_Test.java | 241 ++++-
16 files changed, 3680 insertions(+), 79 deletions(-)
diff --git
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/Listifiers.java
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/Listifiers.java
index 459e1468c2..35bd0045dd 100644
---
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/Listifiers.java
+++
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/Listifiers.java
@@ -135,6 +135,7 @@ public class Listifiers {
public static Listifier<Collection> collectionListifier() {
return (bc, collection) -> {
if (collection instanceof Set && ! (collection
instanceof SortedSet) && ! (collection instanceof LinkedHashSet)) {
+ // TODO - This is too unreliable.
var collection2 = new
TreeSet<>(flexibleComparator(bc));
collection2.addAll(collection);
collection = collection2;
@@ -314,6 +315,7 @@ public class Listifiers {
public static Listifier<Map> mapListifier() {
return (bc, map) -> {
if (! (map instanceof SortedMap) && ! (map instanceof
LinkedHashMap)) {
+ // TODO - This is too unreliable.
var map2 = new
TreeMap<>(flexibleComparator(bc));
map2.putAll(map);
map = map2;
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredList.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredList.java
new file mode 100644
index 0000000000..f5ceac29e2
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredList.java
@@ -0,0 +1,648 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.collections;
+
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.ThrowableUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.*;
+import java.util.function.*;
+
+/**
+ * A list wrapper that filters elements based on a {@link Predicate} when they
are added.
+ *
+ * <p>
+ * This class wraps an underlying list and applies a filter to determine
whether elements should be added.
+ * Only elements that pass the filter (i.e., the predicate returns
<jk>true</jk>) are actually stored in
+ * the underlying list.
+ *
+ * <h5 class='section'>Features:</h5>
+ * <ul class='spaced-list'>
+ * <li><b>Flexible Filtering:</b> Use any {@link Predicate} to filter
elements based on any criteria
+ * <li><b>Optional Filter:</b> Filter is optional when using the builder -
defaults to accepting all elements
+ * <li><b>Multiple Filters:</b> Can combine multiple filters using AND
logic
+ * <li><b>Custom List Types:</b> Works with any list implementation via
the builder's <c>inner</c> method
+ * <li><b>Type Conversion:</b> Supports automatic type conversion via
element function
+ * <li><b>Convenience Methods:</b> Provides {@link #add(Object)}, {@link
#addAll(Collection)}, and {@link #addAny(Object...)} for easy element addition
+ * <li><b>Transparent Interface:</b> Implements the full {@link List}
interface, so it can be used anywhere a list is expected
+ * <li><b>Filter on Add:</b> Filtering happens when elements are added via
{@link #add(Object)}, {@link #addAll(Collection)}, etc.
+ * </ul>
+ *
+ * <h5 class='section'>Usage:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a filtered list that only accepts non-null values</jc>
+ * FilteredList<String> <jv>list</jv> = FilteredList
+ * .<jsm>create</jsm>(String.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk>)
+ * .build();
+ *
+ * <jv>list</jv>.add(<js>"value1"</js>); <jc>// Added</jc>
+ * <jv>list</jv>.add(<jk>null</jk>); <jc>// Filtered out</jc>
+ * <jv>list</jv>.add(<js>"value3"</js>); <jc>// Added</jc>
+ *
+ * <jc>// list now contains: ["value1", "value3"]</jc>
+ * </p>
+ *
+ * <h5 class='section'>Example - Custom List Type:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a filtered ArrayList that only accepts positive
numbers</jc>
+ * FilteredList<Integer> <jv>list</jv> = FilteredList
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .inner(<jk>new</jk> ArrayList<>())
+ * .build();
+ *
+ * <jv>list</jv>.add(5); <jc>// Added</jc>
+ * <jv>list</jv>.add(-1); <jc>// Filtered out</jc>
+ * <jv>list</jv>.add(10); <jc>// Added</jc>
+ * </p>
+ *
+ * <h5 class='section'>Behavior Notes:</h5>
+ * <ul class='spaced-list'>
+ * <li>Filtering is applied to all element addition methods: {@link
#add(Object)}, {@link #addAll(Collection)}, etc.
+ * <li>If the filter returns <jk>false</jk>, the element is silently
ignored (not added)
+ * <li>When {@link #add(Object)} filters out an element, it returns
<jk>false</jk> (element was not added)
+ * <li>The filter is not applied when reading from the list (e.g., {@link
#get(int)}, {@link #contains(Object)})
+ * <li>All other list operations behave as expected on the underlying list
+ * <li>The underlying list type is determined by the <c>inner</c> method
(defaults to {@link ArrayList})
+ * </ul>
+ *
+ * <h5 class='section'>Thread Safety:</h5>
+ * <p>
+ * This class is not thread-safe unless the underlying list is thread-safe. If
thread safety is required,
+ * use a thread-safe list type (e.g., {@link
java.util.concurrent.CopyOnWriteArrayList}) via the <c>inner</c> method.
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ * <li class='link'><a class="doclink"
href="../../../../../index.html#juneau-commons">Overview > juneau-commons</a>
+ * </ul>
+ *
+ * @param <E> The element type.
+ */
+public class FilteredList<E> extends AbstractList<E> {
+
+ /**
+ * Builder for creating {@link FilteredList} instances.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredList<Integer> <jv>list</jv> = FilteredList
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .inner(<jk>new</jk> ArrayList<>())
+ * .build();
+ * </p>
+ *
+ * @param <E> The element type.
+ */
+ public static class Builder<E> {
+ private Predicate<E> filter = v -> true;
+ private List<E> inner;
+ private Class<E> elementType;
+ private Function<Object,E> elementFunction;
+
+ /**
+ * Sets the filter predicate that determines whether elements
should be added.
+ *
+ * <p>
+ * The predicate receives the element value. If it returns
<jk>true</jk>,
+ * the element is added to the list. If it returns
<jk>false</jk>, the element is silently ignored.
+ *
+ * <p>
+ * This method is optional. If not called, the list will accept
all elements (defaults to <c>v -> true</c>).
+ *
+ * <p>
+ * This method can be called multiple times. When called
multiple times, all filters are combined
+ * using AND logic - an element must pass all filters to be
added to the list.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Filter out null values</jc>
+ * Builder<String> <jv>b</jv> =
FilteredList.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk>);
+ *
+ * <jc>// Filter based on value</jc>
+ * Builder<Integer> <jv>b2</jv> =
FilteredList.<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v
> 0);
+ *
+ * <jc>// Multiple filters combined with AND</jc>
+ * Builder<Integer> <jv>b3</jv> =
FilteredList.<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk>)
<jc>// First filter</jc>
+ * .filter(v -> v > 0)
<jc>// Second filter (ANDed with first)</jc>
+ * .filter(v -> v < 100);
<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<E> filter(Predicate<E> value) {
+ Predicate<E> newFilter = assertArgNotNull("value",
value);
+ if (filter == null)
+ filter = newFilter;
+ else
+ filter = filter.and(newFilter);
+ return this;
+ }
+
+ /**
+ * Sets the underlying list instance that will store the
filtered elements.
+ *
+ * <p>
+ * If not specified, a new {@link ArrayList} will be created
during {@link #build()}.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Use a LinkedList</jc>
+ * Builder<String> <jv>b</jv> =
FilteredList.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .inner(<jk>new</jk> LinkedList<>());
+ *
+ * <jc>// Use a CopyOnWriteArrayList for thread safety</jc>
+ * Builder<String> <jv>b2</jv> =
FilteredList.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .inner(<jk>new</jk>
CopyOnWriteArrayList<>());
+ *
+ * <jc>// Use an existing list</jc>
+ * List<String> <jv>existing</jv> = <jk>new</jk>
ArrayList<>();
+ * Builder<String> <jv>b3</jv> =
FilteredList.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .inner(<jv>existing</jv>);
+ * </p>
+ *
+ * @param value The underlying list instance. Must not be
<jk>null</jk>.
+ * @return This object for method chaining.
+ */
+ public Builder<E> inner(List<E> value) {
+ inner = assertArgNotNull("value", value);
+ return this;
+ }
+
+ /**
+ * Sets the function to use for converting elements when using
{@link FilteredList#add(Object)}.
+ *
+ * <p>
+ * If specified, elements passed to {@link
FilteredList#add(Object)} will be converted using
+ * this function before being added to the list. If not
specified, elements must already be of the
+ * correct type (or <c>Object.class</c> is used if types were
not specified, which accepts any type).
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredList<Integer> <jv>list</jv> = FilteredList
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v
> 0)
+ * .elementFunction(o ->
Integer.parseInt(o.toString()))
+ * .build();
+ *
+ * <jv>list</jv>.add(<js>"123"</js>); <jc>// Element will
be converted from String to Integer</jc>
+ * </p>
+ *
+ * @param value The element conversion function. Can be
<jk>null</jk>.
+ * @return This object for method chaining.
+ */
+ public Builder<E> elementFunction(Function<Object,E> value) {
+ elementFunction = value;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link FilteredList} instance.
+ *
+ * <p>
+ * If {@link #filter(Predicate)} was not called, the list will
accept all elements
+ * (defaults to <c>v -> true</c>).
+ *
+ * @return A new filtered list instance.
+ */
+ public FilteredList<E> build() {
+ List<E> list = inner != null ? inner : new
ArrayList<>();
+ return new FilteredList<>(filter, list, elementType,
elementFunction);
+ }
+ }
+
+ /**
+ * Creates a new builder for constructing a filtered list.
+ *
+ * @param <E> The element type.
+ * @param elementType The element type class. Must not be <jk>null</jk>.
+ * @return A new builder.
+ */
+ public static <E> Builder<E> create(Class<E> elementType) {
+ assertArgNotNull("elementType", elementType);
+ var builder = new Builder<E>();
+ builder.elementType = elementType;
+ return builder;
+ }
+
+ /**
+ * Creates a new builder for constructing a filtered list with generic
types.
+ *
+ * <p>
+ * This is a convenience method that creates a builder without
requiring explicit type class parameters.
+ * The generic types must be explicitly specified using the diamond
operator syntax.
+ *
+ * <p>
+ * When using this method, type checking is effectively disabled (using
<c>Object.class</c> as the type),
+ * allowing any element types to be added. However, the generic type
parameter should still be
+ * specified for type safety.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Explicitly specify generic types using diamond
operator</jc>
+ * <jk>var</jk> <jv>list</jv> = FilteredList
+ * .<String><jsm>create</jsm>()
+ * .filter(<jv>v</jv> -> <jv>v</jv> != <jk>null</jk>)
+ * .build();
+ * </p>
+ *
+ * @param <E> The element type.
+ * @return A new builder.
+ */
+ @SuppressWarnings("unchecked")
+ public static <E> Builder<E> create() {
+ var builder = new Builder<E>();
+ builder.elementType = (Class<E>)Object.class;
+ return builder;
+ }
+ private final Predicate<E> filter;
+ private final List<E> list;
+ private final Class<E> elementType;
+ private final Function<Object,E> elementFunction;
+
+ /**
+ * Constructor.
+ *
+ * @param filter The filter predicate. Can be <jk>null</jk> (if null,
all elements are accepted).
+ * @param list The underlying list. Must not be <jk>null</jk>.
+ * @param elementType The element type. Must not be <jk>null</jk> (use
<c>Object.class</c> to disable type checking).
+ * @param elementFunction The element conversion function, or
<jk>null</jk> if not specified.
+ */
+ protected FilteredList(Predicate<E> filter, List<E> list, Class<E>
elementType, Function<Object,E> elementFunction) {
+ this.filter = assertArgNotNull("filter", filter);
+ this.list = assertArgNotNull("list", list);
+ this.elementType = assertArgNotNull("elementType", elementType);
+ this.elementFunction = elementFunction;
+ }
+
+ /**
+ * Appends the specified element to the end of this list, if it passes
the filter.
+ *
+ * <p>
+ * If the element passes the filter, it is added to the list and this
method returns <jk>true</jk>.
+ *
+ * <p>
+ * If the element is filtered out, it is not added to the list and this
method returns <jk>false</jk>.
+ *
+ * @param e The element to be appended to this list.
+ * @return <jk>true</jk> if the element was added, <jk>false</jk> if it
was filtered out.
+ */
+ @Override
+ public boolean add(E e) {
+ if (filter.test(e))
+ return list.add(e);
+ return false;
+ }
+
+ @Override
+ public void add(int index, E element) {
+ if (filter.test(element))
+ list.add(index, element);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends E> c) {
+ boolean modified = false;
+ for (var e : c) {
+ if (filter.test(e)) {
+ list.add(e);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ @Override
+ public boolean addAll(int index, Collection<? extends E> c) {
+ int insertIndex = index;
+ boolean modified = false;
+ for (var e : c) {
+ if (filter.test(e)) {
+ list.add(insertIndex++, e);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ /**
+ * Tests whether the specified element would pass the filter and be
added to this list.
+ *
+ * <p>
+ * This method can be used to check if an element would be accepted by
the filter without actually
+ * adding it to the list. This is useful for validation, debugging, or
pre-checking elements before
+ * attempting to add them.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredList<Integer> <jv>list</jv> = FilteredList
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .build();
+ *
+ * <jk>if</jk> (<jv>list</jv>.wouldAccept(5)) {
+ * <jv>list</jv>.add(5); <jc>// Will be added</jc>
+ * }
+ * </p>
+ *
+ * @param element The element to test.
+ * @return <jk>true</jk> if the element would be added to the list,
<jk>false</jk> if it would be filtered out.
+ */
+ public boolean wouldAccept(E element) {
+ return filter.test(element);
+ }
+
+ /**
+ * Returns the filter predicate used by this list.
+ *
+ * <p>
+ * This method provides access to the filter for debugging, inspection,
or advanced use cases.
+ * The returned predicate is the combined filter used internally by
this list. If multiple filters
+ * were set via the builder, they are combined using AND logic.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredList<Integer> <jv>list</jv> = FilteredList
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .build();
+ *
+ * Predicate<Integer> <jv>filter</jv> =
<jv>list</jv>.getFilter();
+ * <jc>// Use filter for other purposes</jc>
+ * </p>
+ *
+ * @return The filter predicate. Never <jk>null</jk>.
+ */
+ public Predicate<E> getFilter() {
+ return filter;
+ }
+
+ @Override
+ public E get(int index) {
+ return list.get(index);
+ }
+
+ @Override
+ public E set(int index, E element) {
+ if (filter.test(element))
+ return list.set(index, element);
+ return list.get(index); // Return existing element if filtered
out
+ }
+
+ @Override
+ public E remove(int index) {
+ return list.remove(index);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return list.remove(o);
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ return list.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ return list.retainAll(c);
+ }
+
+ @Override
+ public void clear() {
+ list.clear();
+ }
+
+ @Override
+ public int size() {
+ return list.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return list.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return list.contains(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return list.containsAll(c);
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return list.indexOf(o);
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return list.lastIndexOf(o);
+ }
+
+ @Override
+ public ListIterator<E> listIterator() {
+ return list.listIterator();
+ }
+
+ @Override
+ public ListIterator<E> listIterator(int index) {
+ return list.listIterator(index);
+ }
+
+ @Override
+ public List<E> subList(int fromIndex, int toIndex) {
+ return list.subList(fromIndex, toIndex);
+ }
+
+ @Override
+ public Object[] toArray() {
+ return list.toArray();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T[] toArray(T[] a) {
+ return list.toArray(a);
+ }
+
+ /**
+ * Adds an element to this list with automatic type conversion.
+ *
+ * <p>
+ * This method converts the element using the configured element
function (if any) and validates
+ * the type (if element type was specified when creating the list).
After conversion and validation,
+ * the element is added using the standard {@link #add(Object)} method,
which applies the filter.
+ *
+ * <h5 class='section'>Type Conversion:</h5>
+ * <ul>
+ * <li>If an element function is configured, the element is
converted by applying the function
+ * <li>If no function is configured, the object is used as-is (but
may be validated if types were specified)
+ * </ul>
+ *
+ * <h5 class='section'>Type Validation:</h5>
+ * <ul>
+ * <li>If element type was specified (via {@link #create(Class)}),
the converted element must be an instance of that type
+ * <li>If {@link #create()} was used (no types specified),
<c>Object.class</c> is used, which accepts any type
+ * </ul>
+ *
+ * <h5 class='section'>Return Value:</h5>
+ * <ul>
+ * <li>If the element is added successfully, returns <jk>true</jk>
+ * <li>If the element is filtered out, returns <jk>false</jk>
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredList<Integer> <jv>list</jv> = FilteredList
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .elementFunction(o -> Integer.parseInt(o.toString()))
+ * .build();
+ *
+ * <jv>list</jv>.addConverted(<js>"123"</js>); <jc>// Element
converted from String to Integer</jc>
+ * </p>
+ *
+ * @param element The element to add. Will be converted if an element
function is configured.
+ * @return <jk>true</jk> if the element was added, <jk>false</jk> if it
was filtered out.
+ * @throws RuntimeException If conversion fails or type validation
fails.
+ */
+ public boolean addConverted(Object element) {
+ E convertedElement = convertElement(element);
+ return add(convertedElement);
+ }
+
+ /**
+ * Adds all elements from the specified collection with automatic type
conversion.
+ *
+ * <p>
+ * This method iterates over all elements in the source collection and
adds each one using {@link #addConverted(Object)},
+ * which applies conversion and validation before adding.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredList<Integer> <jv>list</jv> = FilteredList
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .elementFunction(o -> Integer.parseInt(o.toString()))
+ * .build();
+ *
+ * Collection<String> <jv>source</jv> =
List.of(<js>"5"</js>, <js>"-1"</js>);
+ * <jv>list</jv>.addAllConverted(<jv>source</jv>); <jc>//
Elements converted, negative value filtered out</jc>
+ * </p>
+ *
+ * @param source The collection containing elements to add. Can be
<jk>null</jk> (no-op).
+ * @return This object for method chaining.
+ */
+ public FilteredList<E> addAllConverted(Collection<?> source) {
+ if (source != null) {
+ for (var e : source) {
+ addConverted(e);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Adds arbitrary values to this list.
+ *
+ * <p>
+ * Objects can be any of the following:
+ * <ul>
+ * <li>The same type or convertible to the element type of this
list.
+ * <li>Collections or arrays of anything on this list.
+ * </ul>
+ *
+ * <p>
+ * Each element from the collections/arrays will be added using {@link
#add(Object)}, which applies
+ * conversion and filtering.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredList<Integer> <jv>list</jv> = FilteredList
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .build();
+ *
+ * List<Integer> <jv>list1</jv> = List.of(5, -1);
+ * List<Integer> <jv>list2</jv> = List.of(10);
+ * <jv>list</jv>.addAny(<jv>list1</jv>, <jv>list2</jv>); <jc>//
Adds 5, 10 (-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 FilteredList<E> addAny(Object...values) {
+ if (values != null) {
+ for (var o : values) {
+ if (o == null)
+ continue;
+ if (o instanceof Collection<?> c) {
+ addAllConverted(c);
+ } else if (o.getClass().isArray()) {
+ for (int i = 0; i <
java.lang.reflect.Array.getLength(o); i++) {
+
addConverted(java.lang.reflect.Array.get(o, i));
+ }
+ } else {
+ addConverted(o);
+ }
+ }
+ }
+ return this;
+ }
+
+ private E convertElement(Object element) {
+ if (elementFunction != null) {
+ element = elementFunction.apply(element);
+ }
+ if (element == null) {
+ // Allow null for non-primitive types
+ if (elementType.isPrimitive())
+ throw rex("Cannot set null element for
primitive type {0}", elementType.getName());
+ return null;
+ }
+ if (elementType.isInstance(element))
+ return elementType.cast(element);
+ throw rex("Object of type {0} could not be converted to element
type {1}", cn(element), cn(elementType));
+ }
+
+ @Override
+ public String toString() {
+ return list.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return list.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return list.hashCode();
+ }
+}
+
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredSet.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredSet.java
new file mode 100644
index 0000000000..b57db45912
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredSet.java
@@ -0,0 +1,595 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.collections;
+
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.ThrowableUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.*;
+import java.util.function.*;
+
+/**
+ * A set wrapper that filters elements based on a {@link Predicate} when they
are added.
+ *
+ * <p>
+ * This class wraps an underlying set and applies a filter to determine
whether elements should be added.
+ * Only elements that pass the filter (i.e., the predicate returns
<jk>true</jk>) are actually stored in
+ * the underlying set.
+ *
+ * <h5 class='section'>Features:</h5>
+ * <ul class='spaced-list'>
+ * <li><b>Flexible Filtering:</b> Use any {@link Predicate} to filter
elements based on any criteria
+ * <li><b>Optional Filter:</b> Filter is optional when using the builder -
defaults to accepting all elements
+ * <li><b>Multiple Filters:</b> Can combine multiple filters using AND
logic
+ * <li><b>Custom Set Types:</b> Works with any set implementation via the
builder's <c>inner</c> method
+ * <li><b>Type Conversion:</b> Supports automatic type conversion via
element function
+ * <li><b>Convenience Methods:</b> Provides {@link #add(Object)}, {@link
#addAll(Collection)}, and {@link #addAny(Object...)} for easy element addition
+ * <li><b>Transparent Interface:</b> Implements the full {@link Set}
interface, so it can be used anywhere a set is expected
+ * <li><b>Filter on Add:</b> Filtering happens when elements are added via
{@link #add(Object)}, {@link #addAll(Collection)}, etc.
+ * <li><b>Automatic Deduplication:</b> Duplicate elements are
automatically handled by the underlying set
+ * </ul>
+ *
+ * <h5 class='section'>Usage:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a filtered set that only accepts non-null values</jc>
+ * FilteredSet<String> <jv>set</jv> = FilteredSet
+ * .<jsm>create</jsm>(String.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk>)
+ * .build();
+ *
+ * <jv>set</jv>.add(<js>"value1"</js>); <jc>// Added</jc>
+ * <jv>set</jv>.add(<jk>null</jk>); <jc>// Filtered out</jc>
+ * <jv>set</jv>.add(<js>"value3"</js>); <jc>// Added</jc>
+ *
+ * <jc>// set now contains: ["value1", "value3"]</jc>
+ * </p>
+ *
+ * <h5 class='section'>Example - Custom Set Type:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a filtered TreeSet that only accepts positive numbers</jc>
+ * FilteredSet<Integer> <jv>set</jv> = FilteredSet
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .inner(<jk>new</jk> TreeSet<>())
+ * .build();
+ *
+ * <jv>set</jv>.add(5); <jc>// Added</jc>
+ * <jv>set</jv>.add(-1); <jc>// Filtered out</jc>
+ * <jv>set</jv>.add(10); <jc>// Added</jc>
+ * </p>
+ *
+ * <h5 class='section'>Behavior Notes:</h5>
+ * <ul class='spaced-list'>
+ * <li>Filtering is applied to all element addition methods: {@link
#add(Object)}, {@link #addAll(Collection)}, etc.
+ * <li>If the filter returns <jk>false</jk>, the element is silently
ignored (not added)
+ * <li>When {@link #add(Object)} filters out an element, it returns
<jk>false</jk> (element was not added)
+ * <li>The filter is not applied when reading from the set (e.g., {@link
#contains(Object)})
+ * <li>All other set operations behave as expected on the underlying set
+ * <li>The underlying set type is determined by the <c>inner</c> method
(defaults to {@link LinkedHashSet})
+ * <li>Duplicate elements are automatically handled by the underlying set
(only one occurrence is stored)
+ * </ul>
+ *
+ * <h5 class='section'>Thread Safety:</h5>
+ * <p>
+ * This class is not thread-safe unless the underlying set is thread-safe. If
thread safety is required,
+ * use a thread-safe set type (e.g., {@link
java.util.concurrent.CopyOnWriteArraySet}) via the <c>inner</c> method.
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ * <li class='link'><a class="doclink"
href="../../../../../index.html#juneau-commons">Overview > juneau-commons</a>
+ * </ul>
+ *
+ * @param <E> The element type.
+ */
+public class FilteredSet<E> extends AbstractSet<E> {
+
+ /**
+ * Builder for creating {@link FilteredSet} instances.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredSet<Integer> <jv>set</jv> = FilteredSet
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .inner(<jk>new</jk> TreeSet<>())
+ * .build();
+ * </p>
+ *
+ * @param <E> The element type.
+ */
+ public static class Builder<E> {
+ private Predicate<E> filter = v -> true;
+ private Set<E> inner;
+ private Class<E> elementType;
+ private Function<Object,E> elementFunction;
+
+ /**
+ * Sets the filter predicate that determines whether elements
should be added.
+ *
+ * <p>
+ * The predicate receives the element value. If it returns
<jk>true</jk>,
+ * the element is added to the set. If it returns
<jk>false</jk>, the element is silently ignored.
+ *
+ * <p>
+ * This method is optional. If not called, the set will accept
all elements (defaults to <c>v -> true</c>).
+ *
+ * <p>
+ * This method can be called multiple times. When called
multiple times, all filters are combined
+ * using AND logic - an element must pass all filters to be
added to the set.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Filter out null values</jc>
+ * Builder<String> <jv>b</jv> =
FilteredSet.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk>);
+ *
+ * <jc>// Filter based on value</jc>
+ * Builder<Integer> <jv>b2</jv> =
FilteredSet.<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v
> 0);
+ *
+ * <jc>// Multiple filters combined with AND</jc>
+ * Builder<Integer> <jv>b3</jv> =
FilteredSet.<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk>)
<jc>// First filter</jc>
+ * .filter(v -> v > 0)
<jc>// Second filter (ANDed with first)</jc>
+ * .filter(v -> v < 100);
<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<E> filter(Predicate<E> value) {
+ Predicate<E> newFilter = assertArgNotNull("value",
value);
+ if (filter == null)
+ filter = newFilter;
+ else
+ filter = filter.and(newFilter);
+ return this;
+ }
+
+ /**
+ * Sets the underlying set instance that will store the
filtered elements.
+ *
+ * <p>
+ * If not specified, a new {@link LinkedHashSet} will be
created during {@link #build()}.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Use a TreeSet for sorted order</jc>
+ * Builder<String> <jv>b</jv> =
FilteredSet.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .inner(<jk>new</jk> TreeSet<>());
+ *
+ * <jc>// Use a CopyOnWriteArraySet for thread safety</jc>
+ * Builder<String> <jv>b2</jv> =
FilteredSet.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .inner(<jk>new</jk>
CopyOnWriteArraySet<>());
+ *
+ * <jc>// Use an existing set</jc>
+ * Set<String> <jv>existing</jv> = <jk>new</jk>
LinkedHashSet<>();
+ * Builder<String> <jv>b3</jv> =
FilteredSet.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .inner(<jv>existing</jv>);
+ * </p>
+ *
+ * @param value The underlying set instance. Must not be
<jk>null</jk>.
+ * @return This object for method chaining.
+ */
+ public Builder<E> inner(Set<E> value) {
+ inner = assertArgNotNull("value", value);
+ return this;
+ }
+
+ /**
+ * Sets the function to use for converting elements when using
{@link FilteredSet#add(Object)}.
+ *
+ * <p>
+ * If specified, elements passed to {@link
FilteredSet#add(Object)} will be converted using
+ * this function before being added to the set. If not
specified, elements must already be of the
+ * correct type (or <c>Object.class</c> is used if types were
not specified, which accepts any type).
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredSet<Integer> <jv>set</jv> = FilteredSet
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v
> 0)
+ * .elementFunction(o ->
Integer.parseInt(o.toString()))
+ * .build();
+ *
+ * <jv>set</jv>.add(<js>"123"</js>); <jc>// Element will
be converted from String to Integer</jc>
+ * </p>
+ *
+ * @param value The element conversion function. Can be
<jk>null</jk>.
+ * @return This object for method chaining.
+ */
+ public Builder<E> elementFunction(Function<Object,E> value) {
+ elementFunction = value;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link FilteredSet} instance.
+ *
+ * <p>
+ * If {@link #filter(Predicate)} was not called, the set will
accept all elements
+ * (defaults to <c>v -> true</c>).
+ *
+ * @return A new filtered set instance.
+ */
+ public FilteredSet<E> build() {
+ Set<E> set = inner != null ? inner : new
LinkedHashSet<>();
+ return new FilteredSet<>(filter, set, elementType,
elementFunction);
+ }
+ }
+
+ /**
+ * Creates a new builder for constructing a filtered set.
+ *
+ * @param <E> The element type.
+ * @param elementType The element type class. Must not be <jk>null</jk>.
+ * @return A new builder.
+ */
+ public static <E> Builder<E> create(Class<E> elementType) {
+ assertArgNotNull("elementType", elementType);
+ var builder = new Builder<E>();
+ builder.elementType = elementType;
+ return builder;
+ }
+
+ /**
+ * Creates a new builder for constructing a filtered set with generic
types.
+ *
+ * <p>
+ * This is a convenience method that creates a builder without
requiring explicit type class parameters.
+ * The generic types must be explicitly specified using the diamond
operator syntax.
+ *
+ * <p>
+ * When using this method, type checking is effectively disabled (using
<c>Object.class</c> as the type),
+ * allowing any element types to be added. However, the generic type
parameter should still be
+ * specified for type safety.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Explicitly specify generic types using diamond
operator</jc>
+ * <jk>var</jk> <jv>set</jv> = FilteredSet
+ * .<String><jsm>create</jsm>()
+ * .filter(<jv>v</jv> -> <jv>v</jv> != <jk>null</jk>)
+ * .build();
+ * </p>
+ *
+ * @param <E> The element type.
+ * @return A new builder.
+ */
+ @SuppressWarnings("unchecked")
+ public static <E> Builder<E> create() {
+ var builder = new Builder<E>();
+ builder.elementType = (Class<E>)Object.class;
+ return builder;
+ }
+ private final Predicate<E> filter;
+ private final Set<E> set;
+ private final Class<E> elementType;
+ private final Function<Object,E> elementFunction;
+
+ /**
+ * Constructor.
+ *
+ * @param filter The filter predicate. Can be <jk>null</jk> (if null,
all elements are accepted).
+ * @param set The underlying set. Must not be <jk>null</jk>.
+ * @param elementType The element type. Must not be <jk>null</jk> (use
<c>Object.class</c> to disable type checking).
+ * @param elementFunction The element conversion function, or
<jk>null</jk> if not specified.
+ */
+ protected FilteredSet(Predicate<E> filter, Set<E> set, Class<E>
elementType, Function<Object,E> elementFunction) {
+ this.filter = assertArgNotNull("filter", filter);
+ this.set = assertArgNotNull("set", set);
+ this.elementType = assertArgNotNull("elementType", elementType);
+ this.elementFunction = elementFunction;
+ }
+
+ /**
+ * Adds the specified element to this set, if it passes the filter.
+ *
+ * <p>
+ * If the element passes the filter, it is added to the set and this
method returns <jk>true</jk>
+ * (or <jk>false</jk> if the element was already present in the set).
+ *
+ * <p>
+ * If the element is filtered out, it is not added to the set and this
method returns <jk>false</jk>.
+ *
+ * @param e The element to be added to this set.
+ * @return <jk>true</jk> if the element was added, <jk>false</jk> if it
was filtered out or already present.
+ */
+ @Override
+ public boolean add(E e) {
+ if (filter.test(e))
+ return set.add(e);
+ return false;
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends E> c) {
+ boolean modified = false;
+ for (var e : c) {
+ if (filter.test(e)) {
+ if (set.add(e))
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ /**
+ * Tests whether the specified element would pass the filter and be
added to this set.
+ *
+ * <p>
+ * This method can be used to check if an element would be accepted by
the filter without actually
+ * adding it to the set. This is useful for validation, debugging, or
pre-checking elements before
+ * attempting to add them.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredSet<Integer> <jv>set</jv> = FilteredSet
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .build();
+ *
+ * <jk>if</jk> (<jv>set</jv>.wouldAccept(5)) {
+ * <jv>set</jv>.add(5); <jc>// Will be added</jc>
+ * }
+ * </p>
+ *
+ * @param element The element to test.
+ * @return <jk>true</jk> if the element would be added to the set,
<jk>false</jk> if it would be filtered out.
+ */
+ public boolean wouldAccept(E element) {
+ return filter.test(element);
+ }
+
+ /**
+ * Returns the filter predicate used by this set.
+ *
+ * <p>
+ * This method provides access to the filter for debugging, inspection,
or advanced use cases.
+ * The returned predicate is the combined filter used internally by
this set. If multiple filters
+ * were set via the builder, they are combined using AND logic.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredSet<Integer> <jv>set</jv> = FilteredSet
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .build();
+ *
+ * Predicate<Integer> <jv>filter</jv> =
<jv>set</jv>.getFilter();
+ * <jc>// Use filter for other purposes</jc>
+ * </p>
+ *
+ * @return The filter predicate. Never <jk>null</jk>.
+ */
+ public Predicate<E> getFilter() {
+ return filter;
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return set.iterator();
+ }
+
+ @Override
+ public int size() {
+ return set.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return set.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return set.contains(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return set.containsAll(c);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return set.remove(o);
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ return set.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ return set.retainAll(c);
+ }
+
+ @Override
+ public void clear() {
+ set.clear();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return set.toArray();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T[] toArray(T[] a) {
+ return set.toArray(a);
+ }
+
+ /**
+ * Adds an element to this set with automatic type conversion.
+ *
+ * <p>
+ * This method converts the element using the configured element
function (if any) and validates
+ * the type (if element type was specified when creating the set).
After conversion and validation,
+ * the element is added using the standard {@link #add(Object)} method,
which applies the filter.
+ *
+ * <h5 class='section'>Type Conversion:</h5>
+ * <ul>
+ * <li>If an element function is configured, the element is
converted by applying the function
+ * <li>If no function is configured, the object is used as-is (but
may be validated if types were specified)
+ * </ul>
+ *
+ * <h5 class='section'>Type Validation:</h5>
+ * <ul>
+ * <li>If element type was specified (via {@link #create(Class)}),
the converted element must be an instance of that type
+ * <li>If {@link #create()} was used (no types specified),
<c>Object.class</c> is used, which accepts any type
+ * </ul>
+ *
+ * <h5 class='section'>Return Value:</h5>
+ * <ul>
+ * <li>If the element is added successfully, returns <jk>true</jk>
+ * <li>If the element is filtered out or already present, returns
<jk>false</jk>
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredSet<Integer> <jv>set</jv> = FilteredSet
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .elementFunction(o -> Integer.parseInt(o.toString()))
+ * .build();
+ *
+ * <jv>set</jv>.addConverted(<js>"123"</js>); <jc>// Element
converted from String to Integer</jc>
+ * </p>
+ *
+ * @param element The element to add. Will be converted if an element
function is configured.
+ * @return <jk>true</jk> if the element was added, <jk>false</jk> if it
was filtered out or already present.
+ * @throws RuntimeException If conversion fails or type validation
fails.
+ */
+ public boolean addConverted(Object element) {
+ E convertedElement = convertElement(element);
+ return add(convertedElement);
+ }
+
+ /**
+ * Adds all elements from the specified collection with automatic type
conversion.
+ *
+ * <p>
+ * This method iterates over all elements in the source collection and
adds each one using {@link #addConverted(Object)},
+ * which applies conversion and validation before adding.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredSet<Integer> <jv>set</jv> = FilteredSet
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .elementFunction(o -> Integer.parseInt(o.toString()))
+ * .build();
+ *
+ * Collection<String> <jv>source</jv> =
List.of(<js>"5"</js>, <js>"-1"</js>);
+ * <jv>set</jv>.addAllConverted(<jv>source</jv>); <jc>// Elements
converted, negative value filtered out</jc>
+ * </p>
+ *
+ * @param source The collection containing elements to add. Can be
<jk>null</jk> (no-op).
+ * @return This object for method chaining.
+ */
+ public FilteredSet<E> addAllConverted(Collection<?> source) {
+ if (source != null) {
+ for (var e : source) {
+ addConverted(e);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Adds arbitrary values to this set.
+ *
+ * <p>
+ * Objects can be any of the following:
+ * <ul>
+ * <li>The same type or convertible to the element type of this
set.
+ * <li>Collections or arrays of anything on this set.
+ * </ul>
+ *
+ * <p>
+ * Each element from the collections/arrays will be added using {@link
#add(Object)}, which applies
+ * conversion and filtering.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FilteredSet<Integer> <jv>set</jv> = FilteredSet
+ * .<jsm>create</jsm>(Integer.<jk>class</jk>)
+ * .filter(v -> v != <jk>null</jk> && v > 0)
+ * .build();
+ *
+ * Set<Integer> <jv>set1</jv> = Set.of(5, -1);
+ * Set<Integer> <jv>set2</jv> = Set.of(10);
+ * <jv>set</jv>.addAny(<jv>set1</jv>, <jv>set2</jv>); <jc>// Adds
5, 10 (-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 FilteredSet<E> addAny(Object...values) {
+ if (values != null) {
+ for (var o : values) {
+ if (o == null)
+ continue;
+ if (o instanceof Collection<?> c) {
+ addAllConverted(c);
+ } else if (o.getClass().isArray()) {
+ for (int i = 0; i <
java.lang.reflect.Array.getLength(o); i++) {
+
addConverted(java.lang.reflect.Array.get(o, i));
+ }
+ } else {
+ addConverted(o);
+ }
+ }
+ }
+ return this;
+ }
+
+ private E convertElement(Object element) {
+ if (elementFunction != null) {
+ element = elementFunction.apply(element);
+ }
+ if (element == null) {
+ // Allow null for non-primitive types
+ if (elementType.isPrimitive())
+ throw rex("Cannot set null element for
primitive type {0}", elementType.getName());
+ return null;
+ }
+ if (elementType.isInstance(element))
+ return elementType.cast(element);
+ throw rex("Object of type {0} could not be converted to element
type {1}", cn(element), cn(elementType));
+ }
+
+ @Override
+ public String toString() {
+ return set.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return set.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return set.hashCode();
+ }
+}
+
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/ListBuilder.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/ListBuilder.java
index b1f53f60ae..0de6e3f0ea 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/ListBuilder.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/ListBuilder.java
@@ -119,7 +119,7 @@ public class ListBuilder<E> {
}
private List<E> list;
- private boolean unmodifiable = false, sparse = false;
+ private boolean unmodifiable = false, sparse = false, concurrent =
false;
private Comparator<E> comparator;
private List<Converter> converters;
@@ -277,6 +277,8 @@ public class ListBuilder<E> {
if (nn(list)) {
if (nn(comparator))
Collections.sort(list, comparator);
+ if (concurrent)
+ list = synchronizedList(list);
if (unmodifiable)
list = unmodifiableList(list);
}
@@ -377,4 +379,51 @@ public class ListBuilder<E> {
this.unmodifiable = true;
return this;
}
+
+ /**
+ * When specified, {@link #build()} will return a thread-safe
synchronized list.
+ *
+ * <p>
+ * The list will be wrapped using {@link
Collections#synchronizedList(List)} to provide thread-safety.
+ * This is useful when the list needs to be accessed from multiple
threads.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a thread-safe list</jc>
+ * List<String> <jv>list</jv> =
ListBuilder.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .add(<js>"one"</js>, <js>"two"</js>)
+ * .concurrent()
+ * .build();
+ * </p>
+ *
+ * @return This object.
+ */
+ public ListBuilder<E> concurrent() {
+ concurrent = true;
+ return this;
+ }
+
+ /**
+ * Sets whether {@link #build()} should return a thread-safe
synchronized list.
+ *
+ * <p>
+ * When <c>true</c>, the list will be wrapped using {@link
Collections#synchronizedList(List)} to provide thread-safety.
+ * This is useful when the list needs to be accessed from multiple
threads.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Conditionally create a thread-safe list</jc>
+ * List<String> <jv>list</jv> =
ListBuilder.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .add(<js>"one"</js>, <js>"two"</js>)
+ * .concurrent(<jv>needsThreadSafety</jv>)
+ * .build();
+ * </p>
+ *
+ * @param value Whether to make the list thread-safe.
+ * @return This object.
+ */
+ public ListBuilder<E> concurrent(boolean value) {
+ concurrent = value;
+ return this;
+ }
}
\ No newline at end of file
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 f619869d11..6073bda4e9 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
@@ -22,6 +22,7 @@ import static org.apache.juneau.commons.utils.Utils.*;
import java.lang.reflect.*;
import java.util.*;
+import java.util.concurrent.*;
import java.util.function.*;
/**
@@ -160,7 +161,7 @@ public class MapBuilder<K,V> {
}
private Map<K,V> map;
- private boolean unmodifiable = false, sparse = false;
+ private boolean unmodifiable = false, sparse = false, concurrent =
false, ordered = false;
private Comparator<K> comparator;
private BiPredicate<K,V> filter;
@@ -274,53 +275,56 @@ public class MapBuilder<K,V> {
* Builds the map.
*
* <p>
- * Applies filtering, sorting, unmodifiable, and sparse options.
+ * Applies filtering, sorting, ordering, concurrent, unmodifiable, and
sparse options.
*
* <p>
- * 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.
+ * Map type selection:
+ * <ul>
+ * <li>If {@link #sorted()} is set: Uses {@link TreeMap} (or
{@link java.util.concurrent.ConcurrentSkipListMap} if concurrent)
+ * <li>If {@link #ordered()} is set: Uses {@link LinkedHashMap}
(or synchronized LinkedHashMap if concurrent)
+ * <li>Otherwise: Uses {@link HashMap} (or {@link
java.util.concurrent.ConcurrentHashMap} if concurrent)
+ * </ul>
+ *
+ * <p>
+ * If filtering is applied, the result is wrapped in a {@link
FilteredMap}.
*
* @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())
- return null;
+
+ if (sparse && isEmpty(map))
+ return null;
+
+ var map2 = (Map<K,V>)null;
+
+ if (ordered) {
+ map2 = new LinkedHashMap<>();
+ if (concurrent)
+ map2 = Collections.synchronizedMap(map2);
+ } else if (nn(comparator)) {
+ map2 = concurrent ? new
ConcurrentSkipListMap<>(comparator) : new TreeMap<>(comparator);
} else {
- if (map == null)
- map = new LinkedHashMap<>();
+ map2 = concurrent ? new ConcurrentHashMap<>() : new
HashMap<>();
}
- 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(result);
- result = m2;
- }
-
- // Apply unmodifiable if specified
- if (unmodifiable)
- result = Collections.unmodifiableMap(result);
+ if (nn(filter) || nn(keyFunction) || nn(valueFunction)) {
+ var map3b = FilteredMap.create(keyType, valueType);
+ if (nn(filter))
+ map3b.filter(filter);
+ if (nn(keyFunction))
+ map3b.keyFunction(keyFunction);
+ if (nn(valueFunction))
+ map3b.valueFunction(valueFunction);
+ map2 = map3b.inner(map2).build();
}
- return result;
+ if (nn(map))
+ map2.putAll(map);
+
+ if (unmodifiable)
+ map2 = Collections.unmodifiableMap(map2);
+
+ return map2;
}
/**
@@ -350,7 +354,12 @@ public class MapBuilder<K,V> {
* 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.
+ * Map type selection:
+ * <ul>
+ * <li>If {@link #sorted()} is set: Uses {@link TreeMap} (or
{@link java.util.concurrent.ConcurrentSkipListMap} if concurrent)
+ * <li>If {@link #ordered()} is set: Uses {@link LinkedHashMap}
(or synchronized LinkedHashMap if concurrent)
+ * <li>Otherwise: Uses {@link HashMap} (or {@link
java.util.concurrent.ConcurrentHashMap} if concurrent)
+ * </ul>
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
@@ -530,6 +539,10 @@ public class MapBuilder<K,V> {
/**
* Converts the set into a {@link SortedMap}.
*
+ * <p>
+ * Note: If {@link #ordered()} was previously called, calling this
method will override it.
+ * The last method called ({@link #ordered()} or {@link #sorted()})
determines the final map type.
+ *
* @return This object.
*/
@SuppressWarnings("unchecked")
@@ -540,11 +553,16 @@ public class MapBuilder<K,V> {
/**
* Converts the set into a {@link SortedMap} using the specified
comparator.
*
+ * <p>
+ * Note: If {@link #ordered()} was previously called, calling this
method will override it.
+ * The last method called ({@link #ordered()} or {@link #sorted()})
determines the final map type.
+ *
* @param comparator The comparator to use for sorting. Must not be
<jk>null</jk>.
* @return This object.
*/
public MapBuilder<K,V> sorted(Comparator<K> comparator) {
this.comparator = assertArgNotNull("comparator", comparator);
+ ordered = false;
return this;
}
@@ -565,7 +583,7 @@ public class MapBuilder<K,V> {
* Specifies the map to append to.
*
* <p>
- * If not specified, uses a new {@link LinkedHashMap}.
+ * If not specified, uses a new {@link HashMap} (or {@link
LinkedHashMap} if {@link #ordered()} is set).
*
* @param map The map to append to.
* @return This object.
@@ -585,6 +603,139 @@ public class MapBuilder<K,V> {
return this;
}
+ /**
+ * When specified, {@link #build()} will return a thread-safe map.
+ *
+ * <p>
+ * The thread-safety implementation depends on other settings:
+ * <ul>
+ * <li>If {@link #sorted()} is set: Uses {@link
java.util.concurrent.ConcurrentSkipListMap}
+ * <li>If {@link #ordered()} is set: Uses {@link
Collections#synchronizedMap(LinkedHashMap)}
+ * <li>Otherwise: Uses {@link
java.util.concurrent.ConcurrentHashMap}
+ * </ul>
+ *
+ * <p>
+ * This is useful when the map needs to be accessed from multiple
threads.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>import static</jk>
org.apache.juneau.commons.utils.CollectionUtils.*;
+ *
+ * <jc>// Create a thread-safe map using ConcurrentHashMap</jc>
+ * Map<String,Integer> <jv>map</jv> =
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+ * .add(<js>"one"</js>, 1)
+ * .add(<js>"two"</js>, 2)
+ * .concurrent()
+ * .build();
+ *
+ * <jc>// Create a thread-safe ordered map</jc>
+ * Map<String,Integer> <jv>map2</jv> =
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+ * .ordered()
+ * .concurrent()
+ * .add(<js>"one"</js>, 1)
+ * .build();
+ * </p>
+ *
+ * @return This object.
+ */
+ public MapBuilder<K,V> concurrent() {
+ concurrent = true;
+ return this;
+ }
+
+ /**
+ * Sets whether {@link #build()} should return a thread-safe map.
+ *
+ * <p>
+ * The thread-safety implementation depends on other settings:
+ * <ul>
+ * <li>If {@link #sorted()} is set: Uses {@link
java.util.concurrent.ConcurrentSkipListMap}
+ * <li>If {@link #ordered()} is set: Uses {@link
Collections#synchronizedMap(LinkedHashMap)}
+ * <li>Otherwise: Uses {@link
java.util.concurrent.ConcurrentHashMap}
+ * </ul>
+ *
+ * <p>
+ * This is useful when the map needs to be accessed from multiple
threads.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>import static</jk>
org.apache.juneau.commons.utils.CollectionUtils.*;
+ *
+ * <jc>// Conditionally create a thread-safe map</jc>
+ * Map<String,Integer> <jv>map</jv> =
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+ * .add(<js>"one"</js>, 1)
+ * .concurrent(<jv>needsThreadSafety</jv>)
+ * .build();
+ * </p>
+ *
+ * @param value Whether to make the map thread-safe.
+ * @return This object.
+ */
+ public MapBuilder<K,V> concurrent(boolean value) {
+ concurrent = value;
+ return this;
+ }
+
+ /**
+ * When specified, {@link #build()} will use a {@link LinkedHashMap} to
preserve insertion order.
+ *
+ * <p>
+ * If not specified, a {@link HashMap} is used by default (no
guaranteed order).
+ *
+ * <p>
+ * Note: If {@link #sorted()} was previously called, calling this
method will override it.
+ * The last method called ({@link #ordered()} or {@link #sorted()})
determines the final map type.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>import static</jk>
org.apache.juneau.commons.utils.CollectionUtils.*;
+ *
+ * <jc>// Create an ordered map (preserves insertion order)</jc>
+ * Map<String,Integer> <jv>map</jv> =
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+ * .ordered()
+ * .add(<js>"one"</js>, 1)
+ * .add(<js>"two"</js>, 2)
+ * .build();
+ * </p>
+ *
+ * @return This object.
+ */
+ public MapBuilder<K,V> ordered() {
+ return ordered(true);
+ }
+
+ /**
+ * Sets whether {@link #build()} should use a {@link LinkedHashMap} to
preserve insertion order.
+ *
+ * <p>
+ * If <c>false</c> (default), a {@link HashMap} is used (no guaranteed
order).
+ * If <c>true</c>, a {@link LinkedHashMap} is used (preserves insertion
order).
+ *
+ * <p>
+ * Note: If {@link #sorted()} was previously called, calling this
method with <c>true</c> will override it.
+ * The last method called ({@link #ordered()} or {@link #sorted()})
determines the final map type.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>import static</jk>
org.apache.juneau.commons.utils.CollectionUtils.*;
+ *
+ * <jc>// Conditionally create an ordered map</jc>
+ * Map<String,Integer> <jv>map</jv> =
<jsm>mapb</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>)
+ * .ordered(<jv>preserveOrder</jv>)
+ * .add(<js>"one"</js>, 1)
+ * .build();
+ * </p>
+ *
+ * @param value Whether to preserve insertion order.
+ * @return This object.
+ */
+ public MapBuilder<K,V> ordered(boolean value) {
+ ordered = value;
+ if (ordered)
+ comparator = null;
+ return this;
+ }
+
/**
* Converts a key object to the key type.
*
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/SetBuilder.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/SetBuilder.java
index 8529e509c6..1f77101821 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/SetBuilder.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/SetBuilder.java
@@ -120,7 +120,7 @@ public class SetBuilder<E> {
}
private Set<E> set;
- private boolean unmodifiable, sparse;
+ private boolean unmodifiable, sparse, concurrent;
private Comparator<E> comparator;
private Class<E> elementType;
@@ -280,6 +280,8 @@ public class SetBuilder<E> {
s.addAll(set);
set = s;
}
+ if (concurrent)
+ set = synchronizedSet(set);
if (unmodifiable)
set = unmodifiableSet(set);
}
@@ -380,4 +382,51 @@ public class SetBuilder<E> {
unmodifiable = true;
return this;
}
+
+ /**
+ * When specified, {@link #build()} will return a thread-safe
synchronized set.
+ *
+ * <p>
+ * The set will be wrapped using {@link
Collections#synchronizedSet(Set)} to provide thread-safety.
+ * This is useful when the set needs to be accessed from multiple
threads.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a thread-safe set</jc>
+ * Set<String> <jv>set</jv> =
SetBuilder.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .add(<js>"one"</js>, <js>"two"</js>)
+ * .concurrent()
+ * .build();
+ * </p>
+ *
+ * @return This object.
+ */
+ public SetBuilder<E> concurrent() {
+ concurrent = true;
+ return this;
+ }
+
+ /**
+ * Sets whether {@link #build()} should return a thread-safe
synchronized set.
+ *
+ * <p>
+ * When <c>true</c>, the set will be wrapped using {@link
Collections#synchronizedSet(Set)} to provide thread-safety.
+ * This is useful when the set needs to be accessed from multiple
threads.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Conditionally create a thread-safe set</jc>
+ * Set<String> <jv>set</jv> =
SetBuilder.<jsm>create</jsm>(String.<jk>class</jk>)
+ * .add(<js>"one"</js>, <js>"two"</js>)
+ * .concurrent(<jv>needsThreadSafety</jv>)
+ * .build();
+ * </p>
+ *
+ * @param value Whether to make the set thread-safe.
+ * @return This object.
+ */
+ public SetBuilder<E> concurrent(boolean value) {
+ concurrent = value;
+ return this;
+ }
}
\ 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 5e94677692..18a0660548 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
@@ -1554,7 +1554,7 @@ public class CollectionUtils {
* @see MapBuilder
*/
public static MapBuilder<String,Object> mapb() {
- return MapBuilder.create(String.class, Object.class);
+ return MapBuilder.create(String.class, Object.class).ordered();
}
/**
@@ -1568,7 +1568,7 @@ public class CollectionUtils {
* @return A new map builder.
*/
public static <K,V> MapBuilder<K,V> mapb(Class<K> keyType, Class<V>
valueType) {
- return MapBuilder.create(keyType, valueType);
+ return MapBuilder.create(keyType, valueType).ordered();
}
/**
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/Utils.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/Utils.java
index 0774097927..da7b7afc7a 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/Utils.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/Utils.java
@@ -1001,6 +1001,18 @@ public class Utils {
return value != null;
}
+ public static <T> boolean isNull(T value) {
+ return value == null;
+ }
+
+ public static <T> boolean isAllNull(T...values) {
+ if (values == null) return true;
+ for (var v : values)
+ if (v != null)
+ return false;
+ return true;
+ }
+
/**
* Checks if the specified Boolean is not <jk>null</jk> and is
<jk>true</jk>.
*
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Callback_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Callback_Test.java
index c52a2f311d..405e18277e 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Callback_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Callback_Test.java
@@ -208,7 +208,7 @@ class Callback_Test extends TestBase {
assertMapped(
TESTER.bean(), (obj,prop) -> cns(obj.get(prop,
Object.class)),
"callbacks,x1,x2",
- "LinkedHashMap,String,<null>"
+ "FilteredMap,String,<null>"
);
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Components_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Components_Test.java
index c22cc82483..7a5a1254e7 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Components_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Components_Test.java
@@ -214,7 +214,7 @@ class Components_Test extends TestBase {
assertMapped(
TESTER.bean(), (obj,prop) -> cns(obj.get(prop,
Object.class)),
"callbacks,examples,headers,links,parameters,requestBodies,responses,schemas,securitySchemes,x1,x2",
-
"LinkedHashMap,LinkedHashMap,LinkedHashMap,LinkedHashMap,LinkedHashMap,LinkedHashMap,LinkedHashMap,LinkedHashMap,LinkedHashMap,String,<null>"
+
"FilteredMap,FilteredMap,FilteredMap,FilteredMap,FilteredMap,FilteredMap,FilteredMap,FilteredMap,FilteredMap,String,<null>"
);
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/OpenApi_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/OpenApi_Test.java
index 388f3da2ee..4d2872c059 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/OpenApi_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/OpenApi_Test.java
@@ -281,7 +281,7 @@ class OpenApi_Test extends TestBase {
assertMapped(
TESTER.bean(), (obj,prop) -> cns(obj.get(prop,
Object.class)),
"components,externalDocs,info,openapi,paths,security,servers,tags,x1,x2",
-
"Components,ExternalDocumentation,Info,String,TreeMap,ArrayList,ArrayList,ArrayList,String,<null>"
+
"Components,ExternalDocumentation,Info,String,FilteredMap,ArrayList,ArrayList,ArrayList,String,<null>"
);
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Operation_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Operation_Test.java
index 35a2ae5442..800bbc93ac 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Operation_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Operation_Test.java
@@ -336,7 +336,7 @@ class Operation_Test extends TestBase {
assertMapped(
TESTER.bean(), (obj,prop) -> cns(obj.get(prop,
Object.class)),
"callbacks,deprecated,description,externalDocs,operationId,parameters,requestBody,responses,security,servers,summary,tags,x1,x2",
-
"LinkedHashMap,Boolean,String,ExternalDocumentation,String,ArrayList,RequestBodyInfo,LinkedHashMap,ArrayList,ArrayList,String,ArrayList,String,<null>"
+
"FilteredMap,Boolean,String,ExternalDocumentation,String,ArrayList,RequestBodyInfo,FilteredMap,ArrayList,ArrayList,String,ArrayList,String,<null>"
);
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
index 987aa6c513..6f6b65acf4 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
@@ -243,7 +243,7 @@ class Parameter_Test extends TestBase {
assertMapped(
TESTER.bean(), (obj,prop) -> cns(obj.get(prop,
Object.class)),
"allowEmptyValue,allowReserved,content,deprecated,description,example,examples,explode,in,name,required,schema,style,x1,x2",
-
"Boolean,Boolean,LinkedHashMap,Boolean,String,String,LinkedHashMap,Boolean,String,String,Boolean,SchemaInfo,String,String,<null>"
+
"Boolean,Boolean,LinkedHashMap,Boolean,String,String,FilteredMap,Boolean,String,String,Boolean,SchemaInfo,String,String,<null>"
);
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FilteredList_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FilteredList_Test.java
new file mode 100644
index 0000000000..2235972aac
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FilteredList_Test.java
@@ -0,0 +1,968 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.collections;
+
+import static org.apache.juneau.TestUtils.assertThrowsWithMessage;
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.junit.bct.BctAssertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+class FilteredList_Test extends TestBase {
+
+
//====================================================================================================
+ // Basic filtering - filter out null values
+
//====================================================================================================
+
+ @Test
+ void a01_filterNullValues_add() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertTrue(list.add("value1")); // Added
+ assertFalse(list.add(null)); // Filtered out
+ assertTrue(list.add("value3")); // Added
+
+ assertSize(2, list);
+ assertEquals("value1", list.get(0));
+ assertEquals("value3", list.get(1));
+ }
+
+ @Test
+ void a02_filterNullValues_addAll() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ var source = list("value1", null, "value3", null);
+
+ list.addAll(source);
+
+ assertSize(2, list);
+ assertEquals("value1", list.get(0));
+ assertEquals("value3", list.get(1));
+ }
+
+ @Test
+ void a03_filterNullValues_addAtIndex() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ list.add("value2");
+ list.add(1, "value3"); // Added at index 1
+ list.add(1, null); // Filtered out, not added
+
+ assertSize(3, list);
+ assertEquals("value1", list.get(0));
+ assertEquals("value3", list.get(1));
+ assertEquals("value2", list.get(2));
+ }
+
+
//====================================================================================================
+ // Filter based on value - positive numbers only
+
//====================================================================================================
+
+ @Test
+ void b01_filterPositiveNumbers() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ assertTrue(list.add(5)); // Added
+ assertFalse(list.add(-1)); // Filtered out
+ assertFalse(list.add(0)); // Filtered out
+ assertTrue(list.add(10)); // Added
+ assertFalse(list.add(null)); // Filtered out
+
+ assertSize(2, list);
+ assertEquals(5, list.get(0));
+ assertEquals(10, list.get(1));
+ }
+
+
//====================================================================================================
+ // Filter based on string length
+
//====================================================================================================
+
+ @Test
+ void c01_filterStringLength() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null && v.length() > 3)
+ .build();
+
+ assertTrue(list.add("short")); // Added
+ assertFalse(list.add("ab")); // Filtered out (length <= 3)
+ assertTrue(list.add("longer")); // Added
+ assertFalse(list.add(null)); // Filtered out
+
+ assertSize(2, list);
+ assertEquals("short", list.get(0));
+ assertEquals("longer", list.get(1));
+ }
+
+
//====================================================================================================
+ // Custom list types - LinkedList
+
//====================================================================================================
+
+ @Test
+ void d01_customListType_LinkedList() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .inner(new LinkedList<>())
+ .build();
+
+ list.add(5);
+ list.add(-1); // Filtered out
+ list.add(10);
+
+ assertSize(2, list);
+ assertTrue(list instanceof FilteredList);
+ }
+
+
//====================================================================================================
+ // Custom list types - CopyOnWriteArrayList
+
//====================================================================================================
+
+ @Test
+ void d02_customListType_CopyOnWriteArrayList() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .inner(new CopyOnWriteArrayList<>())
+ .build();
+
+ list.add("value1");
+ list.add(null); // Filtered out
+ list.add("value3");
+
+ assertSize(2, list);
+ }
+
+
//====================================================================================================
+ // List interface methods - get, contains, indexOf
+
//====================================================================================================
+
+ @Test
+ void e01_listInterface_get() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ list.add(null); // Filtered out
+
+ assertEquals("value1", list.get(0));
+ assertThrows(IndexOutOfBoundsException.class, () ->
list.get(1));
+ }
+
+ @Test
+ void e02_listInterface_contains() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ list.add(null); // Filtered out
+
+ assertTrue(list.contains("value1"));
+ assertFalse(list.contains(null)); // null was filtered out
+ }
+
+ @Test
+ void e03_listInterface_indexOf() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ list.add("value2");
+ list.add(null); // Filtered out
+ list.add("value1");
+
+ assertEquals(0, list.indexOf("value1"));
+ assertEquals(1, list.indexOf("value2"));
+ assertEquals(-1, list.indexOf(null)); // null was filtered out
+ assertEquals(2, list.lastIndexOf("value1"));
+ }
+
+ @Test
+ void e04_listInterface_size() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertEquals(0, list.size());
+ list.add("value1");
+ assertEquals(1, list.size());
+ list.add(null); // Filtered out
+ assertEquals(1, list.size());
+ list.add("value3");
+ assertEquals(2, list.size());
+ }
+
+ @Test
+ void e05_listInterface_isEmpty() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertTrue(list.isEmpty());
+ list.add("value1");
+ assertFalse(list.isEmpty());
+ }
+
+ @Test
+ void e06_listInterface_clear() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ list.add("value2");
+ assertSize(2, list);
+
+ list.clear();
+ assertTrue(list.isEmpty());
+ assertSize(0, list);
+ }
+
+ @Test
+ void e07_listInterface_remove() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ list.add("value2");
+
+ assertTrue(list.remove("value1"));
+ assertFalse(list.remove("value1")); // Already removed
+ assertFalse(list.remove("nonexistent"));
+
+ assertSize(1, list);
+ assertEquals("value2", list.get(0));
+ }
+
+ @Test
+ void e08_listInterface_removeAtIndex() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ list.add("value2");
+ list.add("value3");
+
+ assertEquals("value2", list.remove(1));
+ assertSize(2, list);
+ assertEquals("value1", list.get(0));
+ assertEquals("value3", list.get(1));
+ }
+
+ @Test
+ void e09_listInterface_set() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ list.add("value2");
+
+ assertEquals("value2", list.set(1, "value3")); // Updated
+ assertEquals("value3", list.get(1));
+
+ assertEquals("value3", list.set(1, null)); // Filtered out,
returns existing
+ assertEquals("value3", list.get(1)); // Still has old value
+ }
+
+
//====================================================================================================
+ // Edge cases - empty list
+
//====================================================================================================
+
+ @Test
+ void f01_emptyList() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertTrue(list.isEmpty());
+ assertSize(0, list);
+ assertThrows(IndexOutOfBoundsException.class, () ->
list.get(0));
+ assertFalse(list.contains("any"));
+ }
+
+
//====================================================================================================
+ // Builder validation
+
//====================================================================================================
+
+ @Test
+ void g01_builder_noFilter_acceptsAllElements() {
+ // Filter is optional - defaults to v -> true (accepts all
elements)
+ var list = FilteredList.create(String.class)
+ .build();
+
+ assertNotNull(list);
+ assertTrue(list.add("value1"));
+ assertTrue(list.add(null)); // Should be accepted (no filter)
+ assertTrue(list.add("value3"));
+
+ assertSize(3, list);
+ assertList(list, "value1", "<null>", "value3");
+ }
+
+ @Test
+ void g02_builder_nullFilter_throwsException() {
+ assertThrowsWithMessage(IllegalArgumentException.class,
"value", () -> {
+ FilteredList.create(String.class).filter(null);
+ });
+ }
+
+ @Test
+ void g03_builder_nullInner_throwsException() {
+ assertThrowsWithMessage(IllegalArgumentException.class,
"value", () -> {
+ FilteredList.create(String.class)
+ .filter(v -> true)
+ .inner(null);
+ });
+ }
+
+
//====================================================================================================
+ // Builder - create() without parameters
+
//====================================================================================================
+
+ @Test
+ void h01_builder_createWithoutParameters() {
+ var list = FilteredList
+ .<String>create()
+ .filter(v -> v != null)
+ .build();
+
+ assertTrue(list.add("value1"));
+ assertFalse(list.add(null)); // Filtered out
+
+ assertSize(1, list);
+ assertEquals("value1", list.get(0));
+ }
+
+ @Test
+ void h02_builder_createWithoutParameters_usesObjectClass() {
+ var list = FilteredList
+ .create()
+ .filter(v -> v != null)
+ .build();
+
+ // Object.class accepts any type
+ list.add("string");
+ list.add(123);
+ list.add(List.of(1, 2));
+
+ assertSize(3, list);
+ assertEquals("string", list.get(0));
+ assertEquals(123, list.get(1));
+ assertEquals(List.of(1, 2), list.get(2));
+ }
+
+
//====================================================================================================
+ // add() method - type validation with types specified
+
//====================================================================================================
+
+ @Test
+ void i01_add_withTypeValidation_validTypes() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ assertTrue(list.add(5)); // Valid types, added
+ assertTrue(list.add(10)); // Valid types, added
+ assertFalse(list.add(-1)); // Valid types, but filtered out
+
+ assertSize(2, list);
+ assertEquals(5, list.get(0));
+ assertEquals(10, list.get(1));
+ }
+
+ @Test
+ void i02_add_withTypeValidation_invalidType() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertThrowsWithMessage(RuntimeException.class, "could not be
converted to element type", () -> {
+ list.addConverted("value"); // Invalid type (String
instead of Integer)
+ });
+ }
+
+ @Test
+ void i03_add_withTypeValidation_noTypesSpecified() {
+ var list = FilteredList
+ .<Object>create()
+ .filter(v -> v != null)
+ .build();
+
+ // Object.class is used when types not specified, which accepts
any type
+ list.add("value1");
+ list.add(123); // Different types, but Object.class accepts
any type
+
+ assertSize(2, list);
+ assertEquals("value1", list.get(0));
+ assertEquals(123, list.get(1));
+ }
+
+
//====================================================================================================
+ // add() method - with elementFunction
+
//====================================================================================================
+
+ @Test
+ void j01_add_withElementFunction() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .elementFunction(o -> Integer.parseInt(o.toString()))
+ .build();
+
+ assertTrue(list.addConverted("5")); // Element converted from
String to Integer
+ assertTrue(list.addConverted("10")); // Element converted from
String to Integer
+ assertFalse(list.addConverted("-1")); // Element converted,
but filtered out
+
+ assertSize(2, list);
+ assertEquals(5, list.get(0));
+ assertEquals(10, list.get(1));
+ }
+
+ @Test
+ void j02_add_withElementFunction_noTypeSpecified() {
+ var list = FilteredList
+ .<Integer>create()
+ .filter(v -> v != null)
+ .elementFunction(o -> Integer.parseInt(o.toString()))
+ .build();
+
+ assertTrue(list.addConverted("123")); // Element converted
using function
+
+ assertSize(1, list);
+ assertEquals(123, list.get(0));
+ }
+
+
//====================================================================================================
+ // addAll() method
+
//====================================================================================================
+
+ @Test
+ void k01_addAll_withTypeValidation() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ var source = List.of(5, -1, 10);
+
+ assertTrue(list.addAll(source));
+
+ assertSize(2, list);
+ assertEquals(5, list.get(0));
+ assertEquals(10, list.get(1));
+ }
+
+ @Test
+ void k02_addAll_withElementFunction() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .elementFunction(o -> Integer.parseInt(o.toString()))
+ .build();
+
+ var source = List.of("5", "-1", "10");
+
+ list.addAllConverted(source);
+
+ assertSize(2, list);
+ assertEquals(5, list.get(0));
+ assertEquals(10, list.get(1));
+ }
+
+ @Test
+ void k03_addAll_nullSource() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.addAllConverted(null); // Should be no-op
+
+ assertTrue(list.isEmpty());
+ assertSize(0, list);
+ }
+
+ @Test
+ void k04_addAll_emptySource() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertFalse(list.addAll(List.of())); // Empty list
+
+ assertTrue(list.isEmpty());
+ assertSize(0, list);
+ }
+
+ @Test
+ void k05_addAll_atIndex() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ list.add(1);
+ list.add(5);
+
+ assertTrue(list.addAll(1, List.of(2, 3, -1, 4))); // -1
filtered out
+
+ assertSize(5, list);
+ assertEquals(1, list.get(0));
+ assertEquals(2, list.get(1));
+ assertEquals(3, list.get(2));
+ assertEquals(4, list.get(3));
+ assertEquals(5, list.get(4));
+ }
+
+
//====================================================================================================
+ // add() method - return value
+
//====================================================================================================
+
+ @Test
+ void l01_add_returnValue_newElement() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertTrue(list.add("value1")); // New element, returns true
+ }
+
+ @Test
+ void l02_add_returnValue_filteredOut() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ assertFalse(list.add(null)); // Filtered out, returns false
+ assertEquals("value1", list.get(0)); // Old value still there
+ }
+
+
//====================================================================================================
+ // add() method - edge cases with functions
+
//====================================================================================================
+
+ @Test
+ void m01_add_elementFunctionThrowsException() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null)
+ .elementFunction(o -> {
+ if (o == null)
+ throw new
IllegalArgumentException("Element cannot be null");
+ return Integer.parseInt(o.toString());
+ })
+ .build();
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ list.addConverted(null);
+ });
+ }
+
+ @Test
+ void m02_add_elementFunctionReturnsNull() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .elementFunction(o -> {
+ try {
+ return Integer.parseInt(o.toString());
+ } catch (NumberFormatException e) {
+ return null; // Return null for
invalid numbers
+ }
+ })
+ .build();
+
+ assertTrue(list.addConverted("5")); // Valid, added
+ assertFalse(list.addConverted("abc")); // Function returns
null, filtered out
+
+ assertSize(1, list);
+ assertEquals(5, list.get(0));
+ }
+
+
//====================================================================================================
+ // wouldAccept() method
+
//====================================================================================================
+
+ @Test
+ void n01_wouldAccept_returnsTrue() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ assertTrue(list.wouldAccept(5));
+ assertTrue(list.wouldAccept(10));
+ }
+
+ @Test
+ void n02_wouldAccept_returnsFalse() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ assertFalse(list.wouldAccept(null));
+ assertFalse(list.wouldAccept(-1));
+ assertFalse(list.wouldAccept(0));
+ }
+
+ @Test
+ void n03_wouldAccept_usedForPreValidation() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ // Pre-validate before adding
+ if (list.wouldAccept(5)) {
+ list.add(5);
+ }
+ if (list.wouldAccept(-1)) {
+ list.add(-1);
+ }
+
+ assertSize(1, list);
+ assertTrue(list.contains(5));
+ assertFalse(list.contains(-1));
+ }
+
+
//====================================================================================================
+ // getFilter() method
+
//====================================================================================================
+
+ @Test
+ void o01_getFilter_returnsFilter() {
+ var originalFilter = (Predicate<Integer>)(v -> v != null && v >
0);
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(originalFilter)
+ .build();
+
+ var retrievedFilter = list.getFilter();
+ assertNotNull(retrievedFilter);
+ // The filter may be combined with the default filter, so test
behavior instead of instance equality
+ assertTrue(retrievedFilter.test(5)); // Should accept
positive values
+ assertFalse(retrievedFilter.test(-1)); // Should reject
negative values
+ assertFalse(retrievedFilter.test(null)); // Should reject null
values
+ }
+
+ @Test
+ void o02_getFilter_canBeUsedForOtherPurposes() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ var filter = list.getFilter();
+
+ // Use the filter independently
+ assertTrue(filter.test(5));
+ assertFalse(filter.test(-1));
+ }
+
+
//====================================================================================================
+ // Null handling for primitive types
+
//====================================================================================================
+
+ @Test
+ void p01_add_nullForPrimitiveType_throwsException() {
+ var list = FilteredList
+ .create(int.class)
+ .filter(v -> true)
+ .build();
+
+ assertThrowsWithMessage(RuntimeException.class, "Cannot set
null element for primitive type", () -> {
+ list.addConverted(null);
+ });
+ }
+
+ @Test
+ void p02_add_nullForWrapperType_allowed() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> true) // Accept all, including null
+ .build();
+
+ assertTrue(list.add(null)); // Should work for wrapper types
+
+ assertSize(1, list);
+ assertNull(list.get(0));
+ }
+
+
//====================================================================================================
+ // toString(), equals(), hashCode()
+
//====================================================================================================
+
+ @Test
+ void q01_toString_delegatesToUnderlyingList() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> true)
+ .build();
+ list.add("value1");
+ list.add("value2");
+
+ var underlyingList = new ArrayList<String>();
+ underlyingList.add("value1");
+ underlyingList.add("value2");
+
+ assertEquals(underlyingList.toString(), list.toString());
+ }
+
+ @Test
+ void q02_equals_delegatesToUnderlyingList() {
+ var list1 = FilteredList
+ .create(String.class)
+ .filter(v -> true)
+ .build();
+ list1.add("value1");
+ list1.add("value2");
+
+ var list2 = new ArrayList<String>();
+ list2.add("value1");
+ list2.add("value2");
+
+ assertTrue(list1.equals(list2));
+ assertTrue(list2.equals(list1));
+ }
+
+ @Test
+ void q03_equals_differentContents_returnsFalse() {
+ var list1 = FilteredList
+ .create(String.class)
+ .filter(v -> true)
+ .build();
+ list1.add("value1");
+
+ var list2 = new ArrayList<String>();
+ list2.add("value2");
+
+ assertFalse(list1.equals(list2));
+ assertFalse(list2.equals(list1));
+ }
+
+ @Test
+ void q04_hashCode_delegatesToUnderlyingList() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> true)
+ .build();
+ list.add("value1");
+ list.add("value2");
+
+ var underlyingList = new ArrayList<String>();
+ underlyingList.add("value1");
+ underlyingList.add("value2");
+
+ assertEquals(underlyingList.hashCode(), list.hashCode());
+ }
+
+
//====================================================================================================
+ // addAny() method
+
//====================================================================================================
+
+ @Test
+ void r01_addAny_withCollections() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ var list1 = List.of(5, -1);
+ var list2 = List.of(10);
+ list.addAny(list1, list2); // Adds 5, 10 (-1 filtered out)
+
+ assertSize(2, list);
+ assertEquals(5, list.get(0));
+ assertEquals(10, list.get(1));
+ }
+
+ @Test
+ void r02_addAny_withArrays() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ var array1 = new Integer[]{5, -1};
+ var array2 = new Integer[]{10};
+ list.addAny(array1, array2); // Adds 5, 10 (-1 filtered out)
+
+ assertSize(2, list);
+ assertEquals(5, list.get(0));
+ assertEquals(10, list.get(1));
+ }
+
+ @Test
+ void r03_addAny_withMixedTypes() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ list.addAny(5, List.of(-1, 10), 15); // Adds 5, 10, 15 (-1
filtered out)
+
+ assertSize(3, list);
+ assertEquals(5, list.get(0));
+ assertEquals(10, list.get(1));
+ assertEquals(15, list.get(2));
+ }
+
+ @Test
+ void r04_addAny_nullValues() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.addAny("value1", null, "value2", null); // nulls ignored
+
+ assertSize(2, list);
+ assertEquals("value1", list.get(0));
+ assertEquals("value2", list.get(1));
+ }
+
+
//====================================================================================================
+ // Multiple filters
+
//====================================================================================================
+
+ @Test
+ void t01_multipleFilters() {
+ var list = FilteredList
+ .create(Integer.class)
+ .filter(v -> v != null) // First filter
+ .filter(v -> v > 0) // Second filter
(ANDed with first)
+ .filter(v -> v < 100) // Third filter
(ANDed with previous)
+ .build();
+
+ assertTrue(list.add(5)); // Passes all filters
+ assertFalse(list.add(null)); // Fails first filter
+ assertFalse(list.add(-1)); // Fails second filter
+ assertFalse(list.add(0)); // Fails second filter
+ assertFalse(list.add(100)); // Fails third filter
+ assertTrue(list.add(50)); // Passes all filters
+
+ assertSize(2, list);
+ assertEquals(5, list.get(0));
+ assertEquals(50, list.get(1));
+ }
+
+
//====================================================================================================
+ // ListIterator
+
//====================================================================================================
+
+ @Test
+ void u01_listIterator() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ list.add("value2");
+ list.add("value3");
+
+ var iterator = list.listIterator();
+ assertTrue(iterator.hasNext());
+ assertEquals("value1", iterator.next());
+ assertEquals("value2", iterator.next());
+ assertEquals("value3", iterator.next());
+ assertFalse(iterator.hasNext());
+ }
+
+ @Test
+ void u02_listIterator_atIndex() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ list.add("value2");
+ list.add("value3");
+
+ var iterator = list.listIterator(1);
+ assertTrue(iterator.hasNext());
+ assertEquals("value2", iterator.next());
+ assertEquals("value3", iterator.next());
+ assertFalse(iterator.hasNext());
+ }
+
+
//====================================================================================================
+ // subList
+
//====================================================================================================
+
+ @Test
+ void v01_subList() {
+ var list = FilteredList
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ list.add("value1");
+ list.add("value2");
+ list.add("value3");
+ list.add("value4");
+
+ var subList = list.subList(1, 3);
+ assertSize(2, subList);
+ assertEquals("value2", subList.get(0));
+ assertEquals("value3", subList.get(1));
+ }
+}
+
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FilteredSet_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FilteredSet_Test.java
new file mode 100644
index 0000000000..fe8e3e2032
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FilteredSet_Test.java
@@ -0,0 +1,948 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.collections;
+
+import static org.apache.juneau.TestUtils.assertThrowsWithMessage;
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.junit.bct.BctAssertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.function.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+class FilteredSet_Test extends TestBase {
+
+
//====================================================================================================
+ // Basic filtering - filter out null values
+
//====================================================================================================
+
+ @Test
+ void a01_filterNullValues_add() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertTrue(set.add("value1")); // Added
+ assertFalse(set.add(null)); // Filtered out
+ assertTrue(set.add("value3")); // Added
+
+ assertSize(2, set);
+ assertTrue(set.contains("value1"));
+ assertTrue(set.contains("value3"));
+ assertFalse(set.contains(null));
+ }
+
+ @Test
+ void a02_filterNullValues_addAll() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ var source = list("value1", null, "value3", null);
+
+ assertTrue(set.addAll(source));
+
+ assertSize(2, set);
+ assertTrue(set.contains("value1"));
+ assertTrue(set.contains("value3"));
+ assertFalse(set.contains(null));
+ }
+
+ @Test
+ void a03_filterNullValues_duplicateElements() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertTrue(set.add("value1")); // Added
+ assertFalse(set.add("value1")); // Already present, returns
false
+ assertTrue(set.add("value2")); // Added
+
+ assertSize(2, set);
+ assertTrue(set.contains("value1"));
+ assertTrue(set.contains("value2"));
+ }
+
+
//====================================================================================================
+ // Filter based on value - positive numbers only
+
//====================================================================================================
+
+ @Test
+ void b01_filterPositiveNumbers() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ assertTrue(set.add(5)); // Added
+ assertFalse(set.add(-1)); // Filtered out
+ assertFalse(set.add(0)); // Filtered out
+ assertTrue(set.add(10)); // Added
+ assertFalse(set.add(null)); // Filtered out
+
+ assertSize(2, set);
+ assertTrue(set.contains(5));
+ assertTrue(set.contains(10));
+ assertFalse(set.contains(-1));
+ assertFalse(set.contains(0));
+ }
+
+
//====================================================================================================
+ // Filter based on string length
+
//====================================================================================================
+
+ @Test
+ void c01_filterStringLength() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null && v.length() > 3)
+ .build();
+
+ assertTrue(set.add("short")); // Added
+ assertFalse(set.add("ab")); // Filtered out (length <= 3)
+ assertTrue(set.add("longer")); // Added
+ assertFalse(set.add(null)); // Filtered out
+
+ assertSize(2, set);
+ assertTrue(set.contains("short"));
+ assertTrue(set.contains("longer"));
+ assertFalse(set.contains("ab"));
+ }
+
+
//====================================================================================================
+ // Custom set types - TreeSet
+
//====================================================================================================
+
+ @Test
+ void d01_customSetType_TreeSet() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .inner(new TreeSet<>())
+ .build();
+
+ set.add(5);
+ set.add(-1); // Filtered out
+ set.add(10);
+ set.add(3);
+
+ // TreeSet maintains sorted order
+ var elements = new ArrayList<>(set);
+ assertEquals(List.of(3, 5, 10), elements);
+ }
+
+
//====================================================================================================
+ // Custom set types - CopyOnWriteArraySet
+
//====================================================================================================
+
+ @Test
+ void d02_customSetType_CopyOnWriteArraySet() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .inner(new CopyOnWriteArraySet<>())
+ .build();
+
+ set.add("value1");
+ set.add(null); // Filtered out
+ set.add("value3");
+
+ assertSize(2, set);
+ }
+
+
//====================================================================================================
+ // Set interface methods - contains, containsAll
+
//====================================================================================================
+
+ @Test
+ void e01_setInterface_contains() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.add("value1");
+ set.add(null); // Filtered out
+
+ assertTrue(set.contains("value1"));
+ assertFalse(set.contains(null)); // null was filtered out
+ }
+
+ @Test
+ void e02_setInterface_containsAll() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.add("value1");
+ set.add("value2");
+ set.add("value3");
+
+ assertTrue(set.containsAll(List.of("value1", "value2")));
+ assertFalse(set.containsAll(List.of("value1", "value4")));
+ assertFalse(set.containsAll(Arrays.asList("value1",
(String)null))); // null was filtered out
+ }
+
+ @Test
+ void e03_setInterface_size() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertEquals(0, set.size());
+ set.add("value1");
+ assertEquals(1, set.size());
+ set.add(null); // Filtered out
+ assertEquals(1, set.size());
+ set.add("value3");
+ assertEquals(2, set.size());
+ set.add("value1"); // Duplicate, not added
+ assertEquals(2, set.size());
+ }
+
+ @Test
+ void e04_setInterface_isEmpty() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertTrue(set.isEmpty());
+ set.add("value1");
+ assertFalse(set.isEmpty());
+ }
+
+ @Test
+ void e05_setInterface_clear() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.add("value1");
+ set.add("value2");
+ assertSize(2, set);
+
+ set.clear();
+ assertTrue(set.isEmpty());
+ assertSize(0, set);
+ }
+
+ @Test
+ void e06_setInterface_remove() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.add("value1");
+ set.add("value2");
+
+ assertTrue(set.remove("value1"));
+ assertFalse(set.remove("value1")); // Already removed
+ assertFalse(set.remove("nonexistent"));
+
+ assertSize(1, set);
+ assertTrue(set.contains("value2"));
+ }
+
+ @Test
+ void e07_setInterface_removeAll() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.add("value1");
+ set.add("value2");
+ set.add("value3");
+
+ assertTrue(set.removeAll(List.of("value1", "value2")));
+ assertSize(1, set);
+ assertTrue(set.contains("value3"));
+ }
+
+ @Test
+ void e08_setInterface_retainAll() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.add("value1");
+ set.add("value2");
+ set.add("value3");
+
+ assertTrue(set.retainAll(List.of("value1", "value2")));
+ assertSize(2, set);
+ assertTrue(set.contains("value1"));
+ assertTrue(set.contains("value2"));
+ assertFalse(set.contains("value3"));
+ }
+
+
//====================================================================================================
+ // Edge cases - empty set
+
//====================================================================================================
+
+ @Test
+ void f01_emptySet() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertTrue(set.isEmpty());
+ assertSize(0, set);
+ assertFalse(set.contains("any"));
+ }
+
+
//====================================================================================================
+ // Builder validation
+
//====================================================================================================
+
+ @Test
+ void g01_builder_noFilter_acceptsAllElements() {
+ // Filter is optional - defaults to v -> true (accepts all
elements)
+ var set = FilteredSet.create(String.class)
+ .build();
+
+ assertNotNull(set);
+ assertTrue(set.add("value1"));
+ assertTrue(set.add(null)); // Should be accepted (no filter)
+ assertTrue(set.add("value3"));
+
+ assertSize(3, set);
+ assertTrue(set.contains("value1"));
+ assertTrue(set.contains(null));
+ assertTrue(set.contains("value3"));
+ }
+
+ @Test
+ void g02_builder_nullFilter_throwsException() {
+ assertThrowsWithMessage(IllegalArgumentException.class,
"value", () -> {
+ FilteredSet.create(String.class).filter(null);
+ });
+ }
+
+ @Test
+ void g03_builder_nullInner_throwsException() {
+ assertThrowsWithMessage(IllegalArgumentException.class,
"value", () -> {
+ FilteredSet.create(String.class)
+ .filter(v -> true)
+ .inner(null);
+ });
+ }
+
+
//====================================================================================================
+ // Builder - create() without parameters
+
//====================================================================================================
+
+ @Test
+ void h01_builder_createWithoutParameters() {
+ var set = FilteredSet
+ .<String>create()
+ .filter(v -> v != null)
+ .build();
+
+ assertTrue(set.add("value1"));
+ assertFalse(set.add(null)); // Filtered out
+
+ assertSize(1, set);
+ assertTrue(set.contains("value1"));
+ }
+
+ @Test
+ void h02_builder_createWithoutParameters_usesObjectClass() {
+ var set = FilteredSet
+ .create()
+ .filter(v -> v != null)
+ .build();
+
+ // Object.class accepts any type
+ set.add("string");
+ set.add(123);
+ set.add(List.of(1, 2));
+
+ assertSize(3, set);
+ assertTrue(set.contains("string"));
+ assertTrue(set.contains(123));
+ assertTrue(set.contains(List.of(1, 2)));
+ }
+
+
//====================================================================================================
+ // add() method - type validation with types specified
+
//====================================================================================================
+
+ @Test
+ void i01_add_withTypeValidation_validTypes() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ assertTrue(set.add(5)); // Valid types, added
+ assertTrue(set.add(10)); // Valid types, added
+ assertFalse(set.add(-1)); // Valid types, but filtered out
+
+ assertSize(2, set);
+ assertTrue(set.contains(5));
+ assertTrue(set.contains(10));
+ }
+
+ @Test
+ void i02_add_withTypeValidation_invalidType() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertThrowsWithMessage(RuntimeException.class, "could not be
converted to element type", () -> {
+ set.addConverted("value"); // Invalid type (String
instead of Integer)
+ });
+ }
+
+ @Test
+ void i03_add_withTypeValidation_noTypesSpecified() {
+ var set = FilteredSet
+ .<Object>create()
+ .filter(v -> v != null)
+ .build();
+
+ // Object.class is used when types not specified, which accepts
any type
+ set.add("value1");
+ set.add(123); // Different types, but Object.class accepts any
type
+
+ assertSize(2, set);
+ assertTrue(set.contains("value1"));
+ assertTrue(set.contains(123));
+ }
+
+
//====================================================================================================
+ // add() method - with elementFunction
+
//====================================================================================================
+
+ @Test
+ void j01_add_withElementFunction() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .elementFunction(o -> Integer.parseInt(o.toString()))
+ .build();
+
+ assertTrue(set.addConverted("5")); // Element converted from
String to Integer
+ assertTrue(set.addConverted("10")); // Element converted from
String to Integer
+ assertFalse(set.addConverted("-1")); // Element converted, but
filtered out
+
+ assertSize(2, set);
+ assertTrue(set.contains(5));
+ assertTrue(set.contains(10));
+ }
+
+ @Test
+ void j02_add_withElementFunction_noTypeSpecified() {
+ var set = FilteredSet
+ .<Integer>create()
+ .filter(v -> v != null)
+ .elementFunction(o -> Integer.parseInt(o.toString()))
+ .build();
+
+ assertTrue(set.addConverted("123")); // Element converted
using function
+
+ assertSize(1, set);
+ assertTrue(set.contains(123));
+ }
+
+
//====================================================================================================
+ // addAll() method
+
//====================================================================================================
+
+ @Test
+ void k01_addAll_withTypeValidation() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ var source = List.of(5, -1, 10);
+
+ assertTrue(set.addAll(source));
+
+ assertSize(2, set);
+ assertTrue(set.contains(5));
+ assertTrue(set.contains(10));
+ }
+
+ @Test
+ void k02_addAll_withElementFunction() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .elementFunction(o -> Integer.parseInt(o.toString()))
+ .build();
+
+ var source = List.of("5", "-1", "10");
+
+ set.addAllConverted(source);
+
+ assertSize(2, set);
+ assertTrue(set.contains(5));
+ assertTrue(set.contains(10));
+ }
+
+ @Test
+ void k03_addAll_nullSource() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.addAllConverted(null); // Should be no-op
+
+ assertTrue(set.isEmpty());
+ assertSize(0, set);
+ }
+
+ @Test
+ void k04_addAll_emptySource() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertFalse(set.addAll(Set.of())); // Empty set
+
+ assertTrue(set.isEmpty());
+ assertSize(0, set);
+ }
+
+ @Test
+ void k05_addAll_duplicateElements() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ set.add(5);
+ assertTrue(set.addAll(List.of(5, 10))); // 5 already present,
10 added - returns true because set was modified
+ assertSize(2, set);
+ assertTrue(set.contains(5));
+ assertTrue(set.contains(10));
+ }
+
+
//====================================================================================================
+ // add() method - return value
+
//====================================================================================================
+
+ @Test
+ void l01_add_returnValue_newElement() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ assertTrue(set.add("value1")); // New element, returns true
+ }
+
+ @Test
+ void l02_add_returnValue_duplicateElement() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.add("value1");
+ assertFalse(set.add("value1")); // Duplicate, returns false
+ assertSize(1, set);
+ }
+
+ @Test
+ void l03_add_returnValue_filteredOut() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.add("value1");
+ assertFalse(set.add(null)); // Filtered out, returns false
+ assertTrue(set.contains("value1")); // Old value still there
+ }
+
+
//====================================================================================================
+ // add() method - edge cases with functions
+
//====================================================================================================
+
+ @Test
+ void m01_add_elementFunctionThrowsException() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null)
+ .elementFunction(o -> {
+ if (o == null)
+ throw new
IllegalArgumentException("Element cannot be null");
+ return Integer.parseInt(o.toString());
+ })
+ .build();
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ set.addConverted(null);
+ });
+ }
+
+ @Test
+ void m02_add_elementFunctionReturnsNull() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .elementFunction(o -> {
+ try {
+ return Integer.parseInt(o.toString());
+ } catch (NumberFormatException e) {
+ return null; // Return null for
invalid numbers
+ }
+ })
+ .build();
+
+ assertTrue(set.addConverted("5")); // Valid, added
+ assertFalse(set.addConverted("abc")); // Function returns null,
filtered out
+
+ assertSize(1, set);
+ assertTrue(set.contains(5));
+ }
+
+
//====================================================================================================
+ // wouldAccept() method
+
//====================================================================================================
+
+ @Test
+ void n01_wouldAccept_returnsTrue() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ assertTrue(set.wouldAccept(5));
+ assertTrue(set.wouldAccept(10));
+ }
+
+ @Test
+ void n02_wouldAccept_returnsFalse() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ assertFalse(set.wouldAccept(null));
+ assertFalse(set.wouldAccept(-1));
+ assertFalse(set.wouldAccept(0));
+ }
+
+ @Test
+ void n03_wouldAccept_usedForPreValidation() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ // Pre-validate before adding
+ if (set.wouldAccept(5)) {
+ set.add(5);
+ }
+ if (set.wouldAccept(-1)) {
+ set.add(-1);
+ }
+
+ assertSize(1, set);
+ assertTrue(set.contains(5));
+ assertFalse(set.contains(-1));
+ }
+
+
//====================================================================================================
+ // getFilter() method
+
//====================================================================================================
+
+ @Test
+ void o01_getFilter_returnsFilter() {
+ var originalFilter = (Predicate<Integer>)(v -> v != null && v >
0);
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(originalFilter)
+ .build();
+
+ var retrievedFilter = set.getFilter();
+ assertNotNull(retrievedFilter);
+ // The filter may be combined with the default filter, so test
behavior instead of instance equality
+ assertTrue(retrievedFilter.test(5)); // Should accept
positive values
+ assertFalse(retrievedFilter.test(-1)); // Should reject
negative values
+ assertFalse(retrievedFilter.test(null)); // Should reject null
values
+ }
+
+ @Test
+ void o02_getFilter_canBeUsedForOtherPurposes() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ var filter = set.getFilter();
+
+ // Use the filter independently
+ assertTrue(filter.test(5));
+ assertFalse(filter.test(-1));
+ }
+
+
//====================================================================================================
+ // Null handling for primitive types
+
//====================================================================================================
+
+ @Test
+ void p01_add_nullForPrimitiveType_throwsException() {
+ var set = FilteredSet
+ .create(int.class)
+ .filter(v -> true)
+ .build();
+
+ assertThrowsWithMessage(RuntimeException.class, "Cannot set
null element for primitive type", () -> {
+ set.addConverted(null);
+ });
+ }
+
+ @Test
+ void p02_add_nullForWrapperType_allowed() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> true) // Accept all, including null
+ .build();
+
+ assertTrue(set.add(null)); // Should work for wrapper types
+
+ assertSize(1, set);
+ assertTrue(set.contains(null));
+ }
+
+
//====================================================================================================
+ // toString(), equals(), hashCode()
+
//====================================================================================================
+
+ @Test
+ void q01_toString_delegatesToUnderlyingSet() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> true)
+ .build();
+ set.add("value1");
+ set.add("value2");
+
+ var underlyingSet = new LinkedHashSet<String>();
+ underlyingSet.add("value1");
+ underlyingSet.add("value2");
+
+ assertEquals(underlyingSet.toString(), set.toString());
+ }
+
+ @Test
+ void q02_equals_delegatesToUnderlyingSet() {
+ var set1 = FilteredSet
+ .create(String.class)
+ .filter(v -> true)
+ .build();
+ set1.add("value1");
+ set1.add("value2");
+
+ var set2 = new LinkedHashSet<String>();
+ set2.add("value1");
+ set2.add("value2");
+
+ assertTrue(set1.equals(set2));
+ assertTrue(set2.equals(set1));
+ }
+
+ @Test
+ void q03_equals_differentContents_returnsFalse() {
+ var set1 = FilteredSet
+ .create(String.class)
+ .filter(v -> true)
+ .build();
+ set1.add("value1");
+
+ var set2 = new LinkedHashSet<String>();
+ set2.add("value2");
+
+ assertFalse(set1.equals(set2));
+ assertFalse(set2.equals(set1));
+ }
+
+ @Test
+ void q04_hashCode_delegatesToUnderlyingSet() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> true)
+ .build();
+ set.add("value1");
+ set.add("value2");
+
+ var underlyingSet = new LinkedHashSet<String>();
+ underlyingSet.add("value1");
+ underlyingSet.add("value2");
+
+ assertEquals(underlyingSet.hashCode(), set.hashCode());
+ }
+
+
//====================================================================================================
+ // addAny() method
+
//====================================================================================================
+
+ @Test
+ void r01_addAny_withCollections() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ var set1 = Set.of(5, -1);
+ var set2 = Set.of(10);
+ set.addAny(set1, set2); // Adds 5, 10 (-1 filtered out)
+
+ assertSize(2, set);
+ assertTrue(set.contains(5));
+ assertTrue(set.contains(10));
+ }
+
+ @Test
+ void r02_addAny_withArrays() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ var array1 = new Integer[]{5, -1};
+ var array2 = new Integer[]{10};
+ set.addAny(array1, array2); // Adds 5, 10 (-1 filtered out)
+
+ assertSize(2, set);
+ assertTrue(set.contains(5));
+ assertTrue(set.contains(10));
+ }
+
+ @Test
+ void r03_addAny_withMixedTypes() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null && v > 0)
+ .build();
+
+ set.addAny(5, Set.of(-1, 10), 15); // Adds 5, 10, 15 (-1
filtered out)
+
+ assertSize(3, set);
+ assertTrue(set.contains(5));
+ assertTrue(set.contains(10));
+ assertTrue(set.contains(15));
+ }
+
+ @Test
+ void r04_addAny_nullValues() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.addAny("value1", null, "value2", null); // nulls ignored
+
+ assertSize(2, set);
+ assertTrue(set.contains("value1"));
+ assertTrue(set.contains("value2"));
+ }
+
+
//====================================================================================================
+ // Multiple filters
+
//====================================================================================================
+
+ @Test
+ void t01_multipleFilters() {
+ var set = FilteredSet
+ .create(Integer.class)
+ .filter(v -> v != null) // First filter
+ .filter(v -> v > 0) // Second filter
(ANDed with first)
+ .filter(v -> v < 100) // Third filter
(ANDed with previous)
+ .build();
+
+ assertTrue(set.add(5)); // Passes all filters
+ assertFalse(set.add(null)); // Fails first filter
+ assertFalse(set.add(-1)); // Fails second filter
+ assertFalse(set.add(0)); // Fails second filter
+ assertFalse(set.add(100)); // Fails third filter
+ assertTrue(set.add(50)); // Passes all filters
+
+ assertSize(2, set);
+ assertTrue(set.contains(5));
+ assertTrue(set.contains(50));
+ }
+
+
//====================================================================================================
+ // Iterator
+
//====================================================================================================
+
+ @Test
+ void u01_iterator() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.add("value1");
+ set.add("value2");
+ set.add("value3");
+
+ var iterator = set.iterator();
+ var found = new HashSet<String>();
+ while (iterator.hasNext()) {
+ found.add(iterator.next());
+ }
+
+ assertSize(3, found);
+ assertTrue(found.contains("value1"));
+ assertTrue(found.contains("value2"));
+ assertTrue(found.contains("value3"));
+ }
+
+ @Test
+ void u02_iterator_remove() {
+ var set = FilteredSet
+ .create(String.class)
+ .filter(v -> v != null)
+ .build();
+
+ set.add("value1");
+ set.add("value2");
+ set.add("value3");
+
+ var iterator = set.iterator();
+ iterator.next();
+ iterator.remove();
+
+ assertSize(2, set);
+ }
+}
+
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 f78cb25392..090405f55e 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
@@ -68,7 +68,12 @@ class MapBuilder_Test extends TestBase {
.add("b", 2)
.build();
- assertMap(map, "a=1", "x=10", "y=20", "b=2");
+ // Without ordered(), order is not guaranteed (HashMap)
+ assertSize(4, map);
+ assertEquals(1, map.get("a"));
+ assertEquals(10, map.get("x"));
+ assertEquals(20, map.get("y"));
+ assertEquals(2, map.get("b"));
}
@Test
@@ -101,6 +106,88 @@ class MapBuilder_Test extends TestBase {
assertThrows(IllegalArgumentException.class, () ->
b.addPairs("a", "b", "c"));
}
+
//-----------------------------------------------------------------------------------------------------------------
+ // Ordered
+
//-----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ void c00_ordered_preservesInsertionOrder() {
+ var map = MapBuilder.create(String.class, Integer.class)
+ .ordered()
+ .add("c", 3)
+ .add("a", 1)
+ .add("b", 2)
+ .build();
+
+ assertSize(3, map);
+ assertTrue(map instanceof LinkedHashMap);
+ // With ordered(), insertion order is preserved
+ var keys = new ArrayList<>(map.keySet());
+ assertEquals("c", keys.get(0));
+ assertEquals("a", keys.get(1));
+ assertEquals("b", keys.get(2));
+ }
+
+ @Test
+ void c00b_ordered_defaultIsHashMap() {
+ var map = MapBuilder.create(String.class, Integer.class)
+ .add("a", 1)
+ .add("b", 2)
+ .build();
+
+ assertSize(2, map);
+ // Without ordered(), should be HashMap (unordered)
+ assertTrue(map instanceof HashMap);
+ assertFalse(map instanceof LinkedHashMap);
+ // Verify it's not ordered by checking key order is not
preserved
+ // (HashMap doesn't guarantee order, so we can't rely on it)
+ assertEquals(1, map.get("a"));
+ assertEquals(2, map.get("b"));
+ }
+
+ @Test
+ void c00c_ordered_boolean() {
+ var map1 = MapBuilder.create(String.class, Integer.class)
+ .ordered(true)
+ .add("a", 1)
+ .build();
+ assertTrue(map1 instanceof LinkedHashMap);
+
+ var map2 = MapBuilder.create(String.class, Integer.class)
+ .ordered(false)
+ .add("a", 1)
+ .build();
+ assertTrue(map2 instanceof HashMap);
+ }
+
+ @Test
+ void c00d_ordered_lastOneWins() {
+ // If ordered() is called first, then sorted(), sorted() wins
+ var map1 = MapBuilder.create(String.class, Integer.class)
+ .ordered()
+ .add("c", 3)
+ .add("a", 1)
+ .add("b", 2)
+ .sorted()
+ .build();
+ assertTrue(map1 instanceof TreeMap);
+ assertList(map1.keySet(), "a", "b", "c");
+
+ // If sorted() is called first, then ordered(), ordered() wins
+ var map2 = MapBuilder.create(String.class, Integer.class)
+ .sorted()
+ .add("c", 3)
+ .add("a", 1)
+ .add("b", 2)
+ .ordered()
+ .build();
+ assertTrue(map2 instanceof LinkedHashMap);
+ var keys2 = new ArrayList<>(map2.keySet());
+ assertEquals("c", keys2.get(0));
+ assertEquals("a", keys2.get(1));
+ assertEquals("b", keys2.get(2));
+ }
+
//-----------------------------------------------------------------------------------------------------------------
// Sorting
//-----------------------------------------------------------------------------------------------------------------
@@ -211,21 +298,6 @@ class MapBuilder_Test extends TestBase {
assertNotSame(original, map); // After copy(), they're
different maps
}
- @Test
- void f02_noCopy() {
- var original = new LinkedHashMap<String,Integer>();
- original.put("a", 1);
-
- var map = MapBuilder.create(String.class, Integer.class)
- .to(original)
- .add("b", 2)
- .build();
-
- assertSize(2, map);
- assertSize(2, original); // Original modified
- assertSame(original, map);
- }
-
//-----------------------------------------------------------------------------------------------------------------
// Complex scenarios
//-----------------------------------------------------------------------------------------------------------------
@@ -248,6 +320,29 @@ class MapBuilder_Test extends TestBase {
assertList(map.keySet(), "a", "b", "x", "y");
}
+ @Test
+ void g01b_multipleOperations_withOrdered() {
+ var existing = new LinkedHashMap<String,Integer>();
+ existing.put("x", 10);
+ existing.put("y", 20);
+
+ var map = MapBuilder.create(String.class, Integer.class)
+ .ordered()
+ .add("a", 1)
+ .addAll(existing)
+ .add("b", 2)
+ .build();
+
+ assertSize(4, map);
+ assertTrue(map instanceof LinkedHashMap);
+ // With ordered(), insertion order is preserved
+ var keys = new ArrayList<>(map.keySet());
+ assertEquals("a", keys.get(0));
+ assertEquals("x", keys.get(1));
+ assertEquals("y", keys.get(2));
+ assertEquals("b", keys.get(3));
+ }
+
@Test
void g02_sortedAndUnmodifiable() {
var map = MapBuilder.create(String.class, Integer.class)
@@ -330,21 +425,6 @@ class MapBuilder_Test extends TestBase {
assertMap(map, "a=3"); // Last value wins
}
- @Test
- void h05_toExistingMap() {
- var existing = new LinkedHashMap<String,Integer>();
- existing.put("x", 10);
-
- var map = MapBuilder.create(String.class, Integer.class)
- .to(existing)
- .add("y", 20)
- .build();
-
- assertSize(2, map);
- assertMap(map, "x=10", "y=20");
- assertSame(existing, map);
- }
-
//-----------------------------------------------------------------------------------------------------------------
// Filtering
//-----------------------------------------------------------------------------------------------------------------
@@ -643,6 +723,82 @@ class MapBuilder_Test extends TestBase {
});
}
+
//-----------------------------------------------------------------------------------------------------------------
+ // Concurrent
+
//-----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ void o01_concurrent_defaultHashMap() {
+ var map = MapBuilder.create(String.class, Integer.class)
+ .concurrent()
+ .add("a", 1)
+ .add("b", 2)
+ .build();
+
+ assertSize(2, map);
+ assertTrue(map instanceof
java.util.concurrent.ConcurrentHashMap);
+ }
+
+ @Test
+ void o02_concurrent_ordered() {
+ var map = MapBuilder.create(String.class, Integer.class)
+ .ordered()
+ .concurrent()
+ .add("a", 1)
+ .add("b", 2)
+ .build();
+
+ assertSize(2, map);
+ // Should be a synchronized LinkedHashMap - verify by checking
order is preserved
+ var keys = new ArrayList<>(map.keySet());
+ assertEquals("a", keys.get(0));
+ assertEquals("b", keys.get(1));
+ // Verify it's synchronized by checking it's not a
ConcurrentHashMap
+ assertFalse(map instanceof
java.util.concurrent.ConcurrentHashMap);
+ }
+
+ @Test
+ void o03_concurrent_sorted() {
+ var map = MapBuilder.create(String.class, Integer.class)
+ .sorted()
+ .concurrent()
+ .add("c", 3)
+ .add("a", 1)
+ .add("b", 2)
+ .build();
+
+ assertSize(3, map);
+ assertTrue(map instanceof
java.util.concurrent.ConcurrentSkipListMap);
+ assertList(map.keySet(), "a", "b", "c");
+ }
+
+ @Test
+ void o04_concurrent_boolean() {
+ var map1 = MapBuilder.create(String.class, Integer.class)
+ .concurrent(true)
+ .add("a", 1)
+ .build();
+ assertTrue(map1 instanceof
java.util.concurrent.ConcurrentHashMap);
+
+ var map2 = MapBuilder.create(String.class, Integer.class)
+ .concurrent(false)
+ .add("a", 1)
+ .build();
+ assertTrue(map2 instanceof HashMap);
+ }
+
+ @Test
+ void o05_concurrent_unmodifiable() {
+ var map = MapBuilder.create(String.class, Integer.class)
+ .concurrent()
+ .unmodifiable()
+ .add("a", 1)
+ .build();
+
+ assertSize(1, map);
+ assertThrows(UnsupportedOperationException.class, () ->
map.put("b", 2));
+ }
+
//-----------------------------------------------------------------------------------------------------------------
// Build edge cases
//-----------------------------------------------------------------------------------------------------------------
@@ -688,6 +844,29 @@ class MapBuilder_Test extends TestBase {
assertEmpty(map);
}
+ @Test
+ void l06_build_orderedWithNullMap() {
+ var map = MapBuilder.create(String.class, Integer.class)
+ .ordered()
+ .build();
+
+ assertNotNull(map);
+ assertTrue(map instanceof LinkedHashMap);
+ assertEmpty(map);
+ }
+
+ @Test
+ void l07_build_defaultHashMap() {
+ var map = MapBuilder.create(String.class, Integer.class)
+ .build();
+
+ assertNotNull(map);
+ // Without ordered(), should be HashMap (unordered)
+ assertTrue(map instanceof HashMap);
+ assertFalse(map instanceof LinkedHashMap);
+ assertEmpty(map);
+ }
+
@Test
void l05_build_unmodifiableWithNullMap() {
var map = MapBuilder.create(String.class, Integer.class)