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 * 057 */ 058 059package org.jfree.data; 060 061import java.io.Serializable; 062import java.util.ArrayList; 063import java.util.Arrays; 064import java.util.Comparator; 065import java.util.HashMap; 066import java.util.List; 067 068import org.jfree.util.PublicCloneable; 069import org.jfree.util.SortOrder; 070 071/** 072 * An ordered list of (key, value) items. This class provides a default 073 * implementation of the {@link KeyedValues} interface. 074 */ 075public class DefaultKeyedValues implements KeyedValues, 076 Cloneable, PublicCloneable, 077 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 318 // did we remove the last item? If not, then rebuild the index .. 319 if (index < this.keys.size()) { 320 rebuildIndex(); 321 } 322 } 323 324 /** 325 * Removes a value from the collection. 326 * 327 * @param key the item key (<code>null</code> not permitted). 328 * 329 * @throws IllegalArgumentException if <code>key</code> is 330 * <code>null</code>. 331 * @throws UnknownKeyException if <code>key</code> is not recognised. 332 */ 333 public void removeValue(Comparable key) { 334 int index = getIndex(key); 335 if (index < 0) { 336 throw new UnknownKeyException("The key (" + key 337 + ") is not recognised."); 338 } 339 removeValue(index); 340 } 341 342 /** 343 * Clears all values from the collection. 344 * 345 * @since 1.0.2 346 */ 347 public void clear() { 348 this.keys.clear(); 349 this.values.clear(); 350 this.indexMap.clear(); 351 } 352 353 /** 354 * Sorts the items in the list by key. 355 * 356 * @param order the sort order (<code>null</code> not permitted). 357 */ 358 public void sortByKeys(SortOrder order) { 359 final int size = this.keys.size(); 360 final DefaultKeyedValue[] data = new DefaultKeyedValue[size]; 361 362 for (int i = 0; i < size; i++) { 363 data[i] = new DefaultKeyedValue((Comparable) this.keys.get(i), 364 (Number) this.values.get(i)); 365 } 366 367 Comparator comparator = new KeyedValueComparator( 368 KeyedValueComparatorType.BY_KEY, order); 369 Arrays.sort(data, comparator); 370 clear(); 371 372 for (int i = 0; i < data.length; i++) { 373 final DefaultKeyedValue value = data[i]; 374 addValue(value.getKey(), value.getValue()); 375 } 376 } 377 378 /** 379 * Sorts the items in the list by value. If the list contains 380 * <code>null</code> values, they will sort to the end of the list, 381 * irrespective of the sort order. 382 * 383 * @param order the sort order (<code>null</code> not permitted). 384 */ 385 public void sortByValues(SortOrder order) { 386 final int size = this.keys.size(); 387 final DefaultKeyedValue[] data = new DefaultKeyedValue[size]; 388 for (int i = 0; i < size; i++) { 389 data[i] = new DefaultKeyedValue((Comparable) this.keys.get(i), 390 (Number) this.values.get(i)); 391 } 392 393 Comparator comparator = new KeyedValueComparator( 394 KeyedValueComparatorType.BY_VALUE, order); 395 Arrays.sort(data, comparator); 396 397 clear(); 398 for (int i = 0; i < data.length; i++) { 399 final DefaultKeyedValue value = data[i]; 400 addValue(value.getKey(), value.getValue()); 401 } 402 } 403 404 /** 405 * Tests if this object is equal to another. 406 * 407 * @param obj the object (<code>null</code> permitted). 408 * 409 * @return A boolean. 410 */ 411 public boolean equals(Object obj) { 412 if (obj == this) { 413 return true; 414 } 415 416 if (!(obj instanceof KeyedValues)) { 417 return false; 418 } 419 420 KeyedValues that = (KeyedValues) obj; 421 int count = getItemCount(); 422 if (count != that.getItemCount()) { 423 return false; 424 } 425 426 for (int i = 0; i < count; i++) { 427 Comparable k1 = getKey(i); 428 Comparable k2 = that.getKey(i); 429 if (!k1.equals(k2)) { 430 return false; 431 } 432 Number v1 = getValue(i); 433 Number v2 = that.getValue(i); 434 if (v1 == null) { 435 if (v2 != null) { 436 return false; 437 } 438 } 439 else { 440 if (!v1.equals(v2)) { 441 return false; 442 } 443 } 444 } 445 return true; 446 } 447 448 /** 449 * Returns a hash code. 450 * 451 * @return A hash code. 452 */ 453 public int hashCode() { 454 return (this.keys != null ? this.keys.hashCode() : 0); 455 } 456 457 /** 458 * Returns a clone. 459 * 460 * @return A clone. 461 * 462 * @throws CloneNotSupportedException this class will not throw this 463 * exception, but subclasses might. 464 */ 465 public Object clone() throws CloneNotSupportedException { 466 DefaultKeyedValues clone = (DefaultKeyedValues) super.clone(); 467 clone.keys = (ArrayList) this.keys.clone(); 468 clone.values = (ArrayList) this.values.clone(); 469 clone.indexMap = (HashMap) this.indexMap.clone(); 470 return clone; 471 } 472 473}