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