Source for org.jfree.data.time.TimeSeriesCollection

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2008, 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
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * -------------------------
  28:  * TimeSeriesCollection.java
  29:  * -------------------------
  30:  * (C) Copyright 2001-2008, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 11-Oct-2001 : Version 1 (DG);
  38:  * 18-Oct-2001 : Added implementation of IntervalXYDataSource so that bar plots
  39:  *               (using numerical axes) can be plotted from time series 
  40:  *               data (DG);
  41:  * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
  42:  * 15-Nov-2001 : Added getSeries() method.  Changed name from TimeSeriesDataset
  43:  *               to TimeSeriesCollection (DG);
  44:  * 07-Dec-2001 : TimeSeries --> BasicTimeSeries (DG);
  45:  * 01-Mar-2002 : Added a time zone offset attribute, to enable fast calculation
  46:  *               of the time period start and end values (DG);
  47:  * 29-Mar-2002 : The collection now registers itself with all the time series 
  48:  *               objects as a SeriesChangeListener.  Removed redundant 
  49:  *               calculateZoneOffset method (DG);
  50:  * 06-Jun-2002 : Added a setting to control whether the x-value supplied in the
  51:  *               getXValue() method comes from the START, MIDDLE, or END of the
  52:  *               time period.  This is a workaround for JFreeChart, where the 
  53:  *               current date axis always labels the start of a time 
  54:  *               period (DG);
  55:  * 24-Jun-2002 : Removed unnecessary import (DG);
  56:  * 24-Aug-2002 : Implemented DomainInfo interface, and added the 
  57:  *               DomainIsPointsInTime flag (DG);
  58:  * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  59:  * 16-Oct-2002 : Added remove methods (DG);
  60:  * 10-Jan-2003 : Changed method names in RegularTimePeriod class (DG);
  61:  * 13-Mar-2003 : Moved to com.jrefinery.data.time package and implemented 
  62:  *               Serializable (DG);
  63:  * 04-Sep-2003 : Added getSeries(String) method (DG);
  64:  * 15-Sep-2003 : Added a removeAllSeries() method to match 
  65:  *               XYSeriesCollection (DG);
  66:  * 05-May-2004 : Now extends AbstractIntervalXYDataset (DG);
  67:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
  68:  *               getYValue() (DG);
  69:  * 06-Oct-2004 : Updated for changed in DomainInfo interface (DG);
  70:  * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0 
  71:  *               release (DG);
  72:  * 28-Mar-2005 : Fixed bug in getSeries(int) method (1170825) (DG);
  73:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  74:  * 13-Dec-2005 : Deprecated the 'domainIsPointsInTime' flag as it is 
  75:  *               redundant.  Fixes bug 1243050 (DG);
  76:  * 04-May-2007 : Override getDomainOrder() to indicate that items are sorted
  77:  *               by x-value (ascending) (DG);
  78:  * 08-May-2007 : Added indexOf(TimeSeries) method (DG);
  79:  * 18-Jan-2008 : Changed getSeries(String) to getSeries(Comparable) (DG);
  80:  *
  81:  */
  82: 
  83: package org.jfree.data.time;
  84: 
  85: import java.io.Serializable;
  86: import java.util.ArrayList;
  87: import java.util.Calendar;
  88: import java.util.Collections;
  89: import java.util.Iterator;
  90: import java.util.List;
  91: import java.util.TimeZone;
  92: 
  93: import org.jfree.data.DomainInfo;
  94: import org.jfree.data.DomainOrder;
  95: import org.jfree.data.Range;
  96: import org.jfree.data.general.DatasetChangeEvent;
  97: import org.jfree.data.xy.AbstractIntervalXYDataset;
  98: import org.jfree.data.xy.IntervalXYDataset;
  99: import org.jfree.data.xy.XYDataset;
 100: import org.jfree.util.ObjectUtilities;
 101: 
 102: /**
 103:  * A collection of time series objects.  This class implements the 
 104:  * {@link org.jfree.data.xy.XYDataset} interface, as well as the extended 
 105:  * {@link IntervalXYDataset} interface.  This makes it a convenient dataset for
 106:  * use with the {@link org.jfree.chart.plot.XYPlot} class.
 107:  */
 108: public class TimeSeriesCollection extends AbstractIntervalXYDataset
 109:                                   implements XYDataset,
 110:                                              IntervalXYDataset,
 111:                                              DomainInfo,
 112:                                              Serializable {
 113: 
 114:     /** For serialization. */
 115:     private static final long serialVersionUID = 834149929022371137L;
 116:     
 117:     /** Storage for the time series. */
 118:     private List data;
 119: 
 120:     /** A working calendar (to recycle) */
 121:     private Calendar workingCalendar;
 122:     
 123:     /** 
 124:      * The point within each time period that is used for the X value when this
 125:      * collection is used as an {@link org.jfree.data.xy.XYDataset}.  This can 
 126:      * be the start, middle or end of the time period.   
 127:      */
 128:     private TimePeriodAnchor xPosition;
 129: 
 130:     /**
 131:      * A flag that indicates that the domain is 'points in time'.  If this
 132:      * flag is true, only the x-value is used to determine the range of values
 133:      * in the domain, the start and end x-values are ignored.
 134:      * 
 135:      * @deprecated No longer used (as of 1.0.1).
 136:      */
 137:     private boolean domainIsPointsInTime;
 138: 
 139:     /**
 140:      * Constructs an empty dataset, tied to the default timezone.
 141:      */
 142:     public TimeSeriesCollection() {
 143:         this(null, TimeZone.getDefault());
 144:     }
 145: 
 146:     /**
 147:      * Constructs an empty dataset, tied to a specific timezone.
 148:      *
 149:      * @param zone  the timezone (<code>null</code> permitted, will use 
 150:      *              <code>TimeZone.getDefault()</code> in that case).
 151:      */
 152:     public TimeSeriesCollection(TimeZone zone) {
 153:         this(null, zone);
 154:     }
 155: 
 156:     /**
 157:      * Constructs a dataset containing a single series (more can be added),
 158:      * tied to the default timezone.
 159:      *
 160:      * @param series the series (<code>null</code> permitted).
 161:      */
 162:     public TimeSeriesCollection(TimeSeries series) {
 163:         this(series, TimeZone.getDefault());
 164:     }
 165: 
 166:     /**
 167:      * Constructs a dataset containing a single series (more can be added),
 168:      * tied to a specific timezone.
 169:      *
 170:      * @param series  a series to add to the collection (<code>null</code> 
 171:      *                permitted).
 172:      * @param zone  the timezone (<code>null</code> permitted, will use 
 173:      *              <code>TimeZone.getDefault()</code> in that case).
 174:      */
 175:     public TimeSeriesCollection(TimeSeries series, TimeZone zone) {
 176: 
 177:         if (zone == null) {
 178:             zone = TimeZone.getDefault();
 179:         }
 180:         this.workingCalendar = Calendar.getInstance(zone);
 181:         this.data = new ArrayList();
 182:         if (series != null) {
 183:             this.data.add(series);
 184:             series.addChangeListener(this);
 185:         }
 186:         this.xPosition = TimePeriodAnchor.START;
 187:         this.domainIsPointsInTime = true;
 188: 
 189:     }
 190:     
 191:     /**
 192:      * Returns a flag that controls whether the domain is treated as 'points in
 193:      * time'.  This flag is used when determining the max and min values for 
 194:      * the domain.  If <code>true</code>, then only the x-values are considered
 195:      * for the max and min values.  If <code>false</code>, then the start and
 196:      * end x-values will also be taken into consideration.
 197:      *
 198:      * @return The flag.
 199:      * 
 200:      * @deprecated This flag is no longer used (as of 1.0.1).
 201:      */
 202:     public boolean getDomainIsPointsInTime() {
 203:         return this.domainIsPointsInTime;
 204:     }
 205: 
 206:     /**
 207:      * Sets a flag that controls whether the domain is treated as 'points in 
 208:      * time', or time periods.
 209:      *
 210:      * @param flag  the flag.
 211:      * 
 212:      * @deprecated This flag is no longer used, as of 1.0.1.  The 
 213:      *             <code>includeInterval</code> flag in methods such as 
 214:      *             {@link #getDomainBounds(boolean)} makes this unnecessary.
 215:      */
 216:     public void setDomainIsPointsInTime(boolean flag) {
 217:         this.domainIsPointsInTime = flag;
 218:         notifyListeners(new DatasetChangeEvent(this, this));    
 219:     }
 220:     
 221:     /**
 222:      * Returns the order of the domain values in this dataset.
 223:      *
 224:      * @return {@link DomainOrder#ASCENDING}
 225:      */
 226:     public DomainOrder getDomainOrder() {
 227:         return DomainOrder.ASCENDING;
 228:     }
 229:     
 230:     /**
 231:      * Returns the position within each time period that is used for the X 
 232:      * value when the collection is used as an 
 233:      * {@link org.jfree.data.xy.XYDataset}.
 234:      * 
 235:      * @return The anchor position (never <code>null</code>).
 236:      */
 237:     public TimePeriodAnchor getXPosition() {
 238:         return this.xPosition;
 239:     }
 240: 
 241:     /**
 242:      * Sets the position within each time period that is used for the X values 
 243:      * when the collection is used as an {@link XYDataset}, then sends a 
 244:      * {@link DatasetChangeEvent} is sent to all registered listeners.
 245:      * 
 246:      * @param anchor  the anchor position (<code>null</code> not permitted).
 247:      */
 248:     public void setXPosition(TimePeriodAnchor anchor) {
 249:         if (anchor == null) {
 250:             throw new IllegalArgumentException("Null 'anchor' argument.");
 251:         }
 252:         this.xPosition = anchor;
 253:         notifyListeners(new DatasetChangeEvent(this, this));    
 254:     }
 255:     
 256:     /**
 257:      * Returns a list of all the series in the collection.  
 258:      * 
 259:      * @return The list (which is unmodifiable).
 260:      */
 261:     public List getSeries() {
 262:         return Collections.unmodifiableList(this.data);
 263:     }
 264: 
 265:     /**
 266:      * Returns the number of series in the collection.
 267:      *
 268:      * @return The series count.
 269:      */
 270:     public int getSeriesCount() {
 271:         return this.data.size();
 272:     }
 273: 
 274:     /**
 275:      * Returns the index of the specified series, or -1 if that series is not
 276:      * present in the dataset.
 277:      * 
 278:      * @param series  the series (<code>null</code> not permitted).
 279:      * 
 280:      * @return The series index.
 281:      * 
 282:      * @since 1.0.6
 283:      */
 284:     public int indexOf(TimeSeries series) {
 285:         if (series == null) {
 286:             throw new IllegalArgumentException("Null 'series' argument.");
 287:         }
 288:         return this.data.indexOf(series);
 289:     }
 290: 
 291:     /**
 292:      * Returns a series.
 293:      *
 294:      * @param series  the index of the series (zero-based).
 295:      *
 296:      * @return The series.
 297:      */
 298:     public TimeSeries getSeries(int series) {
 299:         if ((series < 0) || (series >= getSeriesCount())) {
 300:             throw new IllegalArgumentException(
 301:                 "The 'series' argument is out of bounds (" + series + ").");
 302:         }
 303:         return (TimeSeries) this.data.get(series);
 304:     }
 305:     
 306:     /**
 307:      * Returns the series with the specified key, or <code>null</code> if 
 308:      * there is no such series.
 309:      * 
 310:      * @param key  the series key (<code>null</code> permitted).
 311:      * 
 312:      * @return The series with the given key.
 313:      */
 314:     public TimeSeries getSeries(Comparable key) {
 315:         TimeSeries result = null;
 316:         Iterator iterator = this.data.iterator();
 317:         while (iterator.hasNext()) {
 318:             TimeSeries series = (TimeSeries) iterator.next();
 319:             Comparable k = series.getKey();
 320:             if (k != null && k.equals(key)) {
 321:                 result = series;
 322:             }
 323:         }
 324:         return result;   
 325:     }
 326: 
 327:     /**
 328:      * Returns the key for a series.  
 329:      *
 330:      * @param series  the index of the series (zero-based).
 331:      *
 332:      * @return The key for a series.
 333:      */
 334:     public Comparable getSeriesKey(int series) {
 335:         // check arguments...delegated
 336:         // fetch the series name...
 337:         return getSeries(series).getKey();
 338:     }
 339: 
 340:     /**
 341:      * Adds a series to the collection and sends a {@link DatasetChangeEvent} to
 342:      * all registered listeners.
 343:      *
 344:      * @param series  the series (<code>null</code> not permitted).
 345:      */
 346:     public void addSeries(TimeSeries series) {
 347:         if (series == null) {
 348:             throw new IllegalArgumentException("Null 'series' argument.");
 349:         }
 350:         this.data.add(series);
 351:         series.addChangeListener(this);
 352:         fireDatasetChanged();
 353:     }
 354: 
 355:     /**
 356:      * Removes the specified series from the collection and sends a 
 357:      * {@link DatasetChangeEvent} to all registered listeners.
 358:      *
 359:      * @param series  the series (<code>null</code> not permitted).
 360:      */
 361:     public void removeSeries(TimeSeries series) {
 362:         if (series == null) {
 363:             throw new IllegalArgumentException("Null 'series' argument.");
 364:         }
 365:         this.data.remove(series);
 366:         series.removeChangeListener(this);
 367:         fireDatasetChanged();
 368:     }
 369: 
 370:     /**
 371:      * Removes a series from the collection.
 372:      *
 373:      * @param index  the series index (zero-based).
 374:      */
 375:     public void removeSeries(int index) {
 376:         TimeSeries series = getSeries(index);
 377:         if (series != null) {
 378:             removeSeries(series);
 379:         }
 380:     }
 381: 
 382:     /**
 383:      * Removes all the series from the collection and sends a 
 384:      * {@link DatasetChangeEvent} to all registered listeners.
 385:      */
 386:     public void removeAllSeries() {
 387: 
 388:         // deregister the collection as a change listener to each series in the
 389:         // collection
 390:         for (int i = 0; i < this.data.size(); i++) {
 391:             TimeSeries series = (TimeSeries) this.data.get(i);
 392:             series.removeChangeListener(this);
 393:         }
 394: 
 395:         // remove all the series from the collection and notify listeners.
 396:         this.data.clear();
 397:         fireDatasetChanged();
 398: 
 399:     }
 400: 
 401:     /**
 402:      * Returns the number of items in the specified series.  This method is 
 403:      * provided for convenience.
 404:      *
 405:      * @param series  the series index (zero-based).
 406:      *
 407:      * @return The item count.
 408:      */
 409:     public int getItemCount(int series) {
 410:         return getSeries(series).getItemCount();
 411:     }
 412:     
 413:     /**
 414:      * Returns the x-value (as a double primitive) for an item within a series.
 415:      * 
 416:      * @param series  the series (zero-based index).
 417:      * @param item  the item (zero-based index).
 418:      * 
 419:      * @return The x-value.
 420:      */
 421:     public double getXValue(int series, int item) {
 422:         TimeSeries s = (TimeSeries) this.data.get(series);
 423:         TimeSeriesDataItem i = s.getDataItem(item);
 424:         RegularTimePeriod period = i.getPeriod();
 425:         return getX(period);
 426:     }
 427: 
 428:     /**
 429:      * Returns the x-value for the specified series and item.
 430:      *
 431:      * @param series  the series (zero-based index).
 432:      * @param item  the item (zero-based index).
 433:      *
 434:      * @return The value.
 435:      */
 436:     public Number getX(int series, int item) {
 437:         TimeSeries ts = (TimeSeries) this.data.get(series);
 438:         TimeSeriesDataItem dp = ts.getDataItem(item);
 439:         RegularTimePeriod period = dp.getPeriod();
 440:         return new Long(getX(period));
 441:     }
 442:     
 443:     /**
 444:      * Returns the x-value for a time period.
 445:      *
 446:      * @param period  the time period (<code>null</code> not permitted).
 447:      *
 448:      * @return The x-value.
 449:      */
 450:     protected synchronized long getX(RegularTimePeriod period) {
 451:         long result = 0L;
 452:         if (this.xPosition == TimePeriodAnchor.START) {
 453:             result = period.getFirstMillisecond(this.workingCalendar);
 454:         }
 455:         else if (this.xPosition == TimePeriodAnchor.MIDDLE) {
 456:             result = period.getMiddleMillisecond(this.workingCalendar);
 457:         }
 458:         else if (this.xPosition == TimePeriodAnchor.END) {
 459:             result = period.getLastMillisecond(this.workingCalendar); 
 460:         }
 461:         return result;
 462:     }
 463: 
 464:     /**
 465:      * Returns the starting X value for the specified series and item.
 466:      *
 467:      * @param series  the series (zero-based index).
 468:      * @param item  the item (zero-based index).
 469:      *
 470:      * @return The value.
 471:      */
 472:     public synchronized Number getStartX(int series, int item) {
 473:         TimeSeries ts = (TimeSeries) this.data.get(series);
 474:         TimeSeriesDataItem dp = ts.getDataItem(item);
 475:         return new Long(dp.getPeriod().getFirstMillisecond(
 476:                 this.workingCalendar));
 477:     }
 478: 
 479:     /**
 480:      * Returns the ending X value for the specified series and item.
 481:      *
 482:      * @param series The series (zero-based index).
 483:      * @param item  The item (zero-based index).
 484:      *
 485:      * @return The value.
 486:      */
 487:     public synchronized Number getEndX(int series, int item) {
 488:         TimeSeries ts = (TimeSeries) this.data.get(series);
 489:         TimeSeriesDataItem dp = ts.getDataItem(item);
 490:         return new Long(dp.getPeriod().getLastMillisecond(
 491:                 this.workingCalendar));
 492:     }
 493: 
 494:     /**
 495:      * Returns the y-value for the specified series and item.
 496:      *
 497:      * @param series  the series (zero-based index).
 498:      * @param item  the item (zero-based index).
 499:      *
 500:      * @return The value (possibly <code>null</code>).
 501:      */
 502:     public Number getY(int series, int item) {
 503:         TimeSeries ts = (TimeSeries) this.data.get(series);
 504:         TimeSeriesDataItem dp = ts.getDataItem(item);
 505:         return dp.getValue();
 506:     }
 507: 
 508:     /**
 509:      * Returns the starting Y value for the specified series and item.
 510:      *
 511:      * @param series  the series (zero-based index).
 512:      * @param item  the item (zero-based index).
 513:      *
 514:      * @return The value (possibly <code>null</code>).
 515:      */
 516:     public Number getStartY(int series, int item) {
 517:         return getY(series, item);
 518:     }
 519: 
 520:     /**
 521:      * Returns the ending Y value for the specified series and item.
 522:      *
 523:      * @param series  te series (zero-based index).
 524:      * @param item  the item (zero-based index).
 525:      *
 526:      * @return The value (possibly <code>null</code>).
 527:      */
 528:     public Number getEndY(int series, int item) {
 529:         return getY(series, item);
 530:     }
 531: 
 532: 
 533:     /**
 534:      * Returns the indices of the two data items surrounding a particular 
 535:      * millisecond value.  
 536:      * 
 537:      * @param series  the series index.
 538:      * @param milliseconds  the time.
 539:      * 
 540:      * @return An array containing the (two) indices of the items surrounding 
 541:      *         the time.
 542:      */
 543:     public int[] getSurroundingItems(int series, long milliseconds) {
 544:         int[] result = new int[] {-1, -1};
 545:         TimeSeries timeSeries = getSeries(series);
 546:         for (int i = 0; i < timeSeries.getItemCount(); i++) {
 547:             Number x = getX(series, i);
 548:             long m = x.longValue();
 549:             if (m <= milliseconds) {
 550:                 result[0] = i;
 551:             }
 552:             if (m >= milliseconds) {
 553:                 result[1] = i;
 554:                 break;
 555:             }
 556:         }
 557:         return result;
 558:     }
 559:     
 560:     /**
 561:      * Returns the minimum x-value in the dataset.
 562:      *
 563:      * @param includeInterval  a flag that determines whether or not the
 564:      *                         x-interval is taken into account.
 565:      * 
 566:      * @return The minimum value.
 567:      */
 568:     public double getDomainLowerBound(boolean includeInterval) {
 569:         double result = Double.NaN;
 570:         Range r = getDomainBounds(includeInterval);
 571:         if (r != null) {
 572:             result = r.getLowerBound();
 573:         }
 574:         return result;        
 575:     }
 576: 
 577:     /**
 578:      * Returns the maximum x-value in the dataset.
 579:      *
 580:      * @param includeInterval  a flag that determines whether or not the
 581:      *                         x-interval is taken into account.
 582:      * 
 583:      * @return The maximum value.
 584:      */
 585:     public double getDomainUpperBound(boolean includeInterval) {
 586:         double result = Double.NaN;
 587:         Range r = getDomainBounds(includeInterval);
 588:         if (r != null) {
 589:             result = r.getUpperBound();
 590:         }
 591:         return result;
 592:     }
 593: 
 594:     /**
 595:      * Returns the range of the values in this dataset's domain.
 596:      *
 597:      * @param includeInterval  a flag that determines whether or not the
 598:      *                         x-interval is taken into account.
 599:      * 
 600:      * @return The range.
 601:      */
 602:     public Range getDomainBounds(boolean includeInterval) {
 603:         Range result = null;
 604:         Iterator iterator = this.data.iterator();
 605:         while (iterator.hasNext()) {
 606:             TimeSeries series = (TimeSeries) iterator.next();
 607:             int count = series.getItemCount();
 608:             if (count > 0) {
 609:                 RegularTimePeriod start = series.getTimePeriod(0);
 610:                 RegularTimePeriod end = series.getTimePeriod(count - 1);
 611:                 Range temp;
 612:                 if (!includeInterval) {
 613:                     temp = new Range(getX(start), getX(end));
 614:                 }
 615:                 else {
 616:                     temp = new Range(
 617:                             start.getFirstMillisecond(this.workingCalendar),
 618:                             end.getLastMillisecond(this.workingCalendar));
 619:                 }
 620:                 result = Range.combine(result, temp);
 621:             }
 622:         }
 623:         return result;
 624:     }
 625:     
 626:     /**
 627:      * Tests this time series collection for equality with another object.
 628:      *
 629:      * @param obj  the other object.
 630:      *
 631:      * @return A boolean.
 632:      */
 633:     public boolean equals(Object obj) {
 634:         if (obj == this) {
 635:             return true;
 636:         }
 637:         if (!(obj instanceof TimeSeriesCollection)) {
 638:             return false;
 639:         }
 640:         TimeSeriesCollection that = (TimeSeriesCollection) obj;
 641:         if (this.xPosition != that.xPosition) {
 642:             return false;
 643:         }
 644:         if (this.domainIsPointsInTime != that.domainIsPointsInTime) {
 645:             return false;
 646:         }
 647:         if (!ObjectUtilities.equal(this.data, that.data)) {
 648:             return false;
 649:         }
 650:         return true;
 651:     }
 652: 
 653:     /**
 654:      * Returns a hash code value for the object.
 655:      *
 656:      * @return The hashcode
 657:      */
 658:     public int hashCode() {
 659:         int result;
 660:         result = this.data.hashCode();
 661:         result = 29 * result + (this.workingCalendar != null 
 662:                 ? this.workingCalendar.hashCode() : 0);
 663:         result = 29 * result + (this.xPosition != null 
 664:                 ? this.xPosition.hashCode() : 0);
 665:         result = 29 * result + (this.domainIsPointsInTime ? 1 : 0);
 666:         return result;
 667:     }
 668:     
 669: }