1. /*
  2. * Copyright 2001-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.io.IOException;
  18. import java.io.ObjectInputStream;
  19. import java.util.AbstractCollection;
  20. import java.util.ArrayList;
  21. import java.util.Collection;
  22. import java.util.HashMap;
  23. import java.util.Iterator;
  24. import java.util.Map;
  25. import java.util.NoSuchElementException;
  26. import java.util.Set;
  27. import org.apache.commons.collections.iterators.EmptyIterator;
  28. /**
  29. * <code>MultiHashMap</code> is the default implementation of the
  30. * {@link org.apache.commons.collections.MultiMap MultiMap} interface.
  31. * <p>
  32. * A <code>MultiMap</code> is a Map with slightly different semantics.
  33. * Putting a value into the map will add the value to a Collection at that key.
  34. * Getting a value will return a Collection, holding all the values put to that key.
  35. * <p>
  36. * This implementation uses an <code>ArrayList</code> as the collection.
  37. * The internal storage list is made available without cloning via the
  38. * <code>get(Object)</code> and <code>entrySet()</code> methods.
  39. * The implementation returns <code>null</code> when there are no values mapped to a key.
  40. * <p>
  41. * For example:
  42. * <pre>
  43. * MultiMap mhm = new MultiHashMap();
  44. * mhm.put(key, "A");
  45. * mhm.put(key, "B");
  46. * mhm.put(key, "C");
  47. * List list = (List) mhm.get(key);</pre>
  48. * <p>
  49. * <code>list</code> will be a list containing "A", "B", "C".
  50. *
  51. * @since Commons Collections 2.0
  52. * @version $Revision: 1.20 $ $Date: 2004/06/09 22:11:54 $
  53. *
  54. * @author Christopher Berry
  55. * @author James Strachan
  56. * @author Steve Downey
  57. * @author Stephen Colebourne
  58. * @author Julien Buret
  59. * @author Serhiy Yevtushenko
  60. */
  61. public class MultiHashMap extends HashMap implements MultiMap {
  62. // backed values collection
  63. private transient Collection values = null;
  64. // compatibility with commons-collection releases 2.0/2.1
  65. private static final long serialVersionUID = 1943563828307035349L;
  66. /**
  67. * Constructor.
  68. */
  69. public MultiHashMap() {
  70. super();
  71. }
  72. /**
  73. * Constructor.
  74. *
  75. * @param initialCapacity the initial map capacity
  76. */
  77. public MultiHashMap(int initialCapacity) {
  78. super(initialCapacity);
  79. }
  80. /**
  81. * Constructor.
  82. *
  83. * @param initialCapacity the initial map capacity
  84. * @param loadFactor the amount 0.0-1.0 at which to resize the map
  85. */
  86. public MultiHashMap(int initialCapacity, float loadFactor) {
  87. super(initialCapacity, loadFactor);
  88. }
  89. /**
  90. * Constructor that copies the input map creating an independent copy.
  91. * <p>
  92. * This method performs different behaviour depending on whether the map
  93. * specified is a MultiMap or not. If a MultiMap is specified, each internal
  94. * collection is also cloned. If the specified map only implements Map, then
  95. * the values are not cloned.
  96. * <p>
  97. * NOTE: From Commons Collections 3.1 this method correctly copies a MultiMap
  98. * to form a truly independent new map.
  99. *
  100. * @param mapToCopy a Map to copy
  101. */
  102. public MultiHashMap(Map mapToCopy) {
  103. // be careful of JDK 1.3 vs 1.4 differences
  104. super((int) (mapToCopy.size() * 1.4f));
  105. if (mapToCopy instanceof MultiMap) {
  106. for (Iterator it = mapToCopy.entrySet().iterator(); it.hasNext();) {
  107. Map.Entry entry = (Map.Entry) it.next();
  108. Collection coll = (Collection) entry.getValue();
  109. Collection newColl = createCollection(coll);
  110. super.put(entry.getKey(), newColl);
  111. }
  112. } else {
  113. putAll(mapToCopy);
  114. }
  115. }
  116. /**
  117. * Read the object during deserialization.
  118. */
  119. private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
  120. // This method is needed because the 1.2/1.3 Java deserialisation called
  121. // put and thus messed up that method
  122. // default read object
  123. s.defaultReadObject();
  124. // problem only with jvm <1.4
  125. String version = "1.2";
  126. try {
  127. version = System.getProperty("java.version");
  128. } catch (SecurityException ex) {
  129. // ignore and treat as 1.2/1.3
  130. }
  131. if (version.startsWith("1.2") || version.startsWith("1.3")) {
  132. for (Iterator iterator = entrySet().iterator(); iterator.hasNext();) {
  133. Map.Entry entry = (Map.Entry) iterator.next();
  134. // put has created a extra collection level, remove it
  135. super.put(entry.getKey(), ((Collection) entry.getValue()).iterator().next());
  136. }
  137. }
  138. }
  139. //-----------------------------------------------------------------------
  140. /**
  141. * Gets the total size of the map by counting all the values.
  142. *
  143. * @return the total size of the map counting all values
  144. * @since Commons Collections 3.1
  145. */
  146. public int totalSize() {
  147. int total = 0;
  148. Collection values = super.values();
  149. for (Iterator it = values.iterator(); it.hasNext();) {
  150. Collection coll = (Collection) it.next();
  151. total += coll.size();
  152. }
  153. return total;
  154. }
  155. /**
  156. * Gets the collection mapped to the specified key.
  157. * This method is a convenience method to typecast the result of <code>get(key)</code>.
  158. *
  159. * @param key the key to retrieve
  160. * @return the collection mapped to the key, null if no mapping
  161. * @since Commons Collections 3.1
  162. */
  163. public Collection getCollection(Object key) {
  164. return (Collection) get(key);
  165. }
  166. /**
  167. * Gets the size of the collection mapped to the specified key.
  168. *
  169. * @param key the key to get size for
  170. * @return the size of the collection at the key, zero if key not in map
  171. * @since Commons Collections 3.1
  172. */
  173. public int size(Object key) {
  174. Collection coll = getCollection(key);
  175. if (coll == null) {
  176. return 0;
  177. }
  178. return coll.size();
  179. }
  180. /**
  181. * Gets an iterator for the collection mapped to the specified key.
  182. *
  183. * @param key the key to get an iterator for
  184. * @return the iterator of the collection at the key, empty iterator if key not in map
  185. * @since Commons Collections 3.1
  186. */
  187. public Iterator iterator(Object key) {
  188. Collection coll = getCollection(key);
  189. if (coll == null) {
  190. return EmptyIterator.INSTANCE;
  191. }
  192. return coll.iterator();
  193. }
  194. /**
  195. * Adds the value to the collection associated with the specified key.
  196. * <p>
  197. * Unlike a normal <code>Map</code> the previous value is not replaced.
  198. * Instead the new value is added to the collection stored against the key.
  199. *
  200. * @param key the key to store against
  201. * @param value the value to add to the collection at the key
  202. * @return the value added if the map changed and null if the map did not change
  203. */
  204. public Object put(Object key, Object value) {
  205. // NOTE:: put is called during deserialization in JDK < 1.4 !!!!!!
  206. // so we must have a readObject()
  207. Collection coll = getCollection(key);
  208. if (coll == null) {
  209. coll = createCollection(null);
  210. super.put(key, coll);
  211. }
  212. boolean results = coll.add(value);
  213. return (results ? value : null);
  214. }
  215. /**
  216. * Adds a collection of values to the collection associated with the specified key.
  217. *
  218. * @param key the key to store against
  219. * @param values the values to add to the collection at the key, null ignored
  220. * @return true if this map changed
  221. * @since Commons Collections 3.1
  222. */
  223. public boolean putAll(Object key, Collection values) {
  224. if (values == null || values.size() == 0) {
  225. return false;
  226. }
  227. Collection coll = getCollection(key);
  228. if (coll == null) {
  229. coll = createCollection(values);
  230. if (coll.size() == 0) {
  231. return false;
  232. }
  233. super.put(key, coll);
  234. return true;
  235. } else {
  236. return coll.addAll(values);
  237. }
  238. }
  239. /**
  240. * Checks whether the map contains the value specified.
  241. * <p>
  242. * This checks all collections against all keys for the value, and thus could be slow.
  243. *
  244. * @param value the value to search for
  245. * @return true if the map contains the value
  246. */
  247. public boolean containsValue(Object value) {
  248. Set pairs = super.entrySet();
  249. if (pairs == null) {
  250. return false;
  251. }
  252. Iterator pairsIterator = pairs.iterator();
  253. while (pairsIterator.hasNext()) {
  254. Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
  255. Collection coll = (Collection) keyValuePair.getValue();
  256. if (coll.contains(value)) {
  257. return true;
  258. }
  259. }
  260. return false;
  261. }
  262. /**
  263. * Checks whether the collection at the specified key contains the value.
  264. *
  265. * @param value the value to search for
  266. * @return true if the map contains the value
  267. * @since Commons Collections 3.1
  268. */
  269. public boolean containsValue(Object key, Object value) {
  270. Collection coll = getCollection(key);
  271. if (coll == null) {
  272. return false;
  273. }
  274. return coll.contains(value);
  275. }
  276. /**
  277. * Removes a specific value from map.
  278. * <p>
  279. * The item is removed from the collection mapped to the specified key.
  280. * Other values attached to that key are unaffected.
  281. * <p>
  282. * If the last value for a key is removed, <code>null</code> will be returned
  283. * from a subsequant <code>get(key)</code>.
  284. *
  285. * @param key the key to remove from
  286. * @param item the value to remove
  287. * @return the value removed (which was passed in), null if nothing removed
  288. */
  289. public Object remove(Object key, Object item) {
  290. Collection valuesForKey = getCollection(key);
  291. if (valuesForKey == null) {
  292. return null;
  293. }
  294. valuesForKey.remove(item);
  295. // remove the list if it is now empty
  296. // (saves space, and allows equals to work)
  297. if (valuesForKey.isEmpty()){
  298. remove(key);
  299. }
  300. return item;
  301. }
  302. /**
  303. * Clear the map.
  304. * <p>
  305. * This clears each collection in the map, and so may be slow.
  306. */
  307. public void clear() {
  308. // For gc, clear each list in the map
  309. Set pairs = super.entrySet();
  310. Iterator pairsIterator = pairs.iterator();
  311. while (pairsIterator.hasNext()) {
  312. Map.Entry keyValuePair = (Map.Entry) pairsIterator.next();
  313. Collection coll = (Collection) keyValuePair.getValue();
  314. coll.clear();
  315. }
  316. super.clear();
  317. }
  318. /**
  319. * Gets a collection containing all the values in the map.
  320. * <p>
  321. * This returns a collection containing the combination of values from all keys.
  322. *
  323. * @return a collection view of the values contained in this map
  324. */
  325. public Collection values() {
  326. Collection vs = values;
  327. return (vs != null ? vs : (values = new Values()));
  328. }
  329. //-----------------------------------------------------------------------
  330. /**
  331. * Inner class to view the elements.
  332. */
  333. private class Values extends AbstractCollection {
  334. public Iterator iterator() {
  335. return new ValueIterator();
  336. }
  337. public int size() {
  338. int compt = 0;
  339. Iterator it = iterator();
  340. while (it.hasNext()) {
  341. it.next();
  342. compt++;
  343. }
  344. return compt;
  345. }
  346. public void clear() {
  347. MultiHashMap.this.clear();
  348. }
  349. }
  350. /**
  351. * Inner iterator to view the elements.
  352. */
  353. private class ValueIterator implements Iterator {
  354. private Iterator backedIterator;
  355. private Iterator tempIterator;
  356. private ValueIterator() {
  357. backedIterator = MultiHashMap.super.values().iterator();
  358. }
  359. private boolean searchNextIterator() {
  360. while (tempIterator == null || tempIterator.hasNext() == false) {
  361. if (backedIterator.hasNext() == false) {
  362. return false;
  363. }
  364. tempIterator = ((Collection) backedIterator.next()).iterator();
  365. }
  366. return true;
  367. }
  368. public boolean hasNext() {
  369. return searchNextIterator();
  370. }
  371. public Object next() {
  372. if (searchNextIterator() == false) {
  373. throw new NoSuchElementException();
  374. }
  375. return tempIterator.next();
  376. }
  377. public void remove() {
  378. if (tempIterator == null) {
  379. throw new IllegalStateException();
  380. }
  381. tempIterator.remove();
  382. }
  383. }
  384. //-----------------------------------------------------------------------
  385. /**
  386. * Clones the map creating an independent copy.
  387. * <p>
  388. * The clone will shallow clone the collections as well as the map.
  389. *
  390. * @return the cloned map
  391. */
  392. public Object clone() {
  393. MultiHashMap cloned = (MultiHashMap) super.clone();
  394. // clone each Collection container
  395. for (Iterator it = cloned.entrySet().iterator(); it.hasNext();) {
  396. Map.Entry entry = (Map.Entry) it.next();
  397. Collection coll = (Collection) entry.getValue();
  398. Collection newColl = createCollection(coll);
  399. entry.setValue(newColl);
  400. }
  401. return cloned;
  402. }
  403. /**
  404. * Creates a new instance of the map value Collection container.
  405. * <p>
  406. * This method can be overridden to use your own collection type.
  407. *
  408. * @param coll the collection to copy, may be null
  409. * @return the new collection
  410. */
  411. protected Collection createCollection(Collection coll) {
  412. if (coll == null) {
  413. return new ArrayList();
  414. } else {
  415. return new ArrayList(coll);
  416. }
  417. }
  418. }