    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}