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
019
020import java.util.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
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 */
088@SuppressWarnings({
089   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
090})
091public class FluentSet<E> extends AbstractSet<E> {
092
093   private final Set<E> set;
094
095   /**
096    * Constructor.
097    *
098    * @param inner The underlying set to wrap. Must not be <jk>null</jk>.
099    */
100   public FluentSet(Set<E> inner) {
101      this.set = reqnn("inner", inner);
102   }
103
104   /**
105    * Adds a single element to this set.
106    *
107    * <p>
108    * This is a convenience method that calls {@link #add(Object)} and returns <c>this</c>
109    * for method chaining. If the element already exists in the set, it is not added again
110    * (sets do not allow duplicates).
111    *
112    * <h5 class='section'>Example:</h5>
113    * <p class='bjava'>
114    *    FluentSet&lt;String&gt; <jv>set</jv> = <jk>new</jk> FluentSet&lt;&gt;(<jk>new</jk> LinkedHashSet&lt;&gt;());
115    *    <jv>set</jv>.a(<js>"item1"</js>).a(<js>"item2"</js>);
116    * </p>
117    *
118    * @param element The element to add.
119    * @return This object for method chaining.
120    */
121   public FluentSet<E> a(E element) {
122      set.add(element);
123      return this;
124   }
125
126   /**
127    * Adds all elements from the specified collection to this set.
128    *
129    * <p>
130    * This is a convenience method that calls {@link #addAll(Collection)} and returns <c>this</c>
131    * for method chaining. If the specified collection is <jk>null</jk>, this is a no-op.
132    * Duplicate elements in the collection are automatically handled (only one occurrence is stored).
133    *
134    * <h5 class='section'>Example:</h5>
135    * <p class='bjava'>
136    *    FluentSet&lt;String&gt; <jv>set</jv> = <jk>new</jk> FluentSet&lt;&gt;(<jk>new</jk> LinkedHashSet&lt;&gt;());
137    *    List&lt;String&gt; <jv>other</jv> = List.of(<js>"item1"</js>, <js>"item2"</js>);
138    *    <jv>set</jv>.aa(<jv>other</jv>).a(<js>"item3"</js>);
139    * </p>
140    *
141    * @param c The collection whose elements are to be added. Can be <jk>null</jk> (no-op).
142    * @return This object for method chaining.
143    */
144   public FluentSet<E> aa(Collection<? extends E> c) {
145      if (c != null)
146         set.addAll(c);
147      return this;
148   }
149
150   /**
151    * Adds an element to this set if the specified boolean condition is <jk>true</jk>.
152    *
153    * <p>
154    * This method is useful for conditionally adding elements based on runtime conditions.
155    * If the condition is <jk>false</jk>, the element is not added and this method returns <c>this</c>
156    * without modifying the set.
157    *
158    * <h5 class='section'>Example:</h5>
159    * <p class='bjava'>
160    *    <jk>boolean</jk> <jv>includeDebug</jv> = <jk>true</jk>;
161    *    <jk>boolean</jk> <jv>includeTest</jv> = <jk>false</jk>;
162    *
163    *    FluentSet&lt;String&gt; <jv>set</jv> = <jk>new</jk> FluentSet&lt;&gt;(<jk>new</jk> LinkedHashSet&lt;&gt;())
164    *       .a(<js>"basic"</js>)
165    *       .ai(<jv>includeDebug</jv>, <js>"debug"</js>)   <jc>// Added</jc>
166    *       .ai(<jv>includeTest</jv>, <js>"test"</js>);    <jc>// Not added</jc>
167    * </p>
168    *
169    * @param condition The condition to evaluate. If <jk>true</jk>, the element is added; if <jk>false</jk>, it is not.
170    * @param element The element to add if the condition is <jk>true</jk>.
171    * @return This object for method chaining.
172    */
173   public FluentSet<E> ai(boolean condition, E element) {
174      if (condition)
175         set.add(element);
176      return this;
177   }
178
179   @Override
180   public Iterator<E> iterator() {
181      return set.iterator();
182   }
183
184   @Override
185   public int size() {
186      return set.size();
187   }
188
189   @Override
190   public boolean add(E e) {
191      return set.add(e);
192   }
193
194   @Override
195   public boolean addAll(Collection<? extends E> c) {
196      return set.addAll(c);
197   }
198
199   @Override
200   public boolean remove(Object o) {
201      return set.remove(o);
202   }
203
204   @Override
205   public boolean removeAll(Collection<?> c) {
206      return set.removeAll(c);
207   }
208
209   @Override
210   public boolean retainAll(Collection<?> c) {
211      return set.retainAll(c);
212   }
213
214   @Override
215   public void clear() {
216      set.clear();
217   }
218
219   @Override
220   public boolean contains(Object o) {
221      return set.contains(o);
222   }
223
224   @Override
225   public boolean containsAll(Collection<?> c) {
226      return set.containsAll(c);
227   }
228
229   @Override
230   public boolean isEmpty() {
231      return set.isEmpty();
232   }
233
234   @Override
235   public Object[] toArray() {
236      return set.toArray();
237   }
238
239   @Override
240   public <T> T[] toArray(T[] a) {
241      return set.toArray(a);
242   }
243
244   @Override
245   public String toString() {
246      return set.toString();
247   }
248
249   @Override
250   public boolean equals(Object o) {
251      return set.equals(o);
252   }
253
254   @Override
255   public int hashCode() {
256      return set.hashCode();
257   }
258}
259