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