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
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, 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 }