1 /* =========================================================== 2 * JFreeChart : a free chart library for the Java(tm) platform 3 * =========================================================== 4 * 5 * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors. 6 * 7 * Project Info: http://www.jfree.org/jfreechart/index.html 8 * 9 * This library is free software; you can redistribute it and/or modify it 10 * under the terms of the GNU Lesser General Public License as published by 11 * the Free Software Foundation; either version 2.1 of the License, or 12 * (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, but 15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17 * License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this library; if not, write to the Free Software 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22 * USA. 23 * 24 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 25 * Other names may be trademarks of their respective owners.] 26 * 27 * ----------------------- 28 * SegmentedTimeline.java 29 * ----------------------- 30 * (C) Copyright 2003-2008, by Bill Kelemen and Contributors. 31 * 32 * Original Author: Bill Kelemen; 33 * Contributor(s): David Gilbert (for Object Refinery Limited); 34 * 35 * Changes 36 * ------- 37 * 23-May-2003 : Version 1 (BK); 38 * 15-Aug-2003 : Implemented Cloneable (DG); 39 * 01-Jun-2004 : Modified to compile with JDK 1.2.2 (DG); 40 * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG); 41 * 04-Nov-2004 : Reverted change of 30-Sep-2004, won't work with JDK 1.3 (DG); 42 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG); 43 * ------------- JFREECHART 1.0.x --------------------------------------------- 44 * 14-Nov-2006 : Fix in toTimelineValue(long) to avoid stack overflow (DG); 45 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG); 46 * 11-Jul-2007 : Fixed time zone bugs (DG); 47 * 06-Jun-2008 : Performance enhancement posted in forum (DG); 48 * 49 */ 50 51 package org.jfree.chart.axis; 52 53 import java.io.Serializable; 54 import java.util.ArrayList; 55 import java.util.Calendar; 56 import java.util.Collections; 57 import java.util.Date; 58 import java.util.GregorianCalendar; 59 import java.util.Iterator; 60 import java.util.List; 61 import java.util.Locale; 62 import java.util.SimpleTimeZone; 63 import java.util.TimeZone; 64 65 /** 66 * A {@link Timeline} that implements a "segmented" timeline with included, 67 * excluded and exception segments. 68 * <P> 69 * A Timeline will present a series of values to be used for an axis. Each 70 * Timeline must provide transformation methods between domain values and 71 * timeline values. 72 * <P> 73 * A timeline can be used as parameter to a 74 * {@link org.jfree.chart.axis.DateAxis} to define the values that this axis 75 * supports. This class implements a timeline formed by segments of equal 76 * length (ex. days, hours, minutes) where some segments can be included in the 77 * timeline and others excluded. Therefore timelines like "working days" or 78 * "working hours" can be created where non-working days or non-working hours 79 * respectively can be removed from the timeline, and therefore from the axis. 80 * This creates a smooth plot with equal separation between all included 81 * segments. 82 * <P> 83 * Because Timelines were created mainly for Date related axis, values are 84 * represented as longs instead of doubles. In this case, the domain value is 85 * just the number of milliseconds since January 1, 1970, 00:00:00 GMT as 86 * defined by the getTime() method of {@link java.util.Date}. 87 * <P> 88 * In this class, a segment is defined as a unit of time of fixed length. 89 * Examples of segments are: days, hours, minutes, etc. The size of a segment 90 * is defined as the number of milliseconds in the segment. Some useful segment 91 * sizes are defined as constants in this class: DAY_SEGMENT_SIZE, 92 * HOUR_SEGMENT_SIZE, FIFTEEN_MINUTE_SEGMENT_SIZE and MINUTE_SEGMENT_SIZE. 93 * <P> 94 * Segments are group together to form a Segment Group. Each Segment Group will 95 * contain a number of Segments included and a number of Segments excluded. This 96 * Segment Group structure will repeat for the whole timeline. 97 * <P> 98 * For example, a working days SegmentedTimeline would be formed by a group of 99 * 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); cal.set(Calendar.MILLISECOND, 0)301 cal.set(Calendar.MILLISECOND, 0); 302 while (cal.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) { cal.add(Calendar.DATE, 1)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 */ SegmentedTimeline(long segmentSize, int segmentsIncluded, int segmentsExcluded)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 */ firstMondayAfter1900()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 */ newMondayThroughFridayTimeline()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 */ newFifteenMinuteTimeline()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 */ getAdjustForDaylightSaving()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 */ setAdjustForDaylightSaving(boolean adjust)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 */ setStartTime(long millisecond)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 */ getStartTime()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 */ getSegmentsExcluded()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 */ getSegmentsExcludedSize()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 */ getGroupSegmentCount()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 */ getSegmentsGroupSize()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 */ getSegmentsIncluded()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 */ getSegmentsIncludedSize()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 */ getSegmentSize()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 */ getExceptionSegments()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 */ setExceptionSegments(List exceptionSegments)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 */ getBaseTimeline()546 public SegmentedTimeline getBaseTimeline() { 547 return this.baseTimeline; 548 } 549 550 /** 551 * Sets the base timeline. 552 * 553 * @param baseTimeline the timeline. 554 */ setBaseTimeline(SegmentedTimeline baseTimeline)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 @Override toTimelineValue(long millisecond)593 public long toTimelineValue(long millisecond) { 594 595 long result; 596 long rawMilliseconds = millisecond - this.startTime; 597 long groupMilliseconds = rawMilliseconds % this.segmentsGroupSize; 598 long groupIndex = rawMilliseconds / this.segmentsGroupSize; 599 600 if (groupMilliseconds >= this.segmentsIncludedSize) { 601 result = toTimelineValue(this.startTime + this.segmentsGroupSize 602 * (groupIndex + 1)); 603 } 604 else { 605 Segment segment = getSegment(millisecond); 606 if (segment.inExceptionSegments()) { 607 int p; 608 while ((p = binarySearchExceptionSegments(segment)) >= 0) { 609 segment = getSegment(millisecond = ((Segment) 610 this.exceptionSegments.get(p)).getSegmentEnd() + 1); 611 } 612 result = toTimelineValue(millisecond); 613 } 614 else { 615 long shiftedSegmentedValue = millisecond - this.startTime; 616 long x = shiftedSegmentedValue % this.segmentsGroupSize; 617 long y = shiftedSegmentedValue / this.segmentsGroupSize; 618 619 long wholeExceptionsBeforeDomainValue = 620 getExceptionSegmentCount(this.startTime, millisecond - 1); 621 622 // long partialTimeInException = 0; 623 // Segment ss = getSegment(millisecond); 624 // if (ss.inExceptionSegments()) { 625 // partialTimeInException = millisecond 626 // - ss.getSegmentStart(); 627 // } 628 629 if (x < this.segmentsIncludedSize) { 630 result = this.segmentsIncludedSize * y 631 + x - wholeExceptionsBeforeDomainValue 632 * this.segmentSize; 633 // - partialTimeInException; 634 } 635 else { 636 result = this.segmentsIncludedSize * (y + 1) 637 - wholeExceptionsBeforeDomainValue 638 * this.segmentSize; 639 // - partialTimeInException; 640 } 641 } 642 } 643 644 return result; 645 } 646 647 /** 648 * Translates a date into a value relative to the segmented timeline. The 649 * values relative to the segmented timeline are all consecutives starting 650 * at zero at the startTime. 651 * 652 * @param date date relative to the domain. 653 * 654 * @return The timeline value (in milliseconds). 655 */ 656 @Override toTimelineValue(Date date)657 public long toTimelineValue(Date date) { 658 return toTimelineValue(getTime(date)); 659 //return toTimelineValue(dateDomainValue.getTime()); 660 } 661 662 /** 663 * Translates a value relative to the timeline into a millisecond. 664 * 665 * @param timelineValue the timeline value (in milliseconds). 666 * 667 * @return The domain value (in milliseconds). 668 */ 669 @Override toMillisecond(long timelineValue)670 public long toMillisecond(long timelineValue) { 671 672 // calculate the result as if no exceptions 673 Segment result = new Segment(this.startTime + timelineValue 674 + (timelineValue / this.segmentsIncludedSize) 675 * this.segmentsExcludedSize); 676 677 long lastIndex = this.startTime; 678 679 // adjust result for any exceptions in the result calculated 680 while (lastIndex <= result.segmentStart) { 681 682 // skip all whole exception segments in the range 683 long exceptionSegmentCount; 684 while ((exceptionSegmentCount = getExceptionSegmentCount( 685 lastIndex, (result.millisecond / this.segmentSize) 686 * this.segmentSize - 1)) > 0 687 ) { 688 lastIndex = result.segmentStart; 689 // move forward exceptionSegmentCount segments skipping 690 // excluded segments 691 for (int i = 0; i < exceptionSegmentCount; i++) { 692 do { 693 result.inc(); 694 } 695 while (result.inExcludeSegments()); 696 } 697 } 698 lastIndex = result.segmentStart; 699 700 // skip exception or excluded segments we may fall on 701 while (result.inExceptionSegments() || result.inExcludeSegments()) { 702 result.inc(); 703 lastIndex += this.segmentSize; 704 } 705 706 lastIndex++; 707 } 708 709 return getTimeFromLong(result.millisecond); 710 } 711 712 /** 713 * Converts a date/time value to take account of daylight savings time. 714 * 715 * @param date the milliseconds. 716 * 717 * @return The milliseconds. 718 */ getTimeFromLong(long date)719 public long getTimeFromLong(long date) { 720 long result = date; 721 if (this.adjustForDaylightSaving) { 722 this.workingCalendarNoDST.setTime(new Date(date)); 723 this.workingCalendar.set( 724 this.workingCalendarNoDST.get(Calendar.YEAR), 725 this.workingCalendarNoDST.get(Calendar.MONTH), 726 this.workingCalendarNoDST.get(Calendar.DATE), 727 this.workingCalendarNoDST.get(Calendar.HOUR_OF_DAY), 728 this.workingCalendarNoDST.get(Calendar.MINUTE), 729 this.workingCalendarNoDST.get(Calendar.SECOND) 730 ); 731 this.workingCalendar.set(Calendar.MILLISECOND, 732 this.workingCalendarNoDST.get(Calendar.MILLISECOND)); 733 // result = this.workingCalendar.getTimeInMillis(); 734 // preceding code won't work with JDK 1.3 735 result = this.workingCalendar.getTime().getTime(); 736 } 737 return result; 738 } 739 740 /** 741 * Returns <code>true</code> if a value is contained in the timeline. 742 * 743 * @param millisecond the value to verify. 744 * 745 * @return <code>true</code> if value is contained in the timeline. 746 */ 747 @Override containsDomainValue(long millisecond)748 public boolean containsDomainValue(long millisecond) { 749 Segment segment = getSegment(millisecond); 750 return segment.inIncludeSegments(); 751 } 752 753 /** 754 * Returns <code>true</code> if a value is contained in the timeline. 755 * 756 * @param date date to verify 757 * 758 * @return <code>true</code> if value is contained in the timeline 759 */ 760 @Override containsDomainValue(Date date)761 public boolean containsDomainValue(Date date) { 762 return containsDomainValue(getTime(date)); 763 } 764 765 /** 766 * Returns <code>true</code> if a range of values are contained in the 767 * timeline. This is implemented verifying that all segments are in the 768 * range. 769 * 770 * @param domainValueStart start of the range to verify 771 * @param domainValueEnd end of the range to verify 772 * 773 * @return <code>true</code> if the range is contained in the timeline 774 */ 775 @Override containsDomainRange(long domainValueStart, long domainValueEnd)776 public boolean containsDomainRange(long domainValueStart, 777 long domainValueEnd) { 778 if (domainValueEnd < domainValueStart) { 779 throw new IllegalArgumentException( 780 "domainValueEnd (" + domainValueEnd 781 + ") < domainValueStart (" + domainValueStart + ")"); 782 } 783 Segment segment = getSegment(domainValueStart); 784 boolean contains = true; 785 do { 786 contains = (segment.inIncludeSegments()); 787 if (segment.contains(domainValueEnd)) { 788 break; 789 } 790 else { 791 segment.inc(); 792 } 793 } 794 while (contains); 795 return (contains); 796 } 797 798 /** 799 * Returns <code>true</code> if a range of values are contained in the 800 * timeline. This is implemented verifying that all segments are in the 801 * range. 802 * 803 * @param dateDomainValueStart start of the range to verify 804 * @param dateDomainValueEnd end of the range to verify 805 * 806 * @return <code>true</code> if the range is contained in the timeline 807 */ 808 @Override containsDomainRange(Date dateDomainValueStart, Date dateDomainValueEnd)809 public boolean containsDomainRange(Date dateDomainValueStart, 810 Date dateDomainValueEnd) { 811 return containsDomainRange(getTime(dateDomainValueStart), 812 getTime(dateDomainValueEnd)); 813 } 814 815 /** 816 * Adds a segment as an exception. An exception segment is defined as a 817 * segment to exclude from what would otherwise be considered a valid 818 * segment of the timeline. An exception segment can not be contained 819 * inside an already excluded segment. If so, no action will occur (the 820 * proposed exception segment will be discarded). 821 * <p> 822 * The segment is identified by a domainValue into any part of the segment. 823 * Therefore the segmentStart <= domainValue <= segmentEnd. 824 * 825 * @param millisecond domain value to treat as an exception 826 */ addException(long millisecond)827 public void addException(long millisecond) { 828 addException(new Segment(millisecond)); 829 } 830 831 /** 832 * Adds a segment range as an exception. An exception segment is defined as 833 * a segment to exclude from what would otherwise be considered a valid 834 * segment of the timeline. An exception segment can not be contained 835 * inside an already excluded segment. If so, no action will occur (the 836 * proposed exception segment will be discarded). 837 * <p> 838 * The segment range is identified by a domainValue that begins a valid 839 * segment and ends with a domainValue that ends a valid segment. 840 * Therefore the range will contain all segments whose segmentStart 841 * <= domainValue and segmentEnd <= toDomainValue. 842 * 843 * @param fromDomainValue start of domain range to treat as an exception 844 * @param toDomainValue end of domain range to treat as an exception 845 */ addException(long fromDomainValue, long toDomainValue)846 public void addException(long fromDomainValue, long toDomainValue) { 847 addException(new SegmentRange(fromDomainValue, toDomainValue)); 848 } 849 850 /** 851 * Adds a segment as an exception. An exception segment is defined as a 852 * segment to exclude from what would otherwise be considered a valid 853 * segment of the timeline. An exception segment can not be contained 854 * inside an already excluded segment. If so, no action will occur (the 855 * proposed exception segment will be discarded). 856 * <p> 857 * The segment is identified by a Date into any part of the segment. 858 * 859 * @param exceptionDate Date into the segment to exclude. 860 */ addException(Date exceptionDate)861 public void addException(Date exceptionDate) { 862 addException(getTime(exceptionDate)); 863 //addException(exceptionDate.getTime()); 864 } 865 866 /** 867 * Adds a list of dates as segment exceptions. Each exception segment is 868 * defined as a segment to exclude from what would otherwise be considered 869 * a valid segment of the timeline. An exception segment can not be 870 * contained inside an already excluded segment. If so, no action will 871 * occur (the proposed exception segment will be discarded). 872 * <p> 873 * The segment is identified by a Date into any part of the segment. 874 * 875 * @param exceptionList List of Date objects that identify the segments to 876 * exclude. 877 */ addExceptions(List exceptionList)878 public void addExceptions(List exceptionList) { 879 for (Iterator iter = exceptionList.iterator(); iter.hasNext();) { 880 addException((Date) iter.next()); 881 } 882 } 883 884 /** 885 * Adds a segment as an exception. An exception segment is defined as a 886 * segment to exclude from what would otherwise be considered a valid 887 * segment of the timeline. An exception segment can not be contained 888 * inside an already excluded segment. This is verified inside this 889 * method, and if so, no action will occur (the proposed exception segment 890 * will be discarded). 891 * 892 * @param segment the segment to exclude. 893 */ addException(Segment segment)894 private void addException(Segment segment) { 895 if (segment.inIncludeSegments()) { 896 int p = binarySearchExceptionSegments(segment); 897 this.exceptionSegments.add(-(p + 1), segment); 898 } 899 } 900 901 /** 902 * Adds a segment relative to the baseTimeline as an exception. Because a 903 * base segment is normally larger than our segments, this may add one or 904 * more segment ranges to the exception list. 905 * <p> 906 * An exception segment is defined as a segment 907 * to exclude from what would otherwise be considered a valid segment of 908 * the timeline. An exception segment can not be contained inside an 909 * already excluded segment. If so, no action will occur (the proposed 910 * exception segment will be discarded). 911 * <p> 912 * The segment is identified by a domainValue into any part of the 913 * baseTimeline segment. 914 * 915 * @param domainValue domain value to teat as a baseTimeline exception. 916 */ addBaseTimelineException(long domainValue)917 public void addBaseTimelineException(long domainValue) { 918 919 Segment baseSegment = this.baseTimeline.getSegment(domainValue); 920 if (baseSegment.inIncludeSegments()) { 921 922 // cycle through all the segments contained in the BaseTimeline 923 // exception segment 924 Segment segment = getSegment(baseSegment.getSegmentStart()); 925 while (segment.getSegmentStart() <= baseSegment.getSegmentEnd()) { 926 if (segment.inIncludeSegments()) { 927 928 // find all consecutive included segments 929 long fromDomainValue = segment.getSegmentStart(); 930 long toDomainValue; 931 do { 932 toDomainValue = segment.getSegmentEnd(); 933 segment.inc(); 934 } 935 while (segment.inIncludeSegments()); 936 937 // add the interval as an exception 938 addException(fromDomainValue, toDomainValue); 939 940 } 941 else { 942 // this is not one of our included segment, skip it 943 segment.inc(); 944 } 945 } 946 } 947 } 948 949 /** 950 * Adds a segment relative to the baseTimeline as an exception. An 951 * exception segment is defined as a segment to exclude from what would 952 * otherwise be considered a valid segment of the timeline. An exception 953 * segment can not be contained inside an already excluded segment. If so, 954 * no action will occure (the proposed exception segment will be discarded). 955 * <p> 956 * The segment is identified by a domainValue into any part of the segment. 957 * Therefore the segmentStart <= domainValue <= segmentEnd. 958 * 959 * @param date date domain value to treat as a baseTimeline exception 960 */ addBaseTimelineException(Date date)961 public void addBaseTimelineException(Date date) { 962 addBaseTimelineException(getTime(date)); 963 } 964 965 /** 966 * Adds all excluded segments from the BaseTimeline as exceptions to our 967 * timeline. This allows us to combine two timelines for more complex 968 * calculations. 969 * 970 * @param fromBaseDomainValue Start of the range where exclusions will be 971 * extracted. 972 * @param toBaseDomainValue End of the range to process. 973 */ addBaseTimelineExclusions(long fromBaseDomainValue, long toBaseDomainValue)974 public void addBaseTimelineExclusions(long fromBaseDomainValue, 975 long toBaseDomainValue) { 976 977 // find first excluded base segment starting fromDomainValue 978 Segment baseSegment = this.baseTimeline.getSegment(fromBaseDomainValue); 979 while (baseSegment.getSegmentStart() <= toBaseDomainValue 980 && !baseSegment.inExcludeSegments()) { 981 982 baseSegment.inc(); 983 984 } 985 986 // cycle over all the base segments groups in the range 987 while (baseSegment.getSegmentStart() <= toBaseDomainValue) { 988 989 long baseExclusionRangeEnd = baseSegment.getSegmentStart() 990 + this.baseTimeline.getSegmentsExcluded() 991 * this.baseTimeline.getSegmentSize() - 1; 992 993 // cycle through all the segments contained in the base exclusion 994 // area 995 Segment segment = getSegment(baseSegment.getSegmentStart()); 996 while (segment.getSegmentStart() <= baseExclusionRangeEnd) { 997 if (segment.inIncludeSegments()) { 998 999 // find all consecutive included segments 1000 long fromDomainValue = segment.getSegmentStart(); 1001 long toDomainValue; 1002 do { 1003 toDomainValue = segment.getSegmentEnd(); 1004 segment.inc(); 1005 } 1006 while (segment.inIncludeSegments()); 1007 1008 // add the interval as an exception 1009 addException(new BaseTimelineSegmentRange( 1010 fromDomainValue, toDomainValue)); 1011 } 1012 else { 1013 // this is not one of our included segment, skip it 1014 segment.inc(); 1015 } 1016 } 1017 1018 // go to next base segment group 1019 baseSegment.inc(this.baseTimeline.getGroupSegmentCount()); 1020 } 1021 } 1022 1023 /** 1024 * Returns the number of exception segments wholly contained in the 1025 * (fromDomainValue, toDomainValue) interval. 1026 * 1027 * @param fromMillisecond the beginning of the interval. 1028 * @param toMillisecond the end of the interval. 1029 * 1030 * @return Number of exception segments contained in the interval. 1031 */ getExceptionSegmentCount(long fromMillisecond, long toMillisecond)1032 public long getExceptionSegmentCount(long fromMillisecond, 1033 long toMillisecond) { 1034 if (toMillisecond < fromMillisecond) { 1035 return (0); 1036 } 1037 1038 int n = 0; 1039 for (Iterator iter = this.exceptionSegments.iterator(); 1040 iter.hasNext();) { 1041 Segment segment = (Segment) iter.next(); 1042 Segment intersection = segment.intersect(fromMillisecond, 1043 toMillisecond); 1044 if (intersection != null) { 1045 n += intersection.getSegmentCount(); 1046 } 1047 } 1048 1049 return (n); 1050 } 1051 1052 /** 1053 * Returns a segment that contains a domainValue. If the domainValue is 1054 * not contained in the timeline (because it is not contained in the 1055 * baseTimeline), a Segment that contains 1056 * <code>index + segmentSize*m</code> will be returned for the smallest 1057 * <code>m</code> possible. 1058 * 1059 * @param millisecond index into the segment 1060 * 1061 * @return A Segment that contains index, or the next possible Segment. 1062 */ getSegment(long millisecond)1063 public Segment getSegment(long millisecond) { 1064 return new Segment(millisecond); 1065 } 1066 1067 /** 1068 * Returns a segment that contains a date. For accurate calculations, 1069 * the calendar should use TIME_ZONE for its calculation (or any other 1070 * similar time zone). 1071 * 1072 * If the date is not contained in the timeline (because it is not 1073 * contained in the baseTimeline), a Segment that contains 1074 * <code>date + segmentSize*m</code> will be returned for the smallest 1075 * <code>m</code> possible. 1076 * 1077 * @param date date into the segment 1078 * 1079 * @return A Segment that contains date, or the next possible Segment. 1080 */ getSegment(Date date)1081 public Segment getSegment(Date date) { 1082 return (getSegment(getTime(date))); 1083 } 1084 1085 /** 1086 * Convenient method to test equality in two objects, taking into account 1087 * nulls. 1088 * 1089 * @param o first object to compare 1090 * @param p second object to compare 1091 * 1092 * @return <code>true</code> if both objects are equal or both 1093 * <code>null</code>, <code>false</code> otherwise. 1094 */ equals(Object o, Object p)1095 private boolean equals(Object o, Object p) { 1096 return (o == p || ((o != null) && o.equals(p))); 1097 } 1098 1099 /** 1100 * Returns true if we are equal to the parameter 1101 * 1102 * @param o Object to verify with us 1103 * 1104 * @return <code>true</code> or <code>false</code> 1105 */ 1106 @Override equals(Object o)1107 public boolean equals(Object o) { 1108 if (o instanceof SegmentedTimeline) { 1109 SegmentedTimeline other = (SegmentedTimeline) o; 1110 1111 boolean b0 = (this.segmentSize == other.getSegmentSize()); 1112 boolean b1 = (this.segmentsIncluded == other.getSegmentsIncluded()); 1113 boolean b2 = (this.segmentsExcluded == other.getSegmentsExcluded()); 1114 boolean b3 = (this.startTime == other.getStartTime()); 1115 boolean b4 = equals(this.exceptionSegments, 1116 other.getExceptionSegments()); 1117 return b0 && b1 && b2 && b3 && b4; 1118 } 1119 else { 1120 return (false); 1121 } 1122 } 1123 1124 /** 1125 * Returns a hash code for this object. 1126 * 1127 * @return A hash code. 1128 */ 1129 @Override hashCode()1130 public int hashCode() { 1131 int result = 19; 1132 result = 37 * result 1133 + (int) (this.segmentSize ^ (this.segmentSize >>> 32)); 1134 result = 37 * result + (int) (this.startTime ^ (this.startTime >>> 32)); 1135 return result; 1136 } 1137 1138 /** 1139 * Preforms a binary serach in the exceptionSegments sorted array. This 1140 * array can contain Segments or SegmentRange objects. 1141 * 1142 * @param segment the key to be searched for. 1143 * 1144 * @return index of the search segment, if it is contained in the list; 1145 * otherwise, <tt>(-(<i>insertion point</i>) - 1)</tt>. The 1146 * <i>insertion point</i> is defined as the point at which the 1147 * segment would be inserted into the list: the index of the first 1148 * element greater than the key, or <tt>list.size()</tt>, if all 1149 * elements in the list are less than the specified segment. Note 1150 * that this guarantees that the return value will be >= 0 if 1151 * and only if the key is found. 1152 */ binarySearchExceptionSegments(Segment segment)1153 private int binarySearchExceptionSegments(Segment segment) { 1154 int low = 0; 1155 int high = this.exceptionSegments.size() - 1; 1156 1157 while (low <= high) { 1158 int mid = (low + high) / 2; 1159 Segment midSegment = (Segment) this.exceptionSegments.get(mid); 1160 1161 // first test for equality (contains or contained) 1162 if (segment.contains(midSegment) || midSegment.contains(segment)) { 1163 return mid; 1164 } 1165 1166 if (midSegment.before(segment)) { 1167 low = mid + 1; 1168 } 1169 else if (midSegment.after(segment)) { 1170 high = mid - 1; 1171 } 1172 else { 1173 throw new IllegalStateException("Invalid condition."); 1174 } 1175 } 1176 return -(low + 1); // key not found 1177 } 1178 1179 /** 1180 * Special method that handles conversion between the Default Time Zone and 1181 * a UTC time zone with no DST. This is needed so all days have the same 1182 * size. This method is the prefered way of converting a Data into 1183 * milliseconds for usage in this class. 1184 * 1185 * @param date Date to convert to long. 1186 * 1187 * @return The milliseconds. 1188 */ getTime(Date date)1189 public long getTime(Date date) { 1190 long result = date.getTime(); 1191 if (this.adjustForDaylightSaving) { 1192 this.workingCalendar.setTime(date); 1193 this.workingCalendarNoDST.set( 1194 this.workingCalendar.get(Calendar.YEAR), 1195 this.workingCalendar.get(Calendar.MONTH), 1196 this.workingCalendar.get(Calendar.DATE), 1197 this.workingCalendar.get(Calendar.HOUR_OF_DAY), 1198 this.workingCalendar.get(Calendar.MINUTE), 1199 this.workingCalendar.get(Calendar.SECOND)); 1200 this.workingCalendarNoDST.set(Calendar.MILLISECOND, 1201 this.workingCalendar.get(Calendar.MILLISECOND)); 1202 Date revisedDate = this.workingCalendarNoDST.getTime(); 1203 result = revisedDate.getTime(); 1204 } 1205 1206 return result; 1207 } 1208 1209 /** 1210 * Converts a millisecond value into a {@link Date} object. 1211 * 1212 * @param value the millisecond value. 1213 * 1214 * @return The date. 1215 */ getDate(long value)1216 public Date getDate(long value) { 1217 this.workingCalendarNoDST.setTime(new Date(value)); 1218 return (this.workingCalendarNoDST.getTime()); 1219 } 1220 1221 /** 1222 * Returns a clone of the timeline. 1223 * 1224 * @return A clone. 1225 * 1226 * @throws CloneNotSupportedException ??. 1227 */ 1228 @Override clone()1229 public Object clone() throws CloneNotSupportedException { 1230 SegmentedTimeline clone = (SegmentedTimeline) super.clone(); 1231 return clone; 1232 } 1233 1234 /** 1235 * Internal class to represent a valid segment for this timeline. A segment 1236 * is valid on a timeline if it is part of its included, excluded or 1237 * exception segments. 1238 * <p> 1239 * Each segment will know its segment number, segmentStart, segmentEnd and 1240 * index inside the segment. 1241 */ 1242 public class Segment implements Comparable, Cloneable, Serializable { 1243 1244 /** The segment number. */ 1245 protected long segmentNumber; 1246 1247 /** The segment start. */ 1248 protected long segmentStart; 1249 1250 /** The segment end. */ 1251 protected long segmentEnd; 1252 1253 /** A reference point within the segment. */ 1254 protected long millisecond; 1255 1256 /** 1257 * Protected constructor only used by sub-classes. 1258 */ Segment()1259 protected Segment() { 1260 // empty 1261 } 1262 1263 /** 1264 * Creates a segment for a given point in time. 1265 * 1266 * @param millisecond the millisecond (as encoded by java.util.Date). 1267 */ Segment(long millisecond)1268 protected Segment(long millisecond) { 1269 this.segmentNumber = calculateSegmentNumber(millisecond); 1270 this.segmentStart = SegmentedTimeline.this.startTime 1271 + this.segmentNumber * SegmentedTimeline.this.segmentSize; 1272 this.segmentEnd 1273 = this.segmentStart + SegmentedTimeline.this.segmentSize - 1; 1274 this.millisecond = millisecond; 1275 } 1276 1277 /** 1278 * Calculates the segment number for a given millisecond. 1279 * 1280 * @param millis the millisecond (as encoded by java.util.Date). 1281 * 1282 * @return The segment number. 1283 */ calculateSegmentNumber(long millis)1284 public long calculateSegmentNumber(long millis) { 1285 if (millis >= SegmentedTimeline.this.startTime) { 1286 return (millis - SegmentedTimeline.this.startTime) 1287 / SegmentedTimeline.this.segmentSize; 1288 } 1289 else { 1290 return ((millis - SegmentedTimeline.this.startTime) 1291 / SegmentedTimeline.this.segmentSize) - 1; 1292 } 1293 } 1294 1295 /** 1296 * Returns the segment number of this segment. Segments start at 0. 1297 * 1298 * @return The segment number. 1299 */ getSegmentNumber()1300 public long getSegmentNumber() { 1301 return this.segmentNumber; 1302 } 1303 1304 /** 1305 * Returns always one (the number of segments contained in this 1306 * segment). 1307 * 1308 * @return The segment count (always 1 for this class). 1309 */ getSegmentCount()1310 public long getSegmentCount() { 1311 return 1; 1312 } 1313 1314 /** 1315 * Gets the start of this segment in ms. 1316 * 1317 * @return The segment start. 1318 */ getSegmentStart()1319 public long getSegmentStart() { 1320 return this.segmentStart; 1321 } 1322 1323 /** 1324 * Gets the end of this segment in ms. 1325 * 1326 * @return The segment end. 1327 */ getSegmentEnd()1328 public long getSegmentEnd() { 1329 return this.segmentEnd; 1330 } 1331 1332 /** 1333 * Returns the millisecond used to reference this segment (always 1334 * between the segmentStart and segmentEnd). 1335 * 1336 * @return The millisecond. 1337 */ getMillisecond()1338 public long getMillisecond() { 1339 return this.millisecond; 1340 } 1341 1342 /** 1343 * Returns a {@link java.util.Date} that represents the reference point 1344 * for this segment. 1345 * 1346 * @return The date. 1347 */ getDate()1348 public Date getDate() { 1349 return SegmentedTimeline.this.getDate(this.millisecond); 1350 } 1351 1352 /** 1353 * Returns true if a particular millisecond is contained in this 1354 * segment. 1355 * 1356 * @param millis the millisecond to verify. 1357 * 1358 * @return <code>true</code> if the millisecond is contained in the 1359 * segment. 1360 */ contains(long millis)1361 public boolean contains(long millis) { 1362 return (this.segmentStart <= millis && millis <= this.segmentEnd); 1363 } 1364 1365 /** 1366 * Returns <code>true</code> if an interval is contained in this 1367 * segment. 1368 * 1369 * @param from the start of the interval. 1370 * @param to the end of the interval. 1371 * 1372 * @return <code>true</code> if the interval is contained in the 1373 * segment. 1374 */ contains(long from, long to)1375 public boolean contains(long from, long to) { 1376 return (this.segmentStart <= from && to <= this.segmentEnd); 1377 } 1378 1379 /** 1380 * Returns <code>true</code> if a segment is contained in this segment. 1381 * 1382 * @param segment the segment to test for inclusion 1383 * 1384 * @return <code>true</code> if the segment is contained in this 1385 * segment. 1386 */ contains(Segment segment)1387 public boolean contains(Segment segment) { 1388 return contains(segment.getSegmentStart(), segment.getSegmentEnd()); 1389 } 1390 1391 /** 1392 * Returns <code>true</code> if this segment is contained in an 1393 * interval. 1394 * 1395 * @param from the start of the interval. 1396 * @param to the end of the interval. 1397 * 1398 * @return <code>true</code> if this segment is contained in the 1399 * interval. 1400 */ contained(long from, long to)1401 public boolean contained(long from, long to) { 1402 return (from <= this.segmentStart && this.segmentEnd <= to); 1403 } 1404 1405 /** 1406 * Returns a segment that is the intersection of this segment and the 1407 * interval. 1408 * 1409 * @param from the start of the interval. 1410 * @param to the end of the interval. 1411 * 1412 * @return A segment. 1413 */ intersect(long from, long to)1414 public Segment intersect(long from, long to) { 1415 if (from <= this.segmentStart && this.segmentEnd <= to) { 1416 return this; 1417 } 1418 else { 1419 return null; 1420 } 1421 } 1422 1423 /** 1424 * Returns <code>true</code> if this segment is wholly before another 1425 * segment. 1426 * 1427 * @param other the other segment. 1428 * 1429 * @return A boolean. 1430 */ before(Segment other)1431 public boolean before(Segment other) { 1432 return (this.segmentEnd < other.getSegmentStart()); 1433 } 1434 1435 /** 1436 * Returns <code>true</code> if this segment is wholly after another 1437 * segment. 1438 * 1439 * @param other the other segment. 1440 * 1441 * @return A boolean. 1442 */ after(Segment other)1443 public boolean after(Segment other) { 1444 return (this.segmentStart > other.getSegmentEnd()); 1445 } 1446 1447 /** 1448 * Tests an object (usually another <code>Segment</code>) for equality 1449 * with this segment. 1450 * 1451 * @param object The other segment to compare with us 1452 * 1453 * @return <code>true</code> if we are the same segment 1454 */ 1455 @Override equals(Object object)1456 public boolean equals(Object object) { 1457 if (object instanceof Segment) { 1458 Segment other = (Segment) object; 1459 return (this.segmentNumber == other.getSegmentNumber() 1460 && this.segmentStart == other.getSegmentStart() 1461 && this.segmentEnd == other.getSegmentEnd() 1462 && this.millisecond == other.getMillisecond()); 1463 } 1464 else { 1465 return false; 1466 } 1467 } 1468 1469 /** 1470 * Returns a copy of ourselves or <code>null</code> if there was an 1471 * exception during cloning. 1472 * 1473 * @return A copy of this segment. 1474 */ copy()1475 public Segment copy() { 1476 try { 1477 return (Segment) this.clone(); 1478 } 1479 catch (CloneNotSupportedException e) { 1480 return null; 1481 } 1482 } 1483 1484 /** 1485 * Will compare this Segment with another Segment (from Comparable 1486 * interface). 1487 * 1488 * @param object The other Segment to compare with 1489 * 1490 * @return -1: this < object, 0: this.equal(object) and 1491 * +1: this > object 1492 */ 1493 @Override compareTo(Object object)1494 public int compareTo(Object object) { 1495 Segment other = (Segment) object; 1496 if (this.before(other)) { 1497 return -1; 1498 } 1499 else if (this.after(other)) { 1500 return +1; 1501 } 1502 else { 1503 return 0; 1504 } 1505 } 1506 1507 /** 1508 * Returns true if we are an included segment and we are not an 1509 * exception. 1510 * 1511 * @return <code>true</code> or <code>false</code>. 1512 */ inIncludeSegments()1513 public boolean inIncludeSegments() { 1514 if (getSegmentNumberRelativeToGroup() 1515 < SegmentedTimeline.this.segmentsIncluded) { 1516 return !inExceptionSegments(); 1517 } 1518 else { 1519 return false; 1520 } 1521 } 1522 1523 /** 1524 * Returns true if we are an excluded segment. 1525 * 1526 * @return <code>true</code> or <code>false</code>. 1527 */ inExcludeSegments()1528 public boolean inExcludeSegments() { 1529 return getSegmentNumberRelativeToGroup() 1530 >= SegmentedTimeline.this.segmentsIncluded; 1531 } 1532 1533 /** 1534 * Calculate the segment number relative to the segment group. This 1535 * will be a number between 0 and segmentsGroup-1. This value is 1536 * calculated from the segmentNumber. Special care is taken for 1537 * negative segmentNumbers. 1538 * 1539 * @return The segment number. 1540 */ getSegmentNumberRelativeToGroup()1541 private long getSegmentNumberRelativeToGroup() { 1542 long p = (this.segmentNumber 1543 % SegmentedTimeline.this.groupSegmentCount); 1544 if (p < 0) { 1545 p += SegmentedTimeline.this.groupSegmentCount; 1546 } 1547 return p; 1548 } 1549 1550 /** 1551 * Returns true if we are an exception segment. This is implemented via 1552 * a binary search on the exceptionSegments sorted list. 1553 * 1554 * If the segment is not listed as an exception in our list and we have 1555 * a baseTimeline, a check is performed to see if the segment is inside 1556 * an excluded segment from our base. If so, it is also considered an 1557 * exception. 1558 * 1559 * @return <code>true</code> if we are an exception segment. 1560 */ inExceptionSegments()1561 public boolean inExceptionSegments() { 1562 return binarySearchExceptionSegments(this) >= 0; 1563 } 1564 1565 /** 1566 * Increments the internal attributes of this segment by a number of 1567 * segments. 1568 * 1569 * @param n Number of segments to increment. 1570 */ inc(long n)1571 public void inc(long n) { 1572 this.segmentNumber += n; 1573 long m = n * SegmentedTimeline.this.segmentSize; 1574 this.segmentStart += m; 1575 this.segmentEnd += m; 1576 this.millisecond += m; 1577 } 1578 1579 /** 1580 * Increments the internal attributes of this segment by one segment. 1581 * The exact time incremented is segmentSize. 1582 */ inc()1583 public void inc() { 1584 inc(1); 1585 } 1586 1587 /** 1588 * Decrements the internal attributes of this segment by a number of 1589 * segments. 1590 * 1591 * @param n Number of segments to decrement. 1592 */ dec(long n)1593 public void dec(long n) { 1594 this.segmentNumber -= n; 1595 long m = n * SegmentedTimeline.this.segmentSize; 1596 this.segmentStart -= m; 1597 this.segmentEnd -= m; 1598 this.millisecond -= m; 1599 } 1600 1601 /** 1602 * Decrements the internal attributes of this segment by one segment. 1603 * The exact time decremented is segmentSize. 1604 */ dec()1605 public void dec() { 1606 dec(1); 1607 } 1608 1609 /** 1610 * Moves the index of this segment to the beginning if the segment. 1611 */ moveIndexToStart()1612 public void moveIndexToStart() { 1613 this.millisecond = this.segmentStart; 1614 } 1615 1616 /** 1617 * Moves the index of this segment to the end of the segment. 1618 */ moveIndexToEnd()1619 public void moveIndexToEnd() { 1620 this.millisecond = this.segmentEnd; 1621 } 1622 1623 } 1624 1625 /** 1626 * Private internal class to represent a range of segments. This class is 1627 * mainly used to store in one object a range of exception segments. This 1628 * optimizes certain timelines that use a small segment size (like an 1629 * intraday timeline) allowing them to express a day exception as one 1630 * SegmentRange instead of multi Segments. 1631 */ 1632 protected class SegmentRange extends Segment { 1633 1634 /** The number of segments in the range. */ 1635 private long segmentCount; 1636 1637 /** 1638 * Creates a SegmentRange between a start and end domain values. 1639 * 1640 * @param fromMillisecond start of the range 1641 * @param toMillisecond end of the range 1642 */ SegmentRange(long fromMillisecond, long toMillisecond)1643 public SegmentRange(long fromMillisecond, long toMillisecond) { 1644 1645 Segment start = getSegment(fromMillisecond); 1646 Segment end = getSegment(toMillisecond); 1647 // if (start.getSegmentStart() != fromMillisecond 1648 // || end.getSegmentEnd() != toMillisecond) { 1649 // throw new IllegalArgumentException("Invalid Segment Range [" 1650 // + fromMillisecond + "," + toMillisecond + "]"); 1651 // } 1652 1653 this.millisecond = fromMillisecond; 1654 this.segmentNumber = calculateSegmentNumber(fromMillisecond); 1655 this.segmentStart = start.segmentStart; 1656 this.segmentEnd = end.segmentEnd; 1657 this.segmentCount 1658 = (end.getSegmentNumber() - start.getSegmentNumber() + 1); 1659 } 1660 1661 /** 1662 * Returns the number of segments contained in this range. 1663 * 1664 * @return The segment count. 1665 */ 1666 @Override getSegmentCount()1667 public long getSegmentCount() { 1668 return this.segmentCount; 1669 } 1670 1671 /** 1672 * Returns a segment that is the intersection of this segment and the 1673 * interval. 1674 * 1675 * @param from the start of the interval. 1676 * @param to the end of the interval. 1677 * 1678 * @return The intersection. 1679 */ 1680 @Override intersect(long from, long to)1681 public Segment intersect(long from, long to) { 1682 1683 // Segment fromSegment = getSegment(from); 1684 // fromSegment.inc(); 1685 // Segment toSegment = getSegment(to); 1686 // toSegment.dec(); 1687 long start = Math.max(from, this.segmentStart); 1688 long end = Math.min(to, this.segmentEnd); 1689 // long start = Math.max( 1690 // fromSegment.getSegmentStart(), this.segmentStart 1691 // ); 1692 // long end = Math.min(toSegment.getSegmentEnd(), this.segmentEnd); 1693 if (start <= end) { 1694 return new SegmentRange(start, end); 1695 } 1696 else { 1697 return null; 1698 } 1699 } 1700 1701 /** 1702 * Returns true if all Segments of this SegmentRenge are an included 1703 * segment and are not an exception. 1704 * 1705 * @return <code>true</code> or </code>false</code>. 1706 */ 1707 @Override inIncludeSegments()1708 public boolean inIncludeSegments() { 1709 for (Segment segment = getSegment(this.segmentStart); 1710 segment.getSegmentStart() < this.segmentEnd; 1711 segment.inc()) { 1712 if (!segment.inIncludeSegments()) { 1713 return (false); 1714 } 1715 } 1716 return true; 1717 } 1718 1719 /** 1720 * Returns true if we are an excluded segment. 1721 * 1722 * @return <code>true</code> or </code>false</code>. 1723 */ 1724 @Override inExcludeSegments()1725 public boolean inExcludeSegments() { 1726 for (Segment segment = getSegment(this.segmentStart); 1727 segment.getSegmentStart() < this.segmentEnd; 1728 segment.inc()) { 1729 if (!segment.inExceptionSegments()) { 1730 return (false); 1731 } 1732 } 1733 return true; 1734 } 1735 1736 /** 1737 * Not implemented for SegmentRange. Always throws 1738 * IllegalArgumentException. 1739 * 1740 * @param n Number of segments to increment. 1741 */ 1742 @Override inc(long n)1743 public void inc(long n) { 1744 throw new IllegalArgumentException( 1745 "Not implemented in SegmentRange"); 1746 } 1747 1748 } 1749 1750 /** 1751 * Special <code>SegmentRange</code> that came from the BaseTimeline. 1752 */ 1753 protected class BaseTimelineSegmentRange extends SegmentRange { 1754 1755 /** 1756 * Constructor. 1757 * 1758 * @param fromDomainValue the start value. 1759 * @param toDomainValue the end value. 1760 */ BaseTimelineSegmentRange(long fromDomainValue, long toDomainValue)1761 public BaseTimelineSegmentRange(long fromDomainValue, 1762 long toDomainValue) { 1763 super(fromDomainValue, toDomainValue); 1764 } 1765 1766 } 1767 1768 } 1769