001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * -----------------
028 * KeyedObjects.java
029 * -----------------
030 * (C) Copyright 2003-2013, 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 * 03-Jul-2013 : Use ParamChecks (DG);
042 *
043 */
044
045package org.jfree.data;
046
047import java.io.Serializable;
048import java.util.Iterator;
049import java.util.List;
050import org.jfree.chart.util.ParamChecks;
051
052import org.jfree.util.PublicCloneable;
053
054/**
055 * A collection of (key, object) pairs.
056 */
057public class KeyedObjects implements Cloneable, PublicCloneable, Serializable {
058
059    /** For serialization. */
060    private static final long serialVersionUID = 1321582394193530984L;
061
062    /** Storage for the data. */
063    private List data;
064
065    /**
066     * Creates a new collection (initially empty).
067     */
068    public KeyedObjects() {
069        this.data = new java.util.ArrayList();
070    }
071
072    /**
073     * Returns the number of items (values) in the collection.
074     *
075     * @return The item count.
076     */
077    public int getItemCount() {
078        return this.data.size();
079    }
080
081    /**
082     * Returns an object from the list.
083     *
084     * @param item  the item index (zero-based).
085     *
086     * @return The object (possibly <code>null</code>).
087     *
088     * @throws IndexOutOfBoundsException if <code>item</code> is out of bounds.
089     */
090    public Object getObject(int item) {
091        Object result = null;
092        KeyedObject kobj = (KeyedObject) this.data.get(item);
093        if (kobj != null) {
094            result = kobj.getObject();
095        }
096        return result;
097    }
098
099    /**
100     * Returns the key at the specified position in the list.
101     *
102     * @param index  the item index (zero-based).
103     *
104     * @return The row key.
105     *
106     * @throws IndexOutOfBoundsException if <code>item</code> is out of bounds.
107     *
108     * @see #getIndex(Comparable)
109     */
110    public Comparable getKey(int index) {
111        Comparable result = null;
112        KeyedObject item = (KeyedObject) this.data.get(index);
113        if (item != null) {
114            result = item.getKey();
115        }
116        return result;
117    }
118
119    /**
120     * Returns the index for a given key, or <code>-1</code>.
121     *
122     * @param key  the key (<code>null</code> not permitted).
123     *
124     * @return The index, or <code>-1</code> if the key is unrecognised.
125     *
126     * @see #getKey(int)
127     */
128    public int getIndex(Comparable key) {
129        ParamChecks.nullNotPermitted(key, "key");
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        ParamChecks.nullNotPermitted(key, "key");
228        int pos = getIndex(key);
229        if (pos >= 0) {
230            this.data.remove(pos);
231        }
232        KeyedObject item = new KeyedObject(key, value);
233        if (position <= this.data.size()) {
234            this.data.add(position, item);
235        }
236        else {
237            this.data.add(item);
238        }
239    }
240
241    /**
242     * Removes a value from the collection.
243     *
244     * @param index  the index of the item to remove.
245     *
246     * @see #removeValue(Comparable)
247     */
248    public void removeValue(int index) {
249        this.data.remove(index);
250    }
251
252    /**
253     * Removes a value from the collection.
254     *
255     * @param key  the key (<code>null</code> not permitted).
256     *
257     * @see #removeValue(int)
258     *
259     * @throws UnknownKeyException if the key is not recognised.
260     */
261    public void removeValue(Comparable key) {
262        // defer argument checking
263        int index = getIndex(key);
264        if (index < 0) {
265            throw new UnknownKeyException("The key (" + key.toString()
266                    + ") is not recognised.");
267        }
268        removeValue(index);
269    }
270
271    /**
272     * Clears all values from the collection.
273     *
274     * @since 1.0.7
275     */
276    public void clear() {
277        this.data.clear();
278    }
279
280    /**
281     * Returns a clone of this object.  Keys in the list should be immutable
282     * and are not cloned.  Objects in the list are cloned only if they
283     * implement {@link PublicCloneable}.
284     *
285     * @return A clone.
286     *
287     * @throws CloneNotSupportedException if there is a problem cloning.
288     */
289    @Override
290    public Object clone() throws CloneNotSupportedException {
291        KeyedObjects clone = (KeyedObjects) super.clone();
292        clone.data = new java.util.ArrayList();
293        Iterator iterator = this.data.iterator();
294        while (iterator.hasNext()) {
295            KeyedObject ko = (KeyedObject) iterator.next();
296            clone.data.add(ko.clone());
297        }
298        return clone;
299    }
300
301    /**
302     * Tests this object for equality with an arbitrary object.
303     *
304     * @param obj  the object (<code>null</code> permitted).
305     *
306     * @return A boolean.
307     */
308    @Override
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    @Override
352    public int hashCode() {
353        return (this.data != null ? this.data.hashCode() : 0);
354    }
355
356}