1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.collections4.bag;
18
19 import java.io.IOException;
20 import java.io.ObjectInputStream;
21 import java.io.ObjectOutputStream;
22 import java.lang.reflect.Array;
23 import java.util.Collection;
24 import java.util.ConcurrentModificationException;
25 import java.util.Iterator;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Objects;
29 import java.util.Set;
30
31 import org.apache.commons.collections4.Bag;
32 import org.apache.commons.collections4.CollectionUtils;
33 import org.apache.commons.collections4.set.UnmodifiableSet;
34
35 /**
36 * Abstract implementation of the {@link Bag} interface to simplify the creation
37 * of subclass implementations.
38 * <p>
39 * Subclasses specify a Map implementation to use as the internal storage. The
40 * map will be used to map bag elements to a number; the number represents the
41 * number of occurrences of that element in the bag.
42 * </p>
43 *
44 * @param <E> the type of elements in this bag
45 * @since 3.0 (previously DefaultMapBag v2.0)
46 */
47 public abstract class AbstractMapBag<E> implements Bag<E> {
48
49 /**
50 * Inner class iterator for the Bag.
51 */
52 static class BagIterator<E> implements Iterator<E> {
53 private final AbstractMapBag<E> parent;
54 private final Iterator<Map.Entry<E, MutableInteger>> entryIterator;
55 private Map.Entry<E, MutableInteger> current;
56 private int itemCount;
57 private final int mods;
58 private boolean canRemove;
59
60 /**
61 * Constructs a new instance.
62 *
63 * @param parent the parent bag
64 */
65 BagIterator(final AbstractMapBag<E> parent) {
66 this.parent = parent;
67 this.entryIterator = parent.map.entrySet().iterator();
68 this.current = null;
69 this.mods = parent.modCount;
70 this.canRemove = false;
71 }
72
73 /** {@inheritDoc} */
74 @Override
75 public boolean hasNext() {
76 return itemCount > 0 || entryIterator.hasNext();
77 }
78
79 /** {@inheritDoc} */
80 @Override
81 public E next() {
82 if (parent.modCount != mods) {
83 throw new ConcurrentModificationException();
84 }
85 if (itemCount == 0) {
86 current = entryIterator.next();
87 itemCount = current.getValue().value;
88 }
89 canRemove = true;
90 itemCount--;
91 return current.getKey();
92 }
93
94 /** {@inheritDoc} */
95 @Override
96 public void remove() {
97 if (parent.modCount != mods) {
98 throw new ConcurrentModificationException();
99 }
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 }