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 048package org.jfree.util; 049 050import java.io.Serializable; 051import java.util.ArrayList; 052import java.util.HashMap; 053import java.util.Iterator; 054import java.util.List; 055import java.util.NoSuchElementException; 056import 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 */ 065public 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}