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 * KeyedObjects.java 029 * ----------------- 030 * (C) Copyright 2003-2022, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.data; 038 039import java.io.Serializable; 040import java.util.ArrayList; 041import java.util.List; 042import org.jfree.chart.internal.Args; 043import org.jfree.chart.api.PublicCloneable; 044 045/** 046 * A collection of (key, object) pairs. 047 */ 048public class KeyedObjects<K extends Comparable<K>> implements Cloneable, PublicCloneable, Serializable { 049 050 /** For serialization. */ 051 private static final long serialVersionUID = 1321582394193530984L; 052 053 /** Storage for the data. */ 054 private List<KeyedObject<K>> data; 055 056 /** 057 * Creates a new collection (initially empty). 058 */ 059 public KeyedObjects() { 060 this.data = new ArrayList<>(); 061 } 062 063 /** 064 * Returns the number of items (values) in the collection. 065 * 066 * @return The item count. 067 */ 068 public int getItemCount() { 069 return this.data.size(); 070 } 071 072 /** 073 * Returns an object from the list. 074 * 075 * @param item the item index (zero-based). 076 * 077 * @return The object (possibly {@code null}). 078 * 079 * @throws IndexOutOfBoundsException if {@code item} is out of bounds. 080 */ 081 public Object getObject(int item) { 082 Object result = null; 083 KeyedObject kobj = this.data.get(item); 084 if (kobj != null) { 085 result = kobj.getObject(); 086 } 087 return result; 088 } 089 090 /** 091 * Returns the key at the specified position in the list. 092 * 093 * @param index the item index (zero-based). 094 * 095 * @return The row key. 096 * 097 * @throws IndexOutOfBoundsException if {@code item} is out of bounds. 098 * 099 * @see #getIndex(Comparable) 100 */ 101 public K getKey(int index) { 102 K result = null; 103 KeyedObject<K> item = this.data.get(index); 104 if (item != null) { 105 result = item.getKey(); 106 } 107 return result; 108 } 109 110 /** 111 * Returns the index for a given key, or {@code -1}. 112 * 113 * @param key the key ({@code null} not permitted). 114 * 115 * @return The index, or {@code -1} if the key is unrecognised. 116 * 117 * @see #getKey(int) 118 */ 119 public int getIndex(K key) { 120 Args.nullNotPermitted(key, "key"); 121 int i = 0; 122 for (KeyedObject ko : this.data) { 123 if (ko.getKey().equals(key)) { 124 return i; 125 } 126 i++; 127 } 128 return -1; 129 } 130 131 /** 132 * Returns a list containing all the keys in the list. 133 * 134 * @return The keys (never {@code null}). 135 */ 136 public List<K> getKeys() { 137 List<K> result = new ArrayList<>(); 138 for (KeyedObject<K> ko : this.data) { 139 result.add(ko.getKey()); 140 } 141 return result; 142 } 143 144 /** 145 * Returns the object for a given key. If the key is not recognised, the 146 * method should return {@code null}. 147 * 148 * @param key the key. 149 * 150 * @return The object (possibly {@code null}). 151 * 152 * @see #addObject(Comparable, Object) 153 */ 154 public Object getObject(K key) { 155 int index = getIndex(key); 156 if (index < 0) { 157 throw new UnknownKeyException("The key (" + key 158 + ") is not recognised."); 159 } 160 return getObject(index); 161 } 162 163 /** 164 * Adds a new object to the collection, or overwrites an existing object. 165 * This is the same as the {@link #setObject(Comparable, Object)} method. 166 * 167 * @param key the key. 168 * @param object the object. 169 * 170 * @see #getObject(Comparable) 171 */ 172 public void addObject(K key, Object object) { 173 setObject(key, object); 174 } 175 176 /** 177 * Replaces an existing object, or adds a new object to the collection. 178 * This is the same as the {@link #addObject(Comparable, Object)} 179 * method. 180 * 181 * @param key the key ({@code null} not permitted). 182 * @param object the object. 183 * 184 * @see #getObject(Comparable) 185 */ 186 public void setObject(K key, Object object) { 187 int keyIndex = getIndex(key); 188 if (keyIndex >= 0) { 189 KeyedObject ko = this.data.get(keyIndex); 190 ko.setObject(object); 191 } 192 else { 193 KeyedObject ko = new KeyedObject(key, object); 194 this.data.add(ko); 195 } 196 } 197 198 /** 199 * Inserts a new value at the specified position in the dataset or, if 200 * there is an existing item with the specified key, updates the value 201 * for that item and moves it to the specified position. 202 * 203 * @param position the position (in the range {@code 0} to 204 * {@code getItemCount()}). 205 * @param key the key ({@code null} not permitted). 206 * @param value the value ({@code null} permitted). 207 * 208 * @since 1.0.7 209 */ 210 public void insertValue(int position, K key, Object value) { 211 if (position < 0 || position > this.data.size()) { 212 throw new IllegalArgumentException("'position' out of bounds."); 213 } 214 Args.nullNotPermitted(key, "key"); 215 int pos = getIndex(key); 216 if (pos >= 0) { 217 this.data.remove(pos); 218 } 219 KeyedObject item = new KeyedObject(key, value); 220 if (position <= this.data.size()) { 221 this.data.add(position, item); 222 } else { 223 this.data.add(item); 224 } 225 } 226 227 /** 228 * Removes a value from the collection. 229 * 230 * @param index the index of the item to remove. 231 * 232 * @see #removeValue(Comparable) 233 */ 234 public void removeValue(int index) { 235 this.data.remove(index); 236 } 237 238 /** 239 * Removes a value from the collection. 240 * 241 * @param key the key ({@code null} not permitted). 242 * 243 * @see #removeValue(int) 244 * 245 * @throws UnknownKeyException if the key is not recognised. 246 */ 247 public void removeValue(K key) { 248 // defer argument checking 249 int index = getIndex(key); 250 if (index < 0) { 251 throw new UnknownKeyException("The key (" + key.toString() 252 + ") is not recognised."); 253 } 254 removeValue(index); 255 } 256 257 /** 258 * Clears all values from the collection. 259 * 260 * @since 1.0.7 261 */ 262 public void clear() { 263 this.data.clear(); 264 } 265 266 /** 267 * Returns a clone of this object. Keys in the list should be immutable 268 * and are not cloned. Objects in the list are cloned only if they 269 * implement {@link PublicCloneable}. 270 * 271 * @return A clone. 272 * 273 * @throws CloneNotSupportedException if there is a problem cloning. 274 */ 275 @Override 276 public Object clone() throws CloneNotSupportedException { 277 KeyedObjects clone = (KeyedObjects) super.clone(); 278 clone.data = new java.util.ArrayList(); 279 for (KeyedObject ko : this.data) { 280 clone.data.add((KeyedObject) ko.clone()); 281 } 282 return clone; 283 } 284 285 /** 286 * Tests this object for equality with an arbitrary object. 287 * 288 * @param obj the object ({@code null} permitted). 289 * 290 * @return A boolean. 291 */ 292 @Override 293 public boolean equals(Object obj) { 294 295 if (obj == this) { 296 return true; 297 } 298 if (!(obj instanceof KeyedObjects)) { 299 return false; 300 } 301 KeyedObjects that = (KeyedObjects) obj; 302 int count = getItemCount(); 303 if (count != that.getItemCount()) { 304 return false; 305 } 306 307 for (int i = 0; i < count; i++) { 308 Comparable k1 = getKey(i); 309 Comparable k2 = that.getKey(i); 310 if (!k1.equals(k2)) { 311 return false; 312 } 313 Object o1 = getObject(i); 314 Object o2 = that.getObject(i); 315 if (o1 == null) { 316 if (o2 != null) { 317 return false; 318 } 319 } 320 else { 321 if (!o1.equals(o2)) { 322 return false; 323 } 324 } 325 } 326 return true; 327 328 } 329 330 /** 331 * Returns a hash code. 332 * 333 * @return A hash code. 334 */ 335 @Override 336 public int hashCode() { 337 return (this.data != null ? this.data.hashCode() : 0); 338 } 339 340}