001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ----------------------- 028 * SegmentedTimeline.java 029 * ----------------------- 030 * (C) Copyright 2003-2007, by Bill Kelemen and Contributors. 031 * 032 * Original Author: Bill Kelemen; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * 035 * Changes 036 * ------- 037 * 23-May-2003 : Version 1 (BK); 038 * 15-Aug-2003 : Implemented Cloneable (DG); 039 * 01-Jun-2004 : Modified to compile with JDK 1.2.2 (DG); 040 * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG); 041 * 04-Nov-2004 : Reverted change of 30-Sep-2004, won't work with JDK 1.3 (DG); 042 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG); 043 * ------------- JFREECHART 1.0.x --------------------------------------------- 044 * 14-Nov-2006 : Fix in toTimelineValue(long) to avoid stack overflow (DG); 045 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG); 046 * 11-Jul-2007 : Fixed time zone bugs (DG); 047 * 048 */ 049 050 package org.jfree.chart.axis; 051 052 import java.io.Serializable; 053 import java.util.ArrayList; 054 import java.util.Calendar; 055 import java.util.Collections; 056 import java.util.Date; 057 import java.util.GregorianCalendar; 058 import java.util.Iterator; 059 import java.util.List; 060 import java.util.Locale; 061 import java.util.SimpleTimeZone; 062 import java.util.TimeZone; 063 064 /** 065 * A {@link Timeline} that implements a "segmented" timeline with included, 066 * excluded and exception segments. 067 * <P> 068 * A Timeline will present a series of values to be used for an axis. Each 069 * Timeline must provide transformation methods between domain values and 070 * timeline values. 071 * <P> 072 * A timeline can be used as parameter to a 073 * {@link org.jfree.chart.axis.DateAxis} to define the values that this axis 074 * supports. This class implements a timeline formed by segments of equal 075 * length (ex. days, hours, minutes) where some segments can be included in the 076 * timeline and others excluded. Therefore timelines like "working days" or 077 * "working hours" can be created where non-working days or non-working hours 078 * respectively can be removed from the timeline, and therefore from the axis. 079 * This creates a smooth plot with equal separation between all included 080 * segments. 081 * <P> 082 * Because Timelines were created mainly for Date related axis, values are 083 * represented as longs instead of doubles. In this case, the domain value is 084 * just the number of milliseconds since January 1, 1970, 00:00:00 GMT as 085 * defined by the getTime() method of {@link java.util.Date}. 086 * <P> 087 * In this class, a segment is defined as a unit of time of fixed length. 088 * Examples of segments are: days, hours, minutes, etc. The size of a segment 089 * is defined as the number of milliseconds in the segment. Some useful segment 090 * sizes are defined as constants in this class: DAY_SEGMENT_SIZE, 091 * HOUR_SEGMENT_SIZE, FIFTEEN_MINUTE_SEGMENT_SIZE and MINUTE_SEGMENT_SIZE. 092 * <P> 093 * Segments are group together to form a Segment Group. Each Segment Group will 094 * contain a number of Segments included and a number of Segments excluded. This 095 * Segment Group structure will repeat for the whole timeline. 096 * <P> 097 * For example, a working days SegmentedTimeline would be formed by a group of 098 * 7 daily segments, where there are 5 included (Monday through Friday) and 2 099 * 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 * <space> = 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 >= 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 }