View Javadoc

1   /*
2    * $Header: /cvsroot/baritus/openedge-baritus/src/java/nl/openedge/baritus/util/MultiHashMap.java,v 1.2 2004/04/09 18:44:53 eelco12 Exp $
3    * ====================================================================
4    *
5    * The Apache Software License, Version 1.1
6    *
7    * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
8    * reserved.
9    *
10   * Redistribution and use in source and binary forms, with or without
11   * modification, are permitted provided that the following conditions
12   * are met:
13   *
14   * 1. Redistributions of source code must retain the above copyright
15   *    notice, this list of conditions and the following disclaimer.
16   *
17   * 2. Redistributions in binary form must reproduce the above copyright
18   *    notice, this list of conditions and the following disclaimer in
19   *    the documentation and/or other materials provided with the
20   *    distribution.
21   *
22   * 3. The end-user documentation included with the redistribution, if
23   *    any, must include the following acknowledgment:
24   *       "This product includes software developed by the
25   *        Apache Software Foundation (http://www.apache.org/)."
26   *    Alternately, this acknowledgment may appear in the software itself,
27   *    if and wherever such third-party acknowledgments normally appear.
28   *
29   * 4. The names "The Jakarta Project", "Commons", and "Apache Software
30   *    Foundation" must not be used to endorse or promote products derived
31   *    from this software without prior written permission. For written
32   *    permission, please contact apache@apache.org.
33   *
34   * 5. Products derived from this software may not be called "Apache"
35   *    nor may "Apache" appear in their names without prior written
36   *    permission of the Apache Software Foundation.
37   *
38   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
39   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
40   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
41   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
42   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
43   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
44   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
45   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
46   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
47   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
48   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
49   * SUCH DAMAGE.
50   * ====================================================================
51   *
52   * This software consists of voluntary contributions made by many
53   * individuals on behalf of the Apache Software Foundation.  For more
54   * information on the Apache Software Foundation, please see
55   * <http://www.apache.org/>.
56   *
57   */
58  package nl.openedge.baritus.util;
59  
60  import java.io.IOException;
61  import java.io.ObjectInputStream;
62  import java.util.AbstractCollection;
63  import java.util.ArrayList;
64  import java.util.Collection;
65  import java.util.HashMap;
66  import java.util.Iterator;
67  import java.util.Map;
68  import java.util.NoSuchElementException;
69  import java.util.Set;
70  
71  /***
72   * A <code>MultiMap</code> is a Map with slightly different semantics.
73   * Putting a value into the map will add the value to a Collection at that
74   * key. Getting a value will always return a Collection, holding all the
75   * values put to that key. This implementation uses an ArrayList as the 
76   * collection.
77   * <p>
78   * For example:
79   * <pre>
80   * MultiMap mhm = new MultiHashMap();
81   * mhm.put(key, "A");
82   * mhm.put(key, "B");
83   * mhm.put(key, "C");
84   * Collection coll = mhm.get(key);</pre>
85   * <p>
86   * <code>coll</code> will be a list containing "A", "B", "C".
87   * 
88   * NOTE: copied this from Commons Collections, as this is the only class
89   * used from the Collections package, and it's for internal use.
90   *
91   * Commons Collections 2.0
92   * Revision: 1.11 $ $Date: 2003/05/16 14:40:56
93   * 
94   * @author Christopher Berry
95   * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
96   * @author Steve Downey
97   * @author Stephen Colebourne
98   * @author <a href="mailto:jburet@yahoo.com">Julien Buret</a>
99   * @author Serhiy Yevtushenko
100  */
101 public class MultiHashMap extends HashMap implements Map
102 {
103 
104 	//backed values collection
105 	private transient Collection values = null;
106 
107 	/***
108 	 * Constructor.
109 	 */
110 	public MultiHashMap()
111 	{
112 		super();
113 	}
114 
115 	/***
116 	 * Constructor.
117 	 * 
118 	 * @param initialCapacity  the initial map capacity
119 	 */
120 	public MultiHashMap(int initialCapacity)
121 	{
122 		super(initialCapacity);
123 	}
124 
125 	/***
126 	 * Constructor.
127 	 * 
128 	 * @param initialCapacity  the initial map capacity
129 	 * @param loadFactor  the amount 0.0-1.0 at which to resize the map
130 	 */
131 	public MultiHashMap(int initialCapacity, float loadFactor)
132 	{
133 		super(initialCapacity, loadFactor);
134 	}
135 
136 	/***
137 	 * Constructor.
138 	 * 
139 	 * @param mapToCopy  a Map to copy
140 	 */
141 	public MultiHashMap(Map mapToCopy)
142 	{
143 		super(mapToCopy);
144 	}
145 
146 	/***
147 	 * Read the object during deserialization.
148 	 */
149 	private void readObject(ObjectInputStream s)
150 		throws IOException, ClassNotFoundException
151 	{
152 		// This method is needed because the 1.2/1.3 Java deserialisation called
153 		// put and thus messed up that method
154 
155 		// default read object
156 		s.defaultReadObject();
157 
158 		// problem only with jvm <1.4
159 		String version = "1.2";
160 		try
161 		{
162 			version = System.getProperty("java.version");
163 		}
164 		catch (SecurityException ex)
165 		{
166 			// ignore and treat as 1.2/1.3
167 		}
168 
169 		if (version.startsWith("1.2") || version.startsWith("1.3"))
170 		{
171 			for (Iterator iterator = entrySet().iterator();
172 				iterator.hasNext();
173 				)
174 			{
175 				Map.Entry entry = (Map.Entry)iterator.next();
176 				// put has created a extra collection level, remove it
177 				super.put(
178 					entry.getKey(),
179 					((Collection)entry.getValue()).iterator().next());
180 			}
181 		}
182 	}
183 
184 	/***
185 	 * Put a key and value into the map.
186 	 * <p>
187 	 * The value is added to a collection mapped to the key instead of 
188 	 * replacing the previous value.
189 	 * 
190 	 * @param key  the key to set
191 	 * @param value  the value to set the key to
192 	 * @return the value added if the add is successful, <code>null</code> otherwise
193 	 */
194 	public Object put(Object key, Object value)
195 	{
196 		// NOTE:: put is called during deserialization in JDK < 1.4 !!!!!!
197 		//        so we must have a readObject()
198 		Collection coll = (Collection)super.get(key);
199 		if (coll == null)
200 		{
201 			coll = createCollection(null);
202 			super.put(key, coll);
203 		}
204 		boolean results = coll.add(value);
205 
206 		return (results ? value : null);
207 	}
208 
209 	/***
210 	 * Does the map contain a specific value.
211 	 * <p>
212 	 * This searches the collection mapped to each key, and thus could be slow.
213 	 * 
214 	 * @param value  the value to search for
215 	 * @return true if the list contains the value
216 	 */
217 	public boolean containsValue(Object value)
218 	{
219 		Set pairs = super.entrySet();
220 
221 		if (pairs == null)
222 		{
223 			return false;
224 		}
225 		Iterator pairsIterator = pairs.iterator();
226 		while (pairsIterator.hasNext())
227 		{
228 			Map.Entry keyValuePair = (Map.Entry)pairsIterator.next();
229 			Collection coll = (Collection)keyValuePair.getValue();
230 			if (coll.contains(value))
231 			{
232 				return true;
233 			}
234 		}
235 		return false;
236 	}
237 
238 	/***
239 	 * Removes a specific value from map.
240 	 * <p>
241 	 * The item is removed from the collection mapped to the specified key.
242 	 * 
243 	 * @param key  the key to remove from
244 	 * @param value  the value to remove
245 	 * @return the value removed (which was passed in)
246 	 */
247 	public Object remove(Object key, Object value)
248 	{
249 		Collection valuesForKey = (Collection)super.get(key);
250 		if (valuesForKey == null)
251 		{
252 			return null;
253 		}
254 		valuesForKey.remove(value);
255 
256 		// remove the list if it is now empty
257 		// (saves space, and allows equals to work)
258 		if (valuesForKey.isEmpty())
259 		{
260 			remove(key);
261 		}
262 		return value;
263 	}
264 
265 	/***
266 	 * Clear the map.
267 	 * <p>
268 	 * This clears each collection in the map, and so may be slow.
269 	 */
270 	public void clear()
271 	{
272 		// For gc, clear each list in the map
273 		Set pairs = super.entrySet();
274 		Iterator pairsIterator = pairs.iterator();
275 		while (pairsIterator.hasNext())
276 		{
277 			Map.Entry keyValuePair = (Map.Entry)pairsIterator.next();
278 			Collection coll = (Collection)keyValuePair.getValue();
279 			coll.clear();
280 		}
281 		super.clear();
282 	}
283 
284 	/*** 
285 	 * Gets a view over all the values in the map.
286 	 * <p>
287 	 * The values view includes all the entries in the collections at each map key.
288 	 * 
289 	 * @return the collection view of all the values in the map
290 	 */
291 	public Collection values()
292 	{
293 		Collection vs = values;
294 		return (vs != null ? vs : (values = new Values()));
295 	}
296 
297 	/***
298 	 * Inner class to view the elements.
299 	 */
300 	private class Values extends AbstractCollection
301 	{
302 
303 		public Iterator iterator()
304 		{
305 			return new ValueIterator();
306 		}
307 
308 		public int size()
309 		{
310 			int compt = 0;
311 			Iterator it = iterator();
312 			while (it.hasNext())
313 			{
314 				it.next();
315 				compt++;
316 			}
317 			return compt;
318 		}
319 
320 		public void clear()
321 		{
322 			MultiHashMap.this.clear();
323 		}
324 
325 	}
326 
327 	/***
328 	 * Inner iterator to view the elements.
329 	 */
330 	private class ValueIterator implements Iterator
331 	{
332 		private Iterator backedIterator;
333 		private Iterator tempIterator;
334 
335 		private ValueIterator()
336 		{
337 			backedIterator = MultiHashMap.super.values().iterator();
338 		}
339 
340 		private boolean searchNextIterator()
341 		{
342 			while (tempIterator == null || tempIterator.hasNext() == false)
343 			{
344 				if (backedIterator.hasNext() == false)
345 				{
346 					return false;
347 				}
348 				tempIterator = ((Collection)backedIterator.next()).iterator();
349 			}
350 			return true;
351 		}
352 
353 		public boolean hasNext()
354 		{
355 			return searchNextIterator();
356 		}
357 
358 		public Object next()
359 		{
360 			if (searchNextIterator() == false)
361 			{
362 				throw new NoSuchElementException();
363 			}
364 			return tempIterator.next();
365 		}
366 
367 		public void remove()
368 		{
369 			if (tempIterator == null)
370 			{
371 				throw new IllegalStateException();
372 			}
373 			tempIterator.remove();
374 		}
375 
376 	}
377 
378 	/***
379 	 * Clone the map.
380 	 * <p>
381 	 * The clone will shallow clone the collections as well as the map.
382 	 * 
383 	 * @return the cloned map
384 	 */
385 	public Object clone()
386 	{
387 		MultiHashMap obj = (MultiHashMap)super.clone();
388 
389 		// clone each Collection container
390 		for (Iterator it = entrySet().iterator(); it.hasNext();)
391 		{
392 			Map.Entry entry = (Map.Entry)it.next();
393 			Collection coll = (Collection)entry.getValue();
394 			Collection newColl = createCollection(coll);
395 			entry.setValue(newColl);
396 		}
397 		return obj;
398 	}
399 
400 	/*** 
401 	 * Creates a new instance of the map value Collection container.
402 	 * <p>
403 	 * This method can be overridden to use your own collection type.
404 	 *
405 	 * @param coll  the collection to copy, may be null
406 	 * @return the new collection
407 	 */
408 	protected Collection createCollection(Collection coll)
409 	{
410 		if (coll == null)
411 		{
412 			return new ArrayList();
413 		}
414 		else
415 		{
416 			return new ArrayList(coll);
417 		}
418 	}
419 
420 }