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