package com.sap.runlet.abstractinterpreter.util; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * Implementation of the {@link Bag} interface. */ public class HashBag<T> implements Bag<T>, Cloneable { /** The backing map of objects to their count. */ private final HashMap<T, Integer> elementCountMap; /** * Creates a Bag with no entries. */ public HashBag() { elementCountMap = new HashMap<T, Integer>(); } /** * Creates a Bag with all the entries from the collection c. * * @param c * The collection to populate the bag with. */ public HashBag(Collection<T> c) { this(); addAll(c); } private HashBag(HashMap<T, Integer> elementCountMap) { this.elementCountMap = elementCountMap; } @SuppressWarnings("unchecked") public HashBag<T> clone() { HashBag<T> result = new HashBag<T>((HashMap<T, Integer>) elementCountMap.clone()); return result; } /** * Returns the number of times o appears in the bag. * * @param o * The entry to count. * @return The number of times o appears in the bag, or 0 if it is not there. */ public int count(T o) { Integer value = this.elementCountMap.get(o); return value != null ? value.intValue() : 0; } /** * Adds an object to the bag. * * @param o * The object to add. * @return Always <code>true</code> to indicate that that bag has changed. */ public boolean add(T o) { Integer value; if (this.elementCountMap.containsKey(o)) { value = this.elementCountMap.get(o); value = Integer.valueOf(value.intValue() + 1); } else { value = Integer.valueOf(1); } this.elementCountMap.put(o, value); return true; } /** * Adds a collection of objects to the bag. * * @param c * The collection containing the objects to add. * @return <code>true</code> when the bag is changed. This is only false if c is empty. */ public boolean addAll(Collection<? extends T> c) { Iterator<? extends T> i = c.iterator(); while (i.hasNext()) { add(i.next()); } return !c.isEmpty(); } /** * Clears all entries from the bag. */ public void clear() { this.elementCountMap.clear(); } /** * Checks if an object is in the bag. * * @return <code>true</code> if there are one or more instances of o in this bag. */ public boolean contains(Object o) { return this.elementCountMap.containsKey(o); } /** * Returns the hash code value for this bag. * * @return A relatively unique value representing the entries. */ @Override public int hashCode() { return this.elementCountMap.hashCode(); } /** * The size of the collection. * * @return The total number of entries in the bag, counting duplicates. */ public int size() { Iterator<Integer> i = this.elementCountMap.values().iterator(); int total = 0; while (i.hasNext()) { total += i.next().intValue(); } return total; } /** * Checks if the bag has any entries. * * @return <code>true</code> if there are no entries in the bag. */ public boolean isEmpty() { return this.elementCountMap.isEmpty(); } /** * Tests if the bag is equal to another {@link Bag}. The other Bag need not be of this class, * but it must meet the same semantics. * * @return <code>true</code> if o is a bag with identical entries. */ @SuppressWarnings("unchecked") @Override public boolean equals(Object o) { if (!(o instanceof Bag)) { return false; } Bag<T> b = (Bag<T>) o; if (b.size() != size()) { return false; } Iterator<T> i = b.iterator(); while (i.hasNext()) { T entry = i.next(); if (b.count(entry) != count(entry)) { return false; } } return true; } /** * Removes a single instance of an object from the bag, if one exists. * * @return <code>true</code> if an element was removed. */ @SuppressWarnings("unchecked") public boolean remove(Object o) { Integer value = this.elementCountMap.get(o); if (value == null) { return false; } int intValue = value.intValue() - 1; if (intValue == 0) { this.elementCountMap.remove(o); } else { this.elementCountMap.put((T) o, Integer.valueOf(intValue)); } return true; } /** * Tests if this bag contains all the elements in another collection. The number of elements in * this bag and the collection is not considered. * * @return <code>true</code> if every element of <em>a</em> is found in this bag. */ public boolean containsAll(Collection<?> a) { for (Object key : a) { if (!this.elementCountMap.containsKey(key)) { return false; } } return true; } /** * Removes all elements from <em>a</em> from this bag. At the end of this operation the bag * will not contain any elements that are in <em>a</em>. * * @return <code>true</code> if this bag was modified. */ public boolean removeAll(Collection<?> a) { boolean changed = false; for (Object key : a) { if (this.elementCountMap.remove(key) != null) { changed = true; } } return changed; } /** * Removes all entries which are not found in <em>a</em>. Duplicates in this bag which only * appear once in <em>a</em> will be left alone. * * @return <code>true</code> if this bag was modified. */ public boolean retainAll(Collection<?> a) { boolean changed = false; Iterator<T> i = this.elementCountMap.keySet().iterator(); while (i.hasNext()) { Object key = i.next(); if (!a.contains(key)) { i.remove(); changed = true; } } return changed; } /** * Gets an iterator for this bag. Identical elements are returned in groups. * * @return A new iterator. */ public Iterator<T> iterator() { return new BagIterator<T>(this.elementCountMap.entrySet().iterator()); } /** * Converts the entries in this bag to an array. * * @return An array of objects with all the elements from the bag. */ public Object[] toArray() { return toArrayList().toArray(); } /** * Converts the entries in this bag to an array. * * @param a * The array to populate. * @return The populated array. */ @SuppressWarnings("unchecked") public Object[] toArray(Object[] a) { return toArrayList().toArray(a); } /** * Helper method to convert the contents to an array. * * @return An {@link ArrayList} containing the elements of this bag. */ private ArrayList<T> toArrayList() { ArrayList<T> array = new ArrayList<T>(size()); Iterator<Map.Entry<T, Integer>> i = this.elementCountMap.entrySet().iterator(); while (i.hasNext()) { Map.Entry<T, Integer> entry = i.next(); T key = entry.getKey(); int entries = entry.getValue().intValue(); for (int n = 0; n < entries; n++) { array.add(key); } } return array; } /** * toString defined on java.lang.Object. */ @Override public String toString() { return this.elementCountMap.toString(); } /** * An {@link java.util.Iterator} implementation to return the elements of the current bag. */ private static final class BagIterator<T> implements Iterator<T> { // an iterator for the internal map private Iterator<Map.Entry<T, Integer>> elementCountMapIterator; // The current entry in the map private Map.Entry<T, Integer> entry; // The current position within the group of identical elements, // starting at 1 private int entryPosition; // The number of identical elements for the current entry private int entrySize; /** * Default constructor. Picks up an iterator from the map in the encapsulating class. */ BagIterator(Iterator<Map.Entry<T, Integer>> elementCountMapIterator) { this.elementCountMapIterator = elementCountMapIterator; this.entry = null; this.entrySize = 0; this.entryPosition = 1; } /** * Removes the current element. Should not be needed. */ public void remove() { if (--this.entrySize == 0) { this.elementCountMapIterator.remove(); } else { this.entry.setValue(Integer.valueOf(this.entrySize)); } } /** * Tests if there are more elements in the collection. * * @return <code>true</code> if there are more elements in the collection. */ public boolean hasNext() { return this.entryPosition < this.entrySize || this.elementCountMapIterator.hasNext(); } /** * Retrieves the next element. * * @return the next element from the collection. */ public T next() { if (++this.entryPosition > this.entrySize) { this.entry = this.elementCountMapIterator.next(); this.entrySize = (this.entry.getValue()).intValue(); this.entryPosition = 1; } return this.entry.getKey(); } } }