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