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 * DefaultKeyedValues.java 029 * ----------------------- 030 * (C) Copyright 2002-2013, 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 * 03-Jul-2013 : Use ParamChecks (DG); 058 * 059 */ 060 061package org.jfree.data; 062 063import java.io.Serializable; 064import java.util.ArrayList; 065import java.util.Arrays; 066import java.util.Comparator; 067import java.util.HashMap; 068import java.util.List; 069import org.jfree.chart.util.ParamChecks; 070 071import org.jfree.util.PublicCloneable; 072import org.jfree.util.SortOrder; 073 074/** 075 * An ordered list of (key, value) items. This class provides a default 076 * implementation of the {@link KeyedValues} interface. 077 */ 078public class DefaultKeyedValues implements KeyedValues, Cloneable, 079 PublicCloneable, Serializable { 080 081 /** For serialization. */ 082 private static final long serialVersionUID = 8468154364608194797L; 083 084 /** Storage for the keys. */ 085 private ArrayList keys; 086 087 /** Storage for the values. */ 088 private ArrayList values; 089 090 /** 091 * Contains (key, Integer) mappings, where the Integer is the index for 092 * the key in the list. 093 */ 094 private HashMap indexMap; 095 096 /** 097 * Creates a new collection (initially empty). 098 */ 099 public DefaultKeyedValues() { 100 this.keys = new ArrayList(); 101 this.values = new ArrayList(); 102 this.indexMap = new HashMap(); 103 } 104 105 /** 106 * Returns the number of items (values) in the collection. 107 * 108 * @return The item count. 109 */ 110 @Override 111 public int getItemCount() { 112 return this.indexMap.size(); 113 } 114 115 /** 116 * Returns a value. 117 * 118 * @param item the item of interest (zero-based index). 119 * 120 * @return The value (possibly <code>null</code>). 121 * 122 * @throws IndexOutOfBoundsException if <code>item</code> is out of bounds. 123 */ 124 @Override 125 public Number getValue(int item) { 126 return (Number) this.values.get(item); 127 } 128 129 /** 130 * Returns a key. 131 * 132 * @param index the item index (zero-based). 133 * 134 * @return The row key. 135 * 136 * @throws IndexOutOfBoundsException if <code>item</code> is out of bounds. 137 */ 138 @Override 139 public Comparable getKey(int index) { 140 return (Comparable) this.keys.get(index); 141 } 142 143 /** 144 * Returns the index for a given key. 145 * 146 * @param key the key (<code>null</code> not permitted). 147 * 148 * @return The index, or <code>-1</code> if the key is not recognised. 149 * 150 * @throws IllegalArgumentException if <code>key</code> is 151 * <code>null</code>. 152 */ 153 @Override 154 public int getIndex(Comparable key) { 155 ParamChecks.nullNotPermitted(key, "key"); 156 final Integer i = (Integer) this.indexMap.get(key); 157 if (i == null) { 158 return -1; // key not found 159 } 160 return i.intValue(); 161 } 162 163 /** 164 * Returns the keys for the values in the collection. 165 * 166 * @return The keys (never <code>null</code>). 167 */ 168 @Override 169 public List getKeys() { 170 return (List) this.keys.clone(); 171 } 172 173 /** 174 * Returns the value for a given key. 175 * 176 * @param key the key (<code>null</code> not permitted). 177 * 178 * @return The value (possibly <code>null</code>). 179 * 180 * @throws UnknownKeyException if the key is not recognised. 181 * 182 * @see #getValue(int) 183 */ 184 @Override 185 public Number getValue(Comparable key) { 186 int index = getIndex(key); 187 if (index < 0) { 188 throw new UnknownKeyException("Key not found: " + key); 189 } 190 return getValue(index); 191 } 192 193 /** 194 * Updates an existing value, or adds a new value to the collection. 195 * 196 * @param key the key (<code>null</code> not permitted). 197 * @param value the value. 198 * 199 * @see #addValue(Comparable, Number) 200 */ 201 public void addValue(Comparable key, double value) { 202 addValue(key, new Double(value)); 203 } 204 205 /** 206 * Adds a new value to the collection, or updates an existing value. 207 * This method passes control directly to the 208 * {@link #setValue(Comparable, Number)} method. 209 * 210 * @param key the key (<code>null</code> not permitted). 211 * @param value the value (<code>null</code> permitted). 212 */ 213 public void addValue(Comparable key, Number value) { 214 setValue(key, value); 215 } 216 217 /** 218 * Updates an existing value, or adds a new value to the collection. 219 * 220 * @param key the key (<code>null</code> not permitted). 221 * @param value the value. 222 */ 223 public void setValue(Comparable key, double value) { 224 setValue(key, new Double(value)); 225 } 226 227 /** 228 * Updates an existing value, or adds a new value to the collection. 229 * 230 * @param key the key (<code>null</code> not permitted). 231 * @param value the value (<code>null</code> permitted). 232 */ 233 public void setValue(Comparable key, Number value) { 234 ParamChecks.nullNotPermitted(key, "key"); 235 int keyIndex = getIndex(key); 236 if (keyIndex >= 0) { 237 this.keys.set(keyIndex, key); 238 this.values.set(keyIndex, value); 239 } 240 else { 241 this.keys.add(key); 242 this.values.add(value); 243 this.indexMap.put(key, new Integer(this.keys.size() - 1)); 244 } 245 } 246 247 /** 248 * Inserts a new value at the specified position in the dataset or, if 249 * there is an existing item with the specified key, updates the value 250 * for that item and moves it to the specified position. 251 * 252 * @param position the position (in the range 0 to getItemCount()). 253 * @param key the key (<code>null</code> not permitted). 254 * @param value the value. 255 * 256 * @since 1.0.6 257 */ 258 public void insertValue(int position, Comparable key, double value) { 259 insertValue(position, key, new Double(value)); 260 } 261 262 /** 263 * Inserts a new value at the specified position in the dataset or, if 264 * there is an existing item with the specified key, updates the value 265 * for that item and moves it to the specified position. 266 * 267 * @param position the position (in the range 0 to getItemCount()). 268 * @param key the key (<code>null</code> not permitted). 269 * @param value the value (<code>null</code> permitted). 270 * 271 * @since 1.0.6 272 */ 273 public void insertValue(int position, Comparable key, Number value) { 274 if (position < 0 || position > getItemCount()) { 275 throw new IllegalArgumentException("'position' out of bounds."); 276 } 277 ParamChecks.nullNotPermitted(key, "key"); 278 int pos = getIndex(key); 279 if (pos == position) { 280 this.keys.set(pos, key); 281 this.values.set(pos, value); 282 } 283 else { 284 if (pos >= 0) { 285 this.keys.remove(pos); 286 this.values.remove(pos); 287 } 288 289 this.keys.add(position, key); 290 this.values.add(position, value); 291 rebuildIndex(); 292 } 293 } 294 295 /** 296 * Rebuilds the key to indexed-position mapping after an positioned insert 297 * or a remove operation. 298 */ 299 private void rebuildIndex () { 300 this.indexMap.clear(); 301 for (int i = 0; i < this.keys.size(); i++) { 302 final Object key = this.keys.get(i); 303 this.indexMap.put(key, new Integer(i)); 304 } 305 } 306 307 /** 308 * Removes a value from the collection. 309 * 310 * @param index the index of the item to remove (in the range 311 * <code>0</code> to <code>getItemCount() - 1</code>). 312 * 313 * @throws IndexOutOfBoundsException if <code>index</code> is not within 314 * the specified range. 315 */ 316 public void removeValue(int index) { 317 this.keys.remove(index); 318 this.values.remove(index); 319 rebuildIndex(); 320 } 321 322 /** 323 * Removes a value from the collection. 324 * 325 * @param key the item key (<code>null</code> not permitted). 326 * 327 * @throws IllegalArgumentException if <code>key</code> is 328 * <code>null</code>. 329 * @throws UnknownKeyException if <code>key</code> is not recognised. 330 */ 331 public void removeValue(Comparable key) { 332 int index = getIndex(key); 333 if (index < 0) { 334 throw new UnknownKeyException("The key (" + key 335 + ") is not recognised."); 336 } 337 removeValue(index); 338 } 339 340 /** 341 * Clears all values from the collection. 342 * 343 * @since 1.0.2 344 */ 345 public void clear() { 346 this.keys.clear(); 347 this.values.clear(); 348 this.indexMap.clear(); 349 } 350 351 /** 352 * Sorts the items in the list by key. 353 * 354 * @param order the sort order (<code>null</code> not permitted). 355 */ 356 public void sortByKeys(SortOrder order) { 357 final int size = this.keys.size(); 358 final DefaultKeyedValue[] data = new DefaultKeyedValue[size]; 359 360 for (int i = 0; i < size; i++) { 361 data[i] = new DefaultKeyedValue((Comparable) this.keys.get(i), 362 (Number) this.values.get(i)); 363 } 364 365 Comparator comparator = new KeyedValueComparator( 366 KeyedValueComparatorType.BY_KEY, order); 367 Arrays.sort(data, comparator); 368 clear(); 369 370 for (int i = 0; i < data.length; i++) { 371 final DefaultKeyedValue value = data[i]; 372 addValue(value.getKey(), value.getValue()); 373 } 374 } 375 376 /** 377 * Sorts the items in the list by value. If the list contains 378 * <code>null</code> values, they will sort to the end of the list, 379 * irrespective of the sort order. 380 * 381 * @param order the sort order (<code>null</code> not permitted). 382 */ 383 public void sortByValues(SortOrder order) { 384 final int size = this.keys.size(); 385 final DefaultKeyedValue[] data = new DefaultKeyedValue[size]; 386 for (int i = 0; i < size; i++) { 387 data[i] = new DefaultKeyedValue((Comparable) this.keys.get(i), 388 (Number) this.values.get(i)); 389 } 390 391 Comparator comparator = new KeyedValueComparator( 392 KeyedValueComparatorType.BY_VALUE, order); 393 Arrays.sort(data, comparator); 394 395 clear(); 396 for (int i = 0; i < data.length; i++) { 397 final DefaultKeyedValue value = data[i]; 398 addValue(value.getKey(), value.getValue()); 399 } 400 } 401 402 /** 403 * Tests if this object is equal to another. 404 * 405 * @param obj the object (<code>null</code> permitted). 406 * 407 * @return A boolean. 408 */ 409 @Override 410 public boolean equals(Object obj) { 411 if (obj == this) { 412 return true; 413 } 414 415 if (!(obj instanceof KeyedValues)) { 416 return false; 417 } 418 419 KeyedValues that = (KeyedValues) obj; 420 int count = getItemCount(); 421 if (count != that.getItemCount()) { 422 return false; 423 } 424 425 for (int i = 0; i < count; i++) { 426 Comparable k1 = getKey(i); 427 Comparable k2 = that.getKey(i); 428 if (!k1.equals(k2)) { 429 return false; 430 } 431 Number v1 = getValue(i); 432 Number v2 = that.getValue(i); 433 if (v1 == null) { 434 if (v2 != null) { 435 return false; 436 } 437 } 438 else { 439 if (!v1.equals(v2)) { 440 return false; 441 } 442 } 443 } 444 return true; 445 } 446 447 /** 448 * Returns a hash code. 449 * 450 * @return A hash code. 451 */ 452 @Override 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 @Override 466 public Object clone() throws CloneNotSupportedException { 467 DefaultKeyedValues clone = (DefaultKeyedValues) super.clone(); 468 clone.keys = (ArrayList) this.keys.clone(); 469 clone.values = (ArrayList) this.values.clone(); 470 clone.indexMap = (HashMap) this.indexMap.clone(); 471 return clone; 472 } 473 474}