001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, 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-2007, 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 *               
057 */
058
059package org.jfree.data;
060
061import java.io.Serializable;
062import java.util.ArrayList;
063import java.util.Arrays;
064import java.util.Comparator;
065import java.util.HashMap;
066import java.util.List;
067
068import org.jfree.util.PublicCloneable;
069import org.jfree.util.SortOrder;
070
071/**
072 * An ordered list of (key, value) items.  This class provides a default 
073 * implementation of the {@link KeyedValues} interface.
074 */
075public class DefaultKeyedValues implements KeyedValues, 
076                                           Cloneable, PublicCloneable, 
077                                           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
318        // did we remove the last item? If not, then rebuild the index ..
319        if (index < this.keys.size()) {
320            rebuildIndex();
321        }
322    }
323
324    /**
325     * Removes a value from the collection.
326     *
327     * @param key  the item key (<code>null</code> not permitted).
328     * 
329     * @throws IllegalArgumentException if <code>key</code> is 
330     *     <code>null</code>.
331     * @throws UnknownKeyException if <code>key</code> is not recognised.
332     */
333    public void removeValue(Comparable key) {
334        int index = getIndex(key);
335        if (index < 0) {
336            throw new UnknownKeyException("The key (" + key 
337                    + ") is not recognised.");
338        }
339        removeValue(index);
340    }
341    
342    /**
343     * Clears all values from the collection.
344     * 
345     * @since 1.0.2
346     */
347    public void clear() {
348        this.keys.clear();
349        this.values.clear();
350        this.indexMap.clear();
351    }
352
353    /**
354     * Sorts the items in the list by key.
355     *
356     * @param order  the sort order (<code>null</code> not permitted).
357     */
358    public void sortByKeys(SortOrder order) {
359        final int size = this.keys.size();
360        final DefaultKeyedValue[] data = new DefaultKeyedValue[size];
361
362        for (int i = 0; i < size; i++) {
363            data[i] = new DefaultKeyedValue((Comparable) this.keys.get(i), 
364                    (Number) this.values.get(i));
365        }
366
367        Comparator comparator = new KeyedValueComparator(
368                KeyedValueComparatorType.BY_KEY, order);
369        Arrays.sort(data, comparator);
370        clear();
371
372        for (int i = 0; i < data.length; i++) {
373            final DefaultKeyedValue value = data[i];
374            addValue(value.getKey(), value.getValue());
375        }
376    }
377
378    /**
379     * Sorts the items in the list by value.  If the list contains 
380     * <code>null</code> values, they will sort to the end of the list, 
381     * irrespective of the sort order.
382     *
383     * @param order  the sort order (<code>null</code> not permitted).
384     */
385    public void sortByValues(SortOrder order) {
386        final int size = this.keys.size();
387        final DefaultKeyedValue[] data = new DefaultKeyedValue[size];
388        for (int i = 0; i < size; i++) {
389            data[i] = new DefaultKeyedValue((Comparable) this.keys.get(i), 
390                    (Number) this.values.get(i));
391        }
392
393        Comparator comparator = new KeyedValueComparator(
394                KeyedValueComparatorType.BY_VALUE, order);
395        Arrays.sort(data, comparator);
396
397        clear();
398        for (int i = 0; i < data.length; i++) {
399            final DefaultKeyedValue value = data[i];
400            addValue(value.getKey(), value.getValue());
401        }
402    }
403
404    /**
405     * Tests if this object is equal to another.
406     *
407     * @param obj  the object (<code>null</code> permitted).
408     *
409     * @return A boolean.
410     */
411    public boolean equals(Object obj) {
412        if (obj == this) {
413            return true;
414        }
415
416        if (!(obj instanceof KeyedValues)) {
417            return false;
418        }
419
420        KeyedValues that = (KeyedValues) obj;
421        int count = getItemCount();
422        if (count != that.getItemCount()) {
423            return false;
424        }
425
426        for (int i = 0; i < count; i++) {
427            Comparable k1 = getKey(i);
428            Comparable k2 = that.getKey(i);
429            if (!k1.equals(k2)) {
430                return false;
431            }
432            Number v1 = getValue(i);
433            Number v2 = that.getValue(i);
434            if (v1 == null) {
435                if (v2 != null) {
436                    return false;
437                }
438            }
439            else {
440                if (!v1.equals(v2)) {
441                    return false;
442                }
443            }
444        }
445        return true;
446    }
447
448    /**
449     * Returns a hash code.
450     * 
451     * @return A hash code.
452     */
453    public int hashCode() {
454        return (this.keys != null ? this.keys.hashCode() : 0);
455    }
456
457    /**
458     * Returns a clone.
459     * 
460     * @return A clone.
461     * 
462     * @throws CloneNotSupportedException  this class will not throw this 
463     *         exception, but subclasses might.
464     */
465    public Object clone() throws CloneNotSupportedException {
466        DefaultKeyedValues clone = (DefaultKeyedValues) super.clone();
467        clone.keys = (ArrayList) this.keys.clone();
468        clone.values = (ArrayList) this.values.clone();
469        clone.indexMap = (HashMap) this.indexMap.clone();
470        return clone;
471    }
472    
473}