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