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}