    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}