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