001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.collections;
018
019import static org.apache.juneau.commons.utils.AssertionUtils.*;
020
021import java.util.*;
022
023/**
024 * A fluent wrapper around an arbitrary set that provides convenient methods for adding elements.
025 *
026 * <p>
027 * This class wraps an underlying set and provides a fluent API for adding elements. All methods return
028 * <c>this</c> to allow method chaining. The underlying set can be any {@link Set} implementation.
029 *
030 * <h5 class='section'>Features:</h5>
031 * <ul class='spaced-list'>
032 *    <li><b>Fluent API:</b> All methods return <c>this</c> for method chaining
033 *    <li><b>Arbitrary Set Support:</b> Works with any set implementation
034 *    <li><b>Conditional Adding:</b> Add elements conditionally based on boolean expressions
035 *    <li><b>Transparent Interface:</b> Implements the full {@link Set} interface, so it can be used anywhere a set is expected
036 *    <li><b>Automatic Deduplication:</b> Duplicate elements are automatically handled by the underlying set
037 * </ul>
038 *
039 * <h5 class='section'>Usage:</h5>
040 * <p class='bjava'>
041 *    <jc>// Create a FluentSet wrapping a LinkedHashSet</jc>
042 *    FluentSet&lt;String&gt; <jv>set</jv> = <jk>new</jk> FluentSet&lt;&gt;(<jk>new</jk> LinkedHashSet&lt;&gt;());
043 *
044 *    <jv>set</jv>
045 *       .a(<js>"item1"</js>)
046 *       .a(<js>"item2"</js>)
047 *       .ai(<jk>true</jk>, <js>"item3"</js>)   <jc>// Added</jc>
048 *       .ai(<jk>false</jk>, <js>"item4"</js>); <jc>// Not added</jc>
049 *
050 *    <jc>// Add all elements from another collection</jc>
051 *    Set&lt;String&gt; <jv>other</jv> = Set.of(<js>"item5"</js>, <js>"item6"</js>);
052 *    <jv>set</jv>.aa(<jv>other</jv>);
053 * </p>
054 *
055 * <h5 class='section'>Example - Conditional Building:</h5>
056 * <p class='bjava'>
057 *    <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>;
058 *    <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>;
059 *
060 *    FluentSet&lt;String&gt; <jv>set</jv> = <jk>new</jk> FluentSet&lt;&gt;(<jk>new</jk> LinkedHashSet&lt;&gt;())
061 *       .a(<js>"setting1"</js>)
062 *       .a(<js>"setting2"</js>)
063 *       .ai(<jv>includeDebug</jv>, <js>"debug"</js>)   <jc>// Added</jc>
064 *       .ai(<jv>includeTest</jv>, <js>"test"</js>);    <jc>// Not added</jc>
065 * </p>
066 *
067 * <h5 class='section'>Behavior Notes:</h5>
068 * <ul class='spaced-list'>
069 *    <li>All set operations are delegated to the underlying set
070 *    <li>The fluent methods ({@link #a(Object)}, {@link #aa(Collection)}, {@link #ai(boolean, Object)}) return <c>this</c> for chaining
071 *    <li>If a <jk>null</jk> collection is passed to {@link #aa(Collection)}, it is treated as a no-op
072 *    <li>The underlying set is stored by reference (not copied), so modifications affect the original set
073 *    <li>Duplicate elements are automatically handled by the underlying set (only one occurrence is stored)
074 * </ul>
075 *
076 * <h5 class='section'>Thread Safety:</h5>
077 * <p>
078 * This class is not thread-safe unless the underlying set is thread-safe. If thread safety is required,
079 * use a thread-safe set type (e.g., {@link java.util.concurrent.CopyOnWriteArraySet}).
080 *
081 * <h5 class='section'>See Also:</h5>
082 * <ul>
083 *    <li class='link'><a class="doclink" href="../../../../../index.html#juneau-commons">Overview &gt; juneau-commons</a>
084 * </ul>
085 *
086 * @param <E> The element type.
087 */
088public class FluentSet<E> extends AbstractSet<E> {
089
090   private final Set<E> set;
091
092   /**
093    * Constructor.
094    *
095    * @param inner The underlying set to wrap. Must not be <jk>null</jk>.
096    */
097   public FluentSet(Set<E> inner) {
098      this.set = assertArgNotNull("inner", inner);
099   }
100
101   /**
102    * Adds a single element to this set.
103    *
104    * <p>
105    * This is a convenience method that calls {@link #add(Object)} and returns <c>this</c>
106    * for method chaining. If the element already exists in the set, it is not added again
107    * (sets do not allow duplicates).
108    *
109    * <h5 class='section'>Example:</h5>
110    * <p class='bjava'>
111    *    FluentSet&lt;String&gt; <jv>set</jv> = <jk>new</jk> FluentSet&lt;&gt;(<jk>new</jk> LinkedHashSet&lt;&gt;());
112    *    <jv>set</jv>.a(<js>"item1"</js>).a(<js>"item2"</js>);
113    * </p>
114    *
115    * @param element The element to add.
116    * @return This object for method chaining.
117    */
118   public FluentSet<E> a(E element) {
119      set.add(element);
120      return this;
121   }
122
123   /**
124    * Adds all elements from the specified collection to this set.
125    *
126    * <p>
127    * This is a convenience method that calls {@link #addAll(Collection)} and returns <c>this</c>
128    * for method chaining. If the specified collection is <jk>null</jk>, this is a no-op.
129    * Duplicate elements in the collection are automatically handled (only one occurrence is stored).
130    *
131    * <h5 class='section'>Example:</h5>
132    * <p class='bjava'>
133    *    FluentSet&lt;String&gt; <jv>set</jv> = <jk>new</jk> FluentSet&lt;&gt;(<jk>new</jk> LinkedHashSet&lt;&gt;());
134    *    List&lt;String&gt; <jv>other</jv> = List.of(<js>"item1"</js>, <js>"item2"</js>);
135    *    <jv>set</jv>.aa(<jv>other</jv>).a(<js>"item3"</js>);
136    * </p>
137    *
138    * @param c The collection whose elements are to be added. Can be <jk>null</jk> (no-op).
139    * @return This object for method chaining.
140    */
141   public FluentSet<E> aa(Collection<? extends E> c) {
142      if (c != null)
143         set.addAll(c);
144      return this;
145   }
146
147   /**
148    * Adds an element to this set if the specified boolean condition is <jk>true</jk>.
149    *
150    * <p>
151    * This method is useful for conditionally adding elements based on runtime conditions.
152    * If the condition is <jk>false</jk>, the element is not added and this method returns <c>this</c>
153    * without modifying the set.
154    *
155    * <h5 class='section'>Example:</h5>
156    * <p class='bjava'>
157    *    <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>;
158    *    <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>;
159    *
160    *    FluentSet&lt;String&gt; <jv>set</jv> = <jk>new</jk> FluentSet&lt;&gt;(<jk>new</jk> LinkedHashSet&lt;&gt;())
161    *       .a(<js>"basic"</js>)
162    *       .ai(<jv>includeDebug</jv>, <js>"debug"</js>)   <jc>// Added</jc>
163    *       .ai(<jv>includeTest</jv>, <js>"test"</js>);    <jc>// Not added</jc>
164    * </p>
165    *
166    * @param condition The condition to evaluate. If <jk>true</jk>, the element is added; if <jk>false</jk>, it is not.
167    * @param element The element to add if the condition is <jk>true</jk>.
168    * @return This object for method chaining.
169    */
170   public FluentSet<E> ai(boolean condition, E element) {
171      if (condition)
172         set.add(element);
173      return this;
174   }
175
176   @Override
177   public Iterator<E> iterator() {
178      return set.iterator();
179   }
180
181   @Override
182   public int size() {
183      return set.size();
184   }
185
186   @Override
187   public boolean add(E e) {
188      return set.add(e);
189   }
190
191   @Override
192   public boolean addAll(Collection<? extends E> c) {
193      return set.addAll(c);
194   }
195
196   @Override
197   public boolean remove(Object o) {
198      return set.remove(o);
199   }
200
201   @Override
202   public boolean removeAll(Collection<?> c) {
203      return set.removeAll(c);
204   }
205
206   @Override
207   public boolean retainAll(Collection<?> c) {
208      return set.retainAll(c);
209   }
210
211   @Override
212   public void clear() {
213      set.clear();
214   }
215
216   @Override
217   public boolean contains(Object o) {
218      return set.contains(o);
219   }
220
221   @Override
222   public boolean containsAll(Collection<?> c) {
223      return set.containsAll(c);
224   }
225
226   @Override
227   public boolean isEmpty() {
228      return set.isEmpty();
229   }
230
231   @Override
232   public Object[] toArray() {
233      return set.toArray();
234   }
235
236   @Override
237   public <T> T[] toArray(T[] a) {
238      return set.toArray(a);
239   }
240
241   @Override
242   public String toString() {
243      return set.toString();
244   }
245
246   @Override
247   public boolean equals(Object o) {
248      return set.equals(o);
249   }
250
251   @Override
252   public int hashCode() {
253      return set.hashCode();
254   }
255}
256