Source for org.jfree.util.HashNMap

   1: /* ========================================================================
   2:  * JCommon : a free general purpose class library for the Java(tm) platform
   3:  * ========================================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  * 
   7:  * Project Info:  http://www.jfree.org/jcommon/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * -------------
  28:  * HashNMap.java
  29:  * -------------
  30:  * (C)opyright 2002-2005, by Thomas Morgner and Contributors.
  31:  *
  32:  * Original Author:  Thomas Morgner;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * $Id: HashNMap.java,v 1.7 2005/10/18 13:24:19 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 20-May-2002 : Initial version
  40:  * 10-Dec-2002 : Minor Javadoc updates (DG);
  41:  * 29-Jul-2004 : Replaced 'enum' variable name (reserved word in JDK 1.5) (DG);
  42:  * 12-Mar-2005 : Some performance improvements, this implementation is no 
  43:  *               longer forced to use ArrayLists, add/put behaviour changed to 
  44:  *               fit the common behaviour of collections.
  45:  *
  46:  */
  47: 
  48: package org.jfree.util;
  49: 
  50: import java.io.Serializable;
  51: import java.util.ArrayList;
  52: import java.util.HashMap;
  53: import java.util.Iterator;
  54: import java.util.List;
  55: import java.util.NoSuchElementException;
  56: import java.util.Set;
  57: 
  58: /**
  59:  * The HashNMap can be used to store multiple values by a single key value. The
  60:  * values stored can be retrieved using a direct query or by creating an
  61:  * enumeration over the stored elements.
  62:  *
  63:  * @author Thomas Morgner
  64:  */
  65: public class HashNMap implements Serializable, Cloneable {
  66: 
  67:     /** Serialization support. */
  68:     private static final long serialVersionUID = -670924844536074826L;
  69: 
  70:     /**
  71:      * An helper class to implement an empty iterator. This iterator will always
  72:      * return false when <code>hasNext</code> is called.
  73:      */
  74:     private static final class EmptyIterator implements Iterator {
  75: 
  76:         /**
  77:          * DefaultConstructor.
  78:          */
  79:         private EmptyIterator() {
  80:             super();
  81:         }
  82: 
  83:         /**
  84:          * Returns <tt>true</tt> if the iteration has more elements. (In other
  85:          * words, returns <tt>true</tt> if <tt>next</tt> would return an element
  86:          * rather than throwing an exception.)
  87:          *
  88:          * @return <tt>true</tt> if the iterator has more elements.
  89:          */
  90:         public boolean hasNext() {
  91:             return false;
  92:         }
  93: 
  94:         /**
  95:          * Returns the next element in the iteration.
  96:          *
  97:          * @return the next element in the iteration.
  98:          * @throws NoSuchElementException iteration has no more elements.
  99:          */
 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: }