001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/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 * DefaultKeyedValues.java
029 * -----------------------
030 * (C) Copyright 2002-2008, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   Thomas Morgner;
034 *
035 * Changes:
036 * --------
037 * 31-Oct-2002 : Version 1 (DG);
038 * 11-Feb-2003 : Fixed bug in getValue(key) method for unrecognised key (DG);
039 * 05-Mar-2003 : Added methods to sort stored data 'by key' or 'by value' (DG);
040 * 13-Mar-2003 : Implemented Serializable (DG);
041 * 08-Apr-2003 : Modified removeValue(Comparable) method to fix bug 717049 (DG);
042 * 18-Aug-2003 : Implemented Cloneable (DG);
043 * 27-Aug-2003 : Moved SortOrder from org.jfree.data --> org.jfree.util (DG);
044 * 09-Feb-2004 : Modified getIndex() method - see bug report 893256 (DG);
045 * 15-Sep-2004 : Updated clone() method and added PublicCloneable
046 *               interface (DG);
047 * 25-Nov-2004 : Small update to the clone() implementation (DG);
048 * 24-Feb-2005 : Added methods addValue(Comparable, double) and
049 *               setValue(Comparable, double) for convenience (DG);
050 * ------------- JFREECHART 1.0.x ---------------------------------------------
051 * 31-Jul-2006 : Added a clear() method (DG);
052 * 01-Aug-2006 : Added argument check to getIndex() method (DG);
053 * 30-Apr-2007 : Added insertValue() methods (DG);
054 * 31-Oct-2007 : Performance improvements by using separate lists for keys and
055 *               values (TM);
056 * 21-Nov-2007 : Fixed bug in removeValue() method from previous patch (DG);
057 *
058 */
059
060package org.jfree.data;
061
062import java.io.Serializable;
063import java.util.ArrayList;
064import java.util.Arrays;
065import java.util.Comparator;
066import java.util.HashMap;
067import java.util.List;
068
069import org.jfree.util.PublicCloneable;
070import org.jfree.util.SortOrder;
071
072/**
073 * An ordered list of (key, value) items.  This class provides a default
074 * implementation of the {@link KeyedValues} interface.
075 */
076public class DefaultKeyedValues implements KeyedValues, Cloneable,
077        PublicCloneable, Serializable {
078
079    /** For serialization. */
080    private static final long serialVersionUID = 8468154364608194797L;
081
082    /** Storage for the keys. */
083    private ArrayList keys;
084
085    /** Storage for the values. */
086    private ArrayList values;
087
088    /**
089     * Contains (key, Integer) mappings, where the Integer is the index for
090     * the key in the list.
091     */
092    private HashMap indexMap;
093
094  /**
095     * Creates a new collection (initially empty).
096     */
097    public DefaultKeyedValues() {
098        this.keys = new ArrayList();
099        this.values = new ArrayList();
100        this.indexMap = new HashMap();
101    }
102
103    /**
104     * Returns the number of items (values) in the collection.
105     *
106     * @return The item count.
107     */
108    public int getItemCount() {
109        return this.indexMap.size();
110    }
111
112    /**
113     * Returns a value.
114     *
115     * @param item  the item of interest (zero-based index).
116     *
117     * @return The value (possibly <code>null</code>).
118     *
119     * @throws IndexOutOfBoundsException if <code>item</code> is out of bounds.
120     */
121    public Number getValue(int item) {
122        return (Number) this.values.get(item);
123    }
124
125    /**
126     * Returns a key.
127     *
128     * @param index  the item index (zero-based).
129     *
130     * @return The row key.
131     *
132     * @throws IndexOutOfBoundsException if <code>item</code> is out of bounds.
133     */
134    public Comparable getKey(int index) {
135        return (Comparable) this.keys.get(index);
136    }
137
138    /**
139     * Returns the index for a given key.
140     *
141     * @param key  the key (<code>null</code> not permitted).
142     *
143     * @return The index, or <code>-1</code> if the key is not recognised.
144     *
145     * @throws IllegalArgumentException if <code>key</code> is
146     *     <code>null</code>.
147     */
148    public int getIndex(Comparable key) {
149        if (key == null) {
150            throw new IllegalArgumentException("Null 'key' argument.");
151        }
152        final Integer i = (Integer) this.indexMap.get(key);
153        if (i == null) {
154            return -1;  // key not found
155        }
156        return i.intValue();
157    }
158
159    /**
160     * Returns the keys for the values in the collection.
161     *
162     * @return The keys (never <code>null</code>).
163     */
164    public List getKeys() {
165        return (List) this.keys.clone();
166    }
167
168    /**
169     * Returns the value for a given key.
170     *
171     * @param key  the key (<code>null</code> not permitted).
172     *
173     * @return The value (possibly <code>null</code>).
174     *
175     * @throws UnknownKeyException if the key is not recognised.
176     *
177     * @see #getValue(int)
178     */
179    public Number getValue(Comparable key) {
180        int index = getIndex(key);
181        if (index < 0) {
182            throw new UnknownKeyException("Key not found: " + key);
183        }
184        return getValue(index);
185    }
186
187    /**
188     * Updates an existing value, or adds a new value to the collection.
189     *
190     * @param key  the key (<code>null</code> not permitted).
191     * @param value  the value.
192     *
193     * @see #addValue(Comparable, Number)
194     */
195    public void addValue(Comparable key, double value) {
196        addValue(key, new Double(value));
197    }
198
199    /**
200     * Adds a new value to the collection, or updates an existing value.
201     * This method passes control directly to the
202     * {@link #setValue(Comparable, Number)} method.
203     *
204     * @param key  the key (<code>null</code> not permitted).
205     * @param value  the value (<code>null</code> permitted).
206     */
207    public void addValue(Comparable key, Number value) {
208        setValue(key, value);
209    }
210
211    /**
212     * Updates an existing value, or adds a new value to the collection.
213     *
214     * @param key  the key (<code>null</code> not permitted).
215     * @param value  the value.
216     */
217    public void setValue(Comparable key, double value) {
218        setValue(key, new Double(value));
219    }
220
221    /**
222     * Updates an existing value, or adds a new value to the collection.
223     *
224     * @param key  the key (<code>null</code> not permitted).
225     * @param value  the value (<code>null</code> permitted).
226     */
227    public void setValue(Comparable key, Number value) {
228        if (key == null) {
229            throw new IllegalArgumentException("Null 'key' argument.");
230        }
231        int keyIndex = getIndex(key);
232        if (keyIndex >= 0) {
233            this.keys.set(keyIndex, key);
234            this.values.set(keyIndex, value);
235        }
236        else {
237            this.keys.add(key);
238            this.values.add(value);
239            this.indexMap.put(key, new Integer(this.keys.size() - 1));
240        }
241    }
242
243    /**
244     * Inserts a new value at the specified position in the dataset or, if
245     * there is an existing item with the specified key, updates the value
246     * for that item and moves it to the specified position.
247     *
248     * @param position  the position (in the range 0 to getItemCount()).
249     * @param key  the key (<code>null</code> not permitted).
250     * @param value  the value.
251     *
252     * @since 1.0.6
253     */
254    public void insertValue(int position, Comparable key, double value) {
255        insertValue(position, key, new Double(value));
256    }
257
258    /**
259     * Inserts a new value at the specified position in the dataset or, if
260     * there is an existing item with the specified key, updates the value
261     * for that item and moves it to the specified position.
262     *
263     * @param position  the position (in the range 0 to getItemCount()).
264     * @param key  the key (<code>null</code> not permitted).
265     * @param value  the value (<code>null</code> permitted).
266     *
267     * @since 1.0.6
268     */
269    public void insertValue(int position, Comparable key, Number value) {
270        if (position < 0 || position > getItemCount()) {
271            throw new IllegalArgumentException("'position' out of bounds.");
272        }
273        if (key == null) {
274            throw new IllegalArgumentException("Null 'key' argument.");
275        }
276        int pos = getIndex(key);
277        if (pos == position) {
278            this.keys.set(pos, key);
279            this.values.set(pos, value);
280        }
281        else {
282            if (pos >= 0) {
283                this.keys.remove(pos);
284                this.values.remove(pos);
285            }
286
287            this.keys.add(position, key);
288            this.values.add(position, value);
289            rebuildIndex();
290        }
291    }
292
293    /**
294     * Rebuilds the key to indexed-position mapping after an positioned insert
295     * or a remove operation.
296     */
297    private void rebuildIndex () {
298        this.indexMap.clear();
299        for (int i = 0; i < this.keys.size(); i++) {
300            final Object key = this.keys.get(i);
301            this.indexMap.put(key, new Integer(i));
302        }
303    }
304
305    /**
306     * Removes a value from the collection.
307     *
308     * @param index  the index of the item to remove (in the range
309     *     <code>0</code> to <code>getItemCount() - 1</code>).
310     *
311     * @throws IndexOutOfBoundsException if <code>index</code> is not within
312     *     the specified range.
313     */
314    public void removeValue(int index) {
315        this.keys.remove(index);
316        this.values.remove(index);
317        rebuildIndex();
318    }
319
320    /**
321     * Removes a value from the collection.
322     *
323     * @param key  the item key (<code>null</code> not permitted).
324     *
325     * @throws IllegalArgumentException if <code>key</code> is
326     *     <code>null</code>.
327     * @throws UnknownKeyException if <code>key</code> is not recognised.
328     */
329    public void removeValue(Comparable key) {
330        int index = getIndex(key);
331        if (index < 0) {
332            throw new UnknownKeyException("The key (" + key
333                    + ") is not recognised.");
334        }
335        removeValue(index);
336    }
337
338    /**
339     * Clears all values from the collection.
340     *
341     * @since 1.0.2
342     */
343    public void clear() {
344        this.keys.clear();
345        this.values.clear();
346        this.indexMap.clear();
347    }
348
349    /**
350     * Sorts the items in the list by key.
351     *
352     * @param order  the sort order (<code>null</code> not permitted).
353     */
354    public void sortByKeys(SortOrder order) {
355        final int size = this.keys.size();
356        final DefaultKeyedValue[] data = new DefaultKeyedValue[size];
357
358        for (int i = 0; i < size; i++) {
359            data[i] = new DefaultKeyedValue((Comparable) this.keys.get(i),
360                    (Number) this.values.get(i));
361        }
362
363        Comparator comparator = new KeyedValueComparator(
364                KeyedValueComparatorType.BY_KEY, order);
365        Arrays.sort(data, comparator);
366        clear();
367
368        for (int i = 0; i < data.length; i++) {
369            final DefaultKeyedValue value = data[i];
370            addValue(value.getKey(), value.getValue());
371        }
372    }
373
374    /**
375     * Sorts the items in the list by value.  If the list contains
376     * <code>null</code> values, they will sort to the end of the list,
377     * irrespective of the sort order.
378     *
379     * @param order  the sort order (<code>null</code> not permitted).
380     */
381    public void sortByValues(SortOrder order) {
382        final int size = this.keys.size();
383        final DefaultKeyedValue[] data = new DefaultKeyedValue[size];
384        for (int i = 0; i < size; i++) {
385            data[i] = new DefaultKeyedValue((Comparable) this.keys.get(i),
386                    (Number) this.values.get(i));
387        }
388
389        Comparator comparator = new KeyedValueComparator(
390                KeyedValueComparatorType.BY_VALUE, order);
391        Arrays.sort(data, comparator);
392
393        clear();
394        for (int i = 0; i < data.length; i++) {
395            final DefaultKeyedValue value = data[i];
396            addValue(value.getKey(), value.getValue());
397        }
398    }
399
400    /**
401     * Tests if this object is equal to another.
402     *
403     * @param obj  the object (<code>null</code> permitted).
404     *
405     * @return A boolean.
406     */
407    public boolean equals(Object obj) {
408        if (obj == this) {
409            return true;
410        }
411
412        if (!(obj instanceof KeyedValues)) {
413            return false;
414        }
415
416        KeyedValues that = (KeyedValues) obj;
417        int count = getItemCount();
418        if (count != that.getItemCount()) {
419            return false;
420        }
421
422        for (int i = 0; i < count; i++) {
423            Comparable k1 = getKey(i);
424            Comparable k2 = that.getKey(i);
425            if (!k1.equals(k2)) {
426                return false;
427            }
428            Number v1 = getValue(i);
429            Number v2 = that.getValue(i);
430            if (v1 == null) {
431                if (v2 != null) {
432                    return false;
433                }
434            }
435            else {
436                if (!v1.equals(v2)) {
437                    return false;
438                }
439            }
440        }
441        return true;
442    }
443
444    /**
445     * Returns a hash code.
446     *
447     * @return A hash code.
448     */
449    public int hashCode() {
450        return (this.keys != null ? this.keys.hashCode() : 0);
451    }
452
453    /**
454     * Returns a clone.
455     *
456     * @return A clone.
457     *
458     * @throws CloneNotSupportedException  this class will not throw this
459     *         exception, but subclasses might.
460     */
461    public Object clone() throws CloneNotSupportedException {
462        DefaultKeyedValues clone = (DefaultKeyedValues) super.clone();
463        clone.keys = (ArrayList) this.keys.clone();
464        clone.values = (ArrayList) this.values.clone();
465        clone.indexMap = (HashMap) this.indexMap.clone();
466        return clone;
467    }
468
469}