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 *      https://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.commons.collections4;
018
019import java.util.ArrayList;
020import java.util.Objects;
021
022import org.apache.commons.collections4.multiset.HashMultiSet;
023import org.apache.commons.collections4.multiset.PredicatedMultiSet;
024import org.apache.commons.collections4.multiset.PredicatedSortedMultiSet;
025import org.apache.commons.collections4.multiset.SynchronizedMultiSet;
026import org.apache.commons.collections4.multiset.SynchronizedSortedMultiSet;
027import org.apache.commons.collections4.multiset.TransformedMultiSet;
028import org.apache.commons.collections4.multiset.TransformedSortedMultiSet;
029import org.apache.commons.collections4.multiset.TreeMultiSet;
030import org.apache.commons.collections4.multiset.UnmodifiableMultiSet;
031import org.apache.commons.collections4.multiset.UnmodifiableSortedMultiSet;
032
033/**
034 * Provides utility methods and decorators for {@link MultiSet} and
035 * {@link SortedMultiSet} instances.
036 *
037 * @since 4.1
038 */
039public class MultiSetUtils {
040
041    /**
042     * An empty unmodifiable multiset.
043     */
044    @SuppressWarnings("rawtypes") // OK, empty multiset is compatible with any type
045    public static final MultiSet EMPTY_MULTISET =
046        UnmodifiableMultiSet.unmodifiableMultiSet(new HashMultiSet<>());
047
048    /**
049     * An empty unmodifiable sorted multiset.
050     *
051     * @since 4.6.0
052     */
053    @SuppressWarnings("rawtypes") // OK, empty multiset is compatible with any type
054    public static final SortedMultiSet EMPTY_SORTED_MULTISET =
055        UnmodifiableSortedMultiSet.unmodifiableSortedMultiSet(new TreeMultiSet<>());
056
057    /**
058     * Returns {@code true} if {@code superMultiSet} contains at least as many
059     * occurrences of each element as {@code subMultiSet} does; in other words,
060     * whether {@code subMultiSet} is a sub-multiset of {@code superMultiSet}.
061     * <p>
062     * This method provides the cardinality-respecting behavior of
063     * {@link Bag#containsAll(java.util.Collection)} under an explicitly named
064     * method. To compare against a plain collection, wrap it first, for example
065     * {@code containsOccurrences(multiSet, new HashMultiSet<>(coll))}.
066     * </p>
067     *
068     * @param superMultiSet The multiset to check against, must not be null
069     * @param subMultiSet The multiset whose occurrences must all be present, must not be null
070     * @return {@code true} if {@code superMultiSet} contains all occurrences in {@code subMultiSet}
071     * @throws NullPointerException if either MultiSet is null
072     * @since 4.6.0
073     */
074    public static boolean containsOccurrences(final MultiSet<?> superMultiSet, final MultiSet<?> subMultiSet) {
075        Objects.requireNonNull(superMultiSet, "superMultiSet");
076        Objects.requireNonNull(subMultiSet, "subMultiSet");
077        for (final MultiSet.Entry<?> entry : subMultiSet.entrySet()) {
078            if (superMultiSet.getCount(entry.getElement()) < entry.getCount()) {
079                return false;
080            }
081        }
082        return true;
083    }
084
085    /**
086     * Gets an empty {@code MultiSet}.
087     *
088     * @param <E> The element type
089     * @return An empty MultiSet
090     */
091    @SuppressWarnings("unchecked") // OK, empty multiset is compatible with any type
092    public static <E> MultiSet<E> emptyMultiSet() {
093        return EMPTY_MULTISET;
094    }
095
096    /**
097     * Gets an empty {@code SortedMultiSet}.
098     *
099     * @param <E> The element type
100     * @return An empty SortedMultiSet
101     * @since 4.6.0
102     */
103    @SuppressWarnings("unchecked") // OK, empty multiset is compatible with any type
104    public static <E> SortedMultiSet<E> emptySortedMultiSet() {
105        return EMPTY_SORTED_MULTISET;
106    }
107
108    /**
109     * Returns a predicated (validating) multiset backed by the given multiset.
110     * <p>
111     * Only objects that pass the test in the given predicate can be added to
112     * the multiset. Trying to add an invalid object results in an
113     * IllegalArgumentException. It is important not to use the original multiset
114     * after invoking this method, as it is a backdoor for adding invalid
115     * objects.
116     * </p>
117     *
118     * @param <E> The element type
119     * @param multiset The multiset to predicate, must not be null
120     * @param predicate The predicate for the multiset, must not be null
121     * @return A predicated multiset backed by the given multiset
122     * @throws NullPointerException if the MultiSet or Predicate is null
123     */
124    public static <E> MultiSet<E> predicatedMultiSet(final MultiSet<E> multiset,
125            final Predicate<? super E> predicate) {
126        return PredicatedMultiSet.predicatedMultiSet(multiset, predicate);
127    }
128
129    /**
130     * Returns a predicated (validating) sorted multiset backed by the given sorted
131     * multiset.
132     * <p>
133     * Only objects that pass the test in the given predicate can be added to
134     * the multiset. Trying to add an invalid object results in an
135     * IllegalArgumentException. It is important not to use the original multiset
136     * after invoking this method, as it is a backdoor for adding invalid
137     * objects.
138     * </p>
139     *
140     * @param <E> The element type
141     * @param multiset The sorted multiset to predicate, must not be null
142     * @param predicate The predicate for the multiset, must not be null
143     * @return A predicated sorted multiset backed by the given sorted multiset
144     * @throws NullPointerException if the SortedMultiSet or Predicate is null
145     * @since 4.6.0
146     */
147    public static <E> SortedMultiSet<E> predicatedSortedMultiSet(final SortedMultiSet<E> multiset,
148            final Predicate<? super E> predicate) {
149        return PredicatedSortedMultiSet.predicatedSortedMultiSet(multiset, predicate);
150    }
151
152    /**
153     * For each occurrence of an element in {@code occurrencesToRemove}, removes
154     * one occurrence of that element from {@code multiSetToModify}, if present.
155     * That is, if {@code occurrencesToRemove} contains {@code n} occurrences of
156     * an element, {@code multiSetToModify} will have {@code n} fewer occurrences,
157     * assuming it had at least {@code n} to begin with.
158     * <p>
159     * This method provides the cardinality-respecting behavior of
160     * {@link Bag#removeAll(java.util.Collection)} under an explicitly named
161     * method. To remove the occurrences of a plain collection, wrap it first,
162     * for example {@code removeOccurrences(multiSet, new HashMultiSet<>(coll))}.
163     * </p>
164     *
165     * @param multiSetToModify The multiset to remove occurrences from, must not be null
166     * @param occurrencesToRemove The occurrences to remove, must not be null
167     * @return {@code true} if {@code multiSetToModify} was changed as a result of this operation
168     * @throws NullPointerException if either MultiSet is null
169     * @since 4.6.0
170     */
171    public static boolean removeOccurrences(final MultiSet<?> multiSetToModify, final MultiSet<?> occurrencesToRemove) {
172        Objects.requireNonNull(multiSetToModify, "multiSetToModify");
173        Objects.requireNonNull(occurrencesToRemove, "occurrencesToRemove");
174        if (multiSetToModify == occurrencesToRemove) {
175            final boolean changed = !multiSetToModify.isEmpty();
176            multiSetToModify.clear();
177            return changed;
178        }
179        boolean changed = false;
180        // snapshot the entries to avoid ConcurrentModificationException when
181        // occurrencesToRemove is a view backed by multiSetToModify
182        for (final MultiSet.Entry<?> entry : new ArrayList<>(occurrencesToRemove.entrySet())) {
183            if (multiSetToModify.remove(entry.getElement(), entry.getCount()) > 0) {
184                changed = true;
185            }
186        }
187        return changed;
188    }
189
190    /**
191     * Modifies {@code multiSetToModify} so that no element has more occurrences
192     * than it has in {@code occurrencesToRetain}. That is, if
193     * {@code occurrencesToRetain} contains {@code n} occurrences of an element
194     * and {@code multiSetToModify} has {@code m > n} occurrences, {@code m - n}
195     * occurrences are removed; elements not contained in
196     * {@code occurrencesToRetain} are removed entirely.
197     * <p>
198     * This method provides the cardinality-respecting behavior of
199     * {@link Bag#retainAll(java.util.Collection)} under an explicitly named
200     * method. To retain the occurrences of a plain collection, wrap it first,
201     * for example {@code retainOccurrences(multiSet, new HashMultiSet<>(coll))}.
202     * </p>
203     *
204     * @param <E> The element type
205     * @param multiSetToModify The multiset to limit occurrences in, must not be null
206     * @param occurrencesToRetain The occurrences to retain, must not be null
207     * @return {@code true} if {@code multiSetToModify} was changed as a result of this operation
208     * @throws NullPointerException if either MultiSet is null
209     * @since 4.6.0
210     */
211    public static <E> boolean retainOccurrences(final MultiSet<E> multiSetToModify, final MultiSet<?> occurrencesToRetain) {
212        Objects.requireNonNull(multiSetToModify, "multiSetToModify");
213        Objects.requireNonNull(occurrencesToRetain, "occurrencesToRetain");
214        boolean changed = false;
215        for (final E element : new ArrayList<>(multiSetToModify.uniqueSet())) {
216            final int retainCount = occurrencesToRetain.getCount(element);
217            if (multiSetToModify.getCount(element) > retainCount) {
218                multiSetToModify.setCount(element, retainCount);
219                changed = true;
220            }
221        }
222        return changed;
223    }
224
225    /**
226     * Returns a synchronized (thread-safe) multiset backed by the given multiset.
227     * In order to guarantee serial access, it is critical that all access to the
228     * backing multiset is accomplished through the returned multiset.
229     * <p>
230     * It is imperative that the user manually synchronize on the returned multiset
231     * when iterating over it:
232     * </p>
233     * <pre>
234     * MultiSet multiset = MultiSetUtils.synchronizedMultiSet(new HashMultiSet());
235     * ...
236     * synchronized(multiset) {
237     *     Iterator i = multiset.iterator(); // Must be in synchronized block
238     *     while (i.hasNext())
239     *         foo(i.next());
240     *     }
241     * }
242     * </pre>
243     *
244     * Failure to follow this advice may result in non-deterministic behavior.
245     *
246     * @param <E> The element type
247     * @param multiset The multiset to synchronize, must not be null
248     * @return A synchronized multiset backed by that multiset
249     * @throws NullPointerException if the MultiSet is null
250     */
251    public static <E> MultiSet<E> synchronizedMultiSet(final MultiSet<E> multiset) {
252        return SynchronizedMultiSet.synchronizedMultiSet(multiset);
253    }
254
255    /**
256     * Returns a synchronized (thread-safe) sorted multiset backed by the given
257     * sorted multiset. In order to guarantee serial access, it is critical that all
258     * access to the backing multiset is accomplished through the returned multiset.
259     * <p>
260     * It is imperative that the user manually synchronize on the returned multiset
261     * when iterating over it:
262     * </p>
263     * <pre>
264     * SortedMultiSet multiset = MultiSetUtils.synchronizedSortedMultiSet(new TreeMultiSet());
265     * ...
266     * synchronized(multiset) {
267     *     Iterator i = multiset.iterator(); // Must be in synchronized block
268     *     while (i.hasNext())
269     *         foo(i.next());
270     *     }
271     * }
272     * </pre>
273     *
274     * Failure to follow this advice may result in non-deterministic behavior.
275     *
276     * @param <E> The element type
277     * @param multiset The sorted multiset to synchronize, must not be null
278     * @return A synchronized sorted multiset backed by that multiset
279     * @throws NullPointerException if the SortedMultiSet is null
280     * @since 4.6.0
281     */
282    public static <E> SortedMultiSet<E> synchronizedSortedMultiSet(final SortedMultiSet<E> multiset) {
283        return SynchronizedSortedMultiSet.synchronizedSortedMultiSet(multiset);
284    }
285
286    /**
287     * Returns a transformed multiset backed by the given multiset.
288     * <p>
289     * Each object is passed through the transformer as it is added to the
290     * MultiSet. It is important not to use the original multiset after invoking this
291     * method, as it is a backdoor for adding untransformed objects.
292     * </p>
293     * <p>
294     * Existing entries in the specified multiset will not be transformed.
295     * If you want that behavior, see
296     * {@link TransformedMultiSet#transformedMultiSet(MultiSet, Transformer)}.
297     * </p>
298     *
299     * @param <E> The element type
300     * @param multiset The multiset to transform, must not be null
301     * @param transformer The transformer for the multiset, must not be null
302     * @return A transformed multiset backed by the given multiset
303     * @throws NullPointerException if the MultiSet or Transformer is null
304     * @since 4.6.0
305     */
306    public static <E> MultiSet<E> transformingMultiSet(final MultiSet<E> multiset,
307            final Transformer<? super E, ? extends E> transformer) {
308        return TransformedMultiSet.transformingMultiSet(multiset, transformer);
309    }
310
311    /**
312     * Returns a transformed sorted multiset backed by the given multiset.
313     * <p>
314     * Each object is passed through the transformer as it is added to the
315     * MultiSet. It is important not to use the original multiset after invoking this
316     * method, as it is a backdoor for adding untransformed objects.
317     * </p>
318     * <p>
319     * Existing entries in the specified multiset will not be transformed.
320     * If you want that behavior, see
321     * {@link TransformedSortedMultiSet#transformedSortedMultiSet(SortedMultiSet, Transformer)}.
322     * </p>
323     *
324     * @param <E> The element type
325     * @param multiset The sorted multiset to transform, must not be null
326     * @param transformer The transformer for the multiset, must not be null
327     * @return A transformed sorted multiset backed by the given multiset
328     * @throws NullPointerException if the SortedMultiSet or Transformer is null
329     * @since 4.6.0
330     */
331    public static <E> SortedMultiSet<E> transformingSortedMultiSet(final SortedMultiSet<E> multiset,
332            final Transformer<? super E, ? extends E> transformer) {
333        return TransformedSortedMultiSet.transformingSortedMultiSet(multiset, transformer);
334    }
335
336    /**
337     * Returns an unmodifiable view of the given multiset. Any modification attempts
338     * to the returned multiset will raise an {@link UnsupportedOperationException}.
339     *
340     * @param <E> The element type
341     * @param multiset The multiset whose unmodifiable view is to be returned, must not be null
342     * @return An unmodifiable view of that multiset
343     * @throws NullPointerException if the MultiSet is null
344     */
345    public static <E> MultiSet<E> unmodifiableMultiSet(final MultiSet<? extends E> multiset) {
346        return UnmodifiableMultiSet.unmodifiableMultiSet(multiset);
347    }
348
349    /**
350     * Returns an unmodifiable view of the given sorted multiset. Any modification
351     * attempts to the returned multiset will raise an
352     * {@link UnsupportedOperationException}.
353     *
354     * @param <E> The element type
355     * @param multiset The sorted multiset whose unmodifiable view is to be returned, must not be null
356     * @return An unmodifiable view of that sorted multiset
357     * @throws NullPointerException if the SortedMultiSet is null
358     * @since 4.6.0
359     */
360    public static <E> SortedMultiSet<E> unmodifiableSortedMultiSet(final SortedMultiSet<? extends E> multiset) {
361        return UnmodifiableSortedMultiSet.unmodifiableSortedMultiSet(multiset);
362    }
363
364    /**
365     * Don't allow instances.
366     */
367    private MultiSetUtils() {
368        // empty
369    }
370
371}