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}