1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.collections4;
18
19 import java.util.ArrayList;
20 import java.util.Objects;
21
22 import org.apache.commons.collections4.multiset.HashMultiSet;
23 import org.apache.commons.collections4.multiset.PredicatedMultiSet;
24 import org.apache.commons.collections4.multiset.PredicatedSortedMultiSet;
25 import org.apache.commons.collections4.multiset.SynchronizedMultiSet;
26 import org.apache.commons.collections4.multiset.SynchronizedSortedMultiSet;
27 import org.apache.commons.collections4.multiset.TransformedMultiSet;
28 import org.apache.commons.collections4.multiset.TransformedSortedMultiSet;
29 import org.apache.commons.collections4.multiset.TreeMultiSet;
30 import org.apache.commons.collections4.multiset.UnmodifiableMultiSet;
31 import org.apache.commons.collections4.multiset.UnmodifiableSortedMultiSet;
32
33 /**
34 * Provides utility methods and decorators for {@link MultiSet} and
35 * {@link SortedMultiSet} instances.
36 *
37 * @since 4.1
38 */
39 public class MultiSetUtils {
40
41 /**
42 * An empty unmodifiable multiset.
43 */
44 @SuppressWarnings("rawtypes") // OK, empty multiset is compatible with any type
45 public static final MultiSet EMPTY_MULTISET =
46 UnmodifiableMultiSet.unmodifiableMultiSet(new HashMultiSet<>());
47
48 /**
49 * An empty unmodifiable sorted multiset.
50 *
51 * @since 4.6.0
52 */
53 @SuppressWarnings("rawtypes") // OK, empty multiset is compatible with any type
54 public static final SortedMultiSet EMPTY_SORTED_MULTISET =
55 UnmodifiableSortedMultiSet.unmodifiableSortedMultiSet(new TreeMultiSet<>());
56
57 /**
58 * Returns {@code true} if {@code superMultiSet} contains at least as many
59 * occurrences of each element as {@code subMultiSet} does; in other words,
60 * whether {@code subMultiSet} is a sub-multiset of {@code superMultiSet}.
61 * <p>
62 * This method provides the cardinality-respecting behavior of
63 * {@link Bag#containsAll(java.util.Collection)} under an explicitly named
64 * method. To compare against a plain collection, wrap it first, for example
65 * {@code containsOccurrences(multiSet, new HashMultiSet<>(coll))}.
66 * </p>
67 *
68 * @param superMultiSet The multiset to check against, must not be null
69 * @param subMultiSet The multiset whose occurrences must all be present, must not be null
70 * @return {@code true} if {@code superMultiSet} contains all occurrences in {@code subMultiSet}
71 * @throws NullPointerException if either MultiSet is null
72 * @since 4.6.0
73 */
74 public static boolean containsOccurrences(final MultiSet<?> superMultiSet, final MultiSet<?> subMultiSet) {
75 Objects.requireNonNull(superMultiSet, "superMultiSet");
76 Objects.requireNonNull(subMultiSet, "subMultiSet");
77 for (final MultiSet.Entry<?> entry : subMultiSet.entrySet()) {
78 if (superMultiSet.getCount(entry.getElement()) < entry.getCount()) {
79 return false;
80 }
81 }
82 return true;
83 }
84
85 /**
86 * Gets an empty {@code MultiSet}.
87 *
88 * @param <E> The element type
89 * @return An empty MultiSet
90 */
91 @SuppressWarnings("unchecked") // OK, empty multiset is compatible with any type
92 public static <E> MultiSet<E> emptyMultiSet() {
93 return EMPTY_MULTISET;
94 }
95
96 /**
97 * Gets an empty {@code SortedMultiSet}.
98 *
99 * @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 }