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 }