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