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