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.Shorts.*;
020
021import java.util.*;
022import java.util.stream.*;
023
024/**
025 * A composite set that presents multiple collections as a single unified set.
026 *
027 * <p>
028 * This class allows multiple collections to be viewed and iterated over as if they were merged into
029 * a single set, without actually copying the elements. Modifications made through the iterator affect
030 * the underlying collections.
031 *
032 * <h5 class='section'>Features:</h5>
033 * <ul class='spaced-list'>
034 *    <li><b>Zero-Copy Composition:</b> No data is copied when creating a MultiSet; it simply wraps the provided collections
035 *    <li><b>Transparent Iteration:</b> Iterating over a MultiSet seamlessly traverses all underlying collections in order
036 *    <li><b>Modification Support:</b> Elements can be removed via the iterator's {@link Iterator#remove()} method
037 *    <li><b>Efficient Size Calculation:</b> The size is computed by summing the sizes of all underlying collections
038 *    <li><b>Enumeration Support:</b> Provides an {@link Enumeration} view via {@link #enumerator()}
039 * </ul>
040 *
041 * <h5 class='section'>Usage:</h5>
042 * <p class='bjava'>
043 *    <jc>// Create a MultiSet from three separate collections</jc>
044 *    List&lt;String&gt; <jv>list1</jv> = List.of(<js>"a"</js>, <js>"b"</js>);
045 *    Set&lt;String&gt; <jv>set1</jv> = Set.of(<js>"c"</js>, <js>"d"</js>);
046 *    List&lt;String&gt; <jv>list2</jv> = List.of(<js>"e"</js>, <js>"f"</js>);
047 *
048 *    MultiSet&lt;String&gt; <jv>multiSet</jv> = <jk>new</jk> MultiSet&lt;&gt;(<jv>list1</jv>, <jv>set1</jv>, <jv>list2</jv>);
049 *
050 *    <jc>// Iterate over all elements from all collections</jc>
051 *    <jk>for</jk> (String <jv>element</jv> : <jv>multiSet</jv>) {
052 *       System.<jsf>out</jsf>.println(<jv>element</jv>); <jc>// Prints: a, b, c, d, e, f</jc>
053 *    }
054 *
055 *    <jc>// Get total size across all collections</jc>
056 *    <jk>int</jk> <jv>totalSize</jv> = <jv>multiSet</jv>.size(); <jc>// Returns: 6</jc>
057 *
058 *    <jc>// Remove elements via iterator (affects underlying collections)</jc>
059 *    Iterator&lt;String&gt; <jv>it</jv> = <jv>multiSet</jv>.iterator();
060 *    <jk>while</jk> (<jv>it</jv>.hasNext()) {
061 *       <jk>if</jk> (<jv>it</jv>.next().equals(<js>"b"</js>)) {
062 *          <jv>it</jv>.remove(); <jc>// Removes "b" from list1</jc>
063 *       }
064 *    }
065 * </p>
066 *
067 * <h5 class='section'>Behavior Notes:</h5>
068 * <ul class='spaced-list'>
069 *    <li>The order of iteration follows the order of collections as provided in the constructor
070 *    <li>Within each collection, iteration order is determined by that collection's iterator
071 *    <li>The underlying collections must not be <jk>null</jk>, but can be empty
072 *    <li>Modifications via {@link Iterator#remove()} are delegated to the underlying collection's iterator
073 *    <li>This class does not support {@link #add(Object)} or {@link #remove(Object)} operations
074 *    <li>The {@link #size()} method recomputes the sum each time it's called (not cached)
075 * </ul>
076 *
077 * <h5 class='section'>Thread Safety:</h5>
078 * <p>
079 * This class is not inherently thread-safe. If the underlying collections are modified concurrently
080 * during iteration, the behavior is undefined. Synchronization must be handled externally if needed.
081 *
082 * <h5 class='section'>Example - Processing Multiple Data Sources:</h5>
083 * <p class='bjava'>
084 *    <jc>// Combine results from database, cache, and defaults</jc>
085 *    List&lt;User&gt; <jv>dbUsers</jv> = fetchFromDatabase();
086 *    Set&lt;User&gt; <jv>cachedUsers</jv> = getCachedUsers();
087 *    List&lt;User&gt; <jv>defaultUsers</jv> = getDefaultUsers();
088 *
089 *    MultiSet&lt;User&gt; <jv>allUsers</jv> = <jk>new</jk> MultiSet&lt;&gt;(<jv>dbUsers</jv>, <jv>cachedUsers</jv>, <jv>defaultUsers</jv>);
090 *
091 *    <jc>// Process all users from all sources</jc>
092 *    <jv>allUsers</jv>.forEach(user -&gt; processUser(user));
093 * </p>
094 *
095 * <h5 class='section'>See Also:</h5>
096 * <ul>
097 *    <li class='link'><a class="doclink" href="../../../../../index.html#juneau-commons">Overview &gt; juneau-commons</a>
098 * </ul>
099 *
100 * @param <E> The element type of this set.
101 */
102@SuppressWarnings({
103   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
104   "java:S3740" // Raw Set/Iterator types used in multi-set structural operations where element type is erased
105})
106public class MultiSet<E> extends AbstractSet<E> {
107
108   /**
109    * The underlying collections being wrapped by this MultiSet.
110    * <p>
111    * These collections are accessed directly during iteration without copying.
112    */
113   final Collection<E>[] l;
114
115   /**
116    * Creates a new MultiSet that presents the specified collections as a single unified set.
117    *
118    * <p>
119    * The collections are stored by reference (not copied), so modifications made through the
120    * MultiSet's iterator will affect the original collections.
121    *
122    * <h5 class='section'>Example:</h5>
123    * <p class='bjava'>
124    *    List&lt;String&gt; <jv>list1</jv> = <jk>new</jk> ArrayList&lt;&gt;(List.of(<js>"a"</js>, <js>"b"</js>));
125    *    List&lt;String&gt; <jv>list2</jv> = <jk>new</jk> ArrayList&lt;&gt;(List.of(<js>"c"</js>, <js>"d"</js>));
126    *
127    *    MultiSet&lt;String&gt; <jv>multiSet</jv> = <jk>new</jk> MultiSet&lt;&gt;(<jv>list1</jv>, <jv>list2</jv>);
128    *    <jc>// multiSet now represents all elements from both lists</jc>
129    * </p>
130    *
131    * @param c Zero or more collections to combine into this set. Must not be <jk>null</jk>,
132    *           and no individual collection can be <jk>null</jk> (but collections can be empty).
133    * @throws IllegalArgumentException if the collections array or any collection within it is <jk>null</jk>.
134    */
135   @SafeVarargs
136   public MultiSet(Collection<E>...c) {
137      reqnn("c", c);
138      for (var cc : c)
139         reqnn("c", cc);
140      l = c;
141   }
142
143   /**
144    * Returns an {@link Enumeration} view of this set.
145    *
146    * <p>
147    * This is useful for compatibility with legacy APIs that require an {@link Enumeration}
148    * rather than an {@link Iterator}.
149    *
150    * <h5 class='section'>Example:</h5>
151    * <p class='bjava'>
152    *    MultiSet&lt;String&gt; <jv>multiSet</jv> = <jk>new</jk> MultiSet&lt;&gt;(list1, list2);
153    *    Enumeration&lt;String&gt; <jv>enumeration</jv> = <jv>multiSet</jv>.enumerator();
154    *
155    *    <jk>while</jk> (<jv>enumeration</jv>.hasMoreElements()) {
156    *       String <jv>element</jv> = <jv>enumeration</jv>.nextElement();
157    *       <jc>// Process element</jc>
158    *    }
159    * </p>
160    *
161    * @return An {@link Enumeration} that iterates over all elements in all underlying collections.
162    * @see #iterator()
163    */
164   public Enumeration<E> enumerator() {
165      return Collections.enumeration(this);
166   }
167
168   /**
169    * Returns an iterator over all elements in all underlying collections.
170    *
171    * <p>
172    * The iterator traverses each collection in the order they were provided to the constructor.
173    * Within each collection, the iteration order is determined by that collection's iterator.
174    *
175    * <p>
176    * The returned iterator supports the {@link Iterator#remove()} operation, which removes
177    * the current element from its underlying collection.
178    *
179    * <h5 class='section'>Behavior:</h5>
180    * <ul class='spaced-list'>
181    *    <li>Elements from the first collection are iterated first, then the second, and so on
182    *    <li>If a collection is empty, it is skipped during iteration
183    *    <li>Calling {@link Iterator#remove()} removes the element from the underlying collection
184    *    <li>Calling {@link Iterator#next()} when {@link Iterator#hasNext()} returns <jk>false</jk>
185    *       throws {@link NoSuchElementException}
186    *    <li>Calling {@link Iterator#remove()} before calling {@link Iterator#next()} or calling it twice
187    *       in a row may throw {@link IllegalStateException} (behavior depends on underlying collection)
188    * </ul>
189    *
190    * <h5 class='section'>Example:</h5>
191    * <p class='bjava'>
192    *    List&lt;String&gt; <jv>list1</jv> = <jk>new</jk> ArrayList&lt;&gt;(List.of(<js>"a"</js>, <js>"b"</js>));
193    *    List&lt;String&gt; <jv>list2</jv> = <jk>new</jk> ArrayList&lt;&gt;(List.of(<js>"c"</js>, <js>"d"</js>));
194    *    MultiSet&lt;String&gt; <jv>multiSet</jv> = <jk>new</jk> MultiSet&lt;&gt;(<jv>list1</jv>, <jv>list2</jv>);
195    *
196    *    Iterator&lt;String&gt; <jv>it</jv> = <jv>multiSet</jv>.iterator();
197    *    <jk>while</jk> (<jv>it</jv>.hasNext()) {
198    *       String <jv>element</jv> = <jv>it</jv>.next();
199    *       <jk>if</jk> (<jv>element</jv>.equals(<js>"b"</js>)) {
200    *          <jv>it</jv>.remove(); <jc>// Removes "b" from list1</jc>
201    *       }
202    *    }
203    * </p>
204    *
205    * @return An iterator over all elements in all underlying collections.
206    */
207   @Override /* Set */
208   @SuppressWarnings({
209      "java:S3776" // Cognitive complexity acceptable for iterator over multiple sets
210   })
211   public Iterator<E> iterator() {
212      return new Iterator<>() {
213         int i = 0;
214         Iterator<E> i2 = (l.length > 0 ? l[i++].iterator() : null);
215
216         @Override /* Overridden from Iterator */
217         public boolean hasNext() {
218            if (i2 == null)
219               return false;
220            if (i2.hasNext())
221               return true;
222            for (var j = i; j < l.length; j++)
223               if (l[j].size() > 0)
224                  return true;
225            return false;
226         }
227
228         @Override /* Overridden from Iterator */
229         public E next() {
230            if (i2 == null)
231               throw new NoSuchElementException();
232            while (! i2.hasNext()) {
233               if (i >= l.length)
234                  throw new NoSuchElementException();
235               i2 = l[i++].iterator();
236            }
237            return i2.next();
238         }
239
240         @Override /* Overridden from Iterator */
241         public void remove() {
242            if (i2 == null)
243               throw new NoSuchElementException();
244            i2.remove();
245         }
246      };
247   }
248
249   /**
250    * Returns the total number of elements across all underlying collections.
251    *
252    * <p>
253    * This method computes the size by summing the {@link Collection#size()} of each
254    * underlying collection. The size is recalculated each time this method is called
255    * (it is not cached).
256    *
257    * <h5 class='section'>Example:</h5>
258    * <p class='bjava'>
259    *    List&lt;String&gt; <jv>list1</jv> = List.of(<js>"a"</js>, <js>"b"</js>);        <jc>// size = 2</jc>
260    *    List&lt;String&gt; <jv>list2</jv> = List.of(<js>"c"</js>, <js>"d"</js>, <js>"e"</js>); <jc>// size = 3</jc>
261    *    MultiSet&lt;String&gt; <jv>multiSet</jv> = <jk>new</jk> MultiSet&lt;&gt;(<jv>list1</jv>, <jv>list2</jv>);
262    *
263    *    <jk>int</jk> <jv>totalSize</jv> = <jv>multiSet</jv>.size(); <jc>// Returns: 5</jc>
264    * </p>
265    *
266    * @return The sum of sizes of all underlying collections.
267    */
268   @Override /* Set */
269   public int size() {
270      var i = 0;
271      for (var c : l)
272         i += c.size();
273      return i;
274   }
275
276   /**
277    * Returns a string representation of this MultiSet.
278    *
279    * <p>
280    * The format is <c>"[[...],[...],...]"</c> where each <c>[...]</c> is the standard
281    * standard string representation of each underlying collection (as returned by {@link Object#toString()}).
282    *
283    * <h5 class='section'>Example:</h5>
284    * <p class='bjava'>
285    *    List&lt;String&gt; <jv>list1</jv> = List.of(<js>"a"</js>, <js>"b"</js>);
286    *    List&lt;String&gt; <jv>list2</jv> = List.of(<js>"c"</js>, <js>"d"</js>);
287    *    MultiSet&lt;String&gt; <jv>multiSet</jv> = <jk>new</jk> MultiSet&lt;&gt;(<jv>list1</jv>, <jv>list2</jv>);
288    *    <jv>multiSet</jv>.toString(); <jc>// Returns: "[[a, b], [c, d]]"</jc>
289    * </p>
290    *
291    * @return A string representation of this MultiSet.
292    */
293   @Override
294   public String toString() {
295      return Arrays.stream(l).map(Object::toString).collect(Collectors.joining(", ", "[", "]"));
296   }
297
298   /**
299    * Compares the specified object with this set for equality.
300    *
301    * <p>
302    * Returns <jk>true</jk> if the given object is also a set, the two sets have the same size,
303    * and every member of the given set is contained in this set.
304    *
305    * <p>
306    * This implementation checks if the specified object is a set, and if so, compares the sizes
307    * and checks if all elements in the specified set are contained in this set.
308    *
309    * @param o Object to be compared for equality with this set.
310    * @return <jk>true</jk> if the specified object is equal to this set.
311    */
312   @Override
313   public boolean equals(Object o) {
314      return (o instanceof Set o2) && eq(this, o2, (x,y) -> eq(x.size(), y.size()) && x.containsAll(y));
315   }
316
317   /**
318    * Returns the hash code value for this set.
319    *
320    * <p>
321    * The hash code of a set is defined to be the sum of the hash codes of the elements in the set,
322    * where the hash code of a <jk>null</jk> element is defined to be zero. This ensures that
323    * <c>s1.equals(s2)</c> implies that <c>s1.hashCode()==s2.hashCode()</c> for any two sets
324    * <c>s1</c> and <c>s2</c>, as required by the general contract of {@link Object#hashCode()}.
325    *
326    * <p>
327    * This implementation iterates over the set, calling the <c>hashCode</c> method on each element
328    * in the set, and adding up the results.
329    *
330    * @return The hash code value for this set.
331    */
332   @Override
333   public int hashCode() {
334      int h = 0;
335      for (E e : this)
336         h += e == null ? 0 : e.hashCode();
337      return h;
338   }
339}