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 }