001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, 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 * ComparableObjectSeries.java 029 * --------------------------- 030 * (C) Copyright 2006-present, 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.Collections; 041import java.util.List; 042import java.util.Objects; 043import org.jfree.chart.util.Args; 044 045import org.jfree.chart.util.CloneUtils; 046import org.jfree.data.general.Series; 047import org.jfree.data.general.SeriesChangeEvent; 048import org.jfree.data.general.SeriesException; 049 050/** 051 * A (possibly ordered) list of (Comparable, Object) data items. 052 */ 053public class ComparableObjectSeries extends Series 054 implements Cloneable, Serializable { 055 056 /** Storage for the data items in the series. */ 057 protected List data; 058 059 /** The maximum number of items for the series. */ 060 private int maximumItemCount = Integer.MAX_VALUE; 061 062 /** A flag that controls whether the items are automatically sorted. */ 063 private boolean autoSort; 064 065 /** A flag that controls whether or not duplicate x-values are allowed. */ 066 private boolean allowDuplicateXValues; 067 068 /** 069 * Creates a new empty series. By default, items added to the series will 070 * be sorted into ascending order by x-value, and duplicate x-values will 071 * be allowed (these defaults can be modified with another constructor. 072 * 073 * @param key the series key ({@code null} not permitted). 074 */ 075 public ComparableObjectSeries(Comparable key) { 076 this(key, true, true); 077 } 078 079 /** 080 * Constructs a new series that contains no data. You can specify 081 * whether or not duplicate x-values are allowed for the series. 082 * 083 * @param key the series key ({@code null} not permitted). 084 * @param autoSort a flag that controls whether or not the items in the 085 * series are sorted. 086 * @param allowDuplicateXValues a flag that controls whether duplicate 087 * x-values are allowed. 088 */ 089 public ComparableObjectSeries(Comparable key, boolean autoSort, 090 boolean allowDuplicateXValues) { 091 super(key); 092 this.data = new java.util.ArrayList(); 093 this.autoSort = autoSort; 094 this.allowDuplicateXValues = allowDuplicateXValues; 095 } 096 097 /** 098 * Returns the flag that controls whether the items in the series are 099 * automatically sorted. There is no setter for this flag, it must be 100 * defined in the series constructor. 101 * 102 * @return A boolean. 103 */ 104 public boolean getAutoSort() { 105 return this.autoSort; 106 } 107 108 /** 109 * Returns a flag that controls whether duplicate x-values are allowed. 110 * This flag can only be set in the constructor. 111 * 112 * @return A boolean. 113 */ 114 public boolean getAllowDuplicateXValues() { 115 return this.allowDuplicateXValues; 116 } 117 118 /** 119 * Returns the number of items in the series. 120 * 121 * @return The item count. 122 */ 123 @Override 124 public int getItemCount() { 125 return this.data.size(); 126 } 127 128 /** 129 * Returns the maximum number of items that will be retained in the series. 130 * The default value is {@code Integer.MAX_VALUE}. 131 * 132 * @return The maximum item count. 133 * @see #setMaximumItemCount(int) 134 */ 135 public int getMaximumItemCount() { 136 return this.maximumItemCount; 137 } 138 139 /** 140 * Sets the maximum number of items that will be retained in the series. 141 * If you add a new item to the series such that the number of items will 142 * exceed the maximum item count, then the first element in the series is 143 * automatically removed, ensuring that the maximum item count is not 144 * exceeded. 145 * <p> 146 * Typically this value is set before the series is populated with data, 147 * but if it is applied later, it may cause some items to be removed from 148 * the series (in which case a {@link SeriesChangeEvent} will be sent to 149 * all registered listeners. 150 * 151 * @param maximum the maximum number of items for the series. 152 */ 153 public void setMaximumItemCount(int maximum) { 154 this.maximumItemCount = maximum; 155 boolean dataRemoved = false; 156 while (this.data.size() > maximum) { 157 this.data.remove(0); 158 dataRemoved = true; 159 } 160 if (dataRemoved) { 161 fireSeriesChanged(); 162 } 163 } 164 165 /** 166 * Adds new data to the series and sends a {@link SeriesChangeEvent} to 167 * all registered listeners. 168 * <P> 169 * Throws an exception if the x-value is a duplicate AND the 170 * allowDuplicateXValues flag is false. 171 * 172 * @param x the x-value ({@code null} not permitted). 173 * @param y the y-value ({@code null} permitted). 174 */ 175 protected void add(Comparable x, Object y) { 176 // argument checking delegated... 177 add(x, y, true); 178 } 179 180 /** 181 * Adds new data to the series and, if requested, sends a 182 * {@link SeriesChangeEvent} to all registered listeners. 183 * <P> 184 * Throws an exception if the x-value is a duplicate AND the 185 * allowDuplicateXValues flag is false. 186 * 187 * @param x the x-value ({@code null} not permitted). 188 * @param y the y-value ({@code null} permitted). 189 * @param notify a flag the controls whether or not a 190 * {@link SeriesChangeEvent} is sent to all registered 191 * listeners. 192 */ 193 protected void add(Comparable x, Object y, boolean notify) { 194 // delegate argument checking to XYDataItem... 195 ComparableObjectItem item = new ComparableObjectItem(x, y); 196 add(item, notify); 197 } 198 199 /** 200 * Adds a data item to the series and, if requested, sends a 201 * {@link SeriesChangeEvent} to all registered listeners. 202 * 203 * @param item the (x, y) item ({@code null} not permitted). 204 * @param notify a flag that controls whether or not a 205 * {@link SeriesChangeEvent} is sent to all registered 206 * listeners. 207 */ 208 protected void add(ComparableObjectItem item, boolean notify) { 209 210 Args.nullNotPermitted(item, "item"); 211 if (this.autoSort) { 212 int index = Collections.binarySearch(this.data, item); 213 if (index < 0) { 214 this.data.add(-index - 1, item); 215 } 216 else { 217 if (this.allowDuplicateXValues) { 218 // need to make sure we are adding *after* any duplicates 219 int size = this.data.size(); 220 while (index < size 221 && item.compareTo(this.data.get(index)) == 0) { 222 index++; 223 } 224 if (index < this.data.size()) { 225 this.data.add(index, item); 226 } 227 else { 228 this.data.add(item); 229 } 230 } 231 else { 232 throw new SeriesException("X-value already exists."); 233 } 234 } 235 } 236 else { 237 if (!this.allowDuplicateXValues) { 238 // can't allow duplicate values, so we need to check whether 239 // there is an item with the given x-value already 240 int index = indexOf(item.getComparable()); 241 if (index >= 0) { 242 throw new SeriesException("X-value already exists."); 243 } 244 } 245 this.data.add(item); 246 } 247 if (getItemCount() > this.maximumItemCount) { 248 this.data.remove(0); 249 } 250 if (notify) { 251 fireSeriesChanged(); 252 } 253 } 254 255 /** 256 * Returns the index of the item with the specified x-value, or a negative 257 * index if the series does not contain an item with that x-value. Be 258 * aware that for an unsorted series, the index is found by iterating 259 * through all items in the series. 260 * 261 * @param x the x-value ({@code null} not permitted). 262 * 263 * @return The index. 264 */ 265 public int indexOf(Comparable x) { 266 if (this.autoSort) { 267 return Collections.binarySearch(this.data, new ComparableObjectItem( 268 x, null)); 269 } 270 else { 271 for (int i = 0; i < this.data.size(); i++) { 272 ComparableObjectItem item = (ComparableObjectItem) 273 this.data.get(i); 274 if (item.getComparable().equals(x)) { 275 return i; 276 } 277 } 278 return -1; 279 } 280 } 281 282 /** 283 * Updates an item in the series. 284 * 285 * @param x the x-value ({@code null} not permitted). 286 * @param y the y-value ({@code null} permitted). 287 * 288 * @throws SeriesException if there is no existing item with the specified 289 * x-value. 290 */ 291 protected void update(Comparable x, Object y) { 292 int index = indexOf(x); 293 if (index < 0) { 294 throw new SeriesException("No observation for x = " + x); 295 } 296 else { 297 ComparableObjectItem item = getDataItem(index); 298 item.setObject(y); 299 fireSeriesChanged(); 300 } 301 } 302 303 /** 304 * Updates the value of an item in the series and sends a 305 * {@link SeriesChangeEvent} to all registered listeners. 306 * 307 * @param index the item (zero based index). 308 * @param y the new value ({@code null} permitted). 309 */ 310 protected void updateByIndex(int index, Object y) { 311 ComparableObjectItem item = getDataItem(index); 312 item.setObject(y); 313 fireSeriesChanged(); 314 } 315 316 /** 317 * Return the data item with the specified index. 318 * 319 * @param index the index. 320 * 321 * @return The data item with the specified index. 322 */ 323 protected ComparableObjectItem getDataItem(int index) { 324 return (ComparableObjectItem) this.data.get(index); 325 } 326 327 /** 328 * Deletes a range of items from the series and sends a 329 * {@link SeriesChangeEvent} to all registered listeners. 330 * 331 * @param start the start index (zero-based). 332 * @param end the end index (zero-based). 333 */ 334 protected void delete(int start, int end) { 335 if (end >= start) { 336 this.data.subList(start, end + 1).clear(); 337 } 338 fireSeriesChanged(); 339 } 340 341 /** 342 * Removes all data items from the series and, unless the series is 343 * already empty, sends a {@link SeriesChangeEvent} to all registered 344 * listeners. 345 */ 346 public void clear() { 347 if (this.data.size() > 0) { 348 this.data.clear(); 349 fireSeriesChanged(); 350 } 351 } 352 353 /** 354 * Removes the item at the specified index and sends a 355 * {@link SeriesChangeEvent} to all registered listeners. 356 * 357 * @param index the index. 358 * 359 * @return The item removed. 360 */ 361 protected ComparableObjectItem remove(int index) { 362 ComparableObjectItem result = (ComparableObjectItem) this.data.remove( 363 index); 364 fireSeriesChanged(); 365 return result; 366 } 367 368 /** 369 * Removes the item with the specified x-value and sends a 370 * {@link SeriesChangeEvent} to all registered listeners. 371 * 372 * @param x the x-value. 373 374 * @return The item removed. 375 */ 376 public ComparableObjectItem remove(Comparable x) { 377 return remove(indexOf(x)); 378 } 379 380 /** 381 * Tests this series for equality with an arbitrary object. 382 * 383 * @param obj the object to test against for equality 384 * ({@code null} permitted). 385 * 386 * @return A boolean. 387 */ 388 @Override 389 public boolean equals(Object obj) { 390 if (obj == this) { 391 return true; 392 } 393 if (!(obj instanceof ComparableObjectSeries)) { 394 return false; 395 } 396 if (!super.equals(obj)) { 397 return false; 398 } 399 ComparableObjectSeries that = (ComparableObjectSeries) obj; 400 if (this.maximumItemCount != that.maximumItemCount) { 401 return false; 402 } 403 if (this.autoSort != that.autoSort) { 404 return false; 405 } 406 if (this.allowDuplicateXValues != that.allowDuplicateXValues) { 407 return false; 408 } 409 if (!Objects.equals(this.data, that.data)) { 410 return false; 411 } 412 return true; 413 } 414 415 /** 416 * Returns a hash code. 417 * 418 * @return A hash code. 419 */ 420 @Override 421 public int hashCode() { 422 int result = super.hashCode(); 423 // it is too slow to look at every data item, so let's just look at 424 // the first, middle and last items... 425 int count = getItemCount(); 426 if (count > 0) { 427 ComparableObjectItem item = getDataItem(0); 428 result = 29 * result + item.hashCode(); 429 } 430 if (count > 1) { 431 ComparableObjectItem item = getDataItem(count - 1); 432 result = 29 * result + item.hashCode(); 433 } 434 if (count > 2) { 435 ComparableObjectItem item = getDataItem(count / 2); 436 result = 29 * result + item.hashCode(); 437 } 438 result = 29 * result + this.maximumItemCount; 439 result = 29 * result + (this.autoSort ? 1 : 0); 440 result = 29 * result + (this.allowDuplicateXValues ? 1 : 0); 441 return result; 442 } 443 444 /** 445 * Returns a clone of the series. 446 * 447 * @return A clone of the series. 448 * 449 * @throws CloneNotSupportedException if there is a cloning problem. 450 */ 451 @Override 452 @SuppressWarnings("unchecked") 453 public Object clone() throws CloneNotSupportedException { 454 ComparableObjectSeries clone = (ComparableObjectSeries) super.clone(); 455 clone.data = CloneUtils.cloneList(this.data); 456 return clone; 457 } 458 459}