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