001 /* ========================================================================
002 * JCommon : a free general purpose class library for the Java(tm) platform
003 * ========================================================================
004 *
005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jcommon/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * -------------
028 * HashNMap.java
029 * -------------
030 * (C)opyright 2002-2005, by Thomas Morgner and Contributors.
031 *
032 * Original Author: Thomas Morgner;
033 * Contributor(s): David Gilbert (for Object Refinery Limited);
034 *
035 * $Id: HashNMap.java,v 1.7 2005/10/18 13:24:19 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 20-May-2002 : Initial version
040 * 10-Dec-2002 : Minor Javadoc updates (DG);
041 * 29-Jul-2004 : Replaced 'enum' variable name (reserved word in JDK 1.5) (DG);
042 * 12-Mar-2005 : Some performance improvements, this implementation is no
043 * longer forced to use ArrayLists, add/put behaviour changed to
044 * fit the common behaviour of collections.
045 *
046 */
047
048 package org.jfree.util;
049
050 import java.io.Serializable;
051 import java.util.ArrayList;
052 import java.util.HashMap;
053 import java.util.Iterator;
054 import java.util.List;
055 import java.util.NoSuchElementException;
056 import java.util.Set;
057
058 /**
059 * The HashNMap can be used to store multiple values by a single key value. The
060 * values stored can be retrieved using a direct query or by creating an
061 * enumeration over the stored elements.
062 *
063 * @author Thomas Morgner
064 */
065 public class HashNMap implements Serializable, Cloneable {
066
067 /** Serialization support. */
068 private static final long serialVersionUID = -670924844536074826L;
069
070 /**
071 * An helper class to implement an empty iterator. This iterator will always
072 * return false when <code>hasNext</code> is called.
073 */
074 private static final class EmptyIterator implements Iterator {
075
076 /**
077 * DefaultConstructor.
078 */
079 private EmptyIterator() {
080 super();
081 }
082
083 /**
084 * Returns <tt>true</tt> if the iteration has more elements. (In other
085 * words, returns <tt>true</tt> if <tt>next</tt> would return an element
086 * rather than throwing an exception.)
087 *
088 * @return <tt>true</tt> if the iterator has more elements.
089 */
090 public boolean hasNext() {
091 return false;
092 }
093
094 /**
095 * Returns the next element in the iteration.
096 *
097 * @return the next element in the iteration.
098 * @throws NoSuchElementException iteration has no more elements.
099 */
100 public Object next() {
101 throw new NoSuchElementException("This iterator is empty.");
102 }
103
104 /**
105 * Removes from the underlying collection the last element returned by the
106 * iterator (optional operation). This method can be called only once per
107 * call to <tt>next</tt>. The behavior of an iterator is unspecified if
108 * the underlying collection is modified while the iteration is in
109 * progress in any way other than by calling this method.
110 *
111 * @throws UnsupportedOperationException if the <tt>remove</tt>
112 * operation is not supported by this Iterator.
113 * @throws IllegalStateException if the <tt>next</tt> method has not
114 * yet been called, or the <tt>remove</tt> method has already
115 * been called after the last call to the <tt>next</tt>
116 * method.
117 */
118 public void remove() {
119 throw new UnsupportedOperationException("This iterator is empty, no remove supported.");
120 }
121 }
122
123 /**
124 * A singleton instance of the empty iterator. This object can be safely
125 * shared.
126 */
127 private static final Iterator EMPTY_ITERATOR = new EmptyIterator();
128
129 /**
130 * The underlying storage.
131 */
132 private HashMap table;
133
134 /**
135 * An empty array.
136 */
137 private static final Object[] EMPTY_ARRAY = new Object[0];
138
139 /**
140 * Default constructor.
141 */
142 public HashNMap() {
143 this.table = new HashMap();
144 }
145
146 /**
147 * Returns a new empty list.
148 *
149 * @return A new empty list.
150 */
151 protected List createList() {
152 return new ArrayList();
153 }
154
155 /**
156 * Inserts a new key/value pair into the map. If such a pair already
157 * exists, it gets replaced with the given values.
158 *
159 * @param key the key.
160 * @param val the value.
161 * @return A boolean.
162 */
163 public boolean put(final Object key, final Object val) {
164 final List v = (List) this.table.get(key);
165 if (v == null) {
166 final List newList = createList();
167 newList.add(val);
168 this.table.put(key, newList);
169 return true;
170 }
171 else {
172 v.clear();
173 return v.add(val);
174 }
175 }
176
177 /**
178 * Adds a new key/value pair into this map. If the key is not yet in the
179 * map, it gets added to the map and the call is equal to
180 * put(Object,Object).
181 *
182 * @param key the key.
183 * @param val the value.
184 * @return true, if the value has been added, false otherwise
185 */
186 public boolean add(final Object key, final Object val) {
187 final List v = (List) this.table.get(key);
188 if (v == null) {
189 put(key, val);
190 return true;
191 }
192 else {
193 return v.add(val);
194 }
195 }
196
197 /**
198 * Retrieves the first value registered for an key or null if there was no
199 * such key in the list.
200 *
201 * @param key the key.
202 * @return the value.
203 */
204 public Object getFirst(final Object key) {
205 return get(key, 0);
206 }
207
208 /**
209 * Retrieves the n-th value registered for an key or null if there was no
210 * such key in the list. An index out of bounds exception is thrown if
211 * there are less than n elements registered to this key.
212 *
213 * @param key the key.
214 * @param n the index.
215 * @return the object.
216 */
217 public Object get(final Object key, final int n) {
218 final List v = (List) this.table.get(key);
219 if (v == null) {
220 return null;
221 }
222 return v.get(n);
223 }
224
225 /**
226 * Returns an iterator over all elements registered to the given key.
227 *
228 * @param key the key.
229 * @return an iterator.
230 */
231 public Iterator getAll(final Object key) {
232 final List v = (List) this.table.get(key);
233 if (v == null) {
234 return EMPTY_ITERATOR;
235 }
236 return v.iterator();
237 }
238
239 /**
240 * Returns all registered keys as an enumeration.
241 *
242 * @return an enumeration of the keys.
243 */
244 public Iterator keys() {
245 return this.table.keySet().iterator();
246 }
247
248 /**
249 * Returns all registered keys as set.
250 *
251 * @return a set of keys.
252 */
253 public Set keySet() {
254 return this.table.keySet();
255 }
256
257 /**
258 * Removes the key/value pair from the map. If the removed entry was the
259 * last entry for this key, the key gets also removed.
260 *
261 * @param key the key.
262 * @param value the value.
263 * @return true, if removing the element was successfull, false otherwise.
264 */
265 public boolean remove(final Object key, final Object value) {
266 final List v = (List) this.table.get(key);
267 if (v == null) {
268 return false;
269 }
270
271 if (!v.remove(value)) {
272 return false;
273 }
274 if (v.size() == 0) {
275 this.table.remove(key);
276 }
277 return true;
278 }
279
280 /**
281 * Removes all elements for the given key.
282 *
283 * @param key the key.
284 */
285 public void removeAll(final Object key) {
286 this.table.remove(key);
287 }
288
289 /**
290 * Clears all keys and values of this map.
291 */
292 public void clear() {
293 this.table.clear();
294 }
295
296 /**
297 * Tests whether this map contains the given key.
298 *
299 * @param key the key.
300 * @return true if the key is contained in the map
301 */
302 public boolean containsKey(final Object key) {
303 return this.table.containsKey(key);
304 }
305
306 /**
307 * Tests whether this map contains the given value.
308 *
309 * @param value the value.
310 * @return true if the value is registered in the map for an key.
311 */
312 public boolean containsValue(final Object value) {
313 final Iterator e = this.table.values().iterator();
314 boolean found = false;
315 while (e.hasNext() && !found) {
316 final List v = (List) e.next();
317 found = v.contains(value);
318 }
319 return found;
320 }
321
322 /**
323 * Tests whether this map contains the given value.
324 *
325 * @param value the value.
326 * @param key the key under which to find the value
327 * @return true if the value is registered in the map for an key.
328 */
329 public boolean containsValue(final Object key, final Object value) {
330 final List v = (List) this.table.get(key);
331 if (v == null) {
332 return false;
333 }
334 return v.contains(value);
335 }
336
337 /**
338 * Tests whether this map contains the given key or value.
339 *
340 * @param value the value.
341 * @return true if the key or value is contained in the map
342 */
343 public boolean contains(final Object value) {
344 if (containsKey(value)) {
345 return true;
346 }
347 return containsValue(value);
348 }
349
350 /**
351 * Creates a deep copy of this HashNMap.
352 *
353 * @return a clone.
354 * @throws CloneNotSupportedException this should never happen.
355 */
356 public Object clone() throws CloneNotSupportedException {
357 final HashNMap map = (HashNMap) super.clone();
358 map.table = new HashMap();
359 final Iterator iterator = keys();
360 while (iterator.hasNext()) {
361 final Object key = iterator.next();
362 final List list = (List) map.table.get(key);
363 if (list != null) {
364 map.table.put(key, ObjectUtilities.clone(list));
365 }
366 }
367 return map;
368 }
369
370 /**
371 * Returns the contents for the given key as object array. If there were
372 * no objects registered with that key, an empty object array is returned.
373 *
374 * @param key the key.
375 * @param data the object array to receive the contents.
376 * @return the contents.
377 */
378 public Object[] toArray(final Object key, final Object[] data) {
379 if (key == null) {
380 throw new NullPointerException("Key must not be null.");
381 }
382 final List list = (List) this.table.get(key);
383 if (list != null) {
384 return list.toArray(data);
385 }
386 if (data.length > 0) {
387 data[0] = null;
388 }
389 return data;
390 }
391
392 /**
393 * Returns the contents for the given key as object array. If there were
394 * no objects registered with that key, an empty object array is returned.
395 *
396 * @param key the key.
397 * @return the contents.
398 */
399 public Object[] toArray(final Object key) {
400 if (key == null) {
401 throw new NullPointerException("Key must not be null.");
402 }
403 final List list = (List) this.table.get(key);
404 if (list != null) {
405 return list.toArray();
406 }
407 return EMPTY_ARRAY;
408 }
409
410 /**
411 * Returns the number of elements registered with the given key.
412 *
413 * @param key the key.
414 * @return the number of element for this key, or 0 if there are no elements
415 * registered.
416 */
417 public int getValueCount(final Object key) {
418 if (key == null) {
419 throw new NullPointerException("Key must not be null.");
420 }
421 final List list = (List) this.table.get(key);
422 if (list != null) {
423 return list.size();
424 }
425 return 0;
426 }
427 }