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