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