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