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;
  17. import java.util.ArrayList;
  18. import java.util.Collection;
  19. import java.util.ConcurrentModificationException;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Set;
  24. import org.apache.commons.collections.set.UnmodifiableSet;
  25. /**
  26. * A skeletal implementation of the {@link Bag}
  27. * interface to minimize the effort required for target implementations.
  28. * Subclasses need only to call <code>setMap(Map)</code> in their constructor
  29. * (or invoke the Map constructor) specifying a map instance that will be used
  30. * to store the contents of the bag.
  31. * <p>
  32. * The map will be used to map bag elements to a number; the number represents
  33. * the number of occurrences of that element in the bag.
  34. *
  35. * @deprecated Moved to bag subpackage as AbstractMapBag. Due to be removed in v4.0.
  36. * @since Commons Collections 2.0
  37. * @version $Revision: 1.17 $ $Date: 2004/02/18 01:15:42 $
  38. *
  39. * @author Chuck Burdick
  40. * @author Michael A. Smith
  41. * @author Stephen Colebourne
  42. * @author Janek Bogucki
  43. */
  44. public abstract class DefaultMapBag implements Bag {
  45. private Map _map = null;
  46. private int _total = 0;
  47. private int _mods = 0;
  48. /**
  49. * No-argument constructor.
  50. * Subclasses should invoke <code>setMap(Map)</code> in
  51. * their constructors.
  52. */
  53. public DefaultMapBag() {
  54. }
  55. /**
  56. * Constructor that assigns the specified Map as the backing store.
  57. * The map must be empty.
  58. *
  59. * @param map the map to assign
  60. */
  61. protected DefaultMapBag(Map map) {
  62. setMap(map);
  63. }
  64. /**
  65. * Adds a new element to the bag by incrementing its count in the
  66. * underlying map.
  67. *
  68. * @param object the object to add
  69. * @return <code>true</code> if the object was not already in the <code>uniqueSet</code>
  70. */
  71. public boolean add(Object object) {
  72. return add(object, 1);
  73. }
  74. /**
  75. * Adds a new element to the bag by incrementing its count in the map.
  76. *
  77. * @param object the object to search for
  78. * @param nCopies the number of copies to add
  79. * @return <code>true</code> if the object was not already in the <code>uniqueSet</code>
  80. */
  81. public boolean add(Object object, int nCopies) {
  82. _mods++;
  83. if (nCopies > 0) {
  84. int count = (nCopies + getCount(object));
  85. _map.put(object, new Integer(count));
  86. _total += nCopies;
  87. return (count == nCopies);
  88. } else {
  89. return false;
  90. }
  91. }
  92. /**
  93. * Invokes {@link #add(Object)} for each element in the given collection.
  94. *
  95. * @param coll the collection to add
  96. * @return <code>true</code> if this call changed the bag
  97. */
  98. public boolean addAll(Collection coll) {
  99. boolean changed = false;
  100. Iterator i = coll.iterator();
  101. while (i.hasNext()) {
  102. boolean added = add(i.next());
  103. changed = changed || added;
  104. }
  105. return changed;
  106. }
  107. /**
  108. * Clears the bag by clearing the underlying map.
  109. */
  110. public void clear() {
  111. _mods++;
  112. _map.clear();
  113. _total = 0;
  114. }
  115. /**
  116. * Determines if the bag contains the given element by checking if the
  117. * underlying map contains the element as a key.
  118. *
  119. * @param object the object to search for
  120. * @return true if the bag contains the given element
  121. */
  122. public boolean contains(Object object) {
  123. return _map.containsKey(object);
  124. }
  125. /**
  126. * Determines if the bag contains the given elements.
  127. *
  128. * @param coll the collection to check against
  129. * @return <code>true</code> if the Bag contains all the collection
  130. */
  131. public boolean containsAll(Collection coll) {
  132. return containsAll(new HashBag(coll));
  133. }
  134. /**
  135. * Returns <code>true</code> if the bag contains all elements in
  136. * the given collection, respecting cardinality.
  137. *
  138. * @param other the bag to check against
  139. * @return <code>true</code> if the Bag contains all the collection
  140. */
  141. public boolean containsAll(Bag other) {
  142. boolean result = true;
  143. Iterator i = other.uniqueSet().iterator();
  144. while (i.hasNext()) {
  145. Object current = i.next();
  146. boolean contains = getCount(current) >= other.getCount(current);
  147. result = result && contains;
  148. }
  149. return result;
  150. }
  151. /**
  152. * Returns true if the given object is not null, has the precise type
  153. * of this bag, and contains the same number of occurrences of all the
  154. * same elements.
  155. *
  156. * @param object the object to test for equality
  157. * @return true if that object equals this bag
  158. */
  159. public boolean equals(Object object) {
  160. if (object == this) {
  161. return true;
  162. }
  163. if (object instanceof Bag == false) {
  164. return false;
  165. }
  166. Bag other = (Bag) object;
  167. if (other.size() != size()) {
  168. return false;
  169. }
  170. for (Iterator it = _map.keySet().iterator(); it.hasNext();) {
  171. Object element = (Object) it.next();
  172. if (other.getCount(element) != getCount(element)) {
  173. return false;
  174. }
  175. }
  176. return true;
  177. }
  178. /**
  179. * Returns the hash code of the underlying map.
  180. *
  181. * @return the hash code of the underlying map
  182. */
  183. public int hashCode() {
  184. return _map.hashCode();
  185. }
  186. /**
  187. * Returns true if the underlying map is empty.
  188. *
  189. * @return true if there are no elements in this bag
  190. */
  191. public boolean isEmpty() {
  192. return _map.isEmpty();
  193. }
  194. public Iterator iterator() {
  195. return new BagIterator(this, extractList().iterator());
  196. }
  197. static class BagIterator implements Iterator {
  198. private DefaultMapBag _parent = null;
  199. private Iterator _support = null;
  200. private Object _current = null;
  201. private int _mods = 0;
  202. public BagIterator(DefaultMapBag parent, Iterator support) {
  203. _parent = parent;
  204. _support = support;
  205. _current = null;
  206. _mods = parent.modCount();
  207. }
  208. public boolean hasNext() {
  209. return _support.hasNext();
  210. }
  211. public Object next() {
  212. if (_parent.modCount() != _mods) {
  213. throw new ConcurrentModificationException();
  214. }
  215. _current = _support.next();
  216. return _current;
  217. }
  218. public void remove() {
  219. if (_parent.modCount() != _mods) {
  220. throw new ConcurrentModificationException();
  221. }
  222. _support.remove();
  223. _parent.remove(_current, 1);
  224. _mods++;
  225. }
  226. }
  227. public boolean remove(Object object) {
  228. return remove(object, getCount(object));
  229. }
  230. public boolean remove(Object object, int nCopies) {
  231. _mods++;
  232. boolean result = false;
  233. int count = getCount(object);
  234. if (nCopies <= 0) {
  235. result = false;
  236. } else if (count > nCopies) {
  237. _map.put(object, new Integer(count - nCopies));
  238. result = true;
  239. _total -= nCopies;
  240. } else { // count > 0 && count <= i
  241. // need to remove all
  242. result = (_map.remove(object) != null);
  243. _total -= count;
  244. }
  245. return result;
  246. }
  247. public boolean removeAll(Collection coll) {
  248. boolean result = false;
  249. if (coll != null) {
  250. Iterator i = coll.iterator();
  251. while (i.hasNext()) {
  252. boolean changed = remove(i.next(), 1);
  253. result = result || changed;
  254. }
  255. }
  256. return result;
  257. }
  258. /**
  259. * Remove any members of the bag that are not in the given
  260. * bag, respecting cardinality.
  261. *
  262. * @param coll the collection to retain
  263. * @return true if this call changed the collection
  264. */
  265. public boolean retainAll(Collection coll) {
  266. return retainAll(new HashBag(coll));
  267. }
  268. /**
  269. * Remove any members of the bag that are not in the given
  270. * bag, respecting cardinality.
  271. * @see #retainAll(Collection)
  272. *
  273. * @param other the bag to retain
  274. * @return <code>true</code> if this call changed the collection
  275. */
  276. public boolean retainAll(Bag other) {
  277. boolean result = false;
  278. Bag excess = new HashBag();
  279. Iterator i = uniqueSet().iterator();
  280. while (i.hasNext()) {
  281. Object current = i.next();
  282. int myCount = getCount(current);
  283. int otherCount = other.getCount(current);
  284. if (1 <= otherCount && otherCount <= myCount) {
  285. excess.add(current, myCount - otherCount);
  286. } else {
  287. excess.add(current, myCount);
  288. }
  289. }
  290. if (!excess.isEmpty()) {
  291. result = removeAll(excess);
  292. }
  293. return result;
  294. }
  295. /**
  296. * Returns an array of all of this bag's elements.
  297. *
  298. * @return an array of all of this bag's elements
  299. */
  300. public Object[] toArray() {
  301. return extractList().toArray();
  302. }
  303. /**
  304. * Returns an array of all of this bag's elements.
  305. *
  306. * @param array the array to populate
  307. * @return an array of all of this bag's elements
  308. */
  309. public Object[] toArray(Object[] array) {
  310. return extractList().toArray(array);
  311. }
  312. /**
  313. * Returns the number of occurrence of the given element in this bag
  314. * by looking up its count in the underlying map.
  315. *
  316. * @param object the object to search for
  317. * @return the number of occurrences of the object, zero if not found
  318. */
  319. public int getCount(Object object) {
  320. int result = 0;
  321. Integer count = MapUtils.getInteger(_map, object);
  322. if (count != null) {
  323. result = count.intValue();
  324. }
  325. return result;
  326. }
  327. /**
  328. * Returns an unmodifiable view of the underlying map's key set.
  329. *
  330. * @return the set of unique elements in this bag
  331. */
  332. public Set uniqueSet() {
  333. return UnmodifiableSet.decorate(_map.keySet());
  334. }
  335. /**
  336. * Returns the number of elements in this bag.
  337. *
  338. * @return the number of elements in this bag
  339. */
  340. public int size() {
  341. return _total;
  342. }
  343. /**
  344. * Actually walks the bag to make sure the count is correct and
  345. * resets the running total
  346. *
  347. * @return the current total size
  348. */
  349. protected int calcTotalSize() {
  350. _total = extractList().size();
  351. return _total;
  352. }
  353. /**
  354. * Utility method for implementations to set the map that backs
  355. * this bag. Not intended for interactive use outside of
  356. * subclasses.
  357. */
  358. protected void setMap(Map map) {
  359. if (map == null || map.isEmpty() == false) {
  360. throw new IllegalArgumentException("The map must be non-null and empty");
  361. }
  362. _map = map;
  363. }
  364. /**
  365. * Utility method for implementations to access the map that backs
  366. * this bag. Not intended for interactive use outside of
  367. * subclasses.
  368. */
  369. protected Map getMap() {
  370. return _map;
  371. }
  372. /**
  373. * Create a list for use in iteration, etc.
  374. */
  375. private List extractList() {
  376. List result = new ArrayList();
  377. Iterator i = uniqueSet().iterator();
  378. while (i.hasNext()) {
  379. Object current = i.next();
  380. for (int index = getCount(current); index > 0; index--) {
  381. result.add(current);
  382. }
  383. }
  384. return result;
  385. }
  386. /**
  387. * Return number of modifications for iterator.
  388. *
  389. * @return the modification count
  390. */
  391. private int modCount() {
  392. return _mods;
  393. }
  394. /**
  395. * Implement a toString() method suitable for debugging.
  396. *
  397. * @return a debugging toString
  398. */
  399. public String toString() {
  400. StringBuffer buf = new StringBuffer();
  401. buf.append("[");
  402. Iterator i = uniqueSet().iterator();
  403. while (i.hasNext()) {
  404. Object current = i.next();
  405. int count = getCount(current);
  406. buf.append(count);
  407. buf.append(":");
  408. buf.append(current);
  409. if (i.hasNext()) {
  410. buf.append(",");
  411. }
  412. }
  413. buf.append("]");
  414. return buf.toString();
  415. }
  416. }