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 * KeyedObjects.java
029 * -----------------
030 * (C) Copyright 2003-2008, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes:
036 * --------
037 * 31-Oct-2002 : Version 1 (DG);
038 * 11-Jan-2005 : Minor tidy up (DG);
039 * 28-Sep-2007 : Clean up equals() method (DG);
040 * 03-Oct-2007 : Make method behaviour consistent with DefaultKeyedValues (DG);
041 *
042 */
043
044package org.jfree.data;
045
046import java.io.Serializable;
047import java.util.Iterator;
048import java.util.List;
049
050import org.jfree.util.PublicCloneable;
051
052/**
053 * A collection of (key, object) pairs.
054 */
055public class KeyedObjects implements Cloneable, PublicCloneable, Serializable {
056
057    /** For serialization. */
058    private static final long serialVersionUID = 1321582394193530984L;
059
060    /** Storage for the data. */
061    private List data;
062
063    /**
064     * Creates a new collection (initially empty).
065     */
066    public KeyedObjects() {
067        this.data = new java.util.ArrayList();
068    }
069
070    /**
071     * Returns the number of items (values) in the collection.
072     *
073     * @return The item count.
074     */
075    public int getItemCount() {
076        return this.data.size();
077    }
078
079    /**
080     * Returns an object from the list.
081     *
082     * @param item  the item index (zero-based).
083     *
084     * @return The object (possibly <code>null</code>).
085     *
086     * @throws IndexOutOfBoundsException if <code>item</code> is out of bounds.
087     */
088    public Object getObject(int item) {
089        Object result = null;
090        KeyedObject kobj = (KeyedObject) this.data.get(item);
091        if (kobj != null) {
092            result = kobj.getObject();
093        }
094        return result;
095    }
096
097    /**
098     * Returns the key at the specified position in the list.
099     *
100     * @param index  the item index (zero-based).
101     *
102     * @return The row key.
103     *
104     * @throws IndexOutOfBoundsException if <code>item</code> is out of bounds.
105     *
106     * @see #getIndex(Comparable)
107     */
108    public Comparable getKey(int index) {
109        Comparable result = null;
110        KeyedObject item = (KeyedObject) this.data.get(index);
111        if (item != null) {
112            result = item.getKey();
113        }
114        return result;
115    }
116
117    /**
118     * Returns the index for a given key, or <code>-1</code>.
119     *
120     * @param key  the key (<code>null</code> not permitted).
121     *
122     * @return The index, or <code>-1</code> if the key is unrecognised.
123     *
124     * @see #getKey(int)
125     */
126    public int getIndex(Comparable key) {
127        if (key == null) {
128            throw new IllegalArgumentException("Null 'key' argument.");
129        }
130        int i = 0;
131        Iterator iterator = this.data.iterator();
132        while (iterator.hasNext()) {
133            KeyedObject ko = (KeyedObject) iterator.next();
134            if (ko.getKey().equals(key)) {
135                return i;
136            }
137            i++;
138        }
139        return -1;
140    }
141
142    /**
143     * Returns a list containing all the keys in the list.
144     *
145     * @return The keys (never <code>null</code>).
146     */
147    public List getKeys() {
148        List result = new java.util.ArrayList();
149        Iterator iterator = this.data.iterator();
150        while (iterator.hasNext()) {
151            KeyedObject ko = (KeyedObject) iterator.next();
152            result.add(ko.getKey());
153        }
154        return result;
155    }
156
157    /**
158     * Returns the object for a given key. If the key is not recognised, the
159     * method should return <code>null</code>.
160     *
161     * @param key  the key.
162     *
163     * @return The object (possibly <code>null</code>).
164     *
165     * @see #addObject(Comparable, Object)
166     */
167    public Object getObject(Comparable key) {
168        int index = getIndex(key);
169        if (index < 0) {
170            throw new UnknownKeyException("The key (" + key
171                    + ") is not recognised.");
172        }
173        return getObject(index);
174    }
175
176    /**
177     * Adds a new object to the collection, or overwrites an existing object.
178     * This is the same as the {@link #setObject(Comparable, Object)} method.
179     *
180     * @param key  the key.
181     * @param object  the object.
182     *
183     * @see #getObject(Comparable)
184     */
185    public void addObject(Comparable key, Object object) {
186        setObject(key, object);
187    }
188
189    /**
190     * Replaces an existing object, or adds a new object to the collection.
191     * This is the same as the {@link #addObject(Comparable, Object)}
192     * method.
193     *
194     * @param key  the key (<code>null</code> not permitted).
195     * @param object  the object.
196     *
197     * @see #getObject(Comparable)
198     */
199    public void setObject(Comparable key, Object object) {
200        int keyIndex = getIndex(key);
201        if (keyIndex >= 0) {
202            KeyedObject ko = (KeyedObject) this.data.get(keyIndex);
203            ko.setObject(object);
204        }
205        else {
206            KeyedObject ko = new KeyedObject(key, object);
207            this.data.add(ko);
208        }
209    }
210
211    /**
212     * Inserts a new value at the specified position in the dataset or, if
213     * there is an existing item with the specified key, updates the value
214     * for that item and moves it to the specified position.
215     *
216     * @param position  the position (in the range <code>0</code> to
217     *                  <code>getItemCount()</code>).
218     * @param key  the key (<code>null</code> not permitted).
219     * @param value  the value (<code>null</code> permitted).
220     *
221     * @since 1.0.7
222     */
223    public void insertValue(int position, Comparable key, Object value) {
224        if (position < 0 || position > this.data.size()) {
225            throw new IllegalArgumentException("'position' out of bounds.");
226        }
227        if (key == null) {
228            throw new IllegalArgumentException("Null 'key' argument.");
229        }
230        int pos = getIndex(key);
231        if (pos >= 0) {
232            this.data.remove(pos);
233        }
234        KeyedObject item = new KeyedObject(key, value);
235        if (position <= this.data.size()) {
236            this.data.add(position, item);
237        }
238        else {
239            this.data.add(item);
240        }
241    }
242
243    /**
244     * Removes a value from the collection.
245     *
246     * @param index  the index of the item to remove.
247     *
248     * @see #removeValue(Comparable)
249     */
250    public void removeValue(int index) {
251        this.data.remove(index);
252    }
253
254    /**
255     * Removes a value from the collection.
256     *
257     * @param key  the key (<code>null</code> not permitted).
258     *
259     * @see #removeValue(int)
260     *
261     * @throws UnknownKeyException if the key is not recognised.
262     */
263    public void removeValue(Comparable key) {
264        // defer argument checking
265        int index = getIndex(key);
266        if (index < 0) {
267            throw new UnknownKeyException("The key (" + key.toString()
268                    + ") is not recognised.");
269        }
270        removeValue(index);
271    }
272
273    /**
274     * Clears all values from the collection.
275     *
276     * @since 1.0.7
277     */
278    public void clear() {
279        this.data.clear();
280    }
281
282    /**
283     * Returns a clone of this object.  Keys in the list should be immutable
284     * and are not cloned.  Objects in the list are cloned only if they
285     * implement {@link PublicCloneable}.
286     *
287     * @return A clone.
288     *
289     * @throws CloneNotSupportedException if there is a problem cloning.
290     */
291    public Object clone() throws CloneNotSupportedException {
292        KeyedObjects clone = (KeyedObjects) super.clone();
293        clone.data = new java.util.ArrayList();
294        Iterator iterator = this.data.iterator();
295        while (iterator.hasNext()) {
296            KeyedObject ko = (KeyedObject) iterator.next();
297            clone.data.add(ko.clone());
298        }
299        return clone;
300    }
301
302    /**
303     * Tests this object for equality with an arbitrary object.
304     *
305     * @param obj  the object (<code>null</code> permitted).
306     *
307     * @return A boolean.
308     */
309    public boolean equals(Object obj) {
310
311        if (obj == this) {
312            return true;
313        }
314        if (!(obj instanceof KeyedObjects)) {
315            return false;
316        }
317        KeyedObjects that = (KeyedObjects) obj;
318        int count = getItemCount();
319        if (count != that.getItemCount()) {
320            return false;
321        }
322
323        for (int i = 0; i < count; i++) {
324            Comparable k1 = getKey(i);
325            Comparable k2 = that.getKey(i);
326            if (!k1.equals(k2)) {
327                return false;
328            }
329            Object o1 = getObject(i);
330            Object o2 = that.getObject(i);
331            if (o1 == null) {
332                if (o2 != null) {
333                    return false;
334                }
335            }
336            else {
337                if (!o1.equals(o2)) {
338                    return false;
339                }
340            }
341        }
342        return true;
343
344    }
345
346    /**
347     * Returns a hash code.
348     *
349     * @return A hash code.
350     */
351    public int hashCode() {
352        return (this.data != null ? this.data.hashCode() : 0);
353    }
354
355}