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