    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    }