001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, 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 * TimeSeries.java 029 * --------------- 030 * (C) Copyright 2001-2005, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Bryan Scott; 034 * 035 * $Id: TimeSeries.java,v 1.10.2.6 2005/12/01 22:03:07 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 11-Oct-2001 : Version 1 (DG); 040 * 14-Nov-2001 : Added listener mechanism (DG); 041 * 15-Nov-2001 : Updated argument checking and exceptions in add() method (DG); 042 * 29-Nov-2001 : Added properties to describe the domain and range (DG); 043 * 07-Dec-2001 : Renamed TimeSeries --> BasicTimeSeries (DG); 044 * 01-Mar-2002 : Updated import statements (DG); 045 * 28-Mar-2002 : Added a method add(TimePeriod, double) (DG); 046 * 27-Aug-2002 : Changed return type of delete method to void (DG); 047 * 04-Oct-2002 : Added itemCount and historyCount attributes, fixed errors 048 * reported by Checkstyle (DG); 049 * 29-Oct-2002 : Added series change notification to addOrUpdate() method (DG); 050 * 28-Jan-2003 : Changed name back to TimeSeries (DG); 051 * 13-Mar-2003 : Moved to com.jrefinery.data.time package and implemented 052 * Serializable (DG); 053 * 01-May-2003 : Updated equals() method (see bug report 727575) (DG); 054 * 14-Aug-2003 : Added ageHistoryCountItems method (copied existing code for 055 * contents) made a method and added to addOrUpdate. Made a 056 * public method to enable ageing against a specified time 057 * (eg now) as opposed to lastest time in series (BS); 058 * 15-Oct-2003 : Added fix for setItemCount method - see bug report 804425. 059 * Modified exception message in add() method to be more 060 * informative (DG); 061 * 13-Apr-2004 : Added clear() method (DG); 062 * 21-May-2004 : Added an extra addOrUpdate() method (DG); 063 * 15-Jun-2004 : Fixed NullPointerException in equals() method (DG); 064 * 29-Nov-2004 : Fixed bug 1075255 (DG); 065 * 17-Nov-2005 : Renamed historyCount --> maximumItemAge (DG); 066 * 28-Nov-2005 : Changed maximumItemAge from int to long (DG); 067 * 01-Dec-2005 : New add methods accept notify flag (DG); 068 * 069 */ 070 071 package org.jfree.data.time; 072 073 import java.io.Serializable; 074 import java.util.Collection; 075 import java.util.Collections; 076 import java.util.List; 077 078 import org.jfree.data.general.Series; 079 import org.jfree.data.general.SeriesChangeEvent; 080 import org.jfree.data.general.SeriesException; 081 import org.jfree.util.ObjectUtilities; 082 083 /** 084 * Represents a sequence of zero or more data items in the form (period, value). 085 */ 086 public class TimeSeries extends Series implements Cloneable, Serializable { 087 088 /** For serialization. */ 089 private static final long serialVersionUID = -5032960206869675528L; 090 091 /** Default value for the domain description. */ 092 protected static final String DEFAULT_DOMAIN_DESCRIPTION = "Time"; 093 094 /** Default value for the range description. */ 095 protected static final String DEFAULT_RANGE_DESCRIPTION = "Value"; 096 097 /** A description of the domain. */ 098 private String domain; 099 100 /** A description of the range. */ 101 private String range; 102 103 /** The type of period for the data. */ 104 protected Class timePeriodClass; 105 106 /** The list of data items in the series. */ 107 protected List data; 108 109 /** The maximum number of items for the series. */ 110 private int maximumItemCount; 111 112 /** The maximum age of items for the series. */ 113 private long maximumItemAge; 114 115 /** 116 * Creates a new (empty) time series. By default, a daily time series is 117 * created. Use one of the other constructors if you require a different 118 * time period. 119 * 120 * @param name the series name (<code>null</code> not permitted). 121 */ 122 public TimeSeries(String name) { 123 this( 124 name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION, 125 Day.class 126 ); 127 } 128 129 /** 130 * Creates a new (empty) time series. 131 * 132 * @param name the series name (<code>null</code> not permitted). 133 * @param timePeriodClass the type of time period (<code>null</code> not 134 * permitted). 135 */ 136 public TimeSeries(String name, Class timePeriodClass) { 137 this( 138 name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION, 139 timePeriodClass 140 ); 141 } 142 143 /** 144 * Creates a new time series that contains no data. 145 * <P> 146 * Descriptions can be specified for the domain and range. One situation 147 * where this is helpful is when generating a chart for the time series - 148 * axis labels can be taken from the domain and range description. 149 * 150 * @param name the name of the series (<code>null</code> not permitted). 151 * @param domain the domain description (<code>null</code> permitted). 152 * @param range the range description (<code>null</code> permitted). 153 * @param timePeriodClass the type of time period (<code>null</code> not 154 * permitted). 155 */ 156 public TimeSeries(String name, String domain, String range, 157 Class timePeriodClass) { 158 159 super(name); 160 this.domain = domain; 161 this.range = range; 162 this.timePeriodClass = timePeriodClass; 163 this.data = new java.util.ArrayList(); 164 this.maximumItemCount = Integer.MAX_VALUE; 165 this.maximumItemAge = Long.MAX_VALUE; 166 167 } 168 169 /** 170 * Returns the domain description. 171 * 172 * @return The domain description (possibly <code>null</code>). 173 */ 174 public String getDomainDescription() { 175 return this.domain; 176 } 177 178 /** 179 * Sets the domain description. 180 * <P> 181 * A property change event is fired, and an undoable edit is posted. 182 * 183 * @param description the description (<code>null</code> permitted). 184 */ 185 public void setDomainDescription(String description) { 186 String old = this.domain; 187 this.domain = description; 188 firePropertyChange("Domain", old, description); 189 } 190 191 /** 192 * Returns the range description. 193 * 194 * @return The range description (possibly <code>null</code>). 195 */ 196 public String getRangeDescription() { 197 return this.range; 198 } 199 200 /** 201 * Sets the range description and fires a property change event for the 202 * 'Range' property. 203 * 204 * @param description the description (<code>null</code> permitted). 205 */ 206 public void setRangeDescription(String description) { 207 String old = this.range; 208 this.range = description; 209 firePropertyChange("Range", old, description); 210 } 211 212 /** 213 * Returns the number of items in the series. 214 * 215 * @return The item count. 216 */ 217 public int getItemCount() { 218 return this.data.size(); 219 } 220 221 /** 222 * Returns the list of data items for the series (the list contains 223 * {@link TimeSeriesDataItem} objects and is unmodifiable). 224 * 225 * @return The list of data items. 226 */ 227 public List getItems() { 228 return Collections.unmodifiableList(this.data); 229 } 230 231 /** 232 * Returns the maximum number of items that will be retained in the series. 233 * The default value is <code>Integer.MAX_VALUE</code>. 234 * 235 * @return The maximum item count. 236 * 237 * @see #setMaximumItemCount(int) 238 */ 239 public int getMaximumItemCount() { 240 return this.maximumItemCount; 241 } 242 243 /** 244 * Sets the maximum number of items that will be retained in the series. 245 * If you add a new item to the series such that the number of items will 246 * exceed the maximum item count, then the FIRST element in the series is 247 * automatically removed, ensuring that the maximum item count is not 248 * exceeded. 249 * 250 * @param maximum the maximum (requires >= 0). 251 * 252 * @see #getMaximumItemCount() 253 */ 254 public void setMaximumItemCount(int maximum) { 255 if (maximum < 0) { 256 throw new IllegalArgumentException("Negative 'maximum' argument."); 257 } 258 this.maximumItemCount = maximum; 259 int count = this.data.size(); 260 if (count > maximum) { 261 delete(0, count - maximum - 1); 262 } 263 } 264 265 /** 266 * Returns the maximum item age (in time periods) for the series. 267 * 268 * @return The maximum item age. 269 * 270 * @see #setMaximumItemAge(long) 271 */ 272 public long getMaximumItemAge() { 273 return this.maximumItemAge; 274 } 275 276 /** 277 * Sets the number of time units in the 'history' for the series. This 278 * provides one mechanism for automatically dropping old data from the 279 * time series. For example, if a series contains daily data, you might set 280 * the history count to 30. Then, when you add a new data item, all data 281 * items more than 30 days older than the latest value are automatically 282 * dropped from the series. 283 * 284 * @param periods the number of time periods. 285 * 286 * @see #getMaximumItemAge() 287 */ 288 public void setMaximumItemAge(long periods) { 289 if (periods < 0) { 290 throw new IllegalArgumentException("Negative 'periods' argument."); 291 } 292 this.maximumItemAge = periods; 293 removeAgedItems(true); // remove old items and notify if necessary 294 } 295 296 /** 297 * Returns the time period class for this series. 298 * <p> 299 * Only one time period class can be used within a single series (enforced). 300 * If you add a data item with a {@link Year} for the time period, then all 301 * subsequent data items must also have a {@link Year} for the time period. 302 * 303 * @return The time period class (never <code>null</code>). 304 */ 305 public Class getTimePeriodClass() { 306 return this.timePeriodClass; 307 } 308 309 /** 310 * Returns a data item for the series. 311 * 312 * @param index the item index (zero-based). 313 * 314 * @return The data item. 315 */ 316 public TimeSeriesDataItem getDataItem(int index) { 317 return (TimeSeriesDataItem) this.data.get(index); 318 } 319 320 /** 321 * Returns the data item for a specific period. 322 * 323 * @param period the period of interest (<code>null</code> not allowed). 324 * 325 * @return The data item matching the specified period (or 326 * <code>null</code> if there is no match). 327 * 328 */ 329 public TimeSeriesDataItem getDataItem(RegularTimePeriod period) { 330 331 // check arguments... 332 if (period == null) { 333 throw new IllegalArgumentException("Null 'period' argument"); 334 } 335 336 // fetch the value... 337 TimeSeriesDataItem dummy = new TimeSeriesDataItem( 338 period, Integer.MIN_VALUE 339 ); 340 int index = Collections.binarySearch(this.data, dummy); 341 if (index >= 0) { 342 return (TimeSeriesDataItem) this.data.get(index); 343 } 344 else { 345 return null; 346 } 347 348 } 349 350 /** 351 * Returns the time period at the specified index. 352 * 353 * @param index the index of the data item. 354 * 355 * @return The time period. 356 */ 357 public RegularTimePeriod getTimePeriod(int index) { 358 return getDataItem(index).getPeriod(); 359 } 360 361 /** 362 * Returns a time period that would be the next in sequence on the end of 363 * the time series. 364 * 365 * @return The next time period. 366 */ 367 public RegularTimePeriod getNextTimePeriod() { 368 RegularTimePeriod last = getTimePeriod(getItemCount() - 1); 369 return last.next(); 370 } 371 372 /** 373 * Returns a collection of all the time periods in the time series. 374 * 375 * @return A collection of all the time periods. 376 */ 377 public Collection getTimePeriods() { 378 Collection result = new java.util.ArrayList(); 379 for (int i = 0; i < getItemCount(); i++) { 380 result.add(getTimePeriod(i)); 381 } 382 return result; 383 } 384 385 /** 386 * Returns a collection of time periods in the specified series, but not in 387 * this series, and therefore unique to the specified series. 388 * 389 * @param series the series to check against this one. 390 * 391 * @return The unique time periods. 392 */ 393 public Collection getTimePeriodsUniqueToOtherSeries(TimeSeries series) { 394 395 Collection result = new java.util.ArrayList(); 396 397 for (int i = 0; i < series.getItemCount(); i++) { 398 RegularTimePeriod period = series.getTimePeriod(i); 399 int index = getIndex(period); 400 if (index < 0) { 401 result.add(period); 402 } 403 404 } 405 406 return result; 407 408 } 409 410 /** 411 * Returns the index for the item (if any) that corresponds to a time 412 * period. 413 * 414 * @param period the time period (<code>null</code> not permitted). 415 * 416 * @return The index. 417 */ 418 public int getIndex(RegularTimePeriod period) { 419 420 // check argument... 421 if (period == null) { 422 throw new IllegalArgumentException("Null 'period' argument."); 423 } 424 425 // fetch the value... 426 TimeSeriesDataItem dummy = new TimeSeriesDataItem( 427 period, Integer.MIN_VALUE 428 ); 429 int index = Collections.binarySearch(this.data, dummy); 430 return index; 431 432 } 433 434 /** 435 * Returns the value at the specified index. 436 * 437 * @param index index of a value. 438 * 439 * @return The value (possibly <code>null</code>). 440 */ 441 public Number getValue(int index) { 442 return getDataItem(index).getValue(); 443 } 444 445 /** 446 * Returns the value for a time period. If there is no data item with the 447 * specified period, this method will return <code>null</code>. 448 * 449 * @param period time period (<code>null</code> not permitted). 450 * 451 * @return The value (possibly <code>null</code>). 452 */ 453 public Number getValue(RegularTimePeriod period) { 454 455 int index = getIndex(period); 456 if (index >= 0) { 457 return getValue(index); 458 } 459 else { 460 return null; 461 } 462 463 } 464 465 /** 466 * Adds a data item to the series and sends a 467 * {@link org.jfree.data.general.SeriesChangeEvent} to all registered 468 * listeners. 469 * 470 * @param item the (timeperiod, value) pair (<code>null</code> not 471 * permitted). 472 */ 473 public void add(TimeSeriesDataItem item) { 474 add(item, true); 475 } 476 477 /** 478 * Adds a data item to the series and sends a 479 * {@link org.jfree.data.general.SeriesChangeEvent} to all registered 480 * listeners. 481 * 482 * @param item the (timeperiod, value) pair (<code>null</code> not 483 * permitted). 484 * @param notify notify listeners? 485 */ 486 public void add(TimeSeriesDataItem item, boolean notify) { 487 if (item == null) { 488 throw new IllegalArgumentException("Null 'item' argument."); 489 } 490 if (!item.getPeriod().getClass().equals(this.timePeriodClass)) { 491 StringBuffer b = new StringBuffer(); 492 b.append("You are trying to add data where the time period class "); 493 b.append("is "); 494 b.append(item.getPeriod().getClass().getName()); 495 b.append(", but the TimeSeries is expecting an instance of "); 496 b.append(this.timePeriodClass.getName()); 497 b.append("."); 498 throw new SeriesException(b.toString()); 499 } 500 501 // make the change (if it's not a duplicate time period)... 502 boolean added = false; 503 int count = getItemCount(); 504 if (count == 0) { 505 this.data.add(item); 506 added = true; 507 } 508 else { 509 RegularTimePeriod last = getTimePeriod(getItemCount() - 1); 510 if (item.getPeriod().compareTo(last) > 0) { 511 this.data.add(item); 512 added = true; 513 } 514 else { 515 int index = Collections.binarySearch(this.data, item); 516 if (index < 0) { 517 this.data.add(-index - 1, item); 518 added = true; 519 } 520 else { 521 StringBuffer b = new StringBuffer(); 522 b.append("You are attempting to add an observation for "); 523 b.append("the time period "); 524 b.append(item.getPeriod().toString()); 525 b.append(" but the series already contains an observation"); 526 b.append(" for that time period. Duplicates are not "); 527 b.append("permitted. Try using the addOrUpdate() method."); 528 throw new SeriesException(b.toString()); 529 } 530 } 531 } 532 if (added) { 533 // check if this addition will exceed the maximum item count... 534 if (getItemCount() > this.maximumItemCount) { 535 this.data.remove(0); 536 } 537 538 removeAgedItems(false); // remove old items if necessary, but 539 // don't notify anyone, because that 540 // happens next anyway... 541 if (notify) { 542 fireSeriesChanged(); 543 } 544 } 545 546 } 547 548 /** 549 * Adds a new data item to the series and sends 550 * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered 551 * listeners. 552 * 553 * @param period the time period (<code>null</code> not permitted). 554 * @param value the value. 555 */ 556 public void add(RegularTimePeriod period, double value) { 557 // defer argument checking... 558 add(period, value, true); 559 } 560 561 /** 562 * Adds a new data item to the series and sends 563 * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered 564 * listeners. 565 * 566 * @param period the time period (<code>null</code> not permitted). 567 * @param value the value. 568 * @param notify notify listeners? 569 */ 570 public void add(RegularTimePeriod period, double value, boolean notify) { 571 // defer argument checking... 572 TimeSeriesDataItem item = new TimeSeriesDataItem(period, value); 573 add(item, notify); 574 } 575 576 /** 577 * Adds a new data item to the series and sends 578 * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered 579 * listeners. 580 * 581 * @param period the time period (<code>null</code> not permitted). 582 * @param value the value (<code>null</code> permitted). 583 */ 584 public void add(RegularTimePeriod period, Number value) { 585 // defer argument checking... 586 add(period, value, true); 587 } 588 589 /** 590 * Adds a new data item to the series and sends 591 * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered 592 * listeners. 593 * 594 * @param period the time period (<code>null</code> not permitted). 595 * @param value the value (<code>null</code> permitted). 596 * @param notify notify listeners? 597 */ 598 public void add(RegularTimePeriod period, Number value, boolean notify) { 599 // defer argument checking... 600 TimeSeriesDataItem item = new TimeSeriesDataItem(period, value); 601 add(item, notify); 602 } 603 604 /** 605 * Updates (changes) the value for a time period. Throws a 606 * {@link SeriesException} if the period does not exist. 607 * 608 * @param period the period (<code>null</code> not permitted). 609 * @param value the value (<code>null</code> permitted). 610 */ 611 public void update(RegularTimePeriod period, Number value) { 612 TimeSeriesDataItem temp = new TimeSeriesDataItem(period, value); 613 int index = Collections.binarySearch(this.data, temp); 614 if (index >= 0) { 615 TimeSeriesDataItem pair = (TimeSeriesDataItem) this.data.get(index); 616 pair.setValue(value); 617 fireSeriesChanged(); 618 } 619 else { 620 throw new SeriesException( 621 "TimeSeries.update(TimePeriod, Number): period does not exist." 622 ); 623 } 624 625 } 626 627 /** 628 * Updates (changes) the value of a data item. 629 * 630 * @param index the index of the data item. 631 * @param value the new value (<code>null</code> permitted). 632 */ 633 public void update(int index, Number value) { 634 TimeSeriesDataItem item = getDataItem(index); 635 item.setValue(value); 636 fireSeriesChanged(); 637 } 638 639 /** 640 * Adds or updates data from one series to another. Returns another series 641 * containing the values that were overwritten. 642 * 643 * @param series the series to merge with this. 644 * 645 * @return A series containing the values that were overwritten. 646 */ 647 public TimeSeries addAndOrUpdate(TimeSeries series) { 648 TimeSeries overwritten = new TimeSeries( 649 "Overwritten values from: " + getKey(), series.getTimePeriodClass() 650 ); 651 for (int i = 0; i < series.getItemCount(); i++) { 652 TimeSeriesDataItem item = series.getDataItem(i); 653 TimeSeriesDataItem oldItem = addOrUpdate( 654 item.getPeriod(), item.getValue() 655 ); 656 if (oldItem != null) { 657 overwritten.add(oldItem); 658 } 659 } 660 return overwritten; 661 } 662 663 /** 664 * Adds or updates an item in the times series and sends a 665 * {@link org.jfree.data.general.SeriesChangeEvent} to all registered 666 * listeners. 667 * 668 * @param period the time period to add/update (<code>null</code> not 669 * permitted). 670 * @param value the new value. 671 * 672 * @return A copy of the overwritten data item, or <code>null</code> if no 673 * item was overwritten. 674 */ 675 public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period, 676 double value) { 677 return this.addOrUpdate(period, new Double(value)); 678 } 679 680 /** 681 * Adds or updates an item in the times series and sends a 682 * {@link org.jfree.data.general.SeriesChangeEvent} to all registered 683 * listeners. 684 * 685 * @param period the time period to add/update (<code>null</code> not 686 * permitted). 687 * @param value the new value (<code>null</code> permitted). 688 * 689 * @return A copy of the overwritten data item, or <code>null</code> if no 690 * item was overwritten. 691 */ 692 public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period, 693 Number value) { 694 695 if (period == null) { 696 throw new IllegalArgumentException("Null 'period' argument."); 697 } 698 TimeSeriesDataItem overwritten = null; 699 700 TimeSeriesDataItem key = new TimeSeriesDataItem(period, value); 701 int index = Collections.binarySearch(this.data, key); 702 if (index >= 0) { 703 TimeSeriesDataItem existing 704 = (TimeSeriesDataItem) this.data.get(index); 705 overwritten = (TimeSeriesDataItem) existing.clone(); 706 existing.setValue(value); 707 removeAgedItems(false); // remove old items if necessary, but 708 // don't notify anyone, because that 709 // happens next anyway... 710 fireSeriesChanged(); 711 } 712 else { 713 this.data.add(-index - 1, new TimeSeriesDataItem(period, value)); 714 715 // check if this addition will exceed the maximum item count... 716 if (getItemCount() > this.maximumItemCount) { 717 this.data.remove(0); 718 } 719 720 removeAgedItems(false); // remove old items if necessary, but 721 // don't notify anyone, because that 722 // happens next anyway... 723 fireSeriesChanged(); 724 } 725 return overwritten; 726 727 } 728 729 /** 730 * Age items in the series. Ensure that the timespan from the youngest to 731 * the oldest record in the series does not exceed maximumItemAge time 732 * periods. Oldest items will be removed if required. 733 * 734 * @param notify controls whether or not a {@link SeriesChangeEvent} is 735 * sent to registered listeners IF any items are removed. 736 */ 737 public void removeAgedItems(boolean notify) { 738 // check if there are any values earlier than specified by the history 739 // count... 740 if (getItemCount() > 1) { 741 long latest = getTimePeriod(getItemCount() - 1).getSerialIndex(); 742 boolean removed = false; 743 while ((latest - getTimePeriod(0).getSerialIndex()) 744 >= this.maximumItemAge) { 745 this.data.remove(0); 746 removed = true; 747 } 748 if (removed && notify) { 749 fireSeriesChanged(); 750 } 751 } 752 } 753 754 /** 755 * Age items in the series. Ensure that the timespan from the supplied 756 * time to the oldest record in the series does not exceed history count. 757 * oldest items will be removed if required. 758 * 759 * @param latest the time to be compared against when aging data. 760 * @param notify controls whether or not a {@link SeriesChangeEvent} is 761 * sent to registered listeners IF any items are removed. 762 */ 763 public void removeAgedItems(long latest, boolean notify) { 764 // check if there are any values earlier than specified by the history 765 // count... 766 if (getItemCount() > 1) { 767 while ((latest - getTimePeriod(0).getSerialIndex()) 768 >= this.maximumItemAge) { 769 this.data.remove(0); 770 } 771 } 772 } 773 774 /** 775 * Removes all data items from the series and sends 776 * a {@link org.jfree.data.general.SeriesChangeEvent} 777 * to all registered listeners. 778 */ 779 public void clear() { 780 if (this.data.size() > 0) { 781 this.data.clear(); 782 fireSeriesChanged(); 783 } 784 } 785 786 /** 787 * Deletes the data item for the given time period and sends 788 * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered 789 * listeners. 790 * 791 * @param period the period of the item to delete (<code>null</code> not 792 * permitted). 793 */ 794 public void delete(RegularTimePeriod period) { 795 int index = getIndex(period); 796 this.data.remove(index); 797 fireSeriesChanged(); 798 } 799 800 /** 801 * Deletes data from start until end index (end inclusive). 802 * 803 * @param start the index of the first period to delete. 804 * @param end the index of the last period to delete. 805 */ 806 public void delete(int start, int end) { 807 for (int i = 0; i <= (end - start); i++) { 808 this.data.remove(start); 809 } 810 fireSeriesChanged(); 811 } 812 813 /** 814 * Returns a clone of the time series. 815 * <P> 816 * Notes: 817 * <ul> 818 * <li>no need to clone the domain and range descriptions, since String 819 * object is immutable;</li> 820 * <li>we pass over to the more general method clone(start, end).</li> 821 * </ul> 822 * 823 * @return A clone of the time series. 824 * 825 * @throws CloneNotSupportedException not thrown by this class, but 826 * subclasses may differ. 827 */ 828 public Object clone() throws CloneNotSupportedException { 829 Object clone = createCopy(0, getItemCount() - 1); 830 return clone; 831 } 832 833 /** 834 * Creates a new timeseries by copying a subset of the data in this time 835 * series. 836 * 837 * @param start the index of the first time period to copy. 838 * @param end the index of the last time period to copy. 839 * 840 * @return A series containing a copy of this times series from start until 841 * end. 842 * 843 * @throws CloneNotSupportedException if there is a cloning problem. 844 */ 845 public TimeSeries createCopy(int start, int end) 846 throws CloneNotSupportedException { 847 848 TimeSeries copy = (TimeSeries) super.clone(); 849 850 copy.data = new java.util.ArrayList(); 851 if (this.data.size() > 0) { 852 for (int index = start; index <= end; index++) { 853 TimeSeriesDataItem item 854 = (TimeSeriesDataItem) this.data.get(index); 855 TimeSeriesDataItem clone = (TimeSeriesDataItem) item.clone(); 856 try { 857 copy.add(clone); 858 } 859 catch (SeriesException e) { 860 System.err.println("Unable to add cloned data item."); 861 } 862 } 863 } 864 865 return copy; 866 867 } 868 869 /** 870 * Creates a new timeseries by copying a subset of the data in this time 871 * series. 872 * 873 * @param start the first time period to copy. 874 * @param end the last time period to copy. 875 * 876 * @return A time series containing a copy of this time series from start 877 * until end. 878 * 879 * @throws CloneNotSupportedException if there is a cloning problem. 880 */ 881 public TimeSeries createCopy(RegularTimePeriod start, RegularTimePeriod end) 882 throws CloneNotSupportedException { 883 884 int startIndex = getIndex(start); 885 if (startIndex < 0) { 886 startIndex = -(startIndex + 1); 887 } 888 int endIndex = getIndex(end); 889 if (endIndex < 0) { // end period is not in original series 890 endIndex = -(endIndex + 1); // this is first item AFTER end period 891 endIndex = endIndex - 1; // so this is last item BEFORE end 892 } 893 894 TimeSeries result = createCopy(startIndex, endIndex); 895 896 return result; 897 898 } 899 900 /** 901 * Tests the series for equality with an arbitrary object. 902 * 903 * @param object the object to test against (<code>null</code> permitted). 904 * 905 * @return A boolean. 906 */ 907 public boolean equals(Object object) { 908 if (object == this) { 909 return true; 910 } 911 if (!(object instanceof TimeSeries) || !super.equals(object)) { 912 return false; 913 } 914 TimeSeries s = (TimeSeries) object; 915 if (!ObjectUtilities.equal( 916 getDomainDescription(), s.getDomainDescription() 917 )) { 918 return false; 919 } 920 921 if (!ObjectUtilities.equal( 922 getRangeDescription(), s.getRangeDescription() 923 )) { 924 return false; 925 } 926 927 if (!getClass().equals(s.getClass())) { 928 return false; 929 } 930 931 if (getMaximumItemAge() != s.getMaximumItemAge()) { 932 return false; 933 } 934 935 if (getMaximumItemCount() != s.getMaximumItemCount()) { 936 return false; 937 } 938 939 int count = getItemCount(); 940 if (count != s.getItemCount()) { 941 return false; 942 } 943 for (int i = 0; i < count; i++) { 944 if (!getDataItem(i).equals(s.getDataItem(i))) { 945 return false; 946 } 947 } 948 return true; 949 } 950 951 /** 952 * Returns a hash code value for the object. 953 * 954 * @return The hashcode 955 */ 956 public int hashCode() { 957 int result; 958 result = (this.domain != null ? this.domain.hashCode() : 0); 959 result = 29 * result + (this.range != null ? this.range.hashCode() : 0); 960 result = 29 * result + (this.timePeriodClass != null 961 ? this.timePeriodClass.hashCode() : 0); 962 result = 29 * result + this.data.hashCode(); 963 result = 29 * result + this.maximumItemCount; 964 result = 29 * result + (int) this.maximumItemAge; 965 return result; 966 } 967 968 }