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