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.commons.collections4.bag; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022import java.lang.reflect.Array; 023import java.util.Collection; 024import java.util.ConcurrentModificationException; 025import java.util.Iterator; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.Objects; 029import java.util.Set; 030 031import org.apache.commons.collections4.Bag; 032import org.apache.commons.collections4.CollectionUtils; 033import org.apache.commons.collections4.set.UnmodifiableSet; 034 035/** 036 * Abstract implementation of the {@link Bag} interface to simplify the creation 037 * of subclass implementations. 038 * <p> 039 * Subclasses specify a Map implementation to use as the internal storage. The 040 * map will be used to map bag elements to a number; the number represents the 041 * number of occurrences of that element in the bag. 042 * </p> 043 * 044 * @param <E> the type of elements in this bag 045 * @since 3.0 (previously DefaultMapBag v2.0) 046 */ 047public abstract class AbstractMapBag<E> implements Bag<E> { 048 049 /** 050 * Inner class iterator for the Bag. 051 */ 052 static class BagIterator<E> implements Iterator<E> { 053 private final AbstractMapBag<E> parent; 054 private final Iterator<Map.Entry<E, MutableInteger>> entryIterator; 055 private Map.Entry<E, MutableInteger> current; 056 private int itemCount; 057 private final int mods; 058 private boolean canRemove; 059 060 /** 061 * Constructs a new instance. 062 * 063 * @param parent the parent bag 064 */ 065 BagIterator(final AbstractMapBag<E> parent) { 066 this.parent = parent; 067 this.entryIterator = parent.map.entrySet().iterator(); 068 this.current = null; 069 this.mods = parent.modCount; 070 this.canRemove = false; 071 } 072 073 /** {@inheritDoc} */ 074 @Override 075 public boolean hasNext() { 076 return itemCount > 0 || entryIterator.hasNext(); 077 } 078 079 /** {@inheritDoc} */ 080 @Override 081 public E next() { 082 if (parent.modCount != mods) { 083 throw new ConcurrentModificationException(); 084 } 085 if (itemCount == 0) { 086 current = entryIterator.next(); 087 itemCount = current.getValue().value; 088 } 089 canRemove = true; 090 itemCount--; 091 return current.getKey(); 092 } 093 094 /** {@inheritDoc} */ 095 @Override 096 public void remove() { 097 if (parent.modCount != mods) { 098 throw new ConcurrentModificationException(); 099 } 100 if (!canRemove) { 101 throw new IllegalStateException(); 102 } 103 final MutableInteger mut = current.getValue(); 104 if (mut.value > 1) { 105 mut.value--; 106 } else { 107 entryIterator.remove(); 108 } 109 parent.size--; 110 canRemove = false; 111 } 112 } 113 114 /** 115 * Mutable integer class for storing the data. 116 */ 117 protected static class MutableInteger { 118 119 /** The value of this mutable. */ 120 protected int value; 121 122 /** 123 * Constructs a new instance. 124 * @param value the initial value 125 */ 126 MutableInteger(final int value) { 127 this.value = value; 128 } 129 130 @Override 131 public boolean equals(final Object obj) { 132 if (!(obj instanceof MutableInteger)) { 133 return false; 134 } 135 return ((MutableInteger) obj).value == value; 136 } 137 138 @Override 139 public int hashCode() { 140 return value; 141 } 142 } 143 144 /** The map to use to store the data */ 145 private transient Map<E, MutableInteger> map; 146 147 /** The current total size of the bag */ 148 private int size; 149 150 /** The modification count for fail fast iterators */ 151 private transient int modCount; 152 153 /** Unique view of the elements */ 154 private transient Set<E> uniqueSet; 155 156 /** 157 * Constructor needed for subclass serialization. 158 */ 159 protected AbstractMapBag() { 160 } 161 162 /** 163 * Constructor that assigns the specified Map as the backing store. The map 164 * must be empty and non-null. 165 * 166 * @param map the map to assign 167 */ 168 protected AbstractMapBag(final Map<E, MutableInteger> map) { 169 this.map = Objects.requireNonNull(map, "map"); 170 } 171 172 /** 173 * Constructs a new instance that assigns the specified Map as the backing store. The map 174 * must be empty and non-null. The bag is filled from the iterable elements. 175 * 176 * @param map the map to assign. 177 * @param iterable The bag is filled from these iterable elements. 178 */ 179 protected AbstractMapBag(final Map<E, MutableInteger> map, final Iterable<? extends E> iterable) { 180 this(map); 181 iterable.forEach(this::add); 182 } 183 184 /** 185 * Adds a new element to the bag, incrementing its count in the underlying map. 186 * 187 * @param object the object to add 188 * @return {@code true} if the object was not already in the {@code uniqueSet} 189 */ 190 @Override 191 public boolean add(final E object) { 192 return add(object, 1); 193 } 194 195 /** 196 * Adds a new element to the bag, incrementing its count in the map. 197 * 198 * @param object the object to search for 199 * @param nCopies the number of copies to add 200 * @return {@code true} if the object was not already in the {@code uniqueSet} 201 */ 202 @Override 203 public boolean add(final E object, final int nCopies) { 204 modCount++; 205 if (nCopies > 0) { 206 final MutableInteger mut = map.get(object); 207 size += nCopies; 208 if (mut == null) { 209 map.put(object, new MutableInteger(nCopies)); 210 return true; 211 } 212 mut.value += nCopies; 213 } 214 return false; 215 } 216 217 /** 218 * Invokes {@link #add(Object)} for each element in the given collection. 219 * 220 * @param coll the collection to add 221 * @return {@code true} if this call changed the bag 222 */ 223 @Override 224 public boolean addAll(final Collection<? extends E> coll) { 225 boolean changed = false; 226 for (final E current : coll) { 227 final boolean added = add(current); 228 changed = changed || added; 229 } 230 return changed; 231 } 232 233 /** 234 * Clears the bag by clearing the underlying map. 235 */ 236 @Override 237 public void clear() { 238 modCount++; 239 map.clear(); 240 size = 0; 241 } 242 243 /** 244 * Determines if the bag contains the given element by checking if the 245 * underlying map contains the element as a key. 246 * 247 * @param object the object to search for 248 * @return true if the bag contains the given element 249 */ 250 @Override 251 public boolean contains(final Object object) { 252 return map.containsKey(object); 253 } 254 255 /** 256 * Returns {@code true} if the bag contains all elements in the given 257 * collection, respecting cardinality. 258 * 259 * @param other the bag to check against 260 * @return {@code true} if the Bag contains all the collection 261 */ 262 boolean containsAll(final Bag<?> other) { 263 for (final Object current : other.uniqueSet()) { 264 if (getCount(current) < other.getCount(current)) { 265 return false; 266 } 267 } 268 return true; 269 } 270 271 /** 272 * Determines if the bag contains the given elements. 273 * 274 * @param coll the collection to check against 275 * @return {@code true} if the Bag contains all the collection 276 */ 277 @Override 278 public boolean containsAll(final Collection<?> coll) { 279 if (coll instanceof Bag) { 280 return containsAll((Bag<?>) coll); 281 } 282 return containsAll(new HashBag<>(coll)); 283 } 284 285 /** 286 * Reads the map in using a custom routine. 287 * 288 * @param map the map to use 289 * @param in the input stream 290 * @throws IOException any of the usual I/O related exceptions 291 * @throws ClassNotFoundException if the stream contains an object which class cannot be loaded 292 * @throws ClassCastException if the stream does not contain the correct objects 293 */ 294 protected void doReadObject(final Map<E, MutableInteger> map, final ObjectInputStream in) 295 throws IOException, ClassNotFoundException { 296 this.map = map; 297 final int entrySize = in.readInt(); 298 for (int i = 0; i < entrySize; i++) { 299 @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect 300 final E obj = (E) in.readObject(); 301 final int count = in.readInt(); 302 map.put(obj, new MutableInteger(count)); 303 size += count; 304 } 305 } 306 307 /** 308 * Writes the map out using a custom routine. 309 * 310 * @param out the output stream 311 * @throws IOException any of the usual I/O related exceptions 312 */ 313 protected void doWriteObject(final ObjectOutputStream out) throws IOException { 314 out.writeInt(map.size()); 315 for (final Entry<E, MutableInteger> entry : map.entrySet()) { 316 out.writeObject(entry.getKey()); 317 out.writeInt(entry.getValue().value); 318 } 319 } 320 321 /** 322 * Compares this Bag to another. This Bag equals another Bag if it contains 323 * the same number of occurrences of the same elements. 324 * 325 * @param object the Bag to compare to 326 * @return true if equal 327 */ 328 @Override 329 public boolean equals(final Object object) { 330 if (object == this) { 331 return true; 332 } 333 if (!(object instanceof Bag)) { 334 return false; 335 } 336 final Bag<?> other = (Bag<?>) object; 337 if (other.size() != size()) { 338 return false; 339 } 340 for (final E element : map.keySet()) { 341 if (other.getCount(element) != getCount(element)) { 342 return false; 343 } 344 } 345 return true; 346 } 347 348 /** 349 * Gets the number of occurrence of the given element in this bag by 350 * looking up its count in the underlying map. 351 * 352 * @param object the object to search for 353 * @return the number of occurrences of the object, zero if not found 354 */ 355 @Override 356 public int getCount(final Object object) { 357 final MutableInteger count = map.get(object); 358 if (count != null) { 359 return count.value; 360 } 361 return 0; 362 } 363 364 /** 365 * Utility method for implementations to access the map that backs this bag. 366 * Not intended for interactive use outside of subclasses. 367 * 368 * @return the map being used by the Bag 369 */ 370 protected Map<E, MutableInteger> getMap() { 371 return map; 372 } 373 374 /** 375 * Gets a hash code for the Bag compatible with the definition of equals. 376 * The hash code is defined as the sum total of a hash code for each 377 * element. The per element hash code is defined as 378 * {@code (e==null ? 0 : e.hashCode()) ^ noOccurrences)}. This hash code 379 * is compatible with the Set interface. 380 * 381 * @return the hash code of the Bag 382 */ 383 @Override 384 public int hashCode() { 385 int total = 0; 386 for (final Entry<E, MutableInteger> entry : map.entrySet()) { 387 final E element = entry.getKey(); 388 final MutableInteger count = entry.getValue(); 389 total += (element == null ? 0 : element.hashCode()) ^ count.value; 390 } 391 return total; 392 } 393 394 /** 395 * Returns true if the underlying map is empty. 396 * 397 * @return true if bag is empty 398 */ 399 @Override 400 public boolean isEmpty() { 401 return map.isEmpty(); 402 } 403 404 /** 405 * Gets an iterator over the bag elements. Elements present in the Bag more 406 * than once will be returned repeatedly. 407 * 408 * @return the iterator 409 */ 410 @Override 411 public Iterator<E> iterator() { 412 return new BagIterator<>(this); 413 } 414 415 /** 416 * Removes all copies of the specified object from the bag. 417 * 418 * @param object the object to remove 419 * @return true if the bag changed 420 */ 421 @Override 422 public boolean remove(final Object object) { 423 final MutableInteger mut = map.get(object); 424 if (mut == null) { 425 return false; 426 } 427 modCount++; 428 map.remove(object); 429 size -= mut.value; 430 return true; 431 } 432 433 /** 434 * Removes a specified number of copies of an object from the bag. 435 * 436 * @param object the object to remove 437 * @param nCopies the number of copies to remove 438 * @return true if the bag changed 439 */ 440 @Override 441 public boolean remove(final Object object, final int nCopies) { 442 final MutableInteger mut = map.get(object); 443 if (mut == null) { 444 return false; 445 } 446 if (nCopies <= 0) { 447 return false; 448 } 449 modCount++; 450 if (nCopies < mut.value) { 451 mut.value -= nCopies; 452 size -= nCopies; 453 } else { 454 map.remove(object); 455 size -= mut.value; 456 } 457 return true; 458 } 459 460 /** 461 * Removes objects from the bag according to their count in the specified 462 * collection. 463 * 464 * @param coll the collection to use 465 * @return true if the bag changed 466 */ 467 @Override 468 public boolean removeAll(final Collection<?> coll) { 469 boolean result = false; 470 if (coll != null) { 471 for (final Object current : coll) { 472 final boolean changed = remove(current, 1); 473 result = result || changed; 474 } 475 } 476 return result; 477 } 478 479 /** 480 * Remove any members of the bag that are not in the given bag, respecting 481 * cardinality. 482 * @see #retainAll(Collection) 483 * @param other the bag to retain 484 * @return {@code true} if this call changed the collection 485 */ 486 boolean retainAll(final Bag<?> other) { 487 boolean result = false; 488 final Bag<E> excess = new HashBag<>(); 489 for (final E current : uniqueSet()) { 490 final int myCount = getCount(current); 491 final int otherCount = other.getCount(current); 492 if (1 <= otherCount && otherCount <= myCount) { 493 excess.add(current, myCount - otherCount); 494 } else { 495 excess.add(current, myCount); 496 } 497 } 498 if (!excess.isEmpty()) { 499 result = removeAll(excess); 500 } 501 return result; 502 } 503 504 /** 505 * Remove any members of the bag that are not in the given bag, respecting 506 * cardinality. 507 * 508 * @param coll the collection to retain 509 * @return true if this call changed the collection 510 */ 511 @Override 512 public boolean retainAll(final Collection<?> coll) { 513 if (coll instanceof Bag) { 514 return retainAll((Bag<?>) coll); 515 } 516 return retainAll(new HashBag<>(coll)); 517 } 518 519 /** 520 * Returns the number of elements in this bag. 521 * 522 * @return current size of the bag 523 */ 524 @Override 525 public int size() { 526 return size; 527 } 528 529 /** 530 * Returns an array of all of this bag's elements. 531 * 532 * @return an array of all of this bag's elements 533 */ 534 @Override 535 public Object[] toArray() { 536 final Object[] result = new Object[size()]; 537 int i = 0; 538 for (final E current : map.keySet()) { 539 for (int index = getCount(current); index > 0; index--) { 540 result[i++] = current; 541 } 542 } 543 return result; 544 } 545 546 /** 547 * Returns an array of all of this bag's elements. 548 * If the input array has more elements than are in the bag, 549 * trailing elements will be set to null. 550 * 551 * @param <T> the type of the array elements 552 * @param array the array to populate 553 * @return an array of all of this bag's elements 554 * @throws ArrayStoreException if the runtime type of the specified array is not 555 * a supertype of the runtime type of the elements in this list 556 * @throws NullPointerException if the specified array is null 557 */ 558 @Override 559 public <T> T[] toArray(T[] array) { 560 final int size = size(); 561 if (array.length < size) { 562 @SuppressWarnings("unchecked") // safe as both are of type T 563 final T[] unchecked = (T[]) Array.newInstance(array.getClass().getComponentType(), size); 564 array = unchecked; 565 } 566 567 int i = 0; 568 for (final E current : map.keySet()) { 569 for (int index = getCount(current); index > 0; index--) { 570 // unsafe, will throw ArrayStoreException if types are not compatible, see Javadoc 571 @SuppressWarnings("unchecked") 572 final T unchecked = (T) current; 573 array[i++] = unchecked; 574 } 575 } 576 while (i < array.length) { 577 array[i++] = null; 578 } 579 return array; 580 } 581 582 /** 583 * Implement a toString() method suitable for debugging. 584 * 585 * @return a debugging toString 586 */ 587 @Override 588 public String toString() { 589 if (isEmpty()) { 590 return "[]"; 591 } 592 final StringBuilder buf = new StringBuilder(); 593 buf.append(CollectionUtils.DEFAULT_TOSTRING_PREFIX); 594 final Iterator<E> it = uniqueSet().iterator(); 595 while (it.hasNext()) { 596 final Object current = it.next(); 597 final int count = getCount(current); 598 buf.append(count); 599 buf.append(CollectionUtils.COLON); 600 buf.append(current); 601 if (it.hasNext()) { 602 buf.append(CollectionUtils.COMMA); 603 } 604 } 605 buf.append(CollectionUtils.DEFAULT_TOSTRING_SUFFIX); 606 return buf.toString(); 607 } 608 609 /** 610 * Returns an unmodifiable view of the underlying map's key set. 611 * 612 * @return the set of unique elements in this bag 613 */ 614 @Override 615 public Set<E> uniqueSet() { 616 if (uniqueSet == null) { 617 uniqueSet = UnmodifiableSet.<E>unmodifiableSet(map.keySet()); 618 } 619 return uniqueSet; 620 } 621 622}