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