1. /*
  2. * Copyright 2002-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.collections.bag;
  17. import java.io.IOException;
  18. import java.io.ObjectInputStream;
  19. import java.io.ObjectOutputStream;
  20. import java.lang.reflect.Array;
  21. import java.util.Collection;
  22. import java.util.ConcurrentModificationException;
  23. import java.util.Iterator;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import org.apache.commons.collections.Bag;
  27. import org.apache.commons.collections.set.UnmodifiableSet;
  28. /**
  29. * Abstract implementation of the {@link Bag} interface to simplify the creation
  30. * of subclass implementations.
  31. * <p>
  32. * Subclasses specify a Map implementation to use as the internal storage.
  33. * The map will be used to map bag elements to a number; the number represents
  34. * the number of occurrences of that element in the bag.
  35. *
  36. * @since Commons Collections 3.0 (previously DefaultMapBag v2.0)
  37. * @version $Revision: 1.15 $ $Date: 2004/05/15 12:27:04 $
  38. *
  39. * @author Chuck Burdick
  40. * @author Michael A. Smith
  41. * @author Stephen Colebourne
  42. * @author Janek Bogucki
  43. */
  44. public abstract class AbstractMapBag implements Bag {
  45. /** The map to use to store the data */
  46. private transient Map map;
  47. /** The current total size of the bag */
  48. private int size;
  49. /** The modification count for fail fast iterators */
  50. private transient int modCount;
  51. /** The modification count for fail fast iterators */
  52. private transient Set uniqueSet;
  53. /**
  54. * Constructor needed for subclass serialisation.
  55. *
  56. */
  57. protected AbstractMapBag() {
  58. super();
  59. }
  60. /**
  61. * Constructor that assigns the specified Map as the backing store.
  62. * The map must be empty and non-null.
  63. *
  64. * @param map the map to assign
  65. */
  66. protected AbstractMapBag(Map map) {
  67. super();
  68. this.map = map;
  69. }
  70. /**
  71. * Utility method for implementations to access the map that backs
  72. * this bag. Not intended for interactive use outside of subclasses.
  73. *
  74. * @return the map being used by the Bag
  75. */
  76. protected Map getMap() {
  77. return map;
  78. }
  79. //-----------------------------------------------------------------------
  80. /**
  81. * Returns the number of elements in this bag.
  82. *
  83. * @return current size of the bag
  84. */
  85. public int size() {
  86. return size;
  87. }
  88. /**
  89. * Returns true if the underlying map is empty.
  90. *
  91. * @return true if bag is empty
  92. */
  93. public boolean isEmpty() {
  94. return map.isEmpty();
  95. }
  96. /**
  97. * Returns the number of occurrence of the given element in this bag
  98. * by looking up its count in the underlying map.
  99. *
  100. * @param object the object to search for
  101. * @return the number of occurrences of the object, zero if not found
  102. */
  103. public int getCount(Object object) {
  104. MutableInteger count = (MutableInteger) map.get(object);
  105. if (count != null) {
  106. return count.value;
  107. }
  108. return 0;
  109. }
  110. //-----------------------------------------------------------------------
  111. /**
  112. * Determines if the bag contains the given element by checking if the
  113. * underlying map contains the element as a key.
  114. *
  115. * @param object the object to search for
  116. * @return true if the bag contains the given element
  117. */
  118. public boolean contains(Object object) {
  119. return map.containsKey(object);
  120. }
  121. /**
  122. * Determines if the bag contains the given elements.
  123. *
  124. * @param coll the collection to check against
  125. * @return <code>true</code> if the Bag contains all the collection
  126. */
  127. public boolean containsAll(Collection coll) {
  128. if (coll instanceof Bag) {
  129. return containsAll((Bag) coll);
  130. }
  131. return containsAll(new HashBag(coll));
  132. }
  133. /**
  134. * Returns <code>true</code> if the bag contains all elements in
  135. * the given collection, respecting cardinality.
  136. *
  137. * @param other the bag to check against
  138. * @return <code>true</code> if the Bag contains all the collection
  139. */
  140. boolean containsAll(Bag other) {
  141. boolean result = true;
  142. Iterator it = other.uniqueSet().iterator();
  143. while (it.hasNext()) {
  144. Object current = it.next();
  145. boolean contains = getCount(current) >= other.getCount(current);
  146. result = result && contains;
  147. }
  148. return result;
  149. }
  150. //-----------------------------------------------------------------------
  151. /**
  152. * Gets an iterator over the bag elements.
  153. * Elements present in the Bag more than once will be returned repeatedly.
  154. *
  155. * @return the iterator
  156. */
  157. public Iterator iterator() {
  158. return new BagIterator(this);
  159. }
  160. /**
  161. * Inner class iterator for the Bag.
  162. */
  163. static class BagIterator implements Iterator {
  164. private AbstractMapBag parent;
  165. private Iterator entryIterator;
  166. private Map.Entry current;
  167. private int itemCount;
  168. private final int mods;
  169. private boolean canRemove;
  170. /**
  171. * Constructor.
  172. *
  173. * @param parent the parent bag
  174. */
  175. public BagIterator(AbstractMapBag parent) {
  176. this.parent = parent;
  177. this.entryIterator = parent.map.entrySet().iterator();
  178. this.current = null;
  179. this.mods = parent.modCount;
  180. this.canRemove = false;
  181. }
  182. public boolean hasNext() {
  183. return (itemCount > 0 || entryIterator.hasNext());
  184. }
  185. public Object next() {
  186. if (parent.modCount != mods) {
  187. throw new ConcurrentModificationException();
  188. }
  189. if (itemCount == 0) {
  190. current = (Map.Entry) entryIterator.next();
  191. itemCount = ((MutableInteger) current.getValue()).value;
  192. }
  193. canRemove = true;
  194. itemCount--;
  195. return current.getKey();
  196. }
  197. public void remove() {
  198. if (parent.modCount != mods) {
  199. throw new ConcurrentModificationException();
  200. }
  201. if (canRemove == false) {
  202. throw new IllegalStateException();
  203. }
  204. MutableInteger mut = (MutableInteger) current.getValue();
  205. if (mut.value > 0) {
  206. mut.value--;
  207. parent.size--;
  208. } else {
  209. entryIterator.remove();
  210. }
  211. canRemove = false;
  212. }
  213. }
  214. //-----------------------------------------------------------------------
  215. /**
  216. * Adds a new element to the bag, incrementing its count in the underlying map.
  217. *
  218. * @param object the object to add
  219. * @return <code>true</code> if the object was not already in the <code>uniqueSet</code>
  220. */
  221. public boolean add(Object object) {
  222. return add(object, 1);
  223. }
  224. /**
  225. * Adds a new element to the bag, incrementing its count in the map.
  226. *
  227. * @param object the object to search for
  228. * @param nCopies the number of copies to add
  229. * @return <code>true</code> if the object was not already in the <code>uniqueSet</code>
  230. */
  231. public boolean add(Object object, int nCopies) {
  232. modCount++;
  233. if (nCopies > 0) {
  234. MutableInteger mut = (MutableInteger) map.get(object);
  235. size += nCopies;
  236. if (mut == null) {
  237. map.put(object, new MutableInteger(nCopies));
  238. return true;
  239. } else {
  240. mut.value += nCopies;
  241. return false;
  242. }
  243. } else {
  244. return false;
  245. }
  246. }
  247. /**
  248. * Invokes {@link #add(Object)} for each element in the given collection.
  249. *
  250. * @param coll the collection to add
  251. * @return <code>true</code> if this call changed the bag
  252. */
  253. public boolean addAll(Collection coll) {
  254. boolean changed = false;
  255. Iterator i = coll.iterator();
  256. while (i.hasNext()) {
  257. boolean added = add(i.next());
  258. changed = changed || added;
  259. }
  260. return changed;
  261. }
  262. //-----------------------------------------------------------------------
  263. /**
  264. * Clears the bag by clearing the underlying map.
  265. */
  266. public void clear() {
  267. modCount++;
  268. map.clear();
  269. size = 0;
  270. }
  271. /**
  272. * Removes all copies of the specified object from the bag.
  273. *
  274. * @param object the object to remove
  275. * @return true if the bag changed
  276. */
  277. public boolean remove(Object object) {
  278. MutableInteger mut = (MutableInteger) map.get(object);
  279. if (mut == null) {
  280. return false;
  281. }
  282. modCount++;
  283. map.remove(object);
  284. size -= mut.value;
  285. return true;
  286. }
  287. /**
  288. * Removes a specified number of copies of an object from the bag.
  289. *
  290. * @param object the object to remove
  291. * @param nCopies the number of copies to remove
  292. * @return true if the bag changed
  293. */
  294. public boolean remove(Object object, int nCopies) {
  295. MutableInteger mut = (MutableInteger) map.get(object);
  296. if (mut == null) {
  297. return false;
  298. }
  299. if (nCopies <= 0) {
  300. return false;
  301. }
  302. modCount++;
  303. if (nCopies < mut.value) {
  304. mut.value -= nCopies;
  305. size -= nCopies;
  306. } else {
  307. map.remove(object);
  308. size -= mut.value;
  309. }
  310. return true;
  311. }
  312. /**
  313. * Removes objects from the bag according to their count in the specified collection.
  314. *
  315. * @param coll the collection to use
  316. * @return true if the bag changed
  317. */
  318. public boolean removeAll(Collection coll) {
  319. boolean result = false;
  320. if (coll != null) {
  321. Iterator i = coll.iterator();
  322. while (i.hasNext()) {
  323. boolean changed = remove(i.next(), 1);
  324. result = result || changed;
  325. }
  326. }
  327. return result;
  328. }
  329. /**
  330. * Remove any members of the bag that are not in the given
  331. * bag, respecting cardinality.
  332. *
  333. * @param coll the collection to retain
  334. * @return true if this call changed the collection
  335. */
  336. public boolean retainAll(Collection coll) {
  337. if (coll instanceof Bag) {
  338. return retainAll((Bag) coll);
  339. }
  340. return retainAll(new HashBag(coll));
  341. }
  342. /**
  343. * Remove any members of the bag that are not in the given
  344. * bag, respecting cardinality.
  345. * @see #retainAll(Collection)
  346. *
  347. * @param other the bag to retain
  348. * @return <code>true</code> if this call changed the collection
  349. */
  350. boolean retainAll(Bag other) {
  351. boolean result = false;
  352. Bag excess = new HashBag();
  353. Iterator i = uniqueSet().iterator();
  354. while (i.hasNext()) {
  355. Object current = i.next();
  356. int myCount = getCount(current);
  357. int otherCount = other.getCount(current);
  358. if (1 <= otherCount && otherCount <= myCount) {
  359. excess.add(current, myCount - otherCount);
  360. } else {
  361. excess.add(current, myCount);
  362. }
  363. }
  364. if (!excess.isEmpty()) {
  365. result = removeAll(excess);
  366. }
  367. return result;
  368. }
  369. //-----------------------------------------------------------------------
  370. /**
  371. * Mutable integer class for storing the data.
  372. */
  373. protected static class MutableInteger {
  374. /** The value of this mutable. */
  375. protected int value;
  376. /**
  377. * Constructor.
  378. * @param value the initial value
  379. */
  380. MutableInteger(int value) {
  381. this.value = value;
  382. }
  383. public boolean equals(Object obj) {
  384. if (obj instanceof MutableInteger == false) {
  385. return false;
  386. }
  387. return ((MutableInteger) obj).value == value;
  388. }
  389. public int hashCode() {
  390. return value;
  391. }
  392. }
  393. //-----------------------------------------------------------------------
  394. /**
  395. * Returns an array of all of this bag's elements.
  396. *
  397. * @return an array of all of this bag's elements
  398. */
  399. public Object[] toArray() {
  400. Object[] result = new Object[size()];
  401. int i = 0;
  402. Iterator it = map.keySet().iterator();
  403. while (it.hasNext()) {
  404. Object current = it.next();
  405. for (int index = getCount(current); index > 0; index--) {
  406. result[i++] = current;
  407. }
  408. }
  409. return result;
  410. }
  411. /**
  412. * Returns an array of all of this bag's elements.
  413. *
  414. * @param array the array to populate
  415. * @return an array of all of this bag's elements
  416. */
  417. public Object[] toArray(Object[] array) {
  418. int size = size();
  419. if (array.length < size) {
  420. array = (Object[]) Array.newInstance(array.getClass().getComponentType(), size);
  421. }
  422. int i = 0;
  423. Iterator it = map.keySet().iterator();
  424. while (it.hasNext()) {
  425. Object current = it.next();
  426. for (int index = getCount(current); index > 0; index--) {
  427. array[i++] = current;
  428. }
  429. }
  430. if (array.length > size) {
  431. array[size] = null;
  432. }
  433. return array;
  434. }
  435. /**
  436. * Returns an unmodifiable view of the underlying map's key set.
  437. *
  438. * @return the set of unique elements in this bag
  439. */
  440. public Set uniqueSet() {
  441. if (uniqueSet == null) {
  442. uniqueSet = UnmodifiableSet.decorate(map.keySet());
  443. }
  444. return uniqueSet;
  445. }
  446. //-----------------------------------------------------------------------
  447. /**
  448. * Write the map out using a custom routine.
  449. * @param out the output stream
  450. * @throws IOException
  451. */
  452. protected void doWriteObject(ObjectOutputStream out) throws IOException {
  453. out.writeInt(map.size());
  454. for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
  455. Map.Entry entry = (Map.Entry) it.next();
  456. out.writeObject(entry.getKey());
  457. out.writeInt(((MutableInteger) entry.getValue()).value);
  458. }
  459. }
  460. /**
  461. * Read the map in using a custom routine.
  462. * @param map the map to use
  463. * @param in the input stream
  464. * @throws IOException
  465. * @throws ClassNotFoundException
  466. */
  467. protected void doReadObject(Map map, ObjectInputStream in) throws IOException, ClassNotFoundException {
  468. this.map = map;
  469. int entrySize = in.readInt();
  470. for (int i = 0; i < entrySize; i++) {
  471. Object obj = in.readObject();
  472. int count = in.readInt();
  473. map.put(obj, new MutableInteger(count));
  474. size += count;
  475. }
  476. }
  477. //-----------------------------------------------------------------------
  478. /**
  479. * Compares this Bag to another.
  480. * This Bag equals another Bag if it contains the same number of occurrences of
  481. * the same elements.
  482. *
  483. * @param object the Bag to compare to
  484. * @return true if equal
  485. */
  486. public boolean equals(Object object) {
  487. if (object == this) {
  488. return true;
  489. }
  490. if (object instanceof Bag == false) {
  491. return false;
  492. }
  493. Bag other = (Bag) object;
  494. if (other.size() != size()) {
  495. return false;
  496. }
  497. for (Iterator it = map.keySet().iterator(); it.hasNext();) {
  498. Object element = it.next();
  499. if (other.getCount(element) != getCount(element)) {
  500. return false;
  501. }
  502. }
  503. return true;
  504. }
  505. /**
  506. * Gets a hash code for the Bag compatible with the definition of equals.
  507. * The hash code is defined as the sum total of a hash code for each element.
  508. * The per element hash code is defined as
  509. * <code>(e==null ? 0 : e.hashCode()) ^ noOccurances)</code>.
  510. * This hash code is compatible with the Set interface.
  511. *
  512. * @return the hash code of the Bag
  513. */
  514. public int hashCode() {
  515. int total = 0;
  516. for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
  517. Map.Entry entry = (Map.Entry) it.next();
  518. Object element = entry.getKey();
  519. MutableInteger count = (MutableInteger) entry.getValue();
  520. total += (element == null ? 0 : element.hashCode()) ^ count.value;
  521. }
  522. return total;
  523. }
  524. /**
  525. * Implement a toString() method suitable for debugging.
  526. *
  527. * @return a debugging toString
  528. */
  529. public String toString() {
  530. if (size() == 0) {
  531. return "[]";
  532. }
  533. StringBuffer buf = new StringBuffer();
  534. buf.append('[');
  535. Iterator it = uniqueSet().iterator();
  536. while (it.hasNext()) {
  537. Object current = it.next();
  538. int count = getCount(current);
  539. buf.append(count);
  540. buf.append(':');
  541. buf.append(current);
  542. if (it.hasNext()) {
  543. buf.append(',');
  544. }
  545. }
  546. buf.append(']');
  547. return buf.toString();
  548. }
  549. }