Source for org.jfree.chart.axis.SegmentedTimeline

   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:  * SegmentedTimeline.java
  29:  * -----------------------
  30:  * (C) Copyright 2003-2008, by Bill Kelemen and Contributors.
  31:  *
  32:  * Original Author:  Bill Kelemen;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 23-May-2003 : Version 1 (BK);
  38:  * 15-Aug-2003 : Implemented Cloneable (DG);
  39:  * 01-Jun-2004 : Modified to compile with JDK 1.2.2 (DG);
  40:  * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG);
  41:  * 04-Nov-2004 : Reverted change of 30-Sep-2004, won't work with JDK 1.3 (DG);
  42:  * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
  43:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  44:  * 14-Nov-2006 : Fix in toTimelineValue(long) to avoid stack overflow (DG);
  45:  * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
  46:  * 11-Jul-2007 : Fixed time zone bugs (DG);
  47:  * 06-Jun-2008 : Performance enhancement posted in forum (DG);
  48:  *
  49:  */
  50: 
  51: package org.jfree.chart.axis;
  52: 
  53: import java.io.Serializable;
  54: import java.util.ArrayList;
  55: import java.util.Calendar;
  56: import java.util.Collections;
  57: import java.util.Date;
  58: import java.util.GregorianCalendar;
  59: import java.util.Iterator;
  60: import java.util.List;
  61: import java.util.Locale;
  62: import java.util.SimpleTimeZone;
  63: import java.util.TimeZone;
  64: 
  65: /**
  66:  * A {@link Timeline} that implements a "segmented" timeline with included,
  67:  * excluded and exception segments.
  68:  * <P>
  69:  * A Timeline will present a series of values to be used for an axis. Each
  70:  * Timeline must provide transformation methods between domain values and
  71:  * timeline values.
  72:  * <P>
  73:  * A timeline can be used as parameter to a
  74:  * {@link org.jfree.chart.axis.DateAxis} to define the values that this axis
  75:  * supports. This class implements a timeline formed by segments of equal
  76:  * length (ex. days, hours, minutes) where some segments can be included in the
  77:  * timeline and others excluded. Therefore timelines like "working days" or
  78:  * "working hours" can be created where non-working days or non-working hours
  79:  * respectively can be removed from the timeline, and therefore from the axis.
  80:  * This creates a smooth plot with equal separation between all included
  81:  * segments.
  82:  * <P>
  83:  * Because Timelines were created mainly for Date related axis, values are
  84:  * represented as longs instead of doubles. In this case, the domain value is
  85:  * just the number of milliseconds since January 1, 1970, 00:00:00 GMT as
  86:  * defined by the getTime() method of {@link java.util.Date}.
  87:  * <P>
  88:  * In this class, a segment is defined as a unit of time of fixed length.
  89:  * Examples of segments are: days, hours, minutes, etc. The size of a segment
  90:  * is defined as the number of milliseconds in the segment. Some useful segment
  91:  * sizes are defined as constants in this class: DAY_SEGMENT_SIZE,
  92:  * HOUR_SEGMENT_SIZE, FIFTEEN_MINUTE_SEGMENT_SIZE and MINUTE_SEGMENT_SIZE.
  93:  * <P>
  94:  * Segments are group together to form a Segment Group. Each Segment Group will
  95:  * contain a number of Segments included and a number of Segments excluded. This
  96:  * Segment Group structure will repeat for the whole timeline.
  97:  * <P>
  98:  * For example, a working days SegmentedTimeline would be formed by a group of
  99:  * 7 daily segments, where there are 5 included (Monday through Friday) and 2
 100:  * excluded (Saturday and Sunday) segments.
 101:  * <P>
 102:  * Following is a diagram that explains the major attributes that define a
 103:  * segment.  Each box is one segment and must be of fixed length (ms, second,
 104:  * hour, day, etc).
 105:  * <p>
 106:  * <pre>
 107:  * start time
 108:  *   |
 109:  *   v
 110:  *   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 ...
 111:  * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+...
 112:  * |  |  |  |  |  |EE|EE|  |  |  |  |  |EE|EE|  |  |  |  |  |EE|EE|
 113:  * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+...
 114:  *  \____________/ \___/            \_/
 115:  *        \/         |               |
 116:  *     included   excluded        segment
 117:  *     segments   segments         size
 118:  *  \_________  _______/
 119:  *            \/
 120:  *       segment group
 121:  * </pre>
 122:  * Legend:<br>
 123:  * &lt;space&gt; = Included segment<br>
 124:  * EE      = Excluded segments in the base timeline<br>
 125:  * <p>
 126:  * In the example, the following segment attributes are presented:
 127:  * <ul>
 128:  * <li>segment size: the size of each segment in ms.
 129:  * <li>start time: the start of the first segment of the first segment group to
 130:  *     consider.
 131:  * <li>included segments: the number of segments to include in the group.
 132:  * <li>excluded segments: the number of segments to exclude in the group.
 133:  * </ul>
 134:  * <p>
 135:  * Exception Segments are allowed. These exception segments are defined as
 136:  * segments that would have been in the included segments of the Segment Group,
 137:  * but should be excluded for special reasons. In the previous working days
 138:  * SegmentedTimeline example, holidays would be considered exceptions.
 139:  * <P>
 140:  * Additionally the <code>startTime</code>, or start of the first Segment of
 141:  * the smallest segment group needs to be defined. This startTime could be
 142:  * relative to January 1, 1970, 00:00:00 GMT or any other date. This creates a
 143:  * point of reference to start counting Segment Groups. For example, for the
 144:  * working days SegmentedTimeline, the <code>startTime</code> could be
 145:  * 00:00:00 GMT of the first Monday after January 1, 1970. In this class, the
 146:  * constant FIRST_MONDAY_AFTER_1900 refers to a reference point of the first
 147:  * Monday of the last century.
 148:  * <p>
 149:  * A SegmentedTimeline can include a baseTimeline. This combination of
 150:  * timelines allows the creation of more complex timelines. For example, in
 151:  * order to implement a SegmentedTimeline for an intraday stock trading
 152:  * application, where the trading period is defined as 9:00 AM through 4:00 PM
 153:  * Monday through Friday, two SegmentedTimelines are used. The first one (the
 154:  * baseTimeline) would be a working day SegmentedTimeline (daily timeline
 155:  * Monday through Friday). On top of this baseTimeline, a second one is defined
 156:  * that maps the 9:00 AM to 4:00 PM period. Because the baseTimeline defines a
 157:  * timeline of Monday through Friday, the resulting (combined) timeline will
 158:  * expose the period 9:00 AM through 4:00 PM only on Monday through Friday,
 159:  * and will remove all other intermediate intervals.
 160:  * <P>
 161:  * Two factory methods newMondayThroughFridayTimeline() and
 162:  * newFifteenMinuteTimeline() are provided as examples to create special
 163:  * SegmentedTimelines.
 164:  *
 165:  * @see org.jfree.chart.axis.DateAxis
 166:  */
 167: public class SegmentedTimeline implements Timeline, Cloneable, Serializable {
 168: 
 169:     /** For serialization. */
 170:     private static final long serialVersionUID = 1093779862539903110L;
 171: 
 172:     ////////////////////////////////////////////////////////////////////////////
 173:     // predetermined segments sizes
 174:     ////////////////////////////////////////////////////////////////////////////
 175: 
 176:     /** Defines a day segment size in ms. */
 177:     public static final long DAY_SEGMENT_SIZE = 24 * 60 * 60 * 1000;
 178: 
 179:     /** Defines a one hour segment size in ms. */
 180:     public static final long HOUR_SEGMENT_SIZE = 60 * 60 * 1000;
 181: 
 182:     /** Defines a 15-minute segment size in ms. */
 183:     public static final long FIFTEEN_MINUTE_SEGMENT_SIZE = 15 * 60 * 1000;
 184: 
 185:     /** Defines a one-minute segment size in ms. */
 186:     public static final long MINUTE_SEGMENT_SIZE = 60 * 1000;
 187: 
 188:     ////////////////////////////////////////////////////////////////////////////
 189:     // other constants
 190:     ////////////////////////////////////////////////////////////////////////////
 191: 
 192:     /**
 193:      * Utility constant that defines the startTime as the first monday after
 194:      * 1/1/1970.  This should be used when creating a SegmentedTimeline for
 195:      * Monday through Friday. See static block below for calculation of this
 196:      * constant.
 197:      *
 198:      * @deprecated As of 1.0.7.  This field doesn't take into account changes
 199:      *         to the default time zone.
 200:      */
 201:     public static long FIRST_MONDAY_AFTER_1900;
 202: 
 203:     /**
 204:      * Utility TimeZone object that has no DST and an offset equal to the
 205:      * default TimeZone. This allows easy arithmetic between days as each one
 206:      * will have equal size.
 207:      *
 208:      * @deprecated As of 1.0.7.  This field is initialised based on the
 209:      *         default time zone, and doesn't take into account subsequent
 210:      *         changes to the default.
 211:      */
 212:     public static TimeZone NO_DST_TIME_ZONE;
 213: 
 214:     /**
 215:      * This is the default time zone where the application is running. See
 216:      * getTime() below where we make use of certain transformations between
 217:      * times in the default time zone and the no-dst time zone used for our
 218:      * calculations.
 219:      *
 220:      * @deprecated As of 1.0.7.  When the default time zone is required,
 221:      *         just call <code>TimeZone.getDefault()</code>.
 222:      */
 223:     public static TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault();
 224: 
 225:     /**
 226:      * This will be a utility calendar that has no DST but is shifted relative
 227:      * to the default time zone's offset.
 228:      */
 229:     private Calendar workingCalendarNoDST;
 230: 
 231:     /**
 232:      * This will be a utility calendar that used the default time zone.
 233:      */
 234:     private Calendar workingCalendar = Calendar.getInstance();
 235: 
 236:     ////////////////////////////////////////////////////////////////////////////
 237:     // private attributes
 238:     ////////////////////////////////////////////////////////////////////////////
 239: 
 240:     /** Segment size in ms. */
 241:     private long segmentSize;
 242: 
 243:     /** Number of consecutive segments to include in a segment group. */
 244:     private int segmentsIncluded;
 245: 
 246:     /** Number of consecutive segments to exclude in a segment group. */
 247:     private int segmentsExcluded;
 248: 
 249:     /** Number of segments in a group (segmentsIncluded + segmentsExcluded). */
 250:     private int groupSegmentCount;
 251: 
 252:     /**
 253:      * Start of time reference from time zero (1/1/1970).
 254:      * This is the start of segment #0.
 255:      */
 256:     private long startTime;
 257: 
 258:     /** Consecutive ms in segmentsIncluded (segmentsIncluded * segmentSize). */
 259:     private long segmentsIncludedSize;
 260: 
 261:     /** Consecutive ms in segmentsExcluded (segmentsExcluded * segmentSize). */
 262:     private long segmentsExcludedSize;
 263: 
 264:     /** ms in a segment group (segmentsIncludedSize + segmentsExcludedSize). */
 265:     private long segmentsGroupSize;
 266: 
 267:     /**
 268:      * List of exception segments (exceptions segments that would otherwise be
 269:      * included based on the periodic (included, excluded) grouping).
 270:      */
 271:     private List exceptionSegments = new ArrayList();
 272: 
 273:     /**
 274:      * This base timeline is used to specify exceptions at a higher level. For
 275:      * example, if we are a intraday timeline and want to exclude holidays,
 276:      * instead of having to exclude all intraday segments for the holiday,
 277:      * segments from this base timeline can be excluded. This baseTimeline is
 278:      * always optional and is only a convenience method.
 279:      * <p>
 280:      * Additionally, all excluded segments from this baseTimeline will be
 281:      * considered exceptions at this level.
 282:      */
 283:     private SegmentedTimeline baseTimeline;
 284: 
 285:     /** A flag that controls whether or not to adjust for daylight saving. */
 286:     private boolean adjustForDaylightSaving = false;
 287: 
 288:     ////////////////////////////////////////////////////////////////////////////
 289:     // static block
 290:     ////////////////////////////////////////////////////////////////////////////
 291: 
 292:     static {
 293:         // make a time zone with no DST for our Calendar calculations
 294:         int offset = TimeZone.getDefault().getRawOffset();
 295:         NO_DST_TIME_ZONE = new SimpleTimeZone(offset, "UTC-" + offset);
 296: 
 297:         // calculate midnight of first monday after 1/1/1900 relative to
 298:         // current locale
 299:         Calendar cal = new GregorianCalendar(NO_DST_TIME_ZONE);
 300:         cal.set(1900, 0, 1, 0, 0, 0);
 301:         cal.set(Calendar.MILLISECOND, 0);
 302:         while (cal.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
 303:             cal.add(Calendar.DATE, 1);
 304:         }
 305:         // FIRST_MONDAY_AFTER_1900 = cal.getTime().getTime();
 306:         // preceding code won't work with JDK 1.3
 307:         FIRST_MONDAY_AFTER_1900 = cal.getTime().getTime();
 308:     }
 309: 
 310:     ////////////////////////////////////////////////////////////////////////////
 311:     // constructors and factory methods
 312:     ////////////////////////////////////////////////////////////////////////////
 313: 
 314:     /**
 315:      * Constructs a new segmented timeline, optionaly using another segmented
 316:      * timeline as its base. This chaining of SegmentedTimelines allows further
 317:      * segmentation into smaller timelines.
 318:      *
 319:      * If a base
 320:      *
 321:      * @param segmentSize the size of a segment in ms. This time unit will be
 322:      *        used to compute the included and excluded segments of the
 323:      *        timeline.
 324:      * @param segmentsIncluded Number of consecutive segments to include.
 325:      * @param segmentsExcluded Number of consecutive segments to exclude.
 326:      */
 327:     public SegmentedTimeline(long segmentSize,
 328:                              int segmentsIncluded,
 329:                              int segmentsExcluded) {
 330: 
 331:         this.segmentSize = segmentSize;
 332:         this.segmentsIncluded = segmentsIncluded;
 333:         this.segmentsExcluded = segmentsExcluded;
 334: 
 335:         this.groupSegmentCount = this.segmentsIncluded + this.segmentsExcluded;
 336:         this.segmentsIncludedSize = this.segmentsIncluded * this.segmentSize;
 337:         this.segmentsExcludedSize = this.segmentsExcluded * this.segmentSize;
 338:         this.segmentsGroupSize = this.segmentsIncludedSize
 339:                                  + this.segmentsExcludedSize;
 340:         int offset = TimeZone.getDefault().getRawOffset();
 341:         TimeZone z = new SimpleTimeZone(offset, "UTC-" + offset);
 342:         this.workingCalendarNoDST = new GregorianCalendar(z,
 343:                 Locale.getDefault());
 344:     }
 345: 
 346:     /**
 347:      * Returns the milliseconds for midnight of the first Monday after
 348:      * 1-Jan-1900, ignoring daylight savings.
 349:      *
 350:      * @return The milliseconds.
 351:      *
 352:      * @since 1.0.7
 353:      */
 354:     public static long firstMondayAfter1900() {
 355:         int offset = TimeZone.getDefault().getRawOffset();
 356:         TimeZone z = new SimpleTimeZone(offset, "UTC-" + offset);
 357: 
 358:         // calculate midnight of first monday after 1/1/1900 relative to
 359:         // current locale
 360:         Calendar cal = new GregorianCalendar(z);
 361:         cal.set(1900, 0, 1, 0, 0, 0);
 362:         cal.set(Calendar.MILLISECOND, 0);
 363:         while (cal.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
 364:             cal.add(Calendar.DATE, 1);
 365:         }
 366:         //return cal.getTimeInMillis();
 367:         // preceding code won't work with JDK 1.3
 368:         return cal.getTime().getTime();
 369:     }
 370: 
 371:     /**
 372:      * Factory method to create a Monday through Friday SegmentedTimeline.
 373:      * <P>
 374:      * The <code>startTime</code> of the resulting timeline will be midnight
 375:      * of the first Monday after 1/1/1900.
 376:      *
 377:      * @return A fully initialized SegmentedTimeline.
 378:      */
 379:     public static SegmentedTimeline newMondayThroughFridayTimeline() {
 380:         SegmentedTimeline timeline
 381:             = new SegmentedTimeline(DAY_SEGMENT_SIZE, 5, 2);
 382:         timeline.setStartTime(firstMondayAfter1900());
 383:         return timeline;
 384:     }
 385: 
 386:     /**
 387:      * Factory method to create a 15-min, 9:00 AM thought 4:00 PM, Monday
 388:      * through Friday SegmentedTimeline.
 389:      * <P>
 390:      * This timeline uses a segmentSize of FIFTEEN_MIN_SEGMENT_SIZE. The
 391:      * segment group is defined as 28 included segments (9:00 AM through
 392:      * 4:00 PM) and 68 excluded segments (4:00 PM through 9:00 AM the next day).
 393:      * <P>
 394:      * In order to exclude Saturdays and Sundays it uses a baseTimeline that
 395:      * only includes Monday through Friday days.
 396:      * <P>
 397:      * The <code>startTime</code> of the resulting timeline will be 9:00 AM
 398:      * after the startTime of the baseTimeline. This will correspond to 9:00 AM
 399:      * of the first Monday after 1/1/1900.
 400:      *
 401:      * @return A fully initialized SegmentedTimeline.
 402:      */
 403:     public static SegmentedTimeline newFifteenMinuteTimeline() {
 404:         SegmentedTimeline timeline = new SegmentedTimeline(
 405:                 FIFTEEN_MINUTE_SEGMENT_SIZE, 28, 68);
 406:         timeline.setStartTime(firstMondayAfter1900() + 36
 407:                 * timeline.getSegmentSize());
 408:         timeline.setBaseTimeline(newMondayThroughFridayTimeline());
 409:         return timeline;
 410:     }
 411: 
 412:     /**
 413:      * Returns the flag that controls whether or not the daylight saving
 414:      * adjustment is applied.
 415:      *
 416:      * @return A boolean.
 417:      */
 418:     public boolean getAdjustForDaylightSaving() {
 419:         return this.adjustForDaylightSaving;
 420:     }
 421: 
 422:     /**
 423:      * Sets the flag that controls whether or not the daylight saving adjustment
 424:      * is applied.
 425:      *
 426:      * @param adjust  the flag.
 427:      */
 428:     public void setAdjustForDaylightSaving(boolean adjust) {
 429:         this.adjustForDaylightSaving = adjust;
 430:     }
 431: 
 432:     ////////////////////////////////////////////////////////////////////////////
 433:     // operations
 434:     ////////////////////////////////////////////////////////////////////////////
 435: 
 436:     /**
 437:      * Sets the start time for the timeline. This is the beginning of segment
 438:      * zero.
 439:      *
 440:      * @param millisecond  the start time (encoded as in java.util.Date).
 441:      */
 442:     public void setStartTime(long millisecond) {
 443:         this.startTime = millisecond;
 444:     }
 445: 
 446:     /**
 447:      * Returns the start time for the timeline. This is the beginning of
 448:      * segment zero.
 449:      *
 450:      * @return The start time.
 451:      */
 452:     public long getStartTime() {
 453:         return this.startTime;
 454:     }
 455: 
 456:     /**
 457:      * Returns the number of segments excluded per segment group.
 458:      *
 459:      * @return The number of segments excluded.
 460:      */
 461:     public int getSegmentsExcluded() {
 462:         return this.segmentsExcluded;
 463:     }
 464: 
 465:     /**
 466:      * Returns the size in milliseconds of the segments excluded per segment
 467:      * group.
 468:      *
 469:      * @return The size in milliseconds.
 470:      */
 471:     public long getSegmentsExcludedSize() {
 472:         return this.segmentsExcludedSize;
 473:     }
 474: 
 475:     /**
 476:      * Returns the number of segments in a segment group. This will be equal to
 477:      * segments included plus segments excluded.
 478:      *
 479:      * @return The number of segments.
 480:      */
 481:     public int getGroupSegmentCount() {
 482:         return this.groupSegmentCount;
 483:     }
 484: 
 485:     /**
 486:      * Returns the size in milliseconds of a segment group. This will be equal
 487:      * to size of the segments included plus the size of the segments excluded.
 488:      *
 489:      * @return The segment group size in milliseconds.
 490:      */
 491:     public long getSegmentsGroupSize() {
 492:         return this.segmentsGroupSize;
 493:     }
 494: 
 495:     /**
 496:      * Returns the number of segments included per segment group.
 497:      *
 498:      * @return The number of segments.
 499:      */
 500:     public int getSegmentsIncluded() {
 501:         return this.segmentsIncluded;
 502:     }
 503: 
 504:     /**
 505:      * Returns the size in ms of the segments included per segment group.
 506:      *
 507:      * @return The segment size in milliseconds.
 508:      */
 509:     public long getSegmentsIncludedSize() {
 510:         return this.segmentsIncludedSize;
 511:     }
 512: 
 513:     /**
 514:      * Returns the size of one segment in ms.
 515:      *
 516:      * @return The segment size in milliseconds.
 517:      */
 518:     public long getSegmentSize() {
 519:         return this.segmentSize;
 520:     }
 521: 
 522:     /**
 523:      * Returns a list of all the exception segments. This list is not
 524:      * modifiable.
 525:      *
 526:      * @return The exception segments.
 527:      */
 528:     public List getExceptionSegments() {
 529:         return Collections.unmodifiableList(this.exceptionSegments);
 530:     }
 531: 
 532:     /**
 533:      * Sets the exception segments list.
 534:      *
 535:      * @param exceptionSegments  the exception segments.
 536:      */
 537:     public void setExceptionSegments(List exceptionSegments) {
 538:         this.exceptionSegments = exceptionSegments;
 539:     }
 540: 
 541:     /**
 542:      * Returns our baseTimeline, or <code>null</code> if none.
 543:      *
 544:      * @return The base timeline.
 545:      */
 546:     public SegmentedTimeline getBaseTimeline() {
 547:         return this.baseTimeline;
 548:     }
 549: 
 550:     /**
 551:      * Sets the base timeline.
 552:      *
 553:      * @param baseTimeline  the timeline.
 554:      */
 555:     public void setBaseTimeline(SegmentedTimeline baseTimeline) {
 556: 
 557:         // verify that baseTimeline is compatible with us
 558:         if (baseTimeline != null) {
 559:             if (baseTimeline.getSegmentSize() < this.segmentSize) {
 560:                 throw new IllegalArgumentException(
 561:                         "baseTimeline.getSegmentSize() "
 562:                         + "is smaller than segmentSize");
 563:             }
 564:             else if (baseTimeline.getStartTime() > this.startTime) {
 565:                 throw new IllegalArgumentException(
 566:                         "baseTimeline.getStartTime() is after startTime");
 567:             }
 568:             else if ((baseTimeline.getSegmentSize() % this.segmentSize) != 0) {
 569:                 throw new IllegalArgumentException(
 570:                         "baseTimeline.getSegmentSize() is not multiple of "
 571:                         + "segmentSize");
 572:             }
 573:             else if (((this.startTime
 574:                     - baseTimeline.getStartTime()) % this.segmentSize) != 0) {
 575:                 throw new IllegalArgumentException(
 576:                         "baseTimeline is not aligned");
 577:             }
 578:         }
 579: 
 580:         this.baseTimeline = baseTimeline;
 581:     }
 582: 
 583:     /**
 584:      * Translates a value relative to the domain value (all Dates) into a value
 585:      * relative to the segmented timeline. The values relative to the segmented
 586:      * timeline are all consecutives starting at zero at the startTime.
 587:      *
 588:      * @param millisecond  the millisecond (as encoded by java.util.Date).
 589:      *
 590:      * @return The timeline value.
 591:      */
 592:     public long toTimelineValue(long millisecond) {
 593: 
 594:         long result;
 595:         long rawMilliseconds = millisecond - this.startTime;
 596:         long groupMilliseconds = rawMilliseconds % this.segmentsGroupSize;
 597:         long groupIndex = rawMilliseconds / this.segmentsGroupSize;
 598: 
 599:         if (groupMilliseconds >= this.segmentsIncludedSize) {
 600:             result = toTimelineValue(this.startTime + this.segmentsGroupSize
 601:                     * (groupIndex + 1));
 602:         }
 603:         else {
 604:             Segment segment = getSegment(millisecond);
 605:             if (segment.inExceptionSegments()) {
 606:                 int p;
 607:                 while ((p = binarySearchExceptionSegments(segment)) >= 0) {
 608:                     segment = getSegment(millisecond = ((Segment)
 609:                             this.exceptionSegments.get(p)).getSegmentEnd() + 1);
 610:                 }
 611:                 result = toTimelineValue(millisecond);
 612:             }
 613:             else {
 614:                 long shiftedSegmentedValue = millisecond - this.startTime;
 615:                 long x = shiftedSegmentedValue % this.segmentsGroupSize;
 616:                 long y = shiftedSegmentedValue / this.segmentsGroupSize;
 617: 
 618:                 long wholeExceptionsBeforeDomainValue =
 619:                     getExceptionSegmentCount(this.startTime, millisecond - 1);
 620: 
 621: //                long partialTimeInException = 0;
 622: //                Segment ss = getSegment(millisecond);
 623: //                if (ss.inExceptionSegments()) {
 624: //                    partialTimeInException = millisecond
 625:                 //     - ss.getSegmentStart();
 626: //                }
 627: 
 628:                 if (x < this.segmentsIncludedSize) {
 629:                     result = this.segmentsIncludedSize * y
 630:                              + x - wholeExceptionsBeforeDomainValue
 631:                              * this.segmentSize;
 632:                              // - partialTimeInException;
 633:                 }
 634:                 else {
 635:                     result = this.segmentsIncludedSize * (y + 1)
 636:                              - wholeExceptionsBeforeDomainValue
 637:                              * this.segmentSize;
 638:                              // - partialTimeInException;
 639:                 }
 640:             }
 641:         }
 642: 
 643:         return result;
 644:     }
 645: 
 646:     /**
 647:      * Translates a date into a value relative to the segmented timeline. The
 648:      * values relative to the segmented timeline are all consecutives starting
 649:      * at zero at the startTime.
 650:      *
 651:      * @param date  date relative to the domain.
 652:      *
 653:      * @return The timeline value (in milliseconds).
 654:      */
 655:     public long toTimelineValue(Date date) {
 656:         return toTimelineValue(getTime(date));
 657:         //return toTimelineValue(dateDomainValue.getTime());
 658:     }
 659: 
 660:     /**
 661:      * Translates a value relative to the timeline into a millisecond.
 662:      *
 663:      * @param timelineValue  the timeline value (in milliseconds).
 664:      *
 665:      * @return The domain value (in milliseconds).
 666:      */
 667:     public long toMillisecond(long timelineValue) {
 668: 
 669:         // calculate the result as if no exceptions
 670:         Segment result = new Segment(this.startTime + timelineValue
 671:                 + (timelineValue / this.segmentsIncludedSize)
 672:                 * this.segmentsExcludedSize);
 673: 
 674:         long lastIndex = this.startTime;
 675: 
 676:         // adjust result for any exceptions in the result calculated
 677:         while (lastIndex <= result.segmentStart) {
 678: 
 679:             // skip all whole exception segments in the range
 680:             long exceptionSegmentCount;
 681:             while ((exceptionSegmentCount = getExceptionSegmentCount(
 682:                  lastIndex, (result.millisecond / this.segmentSize)
 683:                  * this.segmentSize - 1)) > 0
 684:             ) {
 685:                 lastIndex = result.segmentStart;
 686:                 // move forward exceptionSegmentCount segments skipping
 687:                 // excluded segments
 688:                 for (int i = 0; i < exceptionSegmentCount; i++) {
 689:                     do {
 690:                         result.inc();
 691:                     }
 692:                     while (result.inExcludeSegments());
 693:                 }
 694:             }
 695:             lastIndex = result.segmentStart;
 696: 
 697:             // skip exception or excluded segments we may fall on
 698:             while (result.inExceptionSegments() || result.inExcludeSegments()) {
 699:                 result.inc();
 700:                 lastIndex += this.segmentSize;
 701:             }
 702: 
 703:             lastIndex++;
 704:         }
 705: 
 706:         return getTimeFromLong(result.millisecond);
 707:     }
 708: 
 709:     /**
 710:      * Converts a date/time value to take account of daylight savings time.
 711:      *
 712:      * @param date  the milliseconds.
 713:      *
 714:      * @return The milliseconds.
 715:      */
 716:     public long getTimeFromLong(long date) {
 717:         long result = date;
 718:         if (this.adjustForDaylightSaving) {
 719:             this.workingCalendarNoDST.setTime(new Date(date));
 720:             this.workingCalendar.set(
 721:                 this.workingCalendarNoDST.get(Calendar.YEAR),
 722:                 this.workingCalendarNoDST.get(Calendar.MONTH),
 723:                 this.workingCalendarNoDST.get(Calendar.DATE),
 724:                 this.workingCalendarNoDST.get(Calendar.HOUR_OF_DAY),
 725:                 this.workingCalendarNoDST.get(Calendar.MINUTE),
 726:                 this.workingCalendarNoDST.get(Calendar.SECOND)
 727:             );
 728:             this.workingCalendar.set(Calendar.MILLISECOND,
 729:                     this.workingCalendarNoDST.get(Calendar.MILLISECOND));
 730:             // result = this.workingCalendar.getTimeInMillis();
 731:             // preceding code won't work with JDK 1.3
 732:             result = this.workingCalendar.getTime().getTime();
 733:         }
 734:         return result;
 735:     }
 736: 
 737:     /**
 738:      * Returns <code>true</code> if a value is contained in the timeline.
 739:      *
 740:      * @param millisecond  the value to verify.
 741:      *
 742:      * @return <code>true</code> if value is contained in the timeline.
 743:      */
 744:     public boolean containsDomainValue(long millisecond) {
 745:         Segment segment = getSegment(millisecond);
 746:         return segment.inIncludeSegments();
 747:     }
 748: 
 749:     /**
 750:      * Returns <code>true</code> if a value is contained in the timeline.
 751:      *
 752:      * @param date  date to verify
 753:      *
 754:      * @return <code>true</code> if value is contained in the timeline
 755:      */
 756:     public boolean containsDomainValue(Date date) {
 757:         return containsDomainValue(getTime(date));
 758:     }
 759: 
 760:     /**
 761:      * Returns <code>true</code> if a range of values are contained in the
 762:      * timeline. This is implemented verifying that all segments are in the
 763:      * range.
 764:      *
 765:      * @param domainValueStart start of the range to verify
 766:      * @param domainValueEnd end of the range to verify
 767:      *
 768:      * @return <code>true</code> if the range is contained in the timeline
 769:      */
 770:     public boolean containsDomainRange(long domainValueStart,
 771:                                        long domainValueEnd) {
 772:         if (domainValueEnd < domainValueStart) {
 773:             throw new IllegalArgumentException(
 774:                     "domainValueEnd (" + domainValueEnd
 775:                     + ") < domainValueStart (" + domainValueStart + ")");
 776:         }
 777:         Segment segment = getSegment(domainValueStart);
 778:         boolean contains = true;
 779:         do {
 780:             contains = (segment.inIncludeSegments());
 781:             if (segment.contains(domainValueEnd)) {
 782:                 break;
 783:             }
 784:             else {
 785:                 segment.inc();
 786:             }
 787:         }
 788:         while (contains);
 789:         return (contains);
 790:     }
 791: 
 792:     /**
 793:      * Returns <code>true</code> if a range of values are contained in the
 794:      * timeline. This is implemented verifying that all segments are in the
 795:      * range.
 796:      *
 797:      * @param dateDomainValueStart start of the range to verify
 798:      * @param dateDomainValueEnd end of the range to verify
 799:      *
 800:      * @return <code>true</code> if the range is contained in the timeline
 801:      */
 802:     public boolean containsDomainRange(Date dateDomainValueStart,
 803:                                        Date dateDomainValueEnd) {
 804:         return containsDomainRange(getTime(dateDomainValueStart),
 805:                 getTime(dateDomainValueEnd));
 806:     }
 807: 
 808:     /**
 809:      * Adds a segment as an exception. An exception segment is defined as a
 810:      * segment to exclude from what would otherwise be considered a valid
 811:      * segment of the timeline.  An exception segment can not be contained
 812:      * inside an already excluded segment.  If so, no action will occur (the
 813:      * proposed exception segment will be discarded).
 814:      * <p>
 815:      * The segment is identified by a domainValue into any part of the segment.
 816:      * Therefore the segmentStart <= domainValue <= segmentEnd.
 817:      *
 818:      * @param millisecond  domain value to treat as an exception
 819:      */
 820:     public void addException(long millisecond) {
 821:         addException(new Segment(millisecond));
 822:     }
 823: 
 824:     /**
 825:      * Adds a segment range as an exception. An exception segment is defined as
 826:      * a segment to exclude from what would otherwise be considered a valid
 827:      * segment of the timeline.  An exception segment can not be contained
 828:      * inside an already excluded segment.  If so, no action will occur (the
 829:      * proposed exception segment will be discarded).
 830:      * <p>
 831:      * The segment range is identified by a domainValue that begins a valid
 832:      * segment and ends with a domainValue that ends a valid segment.
 833:      * Therefore the range will contain all segments whose segmentStart
 834:      * <= domainValue and segmentEnd <= toDomainValue.
 835:      *
 836:      * @param fromDomainValue  start of domain range to treat as an exception
 837:      * @param toDomainValue  end of domain range to treat as an exception
 838:      */
 839:     public void addException(long fromDomainValue, long toDomainValue) {
 840:         addException(new SegmentRange(fromDomainValue, toDomainValue));
 841:     }
 842: 
 843:     /**
 844:      * Adds a segment as an exception. An exception segment is defined as a
 845:      * segment to exclude from what would otherwise be considered a valid
 846:      * segment of the timeline.  An exception segment can not be contained
 847:      * inside an already excluded segment.  If so, no action will occur (the
 848:      * proposed exception segment will be discarded).
 849:      * <p>
 850:      * The segment is identified by a Date into any part of the segment.
 851:      *
 852:      * @param exceptionDate  Date into the segment to exclude.
 853:      */
 854:     public void addException(Date exceptionDate) {
 855:         addException(getTime(exceptionDate));
 856:         //addException(exceptionDate.getTime());
 857:     }
 858: 
 859:     /**
 860:      * Adds a list of dates as segment exceptions. Each exception segment is
 861:      * defined as a segment to exclude from what would otherwise be considered
 862:      * a valid segment of the timeline.  An exception segment can not be
 863:      * contained inside an already excluded segment.  If so, no action will
 864:      * occur (the proposed exception segment will be discarded).
 865:      * <p>
 866:      * The segment is identified by a Date into any part of the segment.
 867:      *
 868:      * @param exceptionList  List of Date objects that identify the segments to
 869:      *                       exclude.
 870:      */
 871:     public void addExceptions(List exceptionList) {
 872:         for (Iterator iter = exceptionList.iterator(); iter.hasNext();) {
 873:             addException((Date) iter.next());
 874:         }
 875:     }
 876: 
 877:     /**
 878:      * Adds a segment as an exception. An exception segment is defined as a
 879:      * segment to exclude from what would otherwise be considered a valid
 880:      * segment of the timeline.  An exception segment can not be contained
 881:      * inside an already excluded segment.  This is verified inside this
 882:      * method, and if so, no action will occur (the proposed exception segment
 883:      * will be discarded).
 884:      *
 885:      * @param segment  the segment to exclude.
 886:      */
 887:     private void addException(Segment segment) {
 888:          if (segment.inIncludeSegments()) {
 889:              int p = binarySearchExceptionSegments(segment);
 890:              this.exceptionSegments.add(-(p + 1), segment);
 891:          }
 892:     }
 893: 
 894:     /**
 895:      * Adds a segment relative to the baseTimeline as an exception. Because a
 896:      * base segment is normally larger than our segments, this may add one or
 897:      * more segment ranges to the exception list.
 898:      * <p>
 899:      * An exception segment is defined as a segment
 900:      * to exclude from what would otherwise be considered a valid segment of
 901:      * the timeline.  An exception segment can not be contained inside an
 902:      * already excluded segment.  If so, no action will occur (the proposed
 903:      * exception segment will be discarded).
 904:      * <p>
 905:      * The segment is identified by a domainValue into any part of the
 906:      * baseTimeline segment.
 907:      *
 908:      * @param domainValue  domain value to teat as a baseTimeline exception.
 909:      */
 910:     public void addBaseTimelineException(long domainValue) {
 911: 
 912:         Segment baseSegment = this.baseTimeline.getSegment(domainValue);
 913:         if (baseSegment.inIncludeSegments()) {
 914: 
 915:             // cycle through all the segments contained in the BaseTimeline
 916:             // exception segment
 917:             Segment segment = getSegment(baseSegment.getSegmentStart());
 918:             while (segment.getSegmentStart() <= baseSegment.getSegmentEnd()) {
 919:                 if (segment.inIncludeSegments()) {
 920: 
 921:                     // find all consecutive included segments
 922:                     long fromDomainValue = segment.getSegmentStart();
 923:                     long toDomainValue;
 924:                     do {
 925:                         toDomainValue = segment.getSegmentEnd();
 926:                         segment.inc();
 927:                     }
 928:                     while (segment.inIncludeSegments());
 929: 
 930:                     // add the interval as an exception
 931:                     addException(fromDomainValue, toDomainValue);
 932: 
 933:                 }
 934:                 else {
 935:                     // this is not one of our included segment, skip it
 936:                     segment.inc();
 937:                 }
 938:             }
 939:         }
 940:     }
 941: 
 942:     /**
 943:      * Adds a segment relative to the baseTimeline as an exception. An
 944:      * exception segment is defined as a segment to exclude from what would
 945:      * otherwise be considered a valid segment of the timeline.  An exception
 946:      * segment can not be contained inside an already excluded segment. If so,
 947:      * no action will occure (the proposed exception segment will be discarded).
 948:      * <p>
 949:      * The segment is identified by a domainValue into any part of the segment.
 950:      * Therefore the segmentStart <= domainValue <= segmentEnd.
 951:      *
 952:      * @param date  date domain value to treat as a baseTimeline exception
 953:      */
 954:     public void addBaseTimelineException(Date date) {
 955:         addBaseTimelineException(getTime(date));
 956:     }
 957: 
 958:     /**
 959:      * Adds all excluded segments from the BaseTimeline as exceptions to our
 960:      * timeline. This allows us to combine two timelines for more complex
 961:      * calculations.
 962:      *
 963:      * @param fromBaseDomainValue Start of the range where exclusions will be
 964:      *                            extracted.
 965:      * @param toBaseDomainValue End of the range to process.
 966:      */
 967:     public void addBaseTimelineExclusions(long fromBaseDomainValue,
 968:                                           long toBaseDomainValue) {
 969: 
 970:         // find first excluded base segment starting fromDomainValue
 971:         Segment baseSegment = this.baseTimeline.getSegment(fromBaseDomainValue);
 972:         while (baseSegment.getSegmentStart() <= toBaseDomainValue
 973:                && !baseSegment.inExcludeSegments()) {
 974: 
 975:             baseSegment.inc();
 976: 
 977:         }
 978: 
 979:         // cycle over all the base segments groups in the range
 980:         while (baseSegment.getSegmentStart() <= toBaseDomainValue) {
 981: 
 982:             long baseExclusionRangeEnd = baseSegment.getSegmentStart()
 983:                      + this.baseTimeline.getSegmentsExcluded()
 984:                      * this.baseTimeline.getSegmentSize() - 1;
 985: 
 986:             // cycle through all the segments contained in the base exclusion
 987:             // area
 988:             Segment segment = getSegment(baseSegment.getSegmentStart());
 989:             while (segment.getSegmentStart() <= baseExclusionRangeEnd) {
 990:                 if (segment.inIncludeSegments()) {
 991: 
 992:                     // find all consecutive included segments
 993:                     long fromDomainValue = segment.getSegmentStart();
 994:                     long toDomainValue;
 995:                     do {
 996:                         toDomainValue = segment.getSegmentEnd();
 997:                         segment.inc();
 998:                     }
 999:                     while (segment.inIncludeSegments());
1000: 
1001:                     // add the interval as an exception
1002:                     addException(new BaseTimelineSegmentRange(
1003:                             fromDomainValue, toDomainValue));
1004:                 }
1005:                 else {
1006:                     // this is not one of our included segment, skip it
1007:                     segment.inc();
1008:                 }
1009:             }
1010: 
1011:             // go to next base segment group
1012:             baseSegment.inc(this.baseTimeline.getGroupSegmentCount());
1013:         }
1014:     }
1015: 
1016:     /**
1017:      * Returns the number of exception segments wholly contained in the
1018:      * (fromDomainValue, toDomainValue) interval.
1019:      *
1020:      * @param fromMillisecond  the beginning of the interval.
1021:      * @param toMillisecond  the end of the interval.
1022:      *
1023:      * @return Number of exception segments contained in the interval.
1024:      */
1025:     public long getExceptionSegmentCount(long fromMillisecond,
1026:                                          long toMillisecond) {
1027:         if (toMillisecond < fromMillisecond) {
1028:             return (0);
1029:         }
1030: 
1031:         int n = 0;
1032:         for (Iterator iter = this.exceptionSegments.iterator();
1033:              iter.hasNext();) {
1034:             Segment segment = (Segment) iter.next();
1035:             Segment intersection = segment.intersect(fromMillisecond,
1036:                     toMillisecond);
1037:             if (intersection != null) {
1038:                 n += intersection.getSegmentCount();
1039:             }
1040:         }
1041: 
1042:         return (n);
1043:     }
1044: 
1045:     /**
1046:      * Returns a segment that contains a domainValue. If the domainValue is
1047:      * not contained in the timeline (because it is not contained in the
1048:      * baseTimeline), a Segment that contains
1049:      * <code>index + segmentSize*m</code> will be returned for the smallest
1050:      * <code>m</code> possible.
1051:      *
1052:      * @param millisecond  index into the segment
1053:      *
1054:      * @return A Segment that contains index, or the next possible Segment.
1055:      */
1056:     public Segment getSegment(long millisecond) {
1057:         return new Segment(millisecond);
1058:     }
1059: 
1060:     /**
1061:      * Returns a segment that contains a date. For accurate calculations,
1062:      * the calendar should use TIME_ZONE for its calculation (or any other
1063:      * similar time zone).
1064:      *
1065:      * If the date is not contained in the timeline (because it is not
1066:      * contained in the baseTimeline), a Segment that contains
1067:      * <code>date + segmentSize*m</code> will be returned for the smallest
1068:      * <code>m</code> possible.
1069:      *
1070:      * @param date date into the segment
1071:      *
1072:      * @return A Segment that contains date, or the next possible Segment.
1073:      */
1074:     public Segment getSegment(Date date) {
1075:         return (getSegment(getTime(date)));
1076:     }
1077: 
1078:     /**
1079:      * Convenient method to test equality in two objects, taking into account
1080:      * nulls.
1081:      *
1082:      * @param o first object to compare
1083:      * @param p second object to compare
1084:      *
1085:      * @return <code>true</code> if both objects are equal or both
1086:      *         <code>null</code>, <code>false</code> otherwise.
1087:      */
1088:     private boolean equals(Object o, Object p) {
1089:         return (o == p || ((o != null) && o.equals(p)));
1090:     }
1091: 
1092:     /**
1093:      * Returns true if we are equal to the parameter
1094:      *
1095:      * @param o Object to verify with us
1096:      *
1097:      * @return <code>true</code> or <code>false</code>
1098:      */
1099:     public boolean equals(Object o) {
1100:         if (o instanceof SegmentedTimeline) {
1101:             SegmentedTimeline other = (SegmentedTimeline) o;
1102: 
1103:             boolean b0 = (this.segmentSize == other.getSegmentSize());
1104:             boolean b1 = (this.segmentsIncluded == other.getSegmentsIncluded());
1105:             boolean b2 = (this.segmentsExcluded == other.getSegmentsExcluded());
1106:             boolean b3 = (this.startTime == other.getStartTime());
1107:             boolean b4 = equals(this.exceptionSegments,
1108:                     other.getExceptionSegments());
1109:             return b0 && b1 && b2 && b3 && b4;
1110:         }
1111:         else {
1112:             return (false);
1113:         }
1114:     }
1115: 
1116:     /**
1117:      * Returns a hash code for this object.
1118:      *
1119:      * @return A hash code.
1120:      */
1121:     public int hashCode() {
1122:         int result = 19;
1123:         result = 37 * result
1124:                  + (int) (this.segmentSize ^ (this.segmentSize >>> 32));
1125:         result = 37 * result + (int) (this.startTime ^ (this.startTime >>> 32));
1126:         return result;
1127:     }
1128: 
1129:     /**
1130:      * Preforms a binary serach in the exceptionSegments sorted array. This
1131:      * array can contain Segments or SegmentRange objects.
1132:      *
1133:      * @param  segment the key to be searched for.
1134:      *
1135:      * @return index of the search segment, if it is contained in the list;
1136:      *         otherwise, <tt>(-(<i>insertion point</i>) - 1)</tt>.  The
1137:      *         <i>insertion point</i> is defined as the point at which the
1138:      *         segment would be inserted into the list: the index of the first
1139:      *         element greater than the key, or <tt>list.size()</tt>, if all
1140:      *         elements in the list are less than the specified segment.  Note
1141:      *         that this guarantees that the return value will be &gt;= 0 if
1142:      *         and only if the key is found.
1143:      */
1144:     private int binarySearchExceptionSegments(Segment segment) {
1145:         int low = 0;
1146:         int high = this.exceptionSegments.size() - 1;
1147: 
1148:         while (low <= high) {
1149:             int mid = (low + high) / 2;
1150:             Segment midSegment = (Segment) this.exceptionSegments.get(mid);
1151: 
1152:             // first test for equality (contains or contained)
1153:             if (segment.contains(midSegment) || midSegment.contains(segment)) {
1154:                 return mid;
1155:             }
1156: 
1157:             if (midSegment.before(segment)) {
1158:                 low = mid + 1;
1159:             }
1160:             else if (midSegment.after(segment)) {
1161:                 high = mid - 1;
1162:             }
1163:             else {
1164:                 throw new IllegalStateException("Invalid condition.");
1165:             }
1166:         }
1167:         return -(low + 1);  // key not found
1168:     }
1169: 
1170:     /**
1171:      * Special method that handles conversion between the Default Time Zone and
1172:      * a UTC time zone with no DST. This is needed so all days have the same
1173:      * size. This method is the prefered way of converting a Data into
1174:      * milliseconds for usage in this class.
1175:      *
1176:      * @param date Date to convert to long.
1177:      *
1178:      * @return The milliseconds.
1179:      */
1180:     public long getTime(Date date) {
1181:         long result = date.getTime();
1182:         if (this.adjustForDaylightSaving) {
1183:             this.workingCalendar.setTime(date);
1184:             this.workingCalendarNoDST.set(
1185:                     this.workingCalendar.get(Calendar.YEAR),
1186:                     this.workingCalendar.get(Calendar.MONTH),
1187:                     this.workingCalendar.get(Calendar.DATE),
1188:                     this.workingCalendar.get(Calendar.HOUR_OF_DAY),
1189:                     this.workingCalendar.get(Calendar.MINUTE),
1190:                     this.workingCalendar.get(Calendar.SECOND));
1191:             this.workingCalendarNoDST.set(Calendar.MILLISECOND,
1192:                     this.workingCalendar.get(Calendar.MILLISECOND));
1193:             Date revisedDate = this.workingCalendarNoDST.getTime();
1194:             result = revisedDate.getTime();
1195:         }
1196: 
1197:         return result;
1198:     }
1199: 
1200:     /**
1201:      * Converts a millisecond value into a {@link Date} object.
1202:      *
1203:      * @param value  the millisecond value.
1204:      *
1205:      * @return The date.
1206:      */
1207:     public Date getDate(long value) {
1208:         this.workingCalendarNoDST.setTime(new Date(value));
1209:         return (this.workingCalendarNoDST.getTime());
1210:     }
1211: 
1212:     /**
1213:      * Returns a clone of the timeline.
1214:      *
1215:      * @return A clone.
1216:      *
1217:      * @throws CloneNotSupportedException ??.
1218:      */
1219:     public Object clone() throws CloneNotSupportedException {
1220:         SegmentedTimeline clone = (SegmentedTimeline) super.clone();
1221:         return clone;
1222:     }
1223: 
1224:     /**
1225:      * Internal class to represent a valid segment for this timeline. A segment
1226:      * is valid on a timeline if it is part of its included, excluded or
1227:      * exception segments.
1228:      * <p>
1229:      * Each segment will know its segment number, segmentStart, segmentEnd and
1230:      * index inside the segment.
1231:      */
1232:     public class Segment implements Comparable, Cloneable, Serializable {
1233: 
1234:         /** The segment number. */
1235:         protected long segmentNumber;
1236: 
1237:         /** The segment start. */
1238:         protected long segmentStart;
1239: 
1240:         /** The segment end. */
1241:         protected long segmentEnd;
1242: 
1243:         /** A reference point within the segment. */
1244:         protected long millisecond;
1245: 
1246:         /**
1247:          * Protected constructor only used by sub-classes.
1248:          */
1249:         protected Segment() {
1250:             // empty
1251:         }
1252: 
1253:         /**
1254:          * Creates a segment for a given point in time.
1255:          *
1256:          * @param millisecond  the millisecond (as encoded by java.util.Date).
1257:          */
1258:         protected Segment(long millisecond) {
1259:             this.segmentNumber = calculateSegmentNumber(millisecond);
1260:             this.segmentStart = SegmentedTimeline.this.startTime
1261:                 + this.segmentNumber * SegmentedTimeline.this.segmentSize;
1262:             this.segmentEnd
1263:                 = this.segmentStart + SegmentedTimeline.this.segmentSize - 1;
1264:             this.millisecond = millisecond;
1265:         }
1266: 
1267:         /**
1268:          * Calculates the segment number for a given millisecond.
1269:          *
1270:          * @param millis  the millisecond (as encoded by java.util.Date).
1271:          *
1272:          * @return The segment number.
1273:          */
1274:         public long calculateSegmentNumber(long millis) {
1275:             if (millis >= SegmentedTimeline.this.startTime) {
1276:                 return (millis - SegmentedTimeline.this.startTime)
1277:                     / SegmentedTimeline.this.segmentSize;
1278:             }
1279:             else {
1280:                 return ((millis - SegmentedTimeline.this.startTime)
1281:                     / SegmentedTimeline.this.segmentSize) - 1;
1282:             }
1283:         }
1284: 
1285:         /**
1286:          * Returns the segment number of this segment. Segments start at 0.
1287:          *
1288:          * @return The segment number.
1289:          */
1290:         public long getSegmentNumber() {
1291:             return this.segmentNumber;
1292:         }
1293: 
1294:         /**
1295:          * Returns always one (the number of segments contained in this
1296:          * segment).
1297:          *
1298:          * @return The segment count (always 1 for this class).
1299:          */
1300:         public long getSegmentCount() {
1301:             return 1;
1302:         }
1303: 
1304:         /**
1305:          * Gets the start of this segment in ms.
1306:          *
1307:          * @return The segment start.
1308:          */
1309:         public long getSegmentStart() {
1310:             return this.segmentStart;
1311:         }
1312: 
1313:         /**
1314:          * Gets the end of this segment in ms.
1315:          *
1316:          * @return The segment end.
1317:          */
1318:         public long getSegmentEnd() {
1319:             return this.segmentEnd;
1320:         }
1321: 
1322:         /**
1323:          * Returns the millisecond used to reference this segment (always
1324:          * between the segmentStart and segmentEnd).
1325:          *
1326:          * @return The millisecond.
1327:          */
1328:         public long getMillisecond() {
1329:             return this.millisecond;
1330:         }
1331: 
1332:         /**
1333:          * Returns a {@link java.util.Date} that represents the reference point
1334:          * for this segment.
1335:          *
1336:          * @return The date.
1337:          */
1338:         public Date getDate() {
1339:             return SegmentedTimeline.this.getDate(this.millisecond);
1340:         }
1341: 
1342:         /**
1343:          * Returns true if a particular millisecond is contained in this
1344:          * segment.
1345:          *
1346:          * @param millis  the millisecond to verify.
1347:          *
1348:          * @return <code>true</code> if the millisecond is contained in the
1349:          *         segment.
1350:          */
1351:         public boolean contains(long millis) {
1352:             return (this.segmentStart <= millis && millis <= this.segmentEnd);
1353:         }
1354: 
1355:         /**
1356:          * Returns <code>true</code> if an interval is contained in this
1357:          * segment.
1358:          *
1359:          * @param from  the start of the interval.
1360:          * @param to  the end of the interval.
1361:          *
1362:          * @return <code>true</code> if the interval is contained in the
1363:          *         segment.
1364:          */
1365:         public boolean contains(long from, long to) {
1366:             return (this.segmentStart <= from && to <= this.segmentEnd);
1367:         }
1368: 
1369:         /**
1370:          * Returns <code>true</code> if a segment is contained in this segment.
1371:          *
1372:          * @param segment  the segment to test for inclusion
1373:          *
1374:          * @return <code>true</code> if the segment is contained in this
1375:          *         segment.
1376:          */
1377:         public boolean contains(Segment segment) {
1378:             return contains(segment.getSegmentStart(), segment.getSegmentEnd());
1379:         }
1380: 
1381:         /**
1382:          * Returns <code>true</code> if this segment is contained in an
1383:          * interval.
1384:          *
1385:          * @param from  the start of the interval.
1386:          * @param to  the end of the interval.
1387:          *
1388:          * @return <code>true</code> if this segment is contained in the
1389:          *         interval.
1390:          */
1391:         public boolean contained(long from, long to) {
1392:             return (from <= this.segmentStart && this.segmentEnd <= to);
1393:         }
1394: 
1395:         /**
1396:          * Returns a segment that is the intersection of this segment and the
1397:          * interval.
1398:          *
1399:          * @param from  the start of the interval.
1400:          * @param to  the end of the interval.
1401:          *
1402:          * @return A segment.
1403:          */
1404:         public Segment intersect(long from, long to) {
1405:             if (from <= this.segmentStart && this.segmentEnd <= to) {
1406:                 return this;
1407:             }
1408:             else {
1409:                 return null;
1410:             }
1411:         }
1412: 
1413:         /**
1414:          * Returns <code>true</code> if this segment is wholly before another
1415:          * segment.
1416:          *
1417:          * @param other  the other segment.
1418:          *
1419:          * @return A boolean.
1420:          */
1421:         public boolean before(Segment other) {
1422:             return (this.segmentEnd < other.getSegmentStart());
1423:         }
1424: 
1425:         /**
1426:          * Returns <code>true</code> if this segment is wholly after another
1427:          * segment.
1428:          *
1429:          * @param other  the other segment.
1430:          *
1431:          * @return A boolean.
1432:          */
1433:         public boolean after(Segment other) {
1434:             return (this.segmentStart > other.getSegmentEnd());
1435:         }
1436: 
1437:         /**
1438:          * Tests an object (usually another <code>Segment</code>) for equality
1439:          * with this segment.
1440:          *
1441:          * @param object The other segment to compare with us
1442:          *
1443:          * @return <code>true</code> if we are the same segment
1444:          */
1445:         public boolean equals(Object object) {
1446:             if (object instanceof Segment) {
1447:                 Segment other = (Segment) object;
1448:                 return (this.segmentNumber == other.getSegmentNumber()
1449:                         && this.segmentStart == other.getSegmentStart()
1450:                         && this.segmentEnd == other.getSegmentEnd()
1451:                         && this.millisecond == other.getMillisecond());
1452:             }
1453:             else {
1454:                 return false;
1455:             }
1456:         }
1457: 
1458:         /**
1459:          * Returns a copy of ourselves or <code>null</code> if there was an
1460:          * exception during cloning.
1461:          *
1462:          * @return A copy of this segment.
1463:          */
1464:         public Segment copy() {
1465:             try {
1466:                 return (Segment) this.clone();
1467:             }
1468:             catch (CloneNotSupportedException e) {
1469:                 return null;
1470:             }
1471:         }
1472: 
1473:         /**
1474:          * Will compare this Segment with another Segment (from Comparable
1475:          * interface).
1476:          *
1477:          * @param object The other Segment to compare with
1478:          *
1479:          * @return -1: this < object, 0: this.equal(object) and
1480:          *         +1: this > object
1481:          */
1482:         public int compareTo(Object object) {
1483:             Segment other = (Segment) object;
1484:             if (this.before(other)) {
1485:                 return -1;
1486:             }
1487:             else if (this.after(other)) {
1488:                 return +1;
1489:             }
1490:             else {
1491:                 return 0;
1492:             }
1493:         }
1494: 
1495:         /**
1496:          * Returns true if we are an included segment and we are not an
1497:          * exception.
1498:          *
1499:          * @return <code>true</code> or <code>false</code>.
1500:          */
1501:         public boolean inIncludeSegments() {
1502:             if (getSegmentNumberRelativeToGroup()
1503:                     < SegmentedTimeline.this.segmentsIncluded) {
1504:                 return !inExceptionSegments();
1505:             }
1506:             else {
1507:                 return false;
1508:             }
1509:         }
1510: 
1511:         /**
1512:          * Returns true if we are an excluded segment.
1513:          *
1514:          * @return <code>true</code> or <code>false</code>.
1515:          */
1516:         public boolean inExcludeSegments() {
1517:             return getSegmentNumberRelativeToGroup()
1518:                     >= SegmentedTimeline.this.segmentsIncluded;
1519:         }
1520: 
1521:         /**
1522:          * Calculate the segment number relative to the segment group. This
1523:          * will be a number between 0 and segmentsGroup-1. This value is
1524:          * calculated from the segmentNumber. Special care is taken for
1525:          * negative segmentNumbers.
1526:          *
1527:          * @return The segment number.
1528:          */
1529:         private long getSegmentNumberRelativeToGroup() {
1530:             long p = (this.segmentNumber
1531:                     % SegmentedTimeline.this.groupSegmentCount);
1532:             if (p < 0) {
1533:                 p += SegmentedTimeline.this.groupSegmentCount;
1534:             }
1535:             return p;
1536:         }
1537: 
1538:         /**
1539:          * Returns true if we are an exception segment. This is implemented via
1540:          * a binary search on the exceptionSegments sorted list.
1541:          *
1542:          * If the segment is not listed as an exception in our list and we have
1543:          * a baseTimeline, a check is performed to see if the segment is inside
1544:          * an excluded segment from our base. If so, it is also considered an
1545:          * exception.
1546:          *
1547:          * @return <code>true</code> if we are an exception segment.
1548:          */
1549:         public boolean inExceptionSegments() {
1550:             return binarySearchExceptionSegments(this) >= 0;
1551:         }
1552: 
1553:         /**
1554:          * Increments the internal attributes of this segment by a number of
1555:          * segments.
1556:          *
1557:          * @param n Number of segments to increment.
1558:          */
1559:         public void inc(long n) {
1560:             this.segmentNumber += n;
1561:             long m = n * SegmentedTimeline.this.segmentSize;
1562:             this.segmentStart += m;
1563:             this.segmentEnd += m;
1564:             this.millisecond += m;
1565:         }
1566: 
1567:         /**
1568:          * Increments the internal attributes of this segment by one segment.
1569:          * The exact time incremented is segmentSize.
1570:          */
1571:         public void inc() {
1572:             inc(1);
1573:         }
1574: 
1575:         /**
1576:          * Decrements the internal attributes of this segment by a number of
1577:          * segments.
1578:          *
1579:          * @param n Number of segments to decrement.
1580:          */
1581:         public void dec(long n) {
1582:             this.segmentNumber -= n;
1583:             long m = n * SegmentedTimeline.this.segmentSize;
1584:             this.segmentStart -= m;
1585:             this.segmentEnd -= m;
1586:             this.millisecond -= m;
1587:         }
1588: 
1589:         /**
1590:          * Decrements the internal attributes of this segment by one segment.
1591:          * The exact time decremented is segmentSize.
1592:          */
1593:         public void dec() {
1594:             dec(1);
1595:         }
1596: 
1597:         /**
1598:          * Moves the index of this segment to the beginning if the segment.
1599:          */
1600:         public void moveIndexToStart() {
1601:             this.millisecond = this.segmentStart;
1602:         }
1603: 
1604:         /**
1605:          * Moves the index of this segment to the end of the segment.
1606:          */
1607:         public void moveIndexToEnd() {
1608:             this.millisecond = this.segmentEnd;
1609:         }
1610: 
1611:     }
1612: 
1613:     /**
1614:      * Private internal class to represent a range of segments. This class is
1615:      * mainly used to store in one object a range of exception segments. This
1616:      * optimizes certain timelines that use a small segment size (like an
1617:      * intraday timeline) allowing them to express a day exception as one
1618:      * SegmentRange instead of multi Segments.
1619:      */
1620:     protected class SegmentRange extends Segment {
1621: 
1622:         /** The number of segments in the range. */
1623:         private long segmentCount;
1624: 
1625:         /**
1626:          * Creates a SegmentRange between a start and end domain values.
1627:          *
1628:          * @param fromMillisecond  start of the range
1629:          * @param toMillisecond  end of the range
1630:          */
1631:         public SegmentRange(long fromMillisecond, long toMillisecond) {
1632: 
1633:             Segment start = getSegment(fromMillisecond);
1634:             Segment end = getSegment(toMillisecond);
1635: //            if (start.getSegmentStart() != fromMillisecond
1636: //                || end.getSegmentEnd() != toMillisecond) {
1637: //                throw new IllegalArgumentException("Invalid Segment Range ["
1638: //                    + fromMillisecond + "," + toMillisecond + "]");
1639: //            }
1640: 
1641:             this.millisecond = fromMillisecond;
1642:             this.segmentNumber = calculateSegmentNumber(fromMillisecond);
1643:             this.segmentStart = start.segmentStart;
1644:             this.segmentEnd = end.segmentEnd;
1645:             this.segmentCount
1646:                 = (end.getSegmentNumber() - start.getSegmentNumber() + 1);
1647:         }
1648: 
1649:         /**
1650:          * Returns the number of segments contained in this range.
1651:          *
1652:          * @return The segment count.
1653:          */
1654:         public long getSegmentCount() {
1655:             return this.segmentCount;
1656:         }
1657: 
1658:         /**
1659:          * Returns a segment that is the intersection of this segment and the
1660:          * interval.
1661:          *
1662:          * @param from  the start of the interval.
1663:          * @param to  the end of the interval.
1664:          *
1665:          * @return The intersection.
1666:          */
1667:         public Segment intersect(long from, long to) {
1668: 
1669:             // Segment fromSegment = getSegment(from);
1670:             // fromSegment.inc();
1671:             // Segment toSegment = getSegment(to);
1672:             // toSegment.dec();
1673:             long start = Math.max(from, this.segmentStart);
1674:             long end = Math.min(to, this.segmentEnd);
1675:             // long start = Math.max(
1676:             //     fromSegment.getSegmentStart(), this.segmentStart
1677:             // );
1678:             // long end = Math.min(toSegment.getSegmentEnd(), this.segmentEnd);
1679:             if (start <= end) {
1680:                 return new SegmentRange(start, end);
1681:             }
1682:             else {
1683:                 return null;
1684:             }
1685:         }
1686: 
1687:         /**
1688:          * Returns true if all Segments of this SegmentRenge are an included
1689:          * segment and are not an exception.
1690:          *
1691:          * @return <code>true</code> or </code>false</code>.
1692:          */
1693:         public boolean inIncludeSegments() {
1694:             for (Segment segment = getSegment(this.segmentStart);
1695:                 segment.getSegmentStart() < this.segmentEnd;
1696:                 segment.inc()) {
1697:                 if (!segment.inIncludeSegments()) {
1698:                     return (false);
1699:                 }
1700:             }
1701:             return true;
1702:         }
1703: 
1704:         /**
1705:          * Returns true if we are an excluded segment.
1706:          *
1707:          * @return <code>true</code> or </code>false</code>.
1708:          */
1709:         public boolean inExcludeSegments() {
1710:             for (Segment segment = getSegment(this.segmentStart);
1711:                 segment.getSegmentStart() < this.segmentEnd;
1712:                 segment.inc()) {
1713:                 if (!segment.inExceptionSegments()) {
1714:                     return (false);
1715:                 }
1716:             }
1717:             return true;
1718:         }
1719: 
1720:         /**
1721:          * Not implemented for SegmentRange. Always throws
1722:          * IllegalArgumentException.
1723:          *
1724:          * @param n Number of segments to increment.
1725:          */
1726:         public void inc(long n) {
1727:             throw new IllegalArgumentException(
1728:                     "Not implemented in SegmentRange");
1729:         }
1730: 
1731:     }
1732: 
1733:     /**
1734:      * Special <code>SegmentRange</code> that came from the BaseTimeline.
1735:      */
1736:     protected class BaseTimelineSegmentRange extends SegmentRange {
1737: 
1738:         /**
1739:          * Constructor.
1740:          *
1741:          * @param fromDomainValue  the start value.
1742:          * @param toDomainValue  the end value.
1743:          */
1744:         public BaseTimelineSegmentRange(long fromDomainValue,
1745:                                         long toDomainValue) {
1746:             super(fromDomainValue, toDomainValue);
1747:         }
1748: 
1749:     }
1750: 
1751: }