1. /* ===========================================================
  2. * JFreeChart : a free chart library for the Java(tm) platform
  3. * ===========================================================
  4. *
  5. * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
  6. *
  7. * Project Info: http://www.jfree.org/jfreechart/index.html
  8. *
  9. * This library is free software; you can redistribute it and/or modify it
  10. * under the terms of the GNU Lesser General Public License as published by
  11. * the Free Software Foundation; either version 2.1 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful, but
  15. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17. * License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public License
  20. * along with this library; if not, write to the Free Software Foundation,
  21. * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
  22. *
  23. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  24. * in the United States and other countries.]
  25. *
  26. * ---------------
  27. * TimeSeries.java
  28. * ---------------
  29. * (C) Copyright 2001-2003, by Object Refinery Limited.
  30. *
  31. * Original Author: David Gilbert (for Object Refinery Limited);
  32. * Contributor(s): Bryan Scott;
  33. *
  34. * $Id: TimeSeries.java,v 1.7 2005/03/09 22:05:41 mungady Exp $
  35. *
  36. * Changes
  37. * -------
  38. * 11-Oct-2001 : Version 1 (DG);
  39. * 14-Nov-2001 : Added listener mechanism (DG);
  40. * 15-Nov-2001 : Updated argument checking and exceptions in add() method (DG);
  41. * 29-Nov-2001 : Added properties to describe the domain and range (DG);
  42. * 07-Dec-2001 : Renamed TimeSeries --> BasicTimeSeries (DG);
  43. * 01-Mar-2002 : Updated import statements (DG);
  44. * 28-Mar-2002 : Added a method add(TimePeriod, double) (DG);
  45. * 27-Aug-2002 : Changed return type of delete method to void (DG);
  46. * 04-Oct-2002 : Added itemCount and historyCount attributes, fixed errors
  47. * reported by Checkstyle (DG);
  48. * 29-Oct-2002 : Added series change notification to addOrUpdate() method (DG);
  49. * 28-Jan-2003 : Changed name back to TimeSeries (DG);
  50. * 13-Mar-2003 : Moved to com.jrefinery.data.time package and implemented
  51. * Serializable (DG);
  52. * 01-May-2003 : Updated equals() method (see bug report 727575) (DG);
  53. * 14-Aug-2003 : Added ageHistoryCountItems method (copied existing code for
  54. * contents) made a method and added to addOrUpdate. Made a
  55. * public method to enable ageing against a specified time
  56. * (eg now) as opposed to lastest time in series (BS);
  57. * 15-Oct-2003 : Added fix for setItemCount method - see bug report 804425.
  58. * Modified exception message in add() method to be more
  59. * informative (DG);
  60. * 13-Apr-2004 : Added clear() method (DG);
  61. * 21-May-2004 : Added an extra addOrUpdate() method (DG);
  62. * 15-Jun-2004 : Fixed NullPointerException in equals() method (DG);
  63. * 29-Nov-2004 : Fixed bug 1075255 (DG);
  64. *
  65. */
  66. package org.jfree.data.time;
  67. import java.io.Serializable;
  68. import java.util.Collection;
  69. import java.util.Collections;
  70. import java.util.List;
  71. import org.jfree.data.general.Series;
  72. import org.jfree.data.general.SeriesException;
  73. import org.jfree.util.ObjectUtilities;
  74. /**
  75. * Represents a sequence of zero or more data items in the form (period, value).
  76. */
  77. public class TimeSeries extends Series implements Cloneable, Serializable {
  78. /** Default value for the domain description. */
  79. protected static final String DEFAULT_DOMAIN_DESCRIPTION = "Time";
  80. /** Default value for the range description. */
  81. protected static final String DEFAULT_RANGE_DESCRIPTION = "Value";
  82. /** A description of the domain. */
  83. private String domain;
  84. /** A description of the range. */
  85. private String range;
  86. /** The type of period for the data. */
  87. protected Class timePeriodClass;
  88. /** The list of data items in the series. */
  89. protected List data;
  90. /** The maximum number of items for the series. */
  91. private int maximumItemCount;
  92. /** The history count. */
  93. private int historyCount;
  94. /**
  95. * Creates a new (empty) time series. By default, a daily time series is
  96. * created. Use one of the other constructors if you require a different
  97. * time period.
  98. *
  99. * @param name the series name (<code>null</code> not permitted).
  100. */
  101. public TimeSeries(String name) {
  102. this(
  103. name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION,
  104. Day.class
  105. );
  106. }
  107. /**
  108. * Creates a new (empty) time series.
  109. *
  110. * @param name the series name (<code>null</code> not permitted).
  111. * @param timePeriodClass the type of time period (<code>null</code> not
  112. * permitted).
  113. */
  114. public TimeSeries(String name, Class timePeriodClass) {
  115. this(
  116. name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION,
  117. timePeriodClass
  118. );
  119. }
  120. /**
  121. * Creates a new time series that contains no data.
  122. * <P>
  123. * Descriptions can be specified for the domain and range. One situation
  124. * where this is helpful is when generating a chart for the time series -
  125. * axis labels can be taken from the domain and range description.
  126. *
  127. * @param name the name of the series (<code>null</code> not permitted).
  128. * @param domain the domain description (<code>null</code> permitted).
  129. * @param range the range description (<code>null</code> permitted).
  130. * @param timePeriodClass the type of time period (<code>null</code> not
  131. * permitted).
  132. */
  133. public TimeSeries(String name, String domain, String range,
  134. Class timePeriodClass) {
  135. super(name);
  136. this.domain = domain;
  137. this.range = range;
  138. this.timePeriodClass = timePeriodClass;
  139. this.data = new java.util.ArrayList();
  140. this.maximumItemCount = Integer.MAX_VALUE;
  141. this.historyCount = 0;
  142. }
  143. /**
  144. * Returns the domain description.
  145. *
  146. * @return The domain description (possibly <code>null</code>).
  147. */
  148. public String getDomainDescription() {
  149. return this.domain;
  150. }
  151. /**
  152. * Sets the domain description.
  153. * <P>
  154. * A property change event is fired, and an undoable edit is posted.
  155. *
  156. * @param description the description (<code>null</code> permitted).
  157. */
  158. public void setDomainDescription(String description) {
  159. String old = this.domain;
  160. this.domain = description;
  161. firePropertyChange("Domain", old, description);
  162. }
  163. /**
  164. * Returns the range description.
  165. *
  166. * @return The range description (possibly <code>null</code>).
  167. */
  168. public String getRangeDescription() {
  169. return this.range;
  170. }
  171. /**
  172. * Sets the range description and fires a property change event for the
  173. * 'Range' property.
  174. *
  175. * @param description the description (<code>null</code> permitted).
  176. */
  177. public void setRangeDescription(String description) {
  178. String old = this.range;
  179. this.range = description;
  180. firePropertyChange("Range", old, description);
  181. }
  182. /**
  183. * Returns the number of items in the series.
  184. *
  185. * @return The item count.
  186. */
  187. public int getItemCount() {
  188. return this.data.size();
  189. }
  190. /**
  191. * Returns the list of data items for the series (the list contains
  192. * {@link TimeSeriesDataItem} objects and is unmodifiable).
  193. *
  194. * @return The list of data items.
  195. */
  196. public List getItems() {
  197. return Collections.unmodifiableList(this.data);
  198. }
  199. /**
  200. * Returns the maximum number of items that will be retained in the series.
  201. * <P>
  202. * The default value is <code>Integer.MAX_VALUE</code>).
  203. *
  204. * @return The maximum item count.
  205. */
  206. public int getMaximumItemCount() {
  207. return this.maximumItemCount;
  208. }
  209. /**
  210. * Sets the maximum number of items that will be retained in the series.
  211. * <P>
  212. * If you add a new item to the series such that the number of items will
  213. * exceed the maximum item count, then the FIRST element in the series is
  214. * automatically removed, ensuring that the maximum item count is not
  215. * exceeded.
  216. *
  217. * @param maximum the maximum.
  218. */
  219. public void setMaximumItemCount(int maximum) {
  220. this.maximumItemCount = maximum;
  221. while (this.data.size() > this.maximumItemCount) {
  222. this.data.remove(0);
  223. }
  224. }
  225. /**
  226. * Returns the history count for the series.
  227. *
  228. * @return The history count.
  229. */
  230. public int getHistoryCount() {
  231. return this.historyCount;
  232. }
  233. /**
  234. * Sets the number of time units in the 'history' for the series.
  235. * <P>
  236. * This provides one mechanism for automatically dropping old data from the
  237. * time series. For example, if a series contains daily data, you might set
  238. * the history count to 30. Then, when you add a new data item, all data
  239. * items more than 30 days older than the latest value are automatically
  240. * dropped from the series.
  241. *
  242. * @param periods the number of time periods.
  243. */
  244. public void setHistoryCount(int periods) {
  245. this.historyCount = periods;
  246. }
  247. /**
  248. * Returns the time period class for this series.
  249. * <p>
  250. * Only one time period class can be used within a single series (enforced).
  251. * If you add a data item with a {@link Year} for the time period, then all
  252. * subsequent data items must also have a {@link Year} for the time period.
  253. *
  254. * @return The time period class (never <code>null</code>).
  255. */
  256. public Class getTimePeriodClass() {
  257. return this.timePeriodClass;
  258. }
  259. /**
  260. * Returns a data item for the series.
  261. *
  262. * @param index the item index (zero-based).
  263. *
  264. * @return The data item.
  265. */
  266. public TimeSeriesDataItem getDataItem(int index) {
  267. return (TimeSeriesDataItem) this.data.get(index);
  268. }
  269. /**
  270. * Returns the data item for a specific period.
  271. *
  272. * @param period the period of interest (<code>null</code> not allowed).
  273. *
  274. * @return The data item matching the specified period (or
  275. * <code>null</code> if there is no match).
  276. *
  277. */
  278. public TimeSeriesDataItem getDataItem(RegularTimePeriod period) {
  279. // check arguments...
  280. if (period == null) {
  281. throw new IllegalArgumentException("Null 'period' argument");
  282. }
  283. // fetch the value...
  284. TimeSeriesDataItem dummy = new TimeSeriesDataItem(
  285. period, Integer.MIN_VALUE
  286. );
  287. int index = Collections.binarySearch(this.data, dummy);
  288. if (index >= 0) {
  289. return (TimeSeriesDataItem) this.data.get(index);
  290. }
  291. else {
  292. return null;
  293. }
  294. }
  295. /**
  296. * Returns the time period at the specified index.
  297. *
  298. * @param index the index of the data item.
  299. *
  300. * @return The time period.
  301. */
  302. public RegularTimePeriod getTimePeriod(int index) {
  303. return getDataItem(index).getPeriod();
  304. }
  305. /**
  306. * Returns a time period that would be the next in sequence on the end of
  307. * the time series.
  308. *
  309. * @return The next time period.
  310. */
  311. public RegularTimePeriod getNextTimePeriod() {
  312. RegularTimePeriod last = getTimePeriod(getItemCount() - 1);
  313. return last.next();
  314. }
  315. /**
  316. * Returns a collection of all the time periods in the time series.
  317. *
  318. * @return A collection of all the time periods.
  319. */
  320. public Collection getTimePeriods() {
  321. Collection result = new java.util.ArrayList();
  322. for (int i = 0; i < getItemCount(); i++) {
  323. result.add(getTimePeriod(i));
  324. }
  325. return result;
  326. }
  327. /**
  328. * Returns a collection of time periods in the specified series, but not in
  329. * this series, and therefore unique to the specified series.
  330. *
  331. * @param series the series to check against this one.
  332. *
  333. * @return The unique time periods.
  334. */
  335. public Collection getTimePeriodsUniqueToOtherSeries(TimeSeries series) {
  336. Collection result = new java.util.ArrayList();
  337. for (int i = 0; i < series.getItemCount(); i++) {
  338. RegularTimePeriod period = series.getTimePeriod(i);
  339. int index = getIndex(period);
  340. if (index < 0) {
  341. result.add(period);
  342. }
  343. }
  344. return result;
  345. }
  346. /**
  347. * Returns the index for the item (if any) that corresponds to a time
  348. * period.
  349. *
  350. * @param period the time period (<code>null</code> not permitted).
  351. *
  352. * @return The index.
  353. */
  354. public int getIndex(RegularTimePeriod period) {
  355. // check argument...
  356. if (period == null) {
  357. throw new IllegalArgumentException("Null 'period' argument.");
  358. }
  359. // fetch the value...
  360. TimeSeriesDataItem dummy = new TimeSeriesDataItem(
  361. period, Integer.MIN_VALUE
  362. );
  363. int index = Collections.binarySearch(this.data, dummy);
  364. return index;
  365. }
  366. /**
  367. * Returns the value at the specified index.
  368. *
  369. * @param index index of a value.
  370. *
  371. * @return The value (possibly <code>null</code>).
  372. */
  373. public Number getValue(int index) {
  374. return getDataItem(index).getValue();
  375. }
  376. /**
  377. * Returns the value for a time period. If there is no data item with the
  378. * specified period, this method will return <code>null</code>.
  379. *
  380. * @param period time period (<code>null</code> not permitted).
  381. *
  382. * @return The value (possibly <code>null</code>).
  383. */
  384. public Number getValue(RegularTimePeriod period) {
  385. int index = getIndex(period);
  386. if (index >= 0) {
  387. return getValue(index);
  388. }
  389. else {
  390. return null;
  391. }
  392. }
  393. /**
  394. * Adds a data item to the series and sends a
  395. * {@link org.jfree.data.general.SeriesChangeEvent} to all registered
  396. * listeners.
  397. *
  398. * @param item the (timeperiod, value) pair (<code>null</code> not
  399. * permitted).
  400. */
  401. public void add(TimeSeriesDataItem item) {
  402. if (item == null) {
  403. throw new IllegalArgumentException("Null 'item' argument.");
  404. }
  405. if (!item.getPeriod().getClass().equals(this.timePeriodClass)) {
  406. StringBuffer b = new StringBuffer();
  407. b.append("You are trying to add data where the time period class ");
  408. b.append("is ");
  409. b.append(item.getPeriod().getClass().getName());
  410. b.append(", but the TimeSeries is expecting an instance of ");
  411. b.append(this.timePeriodClass.getName());
  412. b.append(".");
  413. throw new SeriesException(b.toString());
  414. }
  415. // make the change (if it's not a duplicate time period)...
  416. boolean added = false;
  417. int count = getItemCount();
  418. if (count == 0) {
  419. this.data.add(item);
  420. added = true;
  421. }
  422. else {
  423. RegularTimePeriod last = getTimePeriod(getItemCount() - 1);
  424. if (item.getPeriod().compareTo(last) > 0) {
  425. this.data.add(item);
  426. added = true;
  427. }
  428. else {
  429. int index = Collections.binarySearch(this.data, item);
  430. if (index < 0) {
  431. this.data.add(-index - 1, item);
  432. added = true;
  433. }
  434. else {
  435. StringBuffer b = new StringBuffer();
  436. b.append("You are attempting to add an observation for ");
  437. b.append("the time period ");
  438. b.append(item.getPeriod().toString());
  439. b.append(" but the series already contains an observation");
  440. b.append(" for that time period. Duplicates are not ");
  441. b.append("permitted. Try using the addOrUpdate() method.");
  442. throw new SeriesException(b.toString());
  443. }
  444. }
  445. }
  446. if (added) {
  447. // check if this addition will exceed the maximum item count...
  448. if (getItemCount() > this.maximumItemCount) {
  449. this.data.remove(0);
  450. }
  451. // check if there are any values earlier than specified by the
  452. // history count...
  453. ageHistoryCountItems();
  454. fireSeriesChanged();
  455. }
  456. }
  457. /**
  458. * Adds a new data item to the series and sends
  459. * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered
  460. * listeners.
  461. *
  462. * @param period the time period (<code>null</code> not permitted).
  463. * @param value the value.
  464. */
  465. public void add(RegularTimePeriod period, double value) {
  466. // defer argument checking...
  467. TimeSeriesDataItem item = new TimeSeriesDataItem(period, value);
  468. add(item);
  469. }
  470. /**
  471. * Adds a new data item to the series and sends
  472. * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered
  473. * listeners.
  474. *
  475. * @param period the time period (<code>null</code> not permitted).
  476. * @param value the value (<code>null</code> permitted).
  477. */
  478. public void add(RegularTimePeriod period, Number value) {
  479. // defer argument checking...
  480. TimeSeriesDataItem item = new TimeSeriesDataItem(period, value);
  481. add(item);
  482. }
  483. /**
  484. * Updates (changes) the value for a time period. Throws a
  485. * {@link SeriesException} if the period does not exist.
  486. *
  487. * @param period the period (<code>null</code> not permitted).
  488. * @param value the value (<code>null</code> permitted).
  489. */
  490. public void update(RegularTimePeriod period, Number value) {
  491. TimeSeriesDataItem temp = new TimeSeriesDataItem(period, value);
  492. int index = Collections.binarySearch(this.data, temp);
  493. if (index >= 0) {
  494. TimeSeriesDataItem pair = (TimeSeriesDataItem) this.data.get(index);
  495. pair.setValue(value);
  496. fireSeriesChanged();
  497. }
  498. else {
  499. throw new SeriesException(
  500. "TimeSeries.update(TimePeriod, Number): period does not exist."
  501. );
  502. }
  503. }
  504. /**
  505. * Updates (changes) the value of a data item.
  506. *
  507. * @param index the index of the data item.
  508. * @param value the new value (<code>null</code> permitted).
  509. */
  510. public void update(int index, Number value) {
  511. TimeSeriesDataItem item = getDataItem(index);
  512. item.setValue(value);
  513. fireSeriesChanged();
  514. }
  515. /**
  516. * Adds or updates data from one series to another. Returns another series
  517. * containing the values that were overwritten.
  518. *
  519. * @param series the series to merge with this.
  520. *
  521. * @return A series containing the values that were overwritten.
  522. */
  523. public TimeSeries addAndOrUpdate(TimeSeries series) {
  524. TimeSeries overwritten = new TimeSeries(
  525. "Overwritten values from: " + getName(), series.getTimePeriodClass()
  526. );
  527. for (int i = 0; i < series.getItemCount(); i++) {
  528. TimeSeriesDataItem item = series.getDataItem(i);
  529. TimeSeriesDataItem oldItem = addOrUpdate(
  530. item.getPeriod(), item.getValue()
  531. );
  532. if (oldItem != null) {
  533. overwritten.add(oldItem);
  534. }
  535. }
  536. return overwritten;
  537. }
  538. /**
  539. * Adds or updates an item in the times series and sends a
  540. * {@link org.jfree.data.general.SeriesChangeEvent} to all registered
  541. * listeners.
  542. *
  543. * @param period the time period to add/update (<code>null</code> not
  544. * permitted).
  545. * @param value the new value.
  546. *
  547. * @return A copy of the overwritten data item, or <code>null</code> if no
  548. * item was overwritten.
  549. */
  550. public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period,
  551. double value) {
  552. return this.addOrUpdate(period, new Double(value));
  553. }
  554. /**
  555. * Adds or updates an item in the times series and sends a
  556. * {@link org.jfree.data.general.SeriesChangeEvent} to all registered
  557. * listeners.
  558. *
  559. * @param period the time period to add/update (<code>null</code> not
  560. * permitted).
  561. * @param value the new value (<code>null</code> permitted).
  562. *
  563. * @return A copy of the overwritten data item, or <code>null</code> if no
  564. * item was overwritten.
  565. */
  566. public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period,
  567. Number value) {
  568. if (period == null) {
  569. throw new IllegalArgumentException("Null 'period' argument.");
  570. }
  571. TimeSeriesDataItem overwritten = null;
  572. TimeSeriesDataItem key = new TimeSeriesDataItem(period, value);
  573. int index = Collections.binarySearch(this.data, key);
  574. if (index >= 0) {
  575. TimeSeriesDataItem existing
  576. = (TimeSeriesDataItem) this.data.get(index);
  577. overwritten = (TimeSeriesDataItem) existing.clone();
  578. existing.setValue(value);
  579. ageHistoryCountItems();
  580. fireSeriesChanged();
  581. }
  582. else {
  583. this.data.add(-index - 1, new TimeSeriesDataItem(period, value));
  584. // check if this addition will exceed the maximum item count...
  585. if (getItemCount() > this.maximumItemCount) {
  586. this.data.remove(0);
  587. }
  588. ageHistoryCountItems();
  589. fireSeriesChanged();
  590. }
  591. return overwritten;
  592. }
  593. /**
  594. * Age items in the series. Ensure that the timespan from the youngest to
  595. * the oldest record in the series does not exceed history count. oldest
  596. * items will be removed if required.
  597. */
  598. public void ageHistoryCountItems() {
  599. // check if there are any values earlier than specified by the history
  600. // count...
  601. if ((getItemCount() > 1) && (this.historyCount > 0)) {
  602. long latest = getTimePeriod(getItemCount() - 1).getSerialIndex();
  603. while ((latest - getTimePeriod(0).getSerialIndex())
  604. >= this.historyCount) {
  605. this.data.remove(0);
  606. }
  607. }
  608. }
  609. /**
  610. * Age items in the series. Ensure that the timespan from the supplied
  611. * time to the oldest record in the series does not exceed history count.
  612. * oldest items will be removed if required.
  613. *
  614. * @param latest the time to be compared against when aging data.
  615. */
  616. public void ageHistoryCountItems(long latest) {
  617. // check if there are any values earlier than specified by the history
  618. // count...
  619. if ((getItemCount() > 1) && (this.historyCount > 0)) {
  620. while ((latest - getTimePeriod(0).getSerialIndex())
  621. >= this.historyCount) {
  622. this.data.remove(0);
  623. }
  624. }
  625. }
  626. /**
  627. * Removes all data items from the series and sends
  628. * a {@link org.jfree.data.general.SeriesChangeEvent}
  629. * to all registered listeners.
  630. */
  631. public void clear() {
  632. if (this.data.size() > 0) {
  633. this.data.clear();
  634. fireSeriesChanged();
  635. }
  636. }
  637. /**
  638. * Deletes the data item for the given time period and sends
  639. * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered
  640. * listeners.
  641. *
  642. * @param period the period of the item to delete (<code>null</code> not
  643. * permitted).
  644. */
  645. public void delete(RegularTimePeriod period) {
  646. int index = getIndex(period);
  647. this.data.remove(index);
  648. fireSeriesChanged();
  649. }
  650. /**
  651. * Deletes data from start until end index (end inclusive).
  652. *
  653. * @param start the index of the first period to delete.
  654. * @param end the index of the last period to delete.
  655. */
  656. public void delete(int start, int end) {
  657. for (int i = 0; i <= (end - start); i++) {
  658. this.data.remove(start);
  659. }
  660. fireSeriesChanged();
  661. }
  662. /**
  663. * Returns a clone of the time series.
  664. * <P>
  665. * Notes:
  666. * <ul>
  667. * <li>no need to clone the domain and range descriptions, since String
  668. * object is immutable;</li>
  669. * <li>we pass over to the more general method clone(start, end).</li>
  670. * </ul>
  671. *
  672. * @return A clone of the time series.
  673. *
  674. * @throws CloneNotSupportedException not thrown by this class, but
  675. * subclasses may differ.
  676. */
  677. public Object clone() throws CloneNotSupportedException {
  678. Object clone = createCopy(0, getItemCount() - 1);
  679. return clone;
  680. }
  681. /**
  682. * Creates a new timeseries by copying a subset of the data in this time
  683. * series.
  684. *
  685. * @param start the index of the first time period to copy.
  686. * @param end the index of the last time period to copy.
  687. *
  688. * @return A series containing a copy of this times series from start until
  689. * end.
  690. *
  691. * @throws CloneNotSupportedException if there is a cloning problem.
  692. */
  693. public TimeSeries createCopy(int start, int end)
  694. throws CloneNotSupportedException {
  695. TimeSeries copy = (TimeSeries) super.clone();
  696. copy.data = new java.util.ArrayList();
  697. if (this.data.size() > 0) {
  698. for (int index = start; index <= end; index++) {
  699. TimeSeriesDataItem item
  700. = (TimeSeriesDataItem) this.data.get(index);
  701. TimeSeriesDataItem clone = (TimeSeriesDataItem) item.clone();
  702. try {
  703. copy.add(clone);
  704. }
  705. catch (SeriesException e) {
  706. System.err.println("Unable to add cloned data item.");
  707. }
  708. }
  709. }
  710. return copy;
  711. }
  712. /**
  713. * Creates a new timeseries by copying a subset of the data in this time
  714. * series.
  715. *
  716. * @param start the first time period to copy.
  717. * @param end the last time period to copy.
  718. *
  719. * @return A time series containing a copy of this time series from start
  720. * until end.
  721. *
  722. * @throws CloneNotSupportedException if there is a cloning problem.
  723. */
  724. public TimeSeries createCopy(RegularTimePeriod start, RegularTimePeriod end)
  725. throws CloneNotSupportedException {
  726. int startIndex = getIndex(start);
  727. if (startIndex < 0) {
  728. startIndex = -(startIndex + 1);
  729. }
  730. int endIndex = getIndex(end);
  731. if (endIndex < 0) { // end period is not in original series
  732. endIndex = -(endIndex + 1); // this is first item AFTER end period
  733. endIndex = endIndex - 1; // so this is last item BEFORE end
  734. }
  735. TimeSeries result = createCopy(startIndex, endIndex);
  736. return result;
  737. }
  738. /**
  739. * Tests the series for equality with an arbitrary object.
  740. *
  741. * @param object the object to test against (<code>null</code> permitted).
  742. *
  743. * @return A boolean.
  744. */
  745. public boolean equals(Object object) {
  746. if (object == this) {
  747. return true;
  748. }
  749. if (!(object instanceof TimeSeries) || !super.equals(object)) {
  750. return false;
  751. }
  752. TimeSeries s = (TimeSeries) object;
  753. if (!ObjectUtilities.equal(
  754. getDomainDescription(), s.getDomainDescription()
  755. )) {
  756. return false;
  757. }
  758. if (!ObjectUtilities.equal(
  759. getRangeDescription(), s.getRangeDescription()
  760. )) {
  761. return false;
  762. }
  763. if (!getClass().equals(s.getClass())) {
  764. return false;
  765. }
  766. if (getHistoryCount() != s.getHistoryCount()) {
  767. return false;
  768. }
  769. if (getMaximumItemCount() != s.getMaximumItemCount()) {
  770. return false;
  771. }
  772. int count = getItemCount();
  773. if (count != s.getItemCount()) {
  774. return false;
  775. }
  776. for (int i = 0; i < count; i++) {
  777. if (!getDataItem(i).equals(s.getDataItem(i))) {
  778. return false;
  779. }
  780. }
  781. return true;
  782. }
  783. /**
  784. * Returns a hash code value for the object.
  785. *
  786. * @return The hashcode
  787. */
  788. public int hashCode() {
  789. int result;
  790. result = (this.domain != null ? this.domain.hashCode() : 0);
  791. result = 29 * result + (this.range != null ? this.range.hashCode() : 0);
  792. result = 29 * result + (this.timePeriodClass != null ?
  793. this.timePeriodClass.hashCode() : 0);
  794. result = 29 * result + this.data.hashCode();
  795. result = 29 * result + this.maximumItemCount;
  796. result = 29 * result + this.historyCount;
  797. return result;
  798. }
  799. }