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 959b73835a Marshall module improvements
959b73835a is described below
commit 959b73835a95c71668b7f1fe18cd09330ce9d27f
Author: James Bognar <[email protected]>
AuthorDate: Sat Dec 13 08:37:56 2025 -0500
Marshall module improvements
---
.../juneau/commons/collections/FilteredMap.java | 2 -
.../juneau/commons/collections/FluentList.java | 287 +++++++++++++
.../juneau/commons/collections/FluentMap.java | 235 +++++++++++
.../juneau/commons/collections/FluentSet.java | 241 +++++++++++
.../juneau/commons/collections/MapBuilder.java | 21 +
.../juneau/commons/utils/CollectionUtils.java | 11 +
.../commons/collections/FluentList_Test.java | 464 +++++++++++++++++++++
.../juneau/commons/collections/FluentMap_Test.java | 441 ++++++++++++++++++++
.../juneau/commons/collections/FluentSet_Test.java | 416 ++++++++++++++++++
9 files changed, 2116 insertions(+), 2 deletions(-)
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredMap.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredMap.java
index 2e2abbeefa..0d55c27cb0 100644
---
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredMap.java
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FilteredMap.java
@@ -555,7 +555,6 @@ public class FilteredMap<K,V> extends AbstractMap<K,V> {
}
}
- @SuppressWarnings("unchecked")
private K convertKey(Object key) {
if (keyFunction != null) {
key = keyFunction.apply(key);
@@ -571,7 +570,6 @@ public class FilteredMap<K,V> extends AbstractMap<K,V> {
throw rex("Object of type {0} could not be converted to key
type {1}", cn(key), cn(keyType));
}
- @SuppressWarnings("unchecked")
private V convertValue(Object value) {
if (valueFunction != null) {
value = valueFunction.apply(value);
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FluentList.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FluentList.java
new file mode 100644
index 0000000000..a286922e82
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FluentList.java
@@ -0,0 +1,287 @@
+/*
+ * 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 java.util.*;
+
+/**
+ * A fluent wrapper around an arbitrary list that provides convenient methods
for adding elements.
+ *
+ * <p>
+ * This class wraps an underlying list and provides a fluent API for adding
elements. All methods return
+ * <c>this</c> to allow method chaining. The underlying list can be any {@link
List} implementation.
+ *
+ * <h5 class='section'>Features:</h5>
+ * <ul class='spaced-list'>
+ * <li><b>Fluent API:</b> All methods return <c>this</c> for method
chaining
+ * <li><b>Arbitrary List Support:</b> Works with any list implementation
+ * <li><b>Conditional Adding:</b> Add elements conditionally based on
boolean expressions
+ * <li><b>Transparent Interface:</b> Implements the full {@link List}
interface, so it can be used anywhere a list is expected
+ * </ul>
+ *
+ * <h5 class='section'>Usage:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a FluentList wrapping an ArrayList</jc>
+ * FluentList<String> <jv>list</jv> = <jk>new</jk>
FluentList<>(<jk>new</jk> ArrayList<>());
+ *
+ * <jv>list</jv>
+ * .a(<js>"item1"</js>)
+ * .a(<js>"item2"</js>)
+ * .ai(<jk>true</jk>, <js>"item3"</js>) <jc>// Added</jc>
+ * .ai(<jk>false</jk>, <js>"item4"</js>); <jc>// Not added</jc>
+ *
+ * <jc>// Add all elements from another collection</jc>
+ * List<String> <jv>other</jv> = List.of(<js>"item5"</js>,
<js>"item6"</js>);
+ * <jv>list</jv>.aa(<jv>other</jv>);
+ * </p>
+ *
+ * <h5 class='section'>Example - Conditional Building:</h5>
+ * <p class='bjava'>
+ * <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>;
+ * <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>;
+ *
+ * FluentList<String> <jv>config</jv> = <jk>new</jk>
FluentList<>(<jk>new</jk> ArrayList<>())
+ * .a(<js>"setting1"</js>)
+ * .a(<js>"setting2"</js>)
+ * .ai(<jv>includeDebug</jv>, <js>"debug"</js>) <jc>// Added</jc>
+ * .ai(<jv>includeTest</jv>, <js>"test"</js>); <jc>// Not
added</jc>
+ * </p>
+ *
+ * <h5 class='section'>Behavior Notes:</h5>
+ * <ul class='spaced-list'>
+ * <li>All list operations are delegated to the underlying list
+ * <li>The fluent methods ({@link #a(Object)}, {@link #aa(Collection)},
{@link #ai(boolean, Object)}) return <c>this</c> for chaining
+ * <li>If a <jk>null</jk> collection is passed to {@link #a(Collection)},
it is treated as a no-op
+ * <li>The underlying list is stored by reference (not copied), so
modifications affect the original list
+ * </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}).
+ *
+ * <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 FluentList<E> extends AbstractList<E> {
+
+ private final List<E> list;
+
+ /**
+ * Constructor.
+ *
+ * @param inner The underlying list to wrap. Must not be <jk>null</jk>.
+ */
+ public FluentList(List<E> inner) {
+ this.list = assertArgNotNull("inner", inner);
+ }
+
+ /**
+ * Adds a single element to this list.
+ *
+ * <p>
+ * This is a convenience method that calls {@link #add(Object)} and
returns <c>this</c>
+ * for method chaining.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FluentList<String> <jv>list</jv> = <jk>new</jk>
FluentList<>(<jk>new</jk> ArrayList<>());
+ * <jv>list</jv>.a(<js>"item1"</js>).a(<js>"item2"</js>);
+ * </p>
+ *
+ * @param element The element to add.
+ * @return This object for method chaining.
+ */
+ public FluentList<E> a(E element) {
+ list.add(element);
+ return this;
+ }
+
+ /**
+ * Adds all elements from the specified collection to this list.
+ *
+ * <p>
+ * This is a convenience method that calls {@link #addAll(Collection)}
and returns <c>this</c>
+ * for method chaining. If the specified collection is <jk>null</jk>,
this is a no-op.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FluentList<String> <jv>list</jv> = <jk>new</jk>
FluentList<>(<jk>new</jk> ArrayList<>());
+ * List<String> <jv>other</jv> = List.of(<js>"item1"</js>,
<js>"item2"</js>);
+ * <jv>list</jv>.aa(<jv>other</jv>).a(<js>"item3"</js>);
+ * </p>
+ *
+ * @param c The collection whose elements are to be added. Can be
<jk>null</jk> (no-op).
+ * @return This object for method chaining.
+ */
+ public FluentList<E> aa(Collection<? extends E> c) {
+ if (c != null)
+ list.addAll(c);
+ return this;
+ }
+
+ /**
+ * Adds an element to this list if the specified boolean condition is
<jk>true</jk>.
+ *
+ * <p>
+ * This method is useful for conditionally adding elements based on
runtime conditions.
+ * If the condition is <jk>false</jk>, the element is not added and
this method returns <c>this</c>
+ * without modifying the list.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>;
+ * <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>;
+ *
+ * FluentList<String> <jv>list</jv> = <jk>new</jk>
FluentList<>(<jk>new</jk> ArrayList<>())
+ * .a(<js>"basic"</js>)
+ * .ai(<jv>includeDebug</jv>, <js>"debug"</js>) <jc>//
Added</jc>
+ * .ai(<jv>includeTest</jv>, <js>"test"</js>); <jc>//
Not added</jc>
+ * </p>
+ *
+ * @param condition The condition to evaluate. If <jk>true</jk>, the
element is added; if <jk>false</jk>, it is not.
+ * @param element The element to add if the condition is <jk>true</jk>.
+ * @return This object for method chaining.
+ */
+ public FluentList<E> ai(boolean condition, E element) {
+ if (condition)
+ list.add(element);
+ return this;
+ }
+
+ @Override
+ public E get(int index) {
+ return list.get(index);
+ }
+
+ @Override
+ public int size() {
+ return list.size();
+ }
+
+ @Override
+ public boolean add(E e) {
+ return list.add(e);
+ }
+
+ @Override
+ public void add(int index, E element) {
+ list.add(index, element);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends E> c) {
+ return list.addAll(c);
+ }
+
+ @Override
+ public boolean addAll(int index, Collection<? extends E> c) {
+ return list.addAll(index, c);
+ }
+
+ @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 E set(int index, E element) {
+ return list.set(index, element);
+ }
+
+ @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 boolean contains(Object o) {
+ return list.contains(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return list.containsAll(c);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return list.isEmpty();
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return list.iterator();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return list.toArray();
+ }
+
+ @Override
+ public <T> T[] toArray(T[] a) {
+ return list.toArray(a);
+ }
+}
+
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FluentMap.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FluentMap.java
new file mode 100644
index 0000000000..4f69a20f28
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FluentMap.java
@@ -0,0 +1,235 @@
+/*
+ * 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 java.util.*;
+
+/**
+ * A fluent wrapper around an arbitrary map that provides convenient methods
for adding entries.
+ *
+ * <p>
+ * This class wraps an underlying map and provides a fluent API for adding
entries. All methods return
+ * <c>this</c> to allow method chaining. The underlying map can be any {@link
Map} implementation.
+ *
+ * <h5 class='section'>Features:</h5>
+ * <ul class='spaced-list'>
+ * <li><b>Fluent API:</b> All methods return <c>this</c> for method
chaining
+ * <li><b>Arbitrary Map Support:</b> Works with any map implementation
+ * <li><b>Conditional Adding:</b> Add entries conditionally based on
boolean expressions
+ * <li><b>Transparent Interface:</b> Implements the full {@link Map}
interface, so it can be used anywhere a map is expected
+ * </ul>
+ *
+ * <h5 class='section'>Usage:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a FluentMap wrapping a LinkedHashMap</jc>
+ * FluentMap<String, String> <jv>map</jv> = <jk>new</jk>
FluentMap<>(<jk>new</jk> LinkedHashMap<>());
+ *
+ * <jv>map</jv>
+ * .a(<js>"key1"</js>, <js>"value1"</js>)
+ * .a(<js>"key2"</js>, <js>"value2"</js>)
+ * .ai(<jk>true</jk>, <js>"key3"</js>, <js>"value3"</js>) <jc>//
Added</jc>
+ * .ai(<jk>false</jk>, <js>"key4"</js>, <js>"value4"</js>); <jc>//
Not added</jc>
+ *
+ * <jc>// Add all entries from another map</jc>
+ * Map<String, String> <jv>other</jv> = Map.of(<js>"key5"</js>,
<js>"value5"</js>, <js>"key6"</js>, <js>"value6"</js>);
+ * <jv>map</jv>.aa(<jv>other</jv>);
+ * </p>
+ *
+ * <h5 class='section'>Example - Conditional Building:</h5>
+ * <p class='bjava'>
+ * <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>;
+ * <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>;
+ *
+ * FluentMap<String, String> <jv>config</jv> = <jk>new</jk>
FluentMap<>(<jk>new</jk> LinkedHashMap<>())
+ * .a(<js>"host"</js>, <js>"localhost"</js>)
+ * .a(<js>"port"</js>, <js>"8080"</js>)
+ * .ai(<jv>includeDebug</jv>, <js>"debug"</js>, <js>"true"</js>)
<jc>// Added</jc>
+ * .ai(<jv>includeTest</jv>, <js>"test"</js>, <js>"true"</js>);
<jc>// Not added</jc>
+ * </p>
+ *
+ * <h5 class='section'>Behavior Notes:</h5>
+ * <ul class='spaced-list'>
+ * <li>All map operations are delegated to the underlying map
+ * <li>The fluent methods ({@link #a(Object, Object)}, {@link #aa(Map)},
{@link #ai(boolean, Object, Object)}) return <c>this</c> for chaining
+ * <li>If a <jk>null</jk> map is passed to {@link #a(Map)}, it is treated
as a no-op
+ * <li>The underlying map is stored by reference (not copied), so
modifications affect the original map
+ * </ul>
+ *
+ * <h5 class='section'>Thread Safety:</h5>
+ * <p>
+ * This class is not thread-safe unless the underlying map is thread-safe. If
thread safety is required,
+ * use a thread-safe map type (e.g., {@link
java.util.concurrent.ConcurrentHashMap}).
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ * <li class='link'><a class="doclink"
href="../../../../../index.html#juneau-commons">Overview > juneau-commons</a>
+ * </ul>
+ *
+ * @param <K> The key type.
+ * @param <V> The value type.
+ */
+public class FluentMap<K,V> extends AbstractMap<K,V> {
+
+ private final Map<K,V> map;
+
+ /**
+ * Constructor.
+ *
+ * @param inner The underlying map to wrap. Must not be <jk>null</jk>.
+ */
+ public FluentMap(Map<K,V> inner) {
+ this.map = assertArgNotNull("inner", inner);
+ }
+
+ /**
+ * Adds a single key-value pair to this map.
+ *
+ * <p>
+ * This is a convenience method that calls {@link #put(Object, Object)}
and returns <c>this</c>
+ * for method chaining.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FluentMap<String, String> <jv>map</jv> = <jk>new</jk>
FluentMap<>(<jk>new</jk> LinkedHashMap<>());
+ * <jv>map</jv>.a(<js>"key1"</js>,
<js>"value1"</js>).a(<js>"key2"</js>, <js>"value2"</js>);
+ * </p>
+ *
+ * @param key The key to add.
+ * @param value The value to add.
+ * @return This object for method chaining.
+ */
+ public FluentMap<K,V> a(K key, V value) {
+ map.put(key, value);
+ return this;
+ }
+
+ /**
+ * Adds all entries from the specified map to this map.
+ *
+ * <p>
+ * This is a convenience method that calls {@link #putAll(Map)} and
returns <c>this</c>
+ * for method chaining. If the specified map is <jk>null</jk>, this is
a no-op.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FluentMap<String, String> <jv>map</jv> = <jk>new</jk>
FluentMap<>(<jk>new</jk> LinkedHashMap<>());
+ * Map<String, String> <jv>other</jv> =
Map.of(<js>"key1"</js>, <js>"value1"</js>, <js>"key2"</js>, <js>"value2"</js>);
+ * <jv>map</jv>.aa(<jv>other</jv>).a(<js>"key3"</js>,
<js>"value3"</js>);
+ * </p>
+ *
+ * @param m The map whose entries are to be added. Can be <jk>null</jk>
(no-op).
+ * @return This object for method chaining.
+ */
+ public FluentMap<K,V> aa(Map<? extends K, ? extends V> m) {
+ if (m != null)
+ map.putAll(m);
+ return this;
+ }
+
+ /**
+ * Adds a key-value pair to this map if the specified boolean condition
is <jk>true</jk>.
+ *
+ * <p>
+ * This method is useful for conditionally adding entries based on
runtime conditions.
+ * If the condition is <jk>false</jk>, the entry is not added and this
method returns <c>this</c>
+ * without modifying the map.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>;
+ * <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>;
+ *
+ * FluentMap<String, String> <jv>map</jv> = <jk>new</jk>
FluentMap<>(<jk>new</jk> LinkedHashMap<>())
+ * .a(<js>"host"</js>, <js>"localhost"</js>)
+ * .ai(<jv>includeDebug</jv>, <js>"debug"</js>,
<js>"true"</js>) <jc>// Added</jc>
+ * .ai(<jv>includeTest</jv>, <js>"test"</js>,
<js>"true"</js>); <jc>// Not added</jc>
+ * </p>
+ *
+ * @param condition The condition to evaluate. If <jk>true</jk>, the
entry is added; if <jk>false</jk>, it is not.
+ * @param key The key to add if the condition is <jk>true</jk>.
+ * @param value The value to add if the condition is <jk>true</jk>.
+ * @return This object for method chaining.
+ */
+ public FluentMap<K,V> ai(boolean condition, K key, V value) {
+ if (condition)
+ map.put(key, value);
+ return this;
+ }
+
+ @Override
+ public Set<Entry<K,V>> entrySet() {
+ return map.entrySet();
+ }
+
+ @Override
+ public V get(Object key) {
+ return map.get(key);
+ }
+
+ @Override
+ public V put(K key, V value) {
+ return map.put(key, value);
+ }
+
+ @Override
+ public V remove(Object key) {
+ return map.remove(key);
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+ map.putAll(m);
+ }
+
+ @Override
+ public void clear() {
+ map.clear();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return map.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return map.containsValue(value);
+ }
+
+ @Override
+ public int size() {
+ return map.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return map.keySet();
+ }
+
+ @Override
+ public Collection<V> values() {
+ return map.values();
+ }
+}
+
diff --git
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FluentSet.java
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FluentSet.java
new file mode 100644
index 0000000000..5fc6a6de74
--- /dev/null
+++
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/FluentSet.java
@@ -0,0 +1,241 @@
+/*
+ * 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 java.util.*;
+
+/**
+ * A fluent wrapper around an arbitrary set that provides convenient methods
for adding elements.
+ *
+ * <p>
+ * This class wraps an underlying set and provides a fluent API for adding
elements. All methods return
+ * <c>this</c> to allow method chaining. The underlying set can be any {@link
Set} implementation.
+ *
+ * <h5 class='section'>Features:</h5>
+ * <ul class='spaced-list'>
+ * <li><b>Fluent API:</b> All methods return <c>this</c> for method
chaining
+ * <li><b>Arbitrary Set Support:</b> Works with any set implementation
+ * <li><b>Conditional Adding:</b> Add elements conditionally based on
boolean expressions
+ * <li><b>Transparent Interface:</b> Implements the full {@link Set}
interface, so it can be used anywhere a set is expected
+ * <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 FluentSet wrapping a LinkedHashSet</jc>
+ * FluentSet<String> <jv>set</jv> = <jk>new</jk>
FluentSet<>(<jk>new</jk> LinkedHashSet<>());
+ *
+ * <jv>set</jv>
+ * .a(<js>"item1"</js>)
+ * .a(<js>"item2"</js>)
+ * .ai(<jk>true</jk>, <js>"item3"</js>) <jc>// Added</jc>
+ * .ai(<jk>false</jk>, <js>"item4"</js>); <jc>// Not added</jc>
+ *
+ * <jc>// Add all elements from another collection</jc>
+ * Set<String> <jv>other</jv> = Set.of(<js>"item5"</js>,
<js>"item6"</js>);
+ * <jv>set</jv>.aa(<jv>other</jv>);
+ * </p>
+ *
+ * <h5 class='section'>Example - Conditional Building:</h5>
+ * <p class='bjava'>
+ * <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>;
+ * <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>;
+ *
+ * FluentSet<String> <jv>set</jv> = <jk>new</jk>
FluentSet<>(<jk>new</jk> LinkedHashSet<>())
+ * .a(<js>"setting1"</js>)
+ * .a(<js>"setting2"</js>)
+ * .ai(<jv>includeDebug</jv>, <js>"debug"</js>) <jc>// Added</jc>
+ * .ai(<jv>includeTest</jv>, <js>"test"</js>); <jc>// Not
added</jc>
+ * </p>
+ *
+ * <h5 class='section'>Behavior Notes:</h5>
+ * <ul class='spaced-list'>
+ * <li>All set operations are delegated to the underlying set
+ * <li>The fluent methods ({@link #a(Object)}, {@link #aa(Collection)},
{@link #ai(boolean, Object)}) return <c>this</c> for chaining
+ * <li>If a <jk>null</jk> collection is passed to {@link #a(Collection)},
it is treated as a no-op
+ * <li>The underlying set is stored by reference (not copied), so
modifications affect the original set
+ * <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}).
+ *
+ * <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 FluentSet<E> extends AbstractSet<E> {
+
+ private final Set<E> set;
+
+ /**
+ * Constructor.
+ *
+ * @param inner The underlying set to wrap. Must not be <jk>null</jk>.
+ */
+ public FluentSet(Set<E> inner) {
+ this.set = assertArgNotNull("inner", inner);
+ }
+
+ /**
+ * Adds a single element to this set.
+ *
+ * <p>
+ * This is a convenience method that calls {@link #add(Object)} and
returns <c>this</c>
+ * for method chaining. If the element already exists in the set, it is
not added again
+ * (sets do not allow duplicates).
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FluentSet<String> <jv>set</jv> = <jk>new</jk>
FluentSet<>(<jk>new</jk> LinkedHashSet<>());
+ * <jv>set</jv>.a(<js>"item1"</js>).a(<js>"item2"</js>);
+ * </p>
+ *
+ * @param element The element to add.
+ * @return This object for method chaining.
+ */
+ public FluentSet<E> a(E element) {
+ set.add(element);
+ return this;
+ }
+
+ /**
+ * Adds all elements from the specified collection to this set.
+ *
+ * <p>
+ * This is a convenience method that calls {@link #addAll(Collection)}
and returns <c>this</c>
+ * for method chaining. If the specified collection is <jk>null</jk>,
this is a no-op.
+ * Duplicate elements in the collection are automatically handled (only
one occurrence is stored).
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * FluentSet<String> <jv>set</jv> = <jk>new</jk>
FluentSet<>(<jk>new</jk> LinkedHashSet<>());
+ * List<String> <jv>other</jv> = List.of(<js>"item1"</js>,
<js>"item2"</js>);
+ * <jv>set</jv>.aa(<jv>other</jv>).a(<js>"item3"</js>);
+ * </p>
+ *
+ * @param c The collection whose elements are to be added. Can be
<jk>null</jk> (no-op).
+ * @return This object for method chaining.
+ */
+ public FluentSet<E> aa(Collection<? extends E> c) {
+ if (c != null)
+ set.addAll(c);
+ return this;
+ }
+
+ /**
+ * Adds an element to this set if the specified boolean condition is
<jk>true</jk>.
+ *
+ * <p>
+ * This method is useful for conditionally adding elements based on
runtime conditions.
+ * If the condition is <jk>false</jk>, the element is not added and
this method returns <c>this</c>
+ * without modifying the set.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>;
+ * <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>;
+ *
+ * FluentSet<String> <jv>set</jv> = <jk>new</jk>
FluentSet<>(<jk>new</jk> LinkedHashSet<>())
+ * .a(<js>"basic"</js>)
+ * .ai(<jv>includeDebug</jv>, <js>"debug"</js>) <jc>//
Added</jc>
+ * .ai(<jv>includeTest</jv>, <js>"test"</js>); <jc>//
Not added</jc>
+ * </p>
+ *
+ * @param condition The condition to evaluate. If <jk>true</jk>, the
element is added; if <jk>false</jk>, it is not.
+ * @param element The element to add if the condition is <jk>true</jk>.
+ * @return This object for method chaining.
+ */
+ public FluentSet<E> ai(boolean condition, E element) {
+ if (condition)
+ set.add(element);
+ return this;
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return set.iterator();
+ }
+
+ @Override
+ public int size() {
+ return set.size();
+ }
+
+ @Override
+ public boolean add(E e) {
+ return set.add(e);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends E> c) {
+ return set.addAll(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 boolean contains(Object o) {
+ return set.contains(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return set.containsAll(c);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return set.isEmpty();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return set.toArray();
+ }
+
+ @Override
+ public <T> T[] toArray(T[] a) {
+ return set.toArray(a);
+ }
+}
+
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 dc098d4f95..0a63eca7b2 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
@@ -124,6 +124,27 @@ public class MapBuilder<K,V> {
return new MapBuilder<>(assertArgNotNull("keyType", keyType),
assertArgNotNull("valueType", valueType));
}
+ /**
+ * Static creator without explicit type parameters.
+ *
+ * <p>
+ * This is a convenience method that creates a builder without
requiring explicit type parameters.
+ * The types will be inferred from usage context. Internally uses
<c>Object.class</c> for both
+ * key and value types, which allows any types to be added.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * Map<String, Integer> <jv>map</jv> =
MapBuilder.<jsm>create</jsm>()
+ * .add(<js>"one"</js>, 1)
+ * .add(<js>"two"</js>, 2)
+ * .build();
+ * </p>
+ *
+ * @param <K> Key type.
+ * @param <V> Value type.
+ * @return A new builder.
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
public static <K,V> MapBuilder<K,V> create() {
return new MapBuilder(Object.class, Object.class);
}
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 3e30c4c13c..7103207f32 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
@@ -1584,6 +1584,17 @@ public class CollectionUtils {
return Collections.emptyMap();
}
+ /**
+ * Shortcut for creating an empty map with generic types.
+ *
+ * <p>
+ * This is a convenience method that creates an empty unmodifiable map
without requiring
+ * explicit type parameters. The types will be inferred from usage
context.
+ *
+ * @param <K> The key type.
+ * @param <V> The value type.
+ * @return An empty unmodifiable map.
+ */
public static <K,V> Map<K,V> mape() {
return Collections.emptyMap();
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FluentList_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FluentList_Test.java
new file mode 100644
index 0000000000..5e89af5f08
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FluentList_Test.java
@@ -0,0 +1,464 @@
+/*
+ * 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.junit.bct.BctAssertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+class FluentList_Test extends TestBase {
+
+
//====================================================================================================
+ // Basic functionality - a(E element)
+
//====================================================================================================
+
+ @Test
+ void a01_addSingleElement() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ assertSize(3, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item2", list.get(1));
+ assertEquals("item3", list.get(2));
+ }
+
+ @Test
+ void a02_addSingleElement_returnsThis() {
+ var list = new FluentList<>(new ArrayList<String>());
+ var result = list.a("item1");
+
+ assertSame(list, result);
+ }
+
+ @Test
+ void a03_addSingleElement_nullValue() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a(null).a("item3");
+
+ assertSize(3, list);
+ assertEquals("item1", list.get(0));
+ assertNull(list.get(1));
+ assertEquals("item3", list.get(2));
+ }
+
+
//====================================================================================================
+ // a(Collection) method
+
//====================================================================================================
+
+ @Test
+ void b01_addCollection() {
+ var list = new FluentList<>(new ArrayList<String>());
+ var other = List.of("item1", "item2", "item3");
+ list.aa(other);
+
+ assertSize(3, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item2", list.get(1));
+ assertEquals("item3", list.get(2));
+ }
+
+ @Test
+ void b02_addCollection_returnsThis() {
+ var list = new FluentList<>(new ArrayList<String>());
+ var other = List.of("item1", "item2");
+ var result = list.aa(other);
+
+ assertSame(list, result);
+ }
+
+ @Test
+ void b03_addCollection_nullCollection() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1");
+ list.aa((Collection<String>)null);
+ list.a("item2");
+
+ assertSize(2, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item2", list.get(1));
+ }
+
+ @Test
+ void b04_addCollection_emptyCollection() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1");
+ list.aa(List.of());
+ list.a("item2");
+
+ assertSize(2, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item2", list.get(1));
+ }
+
+ @Test
+ void b05_addCollection_multipleCalls() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.aa(List.of("item1", "item2"));
+ list.aa(List.of("item3", "item4"));
+
+ assertSize(4, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item2", list.get(1));
+ assertEquals("item3", list.get(2));
+ assertEquals("item4", list.get(3));
+ }
+
+
//====================================================================================================
+ // ai(boolean, E) method
+
//====================================================================================================
+
+ @Test
+ void c01_ai_conditionTrue() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").ai(true, "item2").a("item3");
+
+ assertSize(3, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item2", list.get(1));
+ assertEquals("item3", list.get(2));
+ }
+
+ @Test
+ void c02_ai_conditionFalse() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").ai(false, "item2").a("item3");
+
+ assertSize(2, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item3", list.get(1));
+ }
+
+ @Test
+ void c03_ai_returnsThis() {
+ var list = new FluentList<>(new ArrayList<String>());
+ var result1 = list.ai(true, "item1");
+ var result2 = list.ai(false, "item2");
+
+ assertSame(list, result1);
+ assertSame(list, result2);
+ }
+
+ @Test
+ void c04_ai_conditionalBuilding() {
+ boolean includeDebug = true;
+ boolean includeTest = false;
+
+ var list = new FluentList<>(new ArrayList<String>())
+ .a("basic")
+ .ai(includeDebug, "debug")
+ .ai(includeTest, "test");
+
+ assertSize(2, list);
+ assertTrue(list.contains("basic"));
+ assertTrue(list.contains("debug"));
+ assertFalse(list.contains("test"));
+ }
+
+
//====================================================================================================
+ // Method chaining
+
//====================================================================================================
+
+ @Test
+ void d01_methodChaining() {
+ var list = new FluentList<>(new ArrayList<String>())
+ .a("item1")
+ .a("item2")
+ .ai(true, "item3")
+ .ai(false, "item4")
+ .aa(List.of("item5", "item6"));
+
+ assertSize(5, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item2", list.get(1));
+ assertEquals("item3", list.get(2));
+ assertEquals("item5", list.get(3));
+ assertEquals("item6", list.get(4));
+ }
+
+
//====================================================================================================
+ // List interface methods
+
//====================================================================================================
+
+ @Test
+ void e01_listInterface_get() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ assertEquals("item1", list.get(0));
+ assertEquals("item2", list.get(1));
+ assertEquals("item3", list.get(2));
+ }
+
+ @Test
+ void e02_listInterface_size() {
+ var list = new FluentList<>(new ArrayList<String>());
+ assertEquals(0, list.size());
+
+ list.a("item1");
+ assertEquals(1, list.size());
+
+ list.a("item2");
+ assertEquals(2, list.size());
+ }
+
+ @Test
+ void e03_listInterface_add() {
+ var list = new FluentList<>(new ArrayList<String>());
+ assertTrue(list.add("item1"));
+ assertTrue(list.add("item2"));
+
+ assertSize(2, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item2", list.get(1));
+ }
+
+ @Test
+ void e04_listInterface_addAtIndex() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item3");
+ list.add(1, "item2");
+
+ assertSize(3, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item2", list.get(1));
+ assertEquals("item3", list.get(2));
+ }
+
+ @Test
+ void e05_listInterface_addAll() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1");
+ assertTrue(list.addAll(List.of("item2", "item3")));
+
+ assertSize(3, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item2", list.get(1));
+ assertEquals("item3", list.get(2));
+ }
+
+ @Test
+ void e06_listInterface_remove() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ assertTrue(list.remove("item2"));
+ assertSize(2, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item3", list.get(1));
+ }
+
+ @Test
+ void e07_listInterface_removeAtIndex() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ assertEquals("item2", list.remove(1));
+ assertSize(2, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item3", list.get(1));
+ }
+
+ @Test
+ void e08_listInterface_set() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ assertEquals("item2", list.set(1, "item2-updated"));
+ assertEquals("item2-updated", list.get(1));
+ }
+
+ @Test
+ void e09_listInterface_indexOf() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item1");
+
+ assertEquals(0, list.indexOf("item1"));
+ assertEquals(1, list.indexOf("item2"));
+ assertEquals(-1, list.indexOf("item3"));
+ }
+
+ @Test
+ void e10_listInterface_lastIndexOf() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item1");
+
+ assertEquals(2, list.lastIndexOf("item1"));
+ assertEquals(1, list.lastIndexOf("item2"));
+ assertEquals(-1, list.lastIndexOf("item3"));
+ }
+
+ @Test
+ void e11_listInterface_contains() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2");
+
+ assertTrue(list.contains("item1"));
+ assertTrue(list.contains("item2"));
+ assertFalse(list.contains("item3"));
+ }
+
+ @Test
+ void e12_listInterface_containsAll() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ assertTrue(list.containsAll(List.of("item1", "item2")));
+ assertTrue(list.containsAll(List.of("item1", "item2",
"item3")));
+ assertFalse(list.containsAll(List.of("item1", "item4")));
+ }
+
+ @Test
+ void e13_listInterface_isEmpty() {
+ var list = new FluentList<>(new ArrayList<String>());
+ assertTrue(list.isEmpty());
+
+ list.a("item1");
+ assertFalse(list.isEmpty());
+ }
+
+ @Test
+ void e14_listInterface_clear() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ list.clear();
+ assertTrue(list.isEmpty());
+ assertSize(0, list);
+ }
+
+ @Test
+ void e15_listInterface_iterator() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ var found = new ArrayList<String>();
+ for (var item : list) {
+ found.add(item);
+ }
+
+ assertEquals(List.of("item1", "item2", "item3"), found);
+ }
+
+ @Test
+ void e16_listInterface_listIterator() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ var it = list.listIterator();
+ assertTrue(it.hasNext());
+ assertEquals("item1", it.next());
+ assertEquals("item2", it.next());
+ assertTrue(it.hasPrevious());
+ assertEquals("item2", it.previous());
+ }
+
+ @Test
+ void e17_listInterface_subList() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3").a("item4");
+
+ var subList = list.subList(1, 3);
+ assertSize(2, subList);
+ assertEquals("item2", subList.get(0));
+ assertEquals("item3", subList.get(1));
+ }
+
+ @Test
+ void e18_listInterface_toArray() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ var array = list.toArray();
+ assertEquals(3, array.length);
+ assertEquals("item1", array[0]);
+ assertEquals("item2", array[1]);
+ assertEquals("item3", array[2]);
+ }
+
+ @Test
+ void e19_listInterface_toArrayTyped() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ var array = list.toArray(new String[0]);
+ assertEquals(3, array.length);
+ assertEquals("item1", array[0]);
+ assertEquals("item2", array[1]);
+ assertEquals("item3", array[2]);
+ }
+
+
//====================================================================================================
+ // Different list implementations
+
//====================================================================================================
+
+ @Test
+ void f01_linkedList() {
+ var list = new FluentList<>(new LinkedList<String>());
+ list.a("item1").a("item2").a("item3");
+
+ assertSize(3, list);
+ }
+
+ @Test
+ void f02_vector() {
+ var list = new FluentList<>(new Vector<String>());
+ list.a("item1").a("item2").a("item3");
+
+ assertSize(3, list);
+ }
+
+
//====================================================================================================
+ // Edge cases
+
//====================================================================================================
+
+ @Test
+ void g01_emptyList() {
+ var list = new FluentList<>(new ArrayList<String>());
+
+ assertTrue(list.isEmpty());
+ assertSize(0, list);
+ assertFalse(list.contains("anything"));
+ }
+
+ @Test
+ void g02_removeAll() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3").a("item4");
+
+ assertTrue(list.removeAll(List.of("item2", "item4")));
+ assertSize(2, list);
+ assertEquals("item1", list.get(0));
+ assertEquals("item3", list.get(1));
+ }
+
+ @Test
+ void g03_retainAll() {
+ var list = new FluentList<>(new ArrayList<String>());
+ list.a("item1").a("item2").a("item3").a("item4");
+
+ assertTrue(list.retainAll(List.of("item2", "item4")));
+ assertSize(2, list);
+ assertEquals("item2", list.get(0));
+ assertEquals("item4", list.get(1));
+ }
+}
+
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FluentMap_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FluentMap_Test.java
new file mode 100644
index 0000000000..83c15e7d2f
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FluentMap_Test.java
@@ -0,0 +1,441 @@
+/*
+ * 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.CollectionUtils.*;
+import static org.apache.juneau.junit.bct.BctAssertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+class FluentMap_Test extends TestBase {
+
+
//====================================================================================================
+ // Basic functionality - a(K key, V value)
+
//====================================================================================================
+
+ @Test
+ void a01_addSingleEntry() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").a("key2", "value2").a("key3", "value3");
+
+ assertSize(3, map);
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ assertEquals("value3", map.get("key3"));
+ }
+
+ @Test
+ void a02_addSingleEntry_returnsThis() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ var result = map.a("key1", "value1");
+
+ assertSame(map, result);
+ }
+
+ @Test
+ void a03_addSingleEntry_nullValue() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").a("key2", null).a("key3", "value3");
+
+ assertSize(3, map);
+ assertEquals("value1", map.get("key1"));
+ assertNull(map.get("key2"));
+ assertEquals("value3", map.get("key3"));
+ }
+
+ @Test
+ void a04_addSingleEntry_nullKey() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a(null, "value1").a("key2", "value2");
+
+ assertSize(2, map);
+ assertEquals("value1", map.get(null));
+ assertEquals("value2", map.get("key2"));
+ }
+
+ @Test
+ void a05_addSingleEntry_updateExisting() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1");
+ map.a("key1", "value1-updated");
+
+ assertSize(1, map);
+ assertEquals("value1-updated", map.get("key1"));
+ }
+
+
//====================================================================================================
+ // a(Map) method
+
//====================================================================================================
+
+ @Test
+ void b01_addMap() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ var other = map("key1", "value1", "key2", "value2", "key3",
"value3");
+ map.aa(other);
+
+ assertSize(3, map);
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ assertEquals("value3", map.get("key3"));
+ }
+
+ @Test
+ void b02_addMap_returnsThis() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ var other = map("key1", "value1", "key2", "value2");
+ var result = map.aa(other);
+
+ assertSame(map, result);
+ }
+
+ @Test
+ void b03_addMap_nullMap() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1");
+ map.aa((Map<String, String>)null);
+ map.a("key2", "value2");
+
+ assertSize(2, map);
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ }
+
+ @Test
+ void b04_addMap_emptyMap() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1");
+ map.aa(map());
+ map.a("key2", "value2");
+
+ assertSize(2, map);
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ }
+
+ @Test
+ void b05_addMap_multipleCalls() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.aa(map("key1", "value1", "key2", "value2"));
+ map.aa(map("key3", "value3", "key4", "value4"));
+
+ assertSize(4, map);
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ assertEquals("value3", map.get("key3"));
+ assertEquals("value4", map.get("key4"));
+ }
+
+ @Test
+ void b06_addMap_overwritesExisting() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1");
+ map.aa(map("key1", "value1-updated", "key2", "value2"));
+
+ assertSize(2, map);
+ assertEquals("value1-updated", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ }
+
+
//====================================================================================================
+ // ai(boolean, K, V) method
+
//====================================================================================================
+
+ @Test
+ void c01_ai_conditionTrue() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").ai(true, "key2", "value2").a("key3",
"value3");
+
+ assertSize(3, map);
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ assertEquals("value3", map.get("key3"));
+ }
+
+ @Test
+ void c02_ai_conditionFalse() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").ai(false, "key2", "value2").a("key3",
"value3");
+
+ assertSize(2, map);
+ assertEquals("value1", map.get("key1"));
+ assertFalse(map.containsKey("key2"));
+ assertEquals("value3", map.get("key3"));
+ }
+
+ @Test
+ void c03_ai_returnsThis() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ var result1 = map.ai(true, "key1", "value1");
+ var result2 = map.ai(false, "key2", "value2");
+
+ assertSame(map, result1);
+ assertSame(map, result2);
+ }
+
+ @Test
+ void c04_ai_conditionalBuilding() {
+ boolean includeDebug = true;
+ boolean includeTest = false;
+
+ var map = new FluentMap<>(new LinkedHashMap<String, String>())
+ .a("host", "localhost")
+ .a("port", "8080")
+ .ai(includeDebug, "debug", "true")
+ .ai(includeTest, "test", "true");
+
+ assertSize(3, map);
+ assertEquals("localhost", map.get("host"));
+ assertEquals("8080", map.get("port"));
+ assertEquals("true", map.get("debug"));
+ assertFalse(map.containsKey("test"));
+ }
+
+
//====================================================================================================
+ // Method chaining
+
//====================================================================================================
+
+ @Test
+ void d01_methodChaining() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>())
+ .a("key1", "value1")
+ .a("key2", "value2")
+ .ai(true, "key3", "value3")
+ .ai(false, "key4", "value4")
+ .aa(map("key5", "value5", "key6", "value6"));
+
+ assertSize(5, map);
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ assertEquals("value3", map.get("key3"));
+ assertEquals("value5", map.get("key5"));
+ assertEquals("value6", map.get("key6"));
+ assertFalse(map.containsKey("key4"));
+ }
+
+
//====================================================================================================
+ // Map interface methods
+
//====================================================================================================
+
+ @Test
+ void e01_mapInterface_get() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").a("key2", "value2");
+
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ assertNull(map.get("key3"));
+ }
+
+ @Test
+ void e02_mapInterface_put() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ assertNull(map.put("key1", "value1"));
+ assertEquals("value1", map.put("key1", "value1-updated"));
+
+ assertSize(1, map);
+ assertEquals("value1-updated", map.get("key1"));
+ }
+
+ @Test
+ void e03_mapInterface_putAll() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1");
+ map.putAll(map("key2", "value2", "key3", "value3"));
+
+ assertSize(3, map);
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ assertEquals("value3", map.get("key3"));
+ }
+
+ @Test
+ void e04_mapInterface_remove() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").a("key2", "value2").a("key3", "value3");
+
+ assertEquals("value2", map.remove("key2"));
+ assertSize(2, map);
+ assertEquals("value1", map.get("key1"));
+ assertFalse(map.containsKey("key2"));
+ assertEquals("value3", map.get("key3"));
+ }
+
+ @Test
+ void e05_mapInterface_containsKey() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").a("key2", "value2");
+
+ assertTrue(map.containsKey("key1"));
+ assertTrue(map.containsKey("key2"));
+ assertFalse(map.containsKey("key3"));
+ }
+
+ @Test
+ void e06_mapInterface_containsValue() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").a("key2", "value2");
+
+ assertTrue(map.containsValue("value1"));
+ assertTrue(map.containsValue("value2"));
+ assertFalse(map.containsValue("value3"));
+ }
+
+ @Test
+ void e07_mapInterface_size() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ assertEquals(0, map.size());
+
+ map.a("key1", "value1");
+ assertEquals(1, map.size());
+
+ map.a("key2", "value2");
+ assertEquals(2, map.size());
+ }
+
+ @Test
+ void e08_mapInterface_isEmpty() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ assertTrue(map.isEmpty());
+
+ map.a("key1", "value1");
+ assertFalse(map.isEmpty());
+ }
+
+ @Test
+ void e09_mapInterface_clear() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").a("key2", "value2").a("key3", "value3");
+
+ map.clear();
+ assertTrue(map.isEmpty());
+ assertSize(0, map);
+ }
+
+ @Test
+ void e10_mapInterface_keySet() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").a("key2", "value2").a("key3", "value3");
+
+ var keySet = map.keySet();
+ assertSize(3, keySet);
+ assertTrue(keySet.contains("key1"));
+ assertTrue(keySet.contains("key2"));
+ assertTrue(keySet.contains("key3"));
+ }
+
+ @Test
+ void e11_mapInterface_values() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").a("key2", "value2").a("key3", "value3");
+
+ var values = map.values();
+ assertSize(3, values);
+ assertTrue(values.contains("value1"));
+ assertTrue(values.contains("value2"));
+ assertTrue(values.contains("value3"));
+ }
+
+ @Test
+ void e12_mapInterface_entrySet() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1").a("key2", "value2").a("key3", "value3");
+
+ var entrySet = map.entrySet();
+ assertSize(3, entrySet);
+
+ var found = new LinkedHashSet<String>();
+ for (var entry : entrySet) {
+ found.add(entry.getKey() + "=" + entry.getValue());
+ }
+ assertTrue(found.contains("key1=value1"));
+ assertTrue(found.contains("key2=value2"));
+ assertTrue(found.contains("key3=value3"));
+ }
+
+
//====================================================================================================
+ // Different map implementations
+
//====================================================================================================
+
+ @Test
+ void f01_hashMap() {
+ var map = new FluentMap<>(new HashMap<String, String>());
+ map.a("key1", "value1").a("key2", "value2").a("key3", "value3");
+
+ assertSize(3, map);
+ }
+
+ @Test
+ void f02_treeMap() {
+ var map = new FluentMap<>(new TreeMap<String, String>());
+ map.a("zebra", "value3").a("apple", "value1").a("banana",
"value2");
+
+ assertSize(3, map);
+ // TreeMap maintains sorted order
+ var keys = new ArrayList<>(map.keySet());
+ assertEquals(List.of("apple", "banana", "zebra"), keys);
+ }
+
+ @Test
+ void f03_concurrentHashMap() {
+ var map = new FluentMap<>(new ConcurrentHashMap<String,
String>());
+ map.a("key1", "value1").a("key2", "value2").a("key3", "value3");
+
+ assertSize(3, map);
+ }
+
+
//====================================================================================================
+ // Edge cases
+
//====================================================================================================
+
+ @Test
+ void g01_emptyMap() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+
+ assertTrue(map.isEmpty());
+ assertSize(0, map);
+ assertFalse(map.containsKey("anything"));
+ assertNull(map.get("anything"));
+ }
+
+ @Test
+ void g02_nullKeyAndValue() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a(null, null).a("key1", "value1");
+
+ assertSize(2, map);
+ assertNull(map.get(null));
+ assertTrue(map.containsKey(null));
+ assertEquals("value1", map.get("key1"));
+ }
+
+ @Test
+ void g03_updateWithNullValue() {
+ var map = new FluentMap<>(new LinkedHashMap<String, String>());
+ map.a("key1", "value1");
+ map.a("key1", null);
+
+ assertSize(1, map);
+ assertTrue(map.containsKey("key1"));
+ assertNull(map.get("key1"));
+ }
+}
+
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FluentSet_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FluentSet_Test.java
new file mode 100644
index 0000000000..583577542a
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/FluentSet_Test.java
@@ -0,0 +1,416 @@
+/*
+ * 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.junit.bct.BctAssertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+class FluentSet_Test extends TestBase {
+
+
//====================================================================================================
+ // Basic functionality - a(E element)
+
//====================================================================================================
+
+ @Test
+ void a01_addSingleElement() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item2").a("item3");
+
+ assertSize(3, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item2"));
+ assertTrue(set.contains("item3"));
+ }
+
+ @Test
+ void a02_addSingleElement_returnsThis() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ var result = set.a("item1");
+
+ assertSame(set, result);
+ }
+
+ @Test
+ void a03_addSingleElement_nullValue() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a(null).a("item3");
+
+ assertSize(3, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains(null));
+ assertTrue(set.contains("item3"));
+ }
+
+ @Test
+ void a04_addSingleElement_duplicateIgnored() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item2").a("item1");
+
+ assertSize(2, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item2"));
+ }
+
+
//====================================================================================================
+ // a(Collection) method
+
//====================================================================================================
+
+ @Test
+ void b01_addCollection() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ var other = List.of("item1", "item2", "item3");
+ set.aa(other);
+
+ assertSize(3, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item2"));
+ assertTrue(set.contains("item3"));
+ }
+
+ @Test
+ void b02_addCollection_returnsThis() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ var other = List.of("item1", "item2");
+ var result = set.aa(other);
+
+ assertSame(set, result);
+ }
+
+ @Test
+ void b03_addCollection_nullCollection() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1");
+ set.aa((Collection<String>)null);
+ set.a("item2");
+
+ assertSize(2, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item2"));
+ }
+
+ @Test
+ void b04_addCollection_emptyCollection() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1");
+ set.aa(List.of());
+ set.a("item2");
+
+ assertSize(2, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item2"));
+ }
+
+ @Test
+ void b05_addCollection_duplicatesIgnored() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.aa(List.of("item1", "item2"));
+ set.aa(List.of("item2", "item3")); // item2 is duplicate
+
+ assertSize(3, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item2"));
+ assertTrue(set.contains("item3"));
+ }
+
+
//====================================================================================================
+ // ai(boolean, E) method
+
//====================================================================================================
+
+ @Test
+ void c01_ai_conditionTrue() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").ai(true, "item2").a("item3");
+
+ assertSize(3, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item2"));
+ assertTrue(set.contains("item3"));
+ }
+
+ @Test
+ void c02_ai_conditionFalse() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").ai(false, "item2").a("item3");
+
+ assertSize(2, set);
+ assertTrue(set.contains("item1"));
+ assertFalse(set.contains("item2"));
+ assertTrue(set.contains("item3"));
+ }
+
+ @Test
+ void c03_ai_returnsThis() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ var result1 = set.ai(true, "item1");
+ var result2 = set.ai(false, "item2");
+
+ assertSame(set, result1);
+ assertSame(set, result2);
+ }
+
+ @Test
+ void c04_ai_conditionalBuilding() {
+ boolean includeDebug = true;
+ boolean includeTest = false;
+
+ var set = new FluentSet<>(new LinkedHashSet<String>())
+ .a("basic")
+ .ai(includeDebug, "debug")
+ .ai(includeTest, "test");
+
+ assertSize(2, set);
+ assertTrue(set.contains("basic"));
+ assertTrue(set.contains("debug"));
+ assertFalse(set.contains("test"));
+ }
+
+
//====================================================================================================
+ // Method chaining
+
//====================================================================================================
+
+ @Test
+ void d01_methodChaining() {
+ var set = new FluentSet<>(new LinkedHashSet<String>())
+ .a("item1")
+ .a("item2")
+ .ai(true, "item3")
+ .ai(false, "item4")
+ .aa(List.of("item5", "item6"));
+
+ assertSize(5, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item2"));
+ assertTrue(set.contains("item3"));
+ assertTrue(set.contains("item5"));
+ assertTrue(set.contains("item6"));
+ assertFalse(set.contains("item4"));
+ }
+
+
//====================================================================================================
+ // Set interface methods
+
//====================================================================================================
+
+ @Test
+ void e01_setInterface_size() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ assertEquals(0, set.size());
+
+ set.a("item1");
+ assertEquals(1, set.size());
+
+ set.a("item2");
+ assertEquals(2, set.size());
+ }
+
+ @Test
+ void e02_setInterface_add() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ assertTrue(set.add("item1"));
+ assertTrue(set.add("item2"));
+ assertFalse(set.add("item1")); // Duplicate, returns false
+
+ assertSize(2, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item2"));
+ }
+
+ @Test
+ void e03_setInterface_addAll() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1");
+ assertTrue(set.addAll(List.of("item2", "item3")));
+
+ assertSize(3, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item2"));
+ assertTrue(set.contains("item3"));
+ }
+
+ @Test
+ void e04_setInterface_remove() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item2").a("item3");
+
+ assertTrue(set.remove("item2"));
+ assertSize(2, set);
+ assertTrue(set.contains("item1"));
+ assertFalse(set.contains("item2"));
+ assertTrue(set.contains("item3"));
+ }
+
+ @Test
+ void e05_setInterface_contains() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item2");
+
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item2"));
+ assertFalse(set.contains("item3"));
+ }
+
+ @Test
+ void e06_setInterface_containsAll() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item2").a("item3");
+
+ assertTrue(set.containsAll(List.of("item1", "item2")));
+ assertTrue(set.containsAll(List.of("item1", "item2", "item3")));
+ assertFalse(set.containsAll(List.of("item1", "item4")));
+ }
+
+ @Test
+ void e07_setInterface_isEmpty() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ assertTrue(set.isEmpty());
+
+ set.a("item1");
+ assertFalse(set.isEmpty());
+ }
+
+ @Test
+ void e08_setInterface_clear() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item2").a("item3");
+
+ set.clear();
+ assertTrue(set.isEmpty());
+ assertSize(0, set);
+ }
+
+ @Test
+ void e09_setInterface_iterator() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item2").a("item3");
+
+ var found = new LinkedHashSet<String>();
+ for (var item : set) {
+ found.add(item);
+ }
+
+ assertSize(3, found);
+ assertTrue(found.contains("item1"));
+ assertTrue(found.contains("item2"));
+ assertTrue(found.contains("item3"));
+ }
+
+ @Test
+ void e10_setInterface_toArray() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item2").a("item3");
+
+ var array = set.toArray();
+ assertEquals(3, array.length);
+ // Order may vary, so just check all items are present
+ var arrayList = Arrays.asList(array);
+ assertTrue(arrayList.contains("item1"));
+ assertTrue(arrayList.contains("item2"));
+ assertTrue(arrayList.contains("item3"));
+ }
+
+ @Test
+ void e11_setInterface_toArrayTyped() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item2").a("item3");
+
+ var array = set.toArray(new String[0]);
+ assertEquals(3, array.length);
+ // Order may vary, so just check all items are present
+ var arrayList = Arrays.asList(array);
+ assertTrue(arrayList.contains("item1"));
+ assertTrue(arrayList.contains("item2"));
+ assertTrue(arrayList.contains("item3"));
+ }
+
+
//====================================================================================================
+ // Different set implementations
+
//====================================================================================================
+
+ @Test
+ void f01_hashSet() {
+ var set = new FluentSet<>(new HashSet<String>());
+ set.a("item1").a("item2").a("item3");
+
+ assertSize(3, set);
+ }
+
+ @Test
+ void f02_treeSet() {
+ var set = new FluentSet<>(new TreeSet<String>());
+ set.a("zebra").a("apple").a("banana");
+
+ assertSize(3, set);
+ // TreeSet maintains sorted order
+ var iterator = set.iterator();
+ assertEquals("apple", iterator.next());
+ assertEquals("banana", iterator.next());
+ assertEquals("zebra", iterator.next());
+ }
+
+
//====================================================================================================
+ // Edge cases
+
//====================================================================================================
+
+ @Test
+ void g01_emptySet() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+
+ assertTrue(set.isEmpty());
+ assertSize(0, set);
+ assertFalse(set.contains("anything"));
+ }
+
+ @Test
+ void g02_removeAll() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item2").a("item3").a("item4");
+
+ assertTrue(set.removeAll(List.of("item2", "item4")));
+ assertSize(2, set);
+ assertTrue(set.contains("item1"));
+ assertTrue(set.contains("item3"));
+ assertFalse(set.contains("item2"));
+ assertFalse(set.contains("item4"));
+ }
+
+ @Test
+ void g03_retainAll() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item2").a("item3").a("item4");
+
+ assertTrue(set.retainAll(List.of("item2", "item4")));
+ assertSize(2, set);
+ assertTrue(set.contains("item2"));
+ assertTrue(set.contains("item4"));
+ assertFalse(set.contains("item1"));
+ assertFalse(set.contains("item3"));
+ }
+
+ @Test
+ void g04_duplicateHandling() {
+ var set = new FluentSet<>(new LinkedHashSet<String>());
+ set.a("item1").a("item1").a("item1");
+
+ assertSize(1, set);
+ assertTrue(set.contains("item1"));
+ }
+}
+