001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * -----------------------
028 * SegmentedTimeline.java
029 * -----------------------
030 * (C) Copyright 2003-2007, by Bill Kelemen and Contributors.
031 *
032 * Original Author: Bill Kelemen;
033 * Contributor(s): David Gilbert (for Object Refinery Limited);
034 *
035 * $Id: SegmentedTimeline.java,v 1.9.2.3 2007/02/02 14:32:42 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 23-May-2003 : Version 1 (BK);
040 * 15-Aug-2003 : Implemented Cloneable (DG);
041 * 01-Jun-2004 : Modified to compile with JDK 1.2.2 (DG);
042 * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG);
043 * 04-Nov-2004 : Reverted change of 30-Sep-2004, won't work with JDK 1.3 (DG);
044 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
045 * ------------- JFREECHART 1.0.x ---------------------------------------------
046 * 14-Nov-2006 : Fix in toTimelineValue(long) to avoid stack overflow (DG);
047 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
048 *
049 */
050
051 package org.jfree.chart.axis;
052
053 import java.io.Serializable;
054 import java.util.ArrayList;
055 import java.util.Calendar;
056 import java.util.Collections;
057 import java.util.Date;
058 import java.util.GregorianCalendar;
059 import java.util.Iterator;
060 import java.util.List;
061 import java.util.SimpleTimeZone;
062 import java.util.TimeZone;
063
064 /**
065 * A {@link Timeline} that implements a "segmented" timeline with included,
066 * excluded and exception segments.
067 * <P>
068 * A Timeline will present a series of values to be used for an axis. Each
069 * Timeline must provide transformation methods between domain values and
070 * timeline values.
071 * <P>
072 * A timeline can be used as parameter to a
073 * {@link org.jfree.chart.axis.DateAxis} to define the values that this axis
074 * supports. This class implements a timeline formed by segments of equal
075 * length (ex. days, hours, minutes) where some segments can be included in the
076 * timeline and others excluded. Therefore timelines like "working days" or
077 * "working hours" can be created where non-working days or non-working hours
078 * respectively can be removed from the timeline, and therefore from the axis.
079 * This creates a smooth plot with equal separation between all included
080 * segments.
081 * <P>
082 * Because Timelines were created mainly for Date related axis, values are
083 * represented as longs instead of doubles. In this case, the domain value is
084 * just the number of milliseconds since January 1, 1970, 00:00:00 GMT as
085 * defined by the getTime() method of {@link java.util.Date}.
086 * <P>
087 * In this class, a segment is defined as a unit of time of fixed length.
088 * Examples of segments are: days, hours, minutes, etc. The size of a segment
089 * is defined as the number of milliseconds in the segment. Some useful segment
090 * sizes are defined as constants in this class: DAY_SEGMENT_SIZE,
091 * HOUR_SEGMENT_SIZE, FIFTEEN_MINUTE_SEGMENT_SIZE and MINUTE_SEGMENT_SIZE.
092 * <P>
093 * Segments are group together to form a Segment Group. Each Segment Group will
094 * contain a number of Segments included and a number of Segments excluded. This
095 * Segment Group structure will repeat for the whole timeline.
096 * <P>
097 * For example, a working days SegmentedTimeline would be formed by a group of
098 * 7 daily segments, where there are 5 included (Monday through Friday) and 2
099 * 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 }