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