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