KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jfree > chart > axis > SegmentedTimeline


1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
6  *
7  * Project Info: http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this library; if not, write to the Free Software Foundation,
21  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22  *
23  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
24  * in the United States and other countries.]
25  *
26  * -----------------------
27  * SegmentedTimeline.java
28  * -----------------------
29  * (C) Copyright 2003-2005, by Bill Kelemen and Contributors.
30  *
31  * Original Author: Bill Kelemen;
32  * Contributor(s): David Gilbert (for Object Refinery Limited);
33  *
34  * $Id: SegmentedTimeline.java,v 1.9 2005/05/19 13:58:11 mungady Exp $
35  *
36  * Changes
37  * -------
38  * 23-May-2003 : Version 1 (BK);
39  * 15-Aug-2003 : Implemented Cloneable (DG);
40  * 01-Jun-2004 : Modified to compile with JDK 1.2.2 (DG);
41  * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG);
42  * 04-Nov-2004 : Reverted change of 30-Sep-2004, won't work with JDK 1.3 (DG);
43  * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
44  *
45  */

46
47 package org.jfree.chart.axis;
48
49 import java.io.Serializable JavaDoc;
50 import java.util.ArrayList JavaDoc;
51 import java.util.Calendar JavaDoc;
52 import java.util.Collections JavaDoc;
53 import java.util.Date JavaDoc;
54 import java.util.GregorianCalendar JavaDoc;
55 import java.util.Iterator JavaDoc;
56 import java.util.List JavaDoc;
57 import java.util.SimpleTimeZone JavaDoc;
58 import java.util.TimeZone JavaDoc;
59
60 /**
61  * A {@link Timeline} that implements a "segmented" timeline with included,
62  * excluded and exception segments.
63  * <P>
64  * A Timeline will present a series of values to be used for an axis. Each
65  * Timeline must provide transformation methods between domain values and
66  * timeline values.
67  * <P>
68  * A timeline can be used as parameter to a
69  * {@link org.jfree.chart.axis.DateAxis} to define the values that this axis
70  * supports. This class implements a timeline formed by segments of equal
71  * length (ex. days, hours, minutes) where some segments can be included in the
72  * timeline and others excluded. Therefore timelines like "working days" or
73  * "working hours" can be created where non-working days or non-working hours
74  * respectively can be removed from the timeline, and therefore from the axis.
75  * This creates a smooth plot with equal separation between all included
76  * segments.
77  * <P>
78  * Because Timelines were created mainly for Date related axis, values are
79  * represented as longs instead of doubles. In this case, the domain value is
80  * just the number of milliseconds since January 1, 1970, 00:00:00 GMT as
81  * defined by the getTime() method of {@link java.util.Date}.
82  * <P>
83  * In this class, a segment is defined as a unit of time of fixed length.
84  * Examples of segments are: days, hours, minutes, etc. The size of a segment
85  * is defined as the number of milliseconds in the segment. Some useful segment
86  * sizes are defined as constants in this class: DAY_SEGMENT_SIZE,
87  * HOUR_SEGMENT_SIZE, FIFTEEN_MINUTE_SEGMENT_SIZE and MINUTE_SEGMENT_SIZE.
88  * <P>
89  * Segments are group together to form a Segment Group. Each Segment Group will
90  * contain a number of Segments included and a number of Segments excluded. This
91  * Segment Group structure will repeat for the whole timeline.
92  * <P>
93  * For example, a working days SegmentedTimeline would be formed by a group of
94  * 7 daily segments, where there are 5 included (Monday through Friday) and 2
95  * excluded (Saturday and Sunday) segments.
96  * <P>
97  * Following is a diagram that explains the major attributes that define a
98  * segment. Each box is one segment and must be of fixed length (ms, second,
99  * hour, day, etc).
100  * <p>
101  * <pre>
102  * start time
103  * |
104  * v
105  * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
106  * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+...
107  * | | | | | |EE|EE| | | | | |EE|EE| | | | | |EE|EE|
108  * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+...
109  * \____________/ \___/ \_/
110  * \/ | |
111  * included excluded segment
112  * segments segments size
113  * \_________ _______/
114  * \/
115  * segment group
116  * </pre>
117  * Legend:<br>
118  * &lt;space&gt; = Included segment<br>
119  * EE = Excluded segments in the base timeline<br>
120  * <p>
121  * In the example, the following segment attributes are presented:
122  * <ul>
123  * <li>segment size: the size of each segment in ms.
124  * <li>start time: the start of the first segment of the first segment group to
125  * consider.
126  * <li>included segments: the number of segments to include in the group.
127  * <li>excluded segments: the number of segments to exclude in the group.
128  * </ul>
129  * <p>
130  * Exception Segments are allowed. These exception segments are defined as
131  * segments that would have been in the included segments of the Segment Group,
132  * but should be excluded for special reasons. In the previous working days
133  * SegmentedTimeline example, holidays would be considered exceptions.
134  * <P>
135  * Additionally the <code>startTime</code>, or start of the first Segment of
136  * the smallest segment group needs to be defined. This startTime could be
137  * relative to January 1, 1970, 00:00:00 GMT or any other date. This creates a
138  * point of reference to start counting Segment Groups. For example, for the
139  * working days SegmentedTimeline, the <code>startTime</code> could be
140  * 00:00:00 GMT of the first Monday after January 1, 1970. In this class, the
141  * constant FIRST_MONDAY_AFTER_1900 refers to a reference point of the first
142  * Monday of the last century.
143  * <p>
144  * A SegmentedTimeline can include a baseTimeline. This combination of
145  * timelines allows the creation of more complex timelines. For example, in
146  * order to implement a SegmentedTimeline for an intraday stock trading
147  * application, where the trading period is defined as 9:00 AM through 4:00 PM
148  * Monday through Friday, two SegmentedTimelines are used. The first one (the
149  * baseTimeline) would be a working day SegmentedTimeline (daily timeline
150  * Monday through Friday). On top of this baseTimeline, a second one is defined
151  * that maps the 9:00 AM to 4:00 PM period. Because the baseTimeline defines a
152  * timeline of Monday through Friday, the resulting (combined) timeline will
153  * expose the period 9:00 AM through 4:00 PM only on Monday through Friday,
154  * and will remove all other intermediate intervals.
155  * <P>
156  * Two factory methods newMondayThroughFridayTimeline() and
157  * newFifteenMinuteTimeline() are provided as examples to create special
158  * SegmentedTimelines.
159  *
160  * @see org.jfree.chart.axis.DateAxis
161  *
162  * @author Bill Kelemen
163  */

164 public class SegmentedTimeline implements Timeline, Cloneable JavaDoc, Serializable JavaDoc {
165
166     /** For serialization. */
167     private static final long serialVersionUID = 1093779862539903110L;
168     
169     ////////////////////////////////////////////////////////////////////////////
170
// predetermined segments sizes
171
////////////////////////////////////////////////////////////////////////////
172

173     /** Defines a day segment size in ms. */
174     public static final long DAY_SEGMENT_SIZE = 24 * 60 * 60 * 1000;
175
176     /** Defines a one hour segment size in ms. */
177     public static final long HOUR_SEGMENT_SIZE = 60 * 60 * 1000;
178
179     /** Defines a 15-minute segment size in ms. */
180     public static final long FIFTEEN_MINUTE_SEGMENT_SIZE = 15 * 60 * 1000;
181
182     /** Defines a one-minute segment size in ms. */
183     public static final long MINUTE_SEGMENT_SIZE = 60 * 1000;
184
185     ////////////////////////////////////////////////////////////////////////////
186
// other constants
187
////////////////////////////////////////////////////////////////////////////
188

189     /**
190      * Utility constant that defines the startTime as the first monday after
191      * 1/1/1970. This should be used when creating a SegmentedTimeline for
192      * Monday through Friday. See static block below for calculation of this
193      * constant.
194      */

195     public static long FIRST_MONDAY_AFTER_1900;
196
197     /**
198      * Utility TimeZone object that has no DST and an offset equal to the
199      * default TimeZone. This allows easy arithmetic between days as each one
200      * will have equal size.
201      */

202     public static TimeZone JavaDoc NO_DST_TIME_ZONE;
203
204     /**
205      * This is the default time zone where the application is running. See
206      * getTime() below where we make use of certain transformations between
207      * times in the default time zone and the no-dst time zone used for our
208      * calculations.
209      */

210     public static TimeZone JavaDoc DEFAULT_TIME_ZONE = TimeZone.getDefault();
211
212     /**
213      * This will be a utility calendar that has no DST but is shifted relative
214      * to the default time zone's offset.
215      */

216     private Calendar JavaDoc workingCalendarNoDST
217         = new GregorianCalendar JavaDoc(NO_DST_TIME_ZONE);
218
219     /**
220      * This will be a utility calendar that used the default time zone.
221      */

222     private Calendar JavaDoc workingCalendar = Calendar.getInstance();
223
224     ////////////////////////////////////////////////////////////////////////////
225
// private attributes
226
////////////////////////////////////////////////////////////////////////////
227

228     /** Segment size in ms. */
229     private long segmentSize;
230
231     /** Number of consecutive segments to include in a segment group. */
232     private int segmentsIncluded;
233
234     /** Number of consecutive segments to exclude in a segment group. */
235     private int segmentsExcluded;
236
237     /** Number of segments in a group (segmentsIncluded + segmentsExcluded). */
238     private int groupSegmentCount;
239
240     /**
241      * Start of time reference from time zero (1/1/1970).
242      * This is the start of segment #0.
243      */

244     private long startTime;
245
246     /** Consecutive ms in segmentsIncluded (segmentsIncluded * segmentSize). */
247     private long segmentsIncludedSize;
248
249     /** Consecutive ms in segmentsExcluded (segmentsExcluded * segmentSize). */
250     private long segmentsExcludedSize;
251
252     /** ms in a segment group (segmentsIncludedSize + segmentsExcludedSize). */
253     private long segmentsGroupSize;
254
255     /**
256      * List of exception segments (exceptions segments that would otherwise be
257      * included based on the periodic (included, excluded) grouping).
258      */

259     private List JavaDoc exceptionSegments = new ArrayList JavaDoc();
260
261     /**
262      * This base timeline is used to specify exceptions at a higher level. For
263      * example, if we are a intraday timeline and want to exclude holidays,
264      * instead of having to exclude all intraday segments for the holiday,
265      * segments from this base timeline can be excluded. This baseTimeline is
266      * always optional and is only a convenience method.
267      * <p>
268      * Additionally, all excluded segments from this baseTimeline will be
269      * considered exceptions at this level.
270      */

271     private SegmentedTimeline baseTimeline;
272
273     /** A flag that controls whether or not to adjust for daylight saving. */
274     private boolean adjustForDaylightSaving = false;
275     
276     ////////////////////////////////////////////////////////////////////////////
277
// static block
278
////////////////////////////////////////////////////////////////////////////
279

280     static {
281         // make a time zone with no DST for our Calendar calculations
282
int offset = TimeZone.getDefault().getRawOffset();
283         NO_DST_TIME_ZONE = new SimpleTimeZone JavaDoc(offset, "UTC-" + offset);
284         
285         // calculate midnight of first monday after 1/1/1900 relative to
286
// current locale
287
Calendar JavaDoc cal = new GregorianCalendar JavaDoc(NO_DST_TIME_ZONE);
288         cal.set(1900, 0, 1, 0, 0, 0);
289         cal.set(Calendar.MILLISECOND, 0);
290         while (cal.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
291             cal.add(Calendar.DATE, 1);
292         }
293         // FIRST_MONDAY_AFTER_1900 = cal.getTime().getTime();
294
// preceding code won't work with JDK 1.3
295
FIRST_MONDAY_AFTER_1900 = cal.getTime().getTime();
296     }
297
298     ////////////////////////////////////////////////////////////////////////////
299
// constructors and factory methods
300
////////////////////////////////////////////////////////////////////////////
301

302     /**
303      * Constructs a new segmented timeline, optionaly using another segmented
304      * timeline as its base. This chaining of SegmentedTimelines allows further
305      * segmentation into smaller timelines.
306      *
307      * If a base
308      *
309      * @param segmentSize the size of a segment in ms. This time unit will be
310      * used to compute the included and excluded segments of the
311      * timeline.
312      * @param segmentsIncluded Number of consecutive segments to include.
313      * @param segmentsExcluded Number of consecutive segments to exclude.
314      */

315     public SegmentedTimeline(long segmentSize,
316                              int segmentsIncluded,
317                              int segmentsExcluded) {
318
319         this.segmentSize = segmentSize;
320         this.segmentsIncluded = segmentsIncluded;
321         this.segmentsExcluded = segmentsExcluded;
322
323         this.groupSegmentCount = this.segmentsIncluded + this.segmentsExcluded;
324         this.segmentsIncludedSize = this.segmentsIncluded * this.segmentSize;
325         this.segmentsExcludedSize = this.segmentsExcluded * this.segmentSize;
326         this.segmentsGroupSize = this.segmentsIncludedSize
327                                  + this.segmentsExcludedSize;
328
329     }
330
331     /**
332      * Factory method to create a Monday through Friday SegmentedTimeline.
333      * <P>
334      * The <code>startTime</code> of the resulting timeline will be midnight
335      * of the first Monday after 1/1/1900.
336      *
337      * @return A fully initialized SegmentedTimeline.
338      */

339     public static SegmentedTimeline newMondayThroughFridayTimeline() {
340         SegmentedTimeline timeline
341             = new SegmentedTimeline(DAY_SEGMENT_SIZE, 5, 2);
342         timeline.setStartTime(FIRST_MONDAY_AFTER_1900);
343         return timeline;
344     }
345
346     /**
347      * Factory method to create a 15-min, 9:00 AM thought 4:00 PM, Monday
348      * through Friday SegmentedTimeline.
349      * <P>
350      * This timeline uses a segmentSize of FIFTEEN_MIN_SEGMENT_SIZE. The
351      * segment group is defined as 28 included segments (9:00 AM through
352      * 4:00 PM) and 68 excluded segments (4:00 PM through 9:00 AM the next day).
353      * <P>
354      * In order to exclude Saturdays and Sundays it uses a baseTimeline that
355      * only includes Monday through Friday days.
356      * <P>
357      * The <code>startTime</code> of the resulting timeline will be 9:00 AM
358      * after the startTime of the baseTimeline. This will correspond to 9:00 AM
359      * of the first Monday after 1/1/1900.
360      *
361      * @return A fully initialized SegmentedTimeline.
362      */

363     public static SegmentedTimeline newFifteenMinuteTimeline() {
364         SegmentedTimeline timeline
365             = new SegmentedTimeline(FIFTEEN_MINUTE_SEGMENT_SIZE, 28, 68);
366         timeline.setStartTime(
367             FIRST_MONDAY_AFTER_1900 + 36 * timeline.getSegmentSize()
368         );
369         timeline.setBaseTimeline(newMondayThroughFridayTimeline());
370         return timeline;
371     }
372     
373     /**
374      * Returns the flag that controls whether or not the daylight saving
375      * adjustment is applied.
376      *
377      * @return A boolean.
378      */

379     public boolean getAdjustForDaylightSaving() {
380         return this.adjustForDaylightSaving;
381     }
382     
383     /**
384      * Sets the flag that controls whether or not the daylight saving adjustment
385      * is applied.
386      *
387      * @param adjust the flag.
388      */

389     public void setAdjustForDaylightSaving(boolean adjust) {
390         this.adjustForDaylightSaving = adjust;
391     }
392
393     ////////////////////////////////////////////////////////////////////////////
394
// operations
395
////////////////////////////////////////////////////////////////////////////
396

397     /**
398      * Sets the start time for the timeline. This is the beginning of segment
399      * zero.
400      *
401      * @param millisecond the start time (encoded as in java.util.Date).
402      */

403     public void setStartTime(long millisecond) {
404         this.startTime = millisecond;
405     }
406
407     /**
408      * Returns the start time for the timeline. This is the beginning of
409      * segment zero.
410      *
411      * @return The start time.
412      */

413     public long getStartTime() {
414         return this.startTime;
415     }
416
417     /**
418      * Returns the number of segments excluded per segment group.
419      *
420      * @return The number of segments excluded.
421      */

422     public int getSegmentsExcluded() {
423         return this.segmentsExcluded;
424     }
425
426     /**
427      * Returns the size in milliseconds of the segments excluded per segment
428      * group.
429      *
430      * @return The size in milliseconds.
431      */

432     public long getSegmentsExcludedSize() {
433         return this.segmentsExcludedSize;
434     }
435
436     /**
437      * Returns the number of segments in a segment group. This will be equal to
438      * segments included plus segments excluded.
439      *
440      * @return The number of segments.
441      */

442     public int getGroupSegmentCount() {
443         return this.groupSegmentCount;
444     }
445
446     /**
447      * Returns the size in milliseconds of a segment group. This will be equal
448      * to size of the segments included plus the size of the segments excluded.
449      *
450      * @return The segment group size in milliseconds.
451      */

452     public long getSegmentsGroupSize() {
453         return this.segmentsGroupSize;
454     }
455
456     /**
457      * Returns the number of segments included per segment group.
458      *
459      * @return The number of segments.
460      */

461     public int getSegmentsIncluded() {
462         return this.segmentsIncluded;
463     }
464
465     /**
466      * Returns the size in ms of the segments included per segment group.
467      *
468      * @return The segment size in milliseconds.
469      */

470     public long getSegmentsIncludedSize() {
471         return this.segmentsIncludedSize;
472     }
473
474     /**
475      * Returns the size of one segment in ms.
476      *
477      * @return The segment size in milliseconds.
478      */

479     public long getSegmentSize() {
480         return this.segmentSize;
481     }
482
483     /**
484      * Returns a list of all the exception segments. This list is not
485      * modifiable.
486      *
487      * @return The exception segments.
488      */

489     public List JavaDoc getExceptionSegments() {
490         return Collections.unmodifiableList(this.exceptionSegments);
491     }
492
493     /**
494      * Sets the exception segments list.
495      *
496      * @param exceptionSegments the exception segments.
497      */

498     public void setExceptionSegments(List JavaDoc exceptionSegments) {
499         this.exceptionSegments = exceptionSegments;
500     }
501
502     /**
503      * Returns our baseTimeline, or <code>null</code> if none.
504      *
505      * @return The base timeline.
506      */

507     public SegmentedTimeline getBaseTimeline() {
508         return this.baseTimeline;
509     }
510
511     /**
512      * Sets the base timeline.
513      *
514      * @param baseTimeline the timeline.
515      */

516     public void setBaseTimeline(SegmentedTimeline baseTimeline) {
517
518         // verify that baseTimeline is compatible with us
519
if (baseTimeline != null) {
520             if (baseTimeline.getSegmentSize() < this.segmentSize) {
521                 throw new IllegalArgumentException JavaDoc(
522                     "baseTimeline.getSegmentSize() is smaller than segmentSize"
523                 );
524             }
525             else if (baseTimeline.getStartTime() > this.startTime) {
526                 throw new IllegalArgumentException JavaDoc(
527                     "baseTimeline.getStartTime() is after startTime"
528                 );
529             }
530             else if ((baseTimeline.getSegmentSize() % this.segmentSize) != 0) {
531                 throw new IllegalArgumentException JavaDoc(
532                     "baseTimeline.getSegmentSize() is not multiple of "
533                     + "segmentSize"
534                 );
535             }
536             else if (((this.startTime
537                     - baseTimeline.getStartTime()) % this.segmentSize) != 0) {
538                 throw new IllegalArgumentException JavaDoc(
539                     "baseTimeline is not aligned"
540                 );
541             }
542         }
543
544         this.baseTimeline = baseTimeline;
545     }
546
547     /**
548      * Translates a value relative to the domain value (all Dates) into a value
549      * relative to the segmented timeline. The values relative to the segmented
550      * timeline are all consecutives starting at zero at the startTime.
551      *
552      * @param millisecond the millisecond (as encoded by java.util.Date).
553      *
554      * @return The timeline value.
555      */

556     public long toTimelineValue(long millisecond) {
557   
558         long result;
559         long rawMilliseconds = millisecond - this.startTime;
560         long groupMilliseconds = rawMilliseconds % this.segmentsGroupSize;
561         long groupIndex = rawMilliseconds / this.segmentsGroupSize;
562         
563         if (groupMilliseconds >= this.segmentsIncludedSize) {
564             result = toTimelineValue(
565                 this.startTime + this.segmentsGroupSize * (groupIndex + 1)
566             );
567         }
568         else {
569             Segment segment = getSegment(millisecond);
570             if (segment.inExceptionSegments()) {
571                 result = toTimelineValue(segment.getSegmentEnd() + 1);
572             }
573             else {
574                 long shiftedSegmentedValue = millisecond - this.startTime;
575                 long x = shiftedSegmentedValue % this.segmentsGroupSize;
576                 long y = shiftedSegmentedValue / this.segmentsGroupSize;
577
578                 long wholeExceptionsBeforeDomainValue =
579                     getExceptionSegmentCount(this.startTime, millisecond - 1);
580
581 // long partialTimeInException = 0;
582
// Segment ss = getSegment(millisecond);
583
// if (ss.inExceptionSegments()) {
584
// partialTimeInException = millisecond
585
// - ss.getSegmentStart();
586
// }
587

588                 if (x < this.segmentsIncludedSize) {
589                     result = this.segmentsIncludedSize * y
590                              + x - wholeExceptionsBeforeDomainValue
591                              * this.segmentSize;
592                              // - partialTimeInException;;
593
}
594                 else {
595                     result = this.segmentsIncludedSize * (y + 1)
596                              - wholeExceptionsBeforeDomainValue
597                              * this.segmentSize;
598                              // - partialTimeInException;
599
}
600             }
601         }
602
603         return result;
604     }
605
606     /**
607      * Translates a date into a value relative to the segmented timeline. The
608      * values relative to the segmented timeline are all consecutives starting
609      * at zero at the startTime.
610      *
611      * @param date date relative to the domain.
612      *
613      * @return The timeline value (in milliseconds).
614      */

615     public long toTimelineValue(Date JavaDoc date) {
616         return toTimelineValue(getTime(date));
617         //return toTimelineValue(dateDomainValue.getTime());
618
}
619
620     /**
621      * Translates a value relative to the timeline into a millisecond.
622      *
623      * @param timelineValue the timeline value (in milliseconds).
624      *
625      * @return The domain value (in milliseconds).
626      */

627     public long toMillisecond(long timelineValue) {
628         
629         // calculate the result as if no exceptions
630
Segment result = new Segment(this.startTime + timelineValue
631             + (timelineValue / this.segmentsIncludedSize)
632             * this.segmentsExcludedSize);
633         
634         long lastIndex = this.startTime;
635
636         // adjust result for any exceptions in the result calculated
637
while (lastIndex <= result.segmentStart) {
638
639             // skip all whole exception segments in the range
640
long exceptionSegmentCount;
641             while ((exceptionSegmentCount = getExceptionSegmentCount(
642                  lastIndex, (result.millisecond / this.segmentSize)
643                  * this.segmentSize - 1)) > 0
644             ) {
645                 lastIndex = result.segmentStart;
646                 // move forward exceptionSegmentCount segments skipping
647
// excluded segments
648
for (int i = 0; i < exceptionSegmentCount; i++) {
649                     do {
650                         result.inc();
651                     }
652                     while (result.inExcludeSegments());
653                 }
654             }
655             lastIndex = result.segmentStart;
656
657             // skip exception or excluded segments we may fall on
658
while (result.inExceptionSegments() || result.inExcludeSegments()) {
659                 result.inc();
660                 lastIndex += this.segmentSize;
661             }
662
663             lastIndex++;
664         }
665
666         return getTimeFromLong(result.millisecond);
667     }
668
669     /**
670      * Converts a date/time value to take account of daylight savings time.
671      *
672      * @param date the milliseconds.
673      *
674      * @return The milliseconds.
675      */

676     public long getTimeFromLong(long date) {
677         long result = date;
678         if (this.adjustForDaylightSaving) {
679             this.workingCalendarNoDST.setTime(new Date JavaDoc(date));
680             this.workingCalendar.set(
681                 this.workingCalendarNoDST.get(Calendar.YEAR),
682                 this.workingCalendarNoDST.get(Calendar.MONTH),
683                 this.workingCalendarNoDST.get(Calendar.DATE),
684                 this.workingCalendarNoDST.get(Calendar.HOUR_OF_DAY),
685                 this.workingCalendarNoDST.get(Calendar.MINUTE),
686                 this.workingCalendarNoDST.get(Calendar.SECOND)
687             );
688             this.workingCalendar.set(
689                 Calendar.MILLISECOND,
690                 this.workingCalendarNoDST.get(Calendar.MILLISECOND)
691             );
692             // result = this.workingCalendar.getTimeInMillis();
693
// preceding code won't work with JDK 1.3
694
result = this.workingCalendar.getTime().getTime();
695         }
696         return result;
697     }
698     
699     /**
700      * Returns <code>true</code> if a value is contained in the timeline.
701      *
702      * @param millisecond the value to verify.
703      *
704      * @return <code>true</code> if value is contained in the timeline.
705      */

706     public boolean containsDomainValue(long millisecond) {
707         Segment segment = getSegment(millisecond);
708         return segment.inIncludeSegments();
709     }
710
711     /**
712      * Returns <code>true</code> if a value is contained in the timeline.
713      *
714      * @param date date to verify
715      *
716      * @return <code>true</code> if value is contained in the timeline
717      */

718     public boolean containsDomainValue(Date JavaDoc date) {
719         return containsDomainValue(getTime(date));
720     }
721
722     /**
723      * Returns <code>true</code> if a range of values are contained in the
724      * timeline. This is implemented verifying that all segments are in the
725      * range.
726      *
727      * @param domainValueStart start of the range to verify
728      * @param domainValueEnd end of the range to verify
729      *
730      * @return <code>true</code> if the range is contained in the timeline
731      */

732     public boolean containsDomainRange(long domainValueStart,
733                                        long domainValueEnd) {
734         if (domainValueEnd < domainValueStart) {
735             throw new IllegalArgumentException JavaDoc(
736                 "domainValueEnd (" + domainValueEnd
737                 + ") < domainValueStart (" + domainValueStart + ")"
738             );
739         }
740         Segment segment = getSegment(domainValueStart);
741         boolean contains = true;
742         do {
743             contains = (segment.inIncludeSegments());
744             if (segment.contains(domainValueEnd)) {
745                 break;
746             }
747             else {
748                 segment.inc();
749             }
750         }
751         while (contains);
752         return (contains);
753     }
754
755     /**
756      * Returns <code>true</code> if a range of values are contained in the
757      * timeline. This is implemented verifying that all segments are in the
758      * range.
759      *
760      * @param dateDomainValueStart start of the range to verify
761      * @param dateDomainValueEnd end of the range to verify
762      *
763      * @return <code>true</code> if the range is contained in the timeline
764      */

765     public boolean containsDomainRange(Date JavaDoc dateDomainValueStart,
766                                        Date JavaDoc dateDomainValueEnd) {
767         return containsDomainRange(
768             getTime(dateDomainValueStart), getTime(dateDomainValueEnd)
769         );
770     }
771
772     /**
773      * Adds a segment as an exception. An exception segment is defined as a
774      * segment to exclude from what would otherwise be considered a valid
775      * segment of the timeline. An exception segment can not be contained
776      * inside an already excluded segment. If so, no action will occur (the
777      * proposed exception segment will be discarded).
778      * <p>
779      * The segment is identified by a domainValue into any part of the segment.
780      * Therefore the segmentStart <= domainValue <= segmentEnd.
781      *
782      * @param millisecond domain value to treat as an exception
783      */

784     public void addException(long millisecond) {
785         addException(new Segment(millisecond));
786     }
787
788     /**
789      * Adds a segment range as an exception. An exception segment is defined as
790      * a segment to exclude from what would otherwise be considered a valid
791      * segment of the timeline. An exception segment can not be contained
792      * inside an already excluded segment. If so, no action will occur (the
793      * proposed exception segment will be discarded).
794      * <p>
795      * The segment range is identified by a domainValue that begins a valid
796      * segment and ends with a domainValue that ends a valid segment.
797      * Therefore the range will contain all segments whose segmentStart
798      * <= domainValue and segmentEnd <= toDomainValue.
799      *
800      * @param fromDomainValue start of domain range to treat as an exception
801      * @param toDomainValue end of domain range to treat as an exception
802      */

803     public void addException(long fromDomainValue, long toDomainValue) {
804         addException(new SegmentRange(fromDomainValue, toDomainValue));
805     }
806
807     /**
808      * Adds a segment as an exception. An exception segment is defined as a
809      * segment to exclude from what would otherwise be considered a valid
810      * segment of the timeline. An exception segment can not be contained
811      * inside an already excluded segment. If so, no action will occur (the
812      * proposed exception segment will be discarded).
813      * <p>
814      * The segment is identified by a Date into any part of the segment.
815      *
816      * @param exceptionDate Date into the segment to exclude.
817      */

818     public void addException(Date JavaDoc exceptionDate) {
819         addException(getTime(exceptionDate));
820         //addException(exceptionDate.getTime());
821
}
822
823     /**
824      * Adds a list of dates as segment exceptions. Each exception segment is
825      * defined as a segment to exclude from what would otherwise be considered
826      * a valid segment of the timeline. An exception segment can not be
827      * contained inside an already excluded segment. If so, no action will
828      * occur (the proposed exception segment will be discarded).
829      * <p>
830      * The segment is identified by a Date into any part of the segment.
831      *
832      * @param exceptionList List of Date objects that identify the segments to
833      * exclude.
834      */

835     public void addExceptions(List JavaDoc exceptionList) {
836         for (Iterator JavaDoc iter = exceptionList.iterator(); iter.hasNext();) {
837             addException((Date JavaDoc) iter.next());
838         }
839     }
840
841     /**
842      * Adds a segment as an exception. An exception segment is defined as a
843      * segment to exclude from what would otherwise be considered a valid
844      * segment of the timeline. An exception segment can not be contained
845      * inside an already excluded segment. This is verified inside this
846      * method, and if so, no action will occur (the proposed exception segment
847      * will be discarded).
848      *
849      * @param segment the segment to exclude.
850      */

851     private void addException(Segment segment) {
852          if (segment.inIncludeSegments()) {
853              int p = binarySearchExceptionSegments(segment);
854              this.exceptionSegments.add(-(p + 1), segment);
855          }
856     }
857
858     /**
859      * Adds a segment relative to the baseTimeline as an exception. Because a
860      * base segment is normally larger than our segments, this may add one or
861      * more segment ranges to the exception list.
862      * <p>
863      * An exception segment is defined as a segment
864      * to exclude from what would otherwise be considered a valid segment of
865      * the timeline. An exception segment can not be contained inside an
866      * already excluded segment. If so, no action will occur (the proposed
867      * exception segment will be discarded).
868      * <p>
869      * The segment is identified by a domainValue into any part of the
870      * baseTimeline segment.
871      *
872      * @param domainValue domain value to teat as a baseTimeline exception.
873      */

874     public void addBaseTimelineException(long domainValue) {
875
876         Segment baseSegment = this.baseTimeline.getSegment(domainValue);
877         if (baseSegment.inIncludeSegments()) {
878
879             // cycle through all the segments contained in the BaseTimeline
880
// exception segment
881
Segment segment = getSegment(baseSegment.getSegmentStart());
882             while (segment.getSegmentStart() <= baseSegment.getSegmentEnd()) {
883                 if (segment.inIncludeSegments()) {
884
885                     // find all consecutive included segments
886
long fromDomainValue = segment.getSegmentStart();
887                     long toDomainValue;
888                     do {
889                         toDomainValue = segment.getSegmentEnd();
890                         segment.inc();
891                     }
892                     while (segment.inIncludeSegments());
893
894                     // add the interval as an exception
895
addException(fromDomainValue, toDomainValue);
896
897                 }
898                 else {
899                     // this is not one of our included segment, skip it
900
segment.inc();
901                 }
902             }
903         }
904     }
905
906     /**
907      * Adds a segment relative to the baseTimeline as an exception. An
908      * exception segment is defined as a segment to exclude from what would
909      * otherwise be considered a valid segment of the timeline. An exception
910      * segment can not be contained inside an already excluded segment. If so,
911      * no action will occure (the proposed exception segment will be discarded).
912      * <p>
913      * The segment is identified by a domainValue into any part of the segment.
914      * Therefore the segmentStart <= domainValue <= segmentEnd.
915      *
916      * @param date date domain value to treat as a baseTimeline exception
917      */

918     public void addBaseTimelineException(Date JavaDoc date) {
919         addBaseTimelineException(getTime(date));
920     }
921
922     /**
923      * Adds all excluded segments from the BaseTimeline as exceptions to our
924      * timeline. This allows us to combine two timelines for more complex
925      * calculations.
926      *
927      * @param fromBaseDomainValue Start of the range where exclusions will be
928      * extracted.
929      * @param toBaseDomainValue End of the range to process.
930      */

931     public void addBaseTimelineExclusions(long fromBaseDomainValue,
932                                           long toBaseDomainValue) {
933
934         // find first excluded base segment starting fromDomainValue
935
Segment baseSegment = this.baseTimeline.getSegment(fromBaseDomainValue);
936         while (baseSegment.getSegmentStart() <= toBaseDomainValue
937                && !baseSegment.inExcludeSegments()) {
938                    
939             baseSegment.inc();
940             
941         }
942
943         // cycle over all the base segments groups in the range
944
while (baseSegment.getSegmentStart() <= toBaseDomainValue) {
945
946             long baseExclusionRangeEnd = baseSegment.getSegmentStart()
947                  + this.baseTimeline.getSegmentsExcluded()
948                  * this.baseTimeline.getSegmentSize() - 1;
949
950             // cycle through all the segments contained in the base exclusion
951
// area
952
Segment segment = getSegment(baseSegment.getSegmentStart());
953             while (segment.getSegmentStart() <= baseExclusionRangeEnd) {
954                 if (segment.inIncludeSegments()) {
955
956                     // find all consecutive included segments
957
long fromDomainValue = segment.getSegmentStart();
958                     long toDomainValue;
959                     do {
960                         toDomainValue = segment.getSegmentEnd();
961                         segment.inc();
962                     }
963                     while (segment.inIncludeSegments());
964
965                     // add the interval as an exception
966
addException(new BaseTimelineSegmentRange(
967                         fromDomainValue, toDomainValue
968                     ));
969                 }
970                 else {
971                     // this is not one of our included segment, skip it
972
segment.inc();
973                 }
974             }
975
976             // go to next base segment group
977
baseSegment.inc(this.baseTimeline.getGroupSegmentCount());
978         }
979     }
980
981     /**
982      * Returns the number of exception segments wholly contained in the
983      * (fromDomainValue, toDomainValue) interval.
984      *
985      * @param fromMillisecond the beginning of the interval.
986      * @param toMillisecond the end of the interval.
987      *
988      * @return Number of exception segments contained in the interval.
989      */

990     public long getExceptionSegmentCount(long fromMillisecond,
991                                          long toMillisecond) {
992         if (toMillisecond < fromMillisecond) {
993             return (0);
994         }
995
996         int n = 0;
997         for (Iterator JavaDoc iter = this.exceptionSegments.iterator();
998              iter.hasNext();) {
999             Segment segment = (Segment) iter.next();
1000            Segment intersection
1001                = segment.intersect(fromMillisecond, toMillisecond);
1002            if (intersection != null) {
1003                n += intersection.getSegmentCount();
1004            }
1005        }
1006
1007        return (n);
1008    }
1009
1010    /**
1011     * Returns a segment that contains a domainValue. If the domainValue is
1012     * not contained in the timeline (because it is not contained in the
1013     * baseTimeline), a Segment that contains
1014     * <code>index + segmentSize*m</code> will be returned for the smallest
1015     * <code>m</code> possible.
1016     *
1017     * @param millisecond index into the segment
1018     *
1019     * @return A Segment that contains index, or the next possible Segment.
1020     */

1021    public Segment getSegment(long millisecond) {
1022        return new Segment(millisecond);
1023    }
1024
1025    /**
1026     * Returns a segment that contains a date. For accurate calculations,
1027     * the calendar should use TIME_ZONE for its calculation (or any other
1028     * similar time zone).
1029     *
1030     * If the date is not contained in the timeline (because it is not
1031     * contained in the baseTimeline), a Segment that contains
1032     * <code>date + segmentSize*m</code> will be returned for the smallest
1033     * <code>m</code> possible.
1034     *
1035     * @param date date into the segment
1036     *
1037     * @return A Segment that contains date, or the next possible Segment.
1038     */

1039    public Segment getSegment(Date JavaDoc date) {
1040        return (getSegment(getTime(date)));
1041    }
1042
1043    /**
1044     * Convenient method to test equality in two objects, taking into account
1045     * nulls.
1046     *
1047     * @param o first object to compare
1048     * @param p second object to compare
1049     *
1050     * @return <code>true</code> if both objects are equal or both
1051     * <code>null</code>, <code>false</code> otherwise.
1052     */

1053    private boolean equals(Object JavaDoc o, Object JavaDoc p) {
1054        return (o == p || ((o != null) && o.equals(p)));
1055    }
1056
1057    /**
1058     * Returns true if we are equal to the parameter
1059     *
1060     * @param o Object to verify with us
1061     *
1062     * @return <code>true</code> or <code>false</code>
1063     */

1064    public boolean equals(Object JavaDoc o) {
1065        if (o instanceof SegmentedTimeline) {
1066            SegmentedTimeline other = (SegmentedTimeline) o;
1067            
1068            boolean b0 = (this.segmentSize == other.getSegmentSize());
1069            boolean b1 = (this.segmentsIncluded == other.getSegmentsIncluded());
1070            boolean b2 = (this.segmentsExcluded == other.getSegmentsExcluded());
1071            boolean b3 = (this.startTime == other.getStartTime());
1072            boolean b4 = equals(
1073                this.exceptionSegments, other.getExceptionSegments()
1074            );
1075            return b0 && b1 && b2 && b3 && b4;
1076        }
1077        else {
1078            return (false);
1079        }
1080    }
1081    
1082    /**
1083     * Returns a hash code for this object.
1084     *
1085     * @return A hash code.
1086     */

1087    public int hashCode() {
1088        int result = 19;
1089        result = 37 * result
1090                 + (int) (this.segmentSize ^ (this.segmentSize >>> 32));
1091        result = 37 * result + (int) (this.startTime ^ (this.startTime >>> 32));
1092        return result;
1093    }
1094
1095    /**
1096     * Preforms a binary serach in the exceptionSegments sorted array. This
1097     * array can contain Segments or SegmentRange objects.
1098     *
1099     * @param segment the key to be searched for.
1100     *
1101     * @return index of the search segment, if it is contained in the list;
1102     * otherwise, <tt>(-(<i>insertion point</i>) - 1)</tt>. The
1103     * <i>insertion point</i> is defined as the point at which the
1104     * segment would be inserted into the list: the index of the first
1105     * element greater than the key, or <tt>list.size()</tt>, if all
1106     * elements in the list are less than the specified segment. Note
1107     * that this guarantees that the return value will be &gt;= 0 if
1108     * and only if the key is found.
1109     */

1110    private int binarySearchExceptionSegments(Segment segment) {
1111        int low = 0;
1112        int high = this.exceptionSegments.size() - 1;
1113
1114        while (low <= high) {
1115            int mid = (low + high) / 2;
1116            Segment midSegment = (Segment) this.exceptionSegments.get(mid);
1117
1118            // first test for equality (contains or contained)
1119
if (segment.contains(midSegment) || midSegment.contains(segment)) {
1120                return mid;
1121            }
1122
1123            if (midSegment.before(segment)) {
1124                low = mid + 1;
1125            }
1126            else if (midSegment.after(segment)) {
1127                high = mid - 1;
1128            }
1129            else {
1130                throw new IllegalStateException JavaDoc("Invalid condition.");
1131            }
1132        }
1133        return -(low + 1); // key not found
1134
}
1135
1136    /**
1137     * Special method that handles conversion between the Default Time Zone and
1138     * a UTC time zone with no DST. This is needed so all days have the same
1139     * size. This method is the prefered way of converting a Data into
1140     * milliseconds for usage in this class.
1141     *
1142     * @param date Date to convert to long.
1143     *
1144     * @return The milliseconds.
1145     */

1146    public long getTime(Date JavaDoc date) {
1147        long result = date.getTime();
1148        if (this.adjustForDaylightSaving) {
1149            this.workingCalendar.setTime(date);
1150            this.workingCalendarNoDST.set(
1151                this.workingCalendar.get(Calendar.YEAR),
1152                this.workingCalendar.get(Calendar.MONTH),
1153                this.workingCalendar.get(Calendar.DATE),
1154                this.workingCalendar.get(Calendar.HOUR_OF_DAY),
1155                this.workingCalendar.get(Calendar.MINUTE),
1156                this.workingCalendar.get(Calendar.SECOND)
1157            );
1158            this.workingCalendarNoDST.set(
1159                Calendar.MILLISECOND,
1160                this.workingCalendar.get(Calendar.MILLISECOND)
1161            );
1162            Date JavaDoc revisedDate = this.workingCalendarNoDST.getTime();
1163            result = revisedDate.getTime();
1164        }
1165        
1166        return result;
1167    }
1168
1169    /**
1170     * Converts a millisecond value into a {@link Date} object.
1171     *
1172     * @param value the millisecond value.
1173     *
1174     * @return The date.
1175     */

1176    public Date JavaDoc getDate(long value) {
1177        this.workingCalendarNoDST.setTime(new Date JavaDoc(value));
1178        return (this.workingCalendarNoDST.getTime());
1179    }
1180
1181    /**
1182     * Returns a clone of the timeline.
1183     *
1184     * @return A clone.
1185     *
1186     * @throws CloneNotSupportedException ??.
1187     */

1188    public Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
1189        SegmentedTimeline clone = (SegmentedTimeline) super.clone();
1190        return clone;
1191    }
1192
1193    /**
1194     * Internal class to represent a valid segment for this timeline. A segment
1195     * is valid on a timeline if it is part of its included, excluded or
1196     * exception segments.
1197     * <p>
1198     * Each segment will know its segment number, segmentStart, segmentEnd and
1199     * index inside the segment.
1200     */

1201    public class Segment implements Comparable JavaDoc, Cloneable JavaDoc, Serializable JavaDoc {
1202
1203        /** The segment number. */
1204        protected long segmentNumber;
1205        
1206        /** The segment start. */
1207        protected long segmentStart;
1208        
1209        /** The segment end. */
1210        protected long segmentEnd;
1211        
1212        /** A reference point within the segment. */
1213        protected long millisecond;
1214
1215        /**
1216         * Protected constructor only used by sub-classes.
1217         */

1218        protected Segment() {
1219            // empty
1220
}
1221
1222        /**
1223         * Creates a segment for a given point in time.
1224         *
1225         * @param millisecond the millisecond (as encoded by java.util.Date).
1226         */

1227        protected Segment(long millisecond) {
1228            this.segmentNumber = calculateSegmentNumber(millisecond);
1229            this.segmentStart = SegmentedTimeline.this.startTime
1230                + this.segmentNumber * SegmentedTimeline.this.segmentSize;
1231            this.segmentEnd
1232                = this.segmentStart + SegmentedTimeline.this.segmentSize - 1;
1233            this.millisecond = millisecond;
1234        }
1235
1236        /**
1237         * Calculates the segment number for a given millisecond.
1238         *
1239         * @param millis the millisecond (as encoded by java.util.Date).
1240         *
1241         * @return The segment number.
1242         */

1243        public long calculateSegmentNumber(long millis) {
1244            if (millis >= SegmentedTimeline.this.startTime) {
1245                return (millis - SegmentedTimeline.this.startTime)
1246                    / SegmentedTimeline.this.segmentSize;
1247            }
1248            else {
1249                return ((millis - SegmentedTimeline.this.startTime)
1250                    / SegmentedTimeline.this.segmentSize) - 1;
1251            }
1252        }
1253
1254        /**
1255         * Returns the segment number of this segment. Segments start at 0.
1256         *
1257         * @return The segment number.
1258         */

1259        public long getSegmentNumber() {
1260            return this.segmentNumber;
1261        }
1262
1263        /**
1264         * Returns always one (the number of segments contained in this
1265         * segment).
1266         *
1267         * @return The segment count (always 1 for this class).
1268         */

1269        public long getSegmentCount() {
1270            return 1;
1271        }
1272
1273        /**
1274         * Gets the start of this segment in ms.
1275         *
1276         * @return The segment start.
1277         */

1278        public long getSegmentStart() {
1279            return this.segmentStart;
1280        }
1281
1282        /**
1283         * Gets the end of this segment in ms.
1284         *
1285         * @return The segment end.
1286         */

1287        public long getSegmentEnd() {
1288            return this.segmentEnd;
1289        }
1290
1291        /**
1292         * Returns the millisecond used to reference this segment (always
1293         * between the segmentStart and segmentEnd).
1294         *
1295         * @return The millisecond.
1296         */

1297        public long getMillisecond() {
1298            return this.millisecond;
1299        }
1300        
1301        /**
1302         * Returns a {@link java.util.Date} that represents the reference point
1303         * for this segment.
1304         *
1305         * @return The date.
1306         */

1307        public Date JavaDoc getDate() {
1308            return SegmentedTimeline.this.getDate(this.millisecond);
1309        }
1310
1311        /**
1312         * Returns true if a particular millisecond is contained in this
1313         * segment.
1314         *
1315         * @param millis the millisecond to verify.
1316         *
1317         * @return <code>true</code> if the millisecond is contained in the
1318         * segment.
1319         */

1320        public boolean contains(long millis) {
1321            return (this.segmentStart <= millis && millis <= this.segmentEnd);
1322        }
1323
1324        /**
1325         * Returns <code>true</code> if an interval is contained in this
1326         * segment.
1327         *
1328         * @param from the start of the interval.
1329         * @param to the end of the interval.
1330         *
1331         * @return <code>true</code> if the interval is contained in the
1332         * segment.
1333         */

1334        public boolean contains(long from, long to) {
1335            return (this.segmentStart <= from && to <= this.segmentEnd);
1336        }
1337
1338        /**
1339         * Returns <code>true</code> if a segment is contained in this segment.
1340         *
1341         * @param segment the segment to test for inclusion
1342         *
1343         * @return <code>true</code> if the segment is contained in this
1344         * segment.
1345         */

1346        public boolean contains(Segment segment) {
1347            return contains(segment.getSegmentStart(), segment.getSegmentEnd());
1348        }
1349
1350        /**
1351         * Returns <code>true</code> if this segment is contained in an
1352         * interval.
1353         *
1354         * @param from the start of the interval.
1355         * @param to the end of the interval.
1356         *
1357         * @return <code>true</code> if this segment is contained in the
1358         * interval.
1359         */

1360        public boolean contained(long from, long to) {
1361            return (from <= this.segmentStart && this.segmentEnd <= to);
1362        }
1363
1364        /**
1365         * Returns a segment that is the intersection of this segment and the
1366         * interval.
1367         *
1368         * @param from the start of the interval.
1369         * @param to the end of the interval.
1370         *
1371         * @return A segment.
1372         */

1373        public Segment intersect(long from, long to) {
1374            if (from <= this.segmentStart && this.segmentEnd <= to) {
1375                return this;
1376            }
1377            else {
1378                return null;
1379            }
1380        }
1381
1382        /**
1383         * Returns <code>true</code> if this segment is wholly before another
1384         * segment.
1385         *
1386         * @param other the other segment.
1387         *
1388         * @return A boolean.
1389         */

1390        public boolean before(Segment other) {
1391            return (this.segmentEnd < other.getSegmentStart());
1392        }
1393
1394        /**
1395         * Returns <code>true</code> if this segment is wholly after another
1396         * segment.
1397         *
1398         * @param other the other segment.
1399         *
1400         * @return A boolean.
1401         */

1402        public boolean after(Segment other) {
1403            return (this.segmentStart > other.getSegmentEnd());
1404        }
1405
1406        /**
1407         * Tests an object (usually another <code>Segment</code>) for equality
1408         * with this segment.
1409         *
1410         * @param object The other segment to compare with us
1411         *
1412         * @return <code>true</code> if we are the same segment
1413         */

1414        public boolean equals(Object JavaDoc object) {
1415            if (object instanceof Segment) {
1416                Segment other = (Segment) object;
1417                return (this.segmentNumber == other.getSegmentNumber()
1418                        && this.segmentStart == other.getSegmentStart()
1419                        && this.segmentEnd == other.getSegmentEnd()
1420                        && this.millisecond == other.getMillisecond());
1421            }
1422            else {
1423                return false;
1424            }
1425        }
1426
1427        /**
1428         * Returns a copy of ourselves or <code>null</code> if there was an
1429         * exception during cloning.
1430         *
1431         * @return A copy of this segment.
1432         */

1433        public Segment copy() {
1434            try {
1435                return (Segment) this.clone();
1436            }
1437            catch (CloneNotSupportedException JavaDoc e) {
1438                return null;
1439            }
1440        }
1441
1442        /**
1443         * Will compare this Segment with another Segment (from Comparable
1444         * interface).
1445         *
1446         * @param object The other Segment to compare with
1447         *
1448         * @return -1: this < object, 0: this.equal(object) and
1449         * +1: this > object
1450         */

1451        public int compareTo(Object JavaDoc object) {
1452            Segment other = (Segment) object;
1453            if (this.before(other)) {
1454                return -1;
1455            }
1456            else if (this.after(other)) {
1457                return +1;
1458            }
1459            else {
1460                return 0;
1461            }
1462        }
1463
1464        /**
1465         * Returns true if we are an included segment and we are not an
1466         * exception.
1467         *
1468         * @return <code>true</code> or <code>false</code>.
1469         */

1470        public boolean inIncludeSegments() {
1471            if (getSegmentNumberRelativeToGroup()
1472                    < SegmentedTimeline.this.segmentsIncluded) {
1473                return !inExceptionSegments();
1474            }
1475            else {
1476                return false;
1477            }
1478        }
1479
1480        /**
1481         * Returns true if we are an excluded segment.
1482         *
1483         * @return <code>true</code> or <code>false</code>.
1484         */

1485        public boolean inExcludeSegments() {
1486            return getSegmentNumberRelativeToGroup()
1487                >= SegmentedTimeline.this.segmentsIncluded;
1488        }
1489
1490        /**
1491         * Calculate the segment number relative to the segment group. This
1492         * will be a number between 0 and segmentsGroup-1. This value is
1493         * calculated from the segmentNumber. Special care is taken for
1494         * negative segmentNumbers.
1495         *
1496         * @return The segment number.
1497         */

1498        private long getSegmentNumberRelativeToGroup() {
1499            long p = (this.segmentNumber
1500                    % SegmentedTimeline.this.groupSegmentCount);
1501            if (p < 0) {
1502                p += SegmentedTimeline.this.groupSegmentCount;
1503            }
1504            return p;
1505        }
1506
1507        /**
1508         * Returns true if we are an exception segment. This is implemented via
1509         * a binary search on the exceptionSegments sorted list.
1510         *
1511         * If the segment is not listed as an exception in our list and we have
1512         * a baseTimeline, a check is performed to see if the segment is inside
1513         * an excluded segment from our base. If so, it is also considered an
1514         * exception.
1515         *
1516         * @return <code>true</code> if we are an exception segment.
1517         */

1518        public boolean inExceptionSegments() {
1519            return binarySearchExceptionSegments(this) >= 0;
1520        }
1521
1522        /**
1523         * Increments the internal attributes of this segment by a number of
1524         * segments.
1525         *
1526         * @param n Number of segments to increment.
1527         */

1528        public void inc(long n) {
1529            this.segmentNumber += n;
1530            long m = n * SegmentedTimeline.this.segmentSize;
1531            this.segmentStart += m;
1532            this.segmentEnd += m;
1533            this.millisecond += m;
1534        }
1535
1536        /**
1537         * Increments the internal attributes of this segment by one segment.
1538         * The exact time incremented is segmentSize.
1539         */

1540        public void inc() {
1541            inc(1);
1542        }
1543
1544        /**
1545         * Decrements the internal attributes of this segment by a number of
1546         * segments.
1547         *
1548         * @param n Number of segments to decrement.
1549         */

1550        public void dec(long n) {
1551            this.segmentNumber -= n;
1552            long m = n * SegmentedTimeline.this.segmentSize;
1553            this.segmentStart -= m;
1554            this.segmentEnd -= m;
1555            this.millisecond -= m;
1556        }
1557
1558        /**
1559         * Decrements the internal attributes of this segment by one segment.
1560         * The exact time decremented is segmentSize.
1561         */

1562        public void dec() {
1563            dec(1);
1564        }
1565
1566        /**
1567         * Moves the index of this segment to the beginning if the segment.
1568         */

1569        public void moveIndexToStart() {
1570            this.millisecond = this.segmentStart;
1571        }
1572
1573        /**
1574         * Moves the index of this segment to the end of the segment.
1575         */

1576        public void moveIndexToEnd() {
1577            this.millisecond = this.segmentEnd;
1578        }
1579
1580    }
1581
1582    /**
1583     * Private internal class to represent a range of segments. This class is
1584     * mainly used to store in one object a range of exception segments. This
1585     * optimizes certain timelines that use a small segment size (like an
1586     * intraday timeline) allowing them to express a day exception as one
1587     * SegmentRange instead of multi Segments.
1588     */

1589    protected class SegmentRange extends Segment {
1590
1591        /** The number of segments in the range. */
1592        private long segmentCount;
1593
1594        /**
1595         * Creates a SegmentRange between a start and end domain values.
1596         *
1597         * @param fromMillisecond start of the range
1598         * @param toMillisecond end of the range
1599         */

1600        public SegmentRange(long fromMillisecond, long toMillisecond) {
1601
1602            Segment start = getSegment(fromMillisecond);
1603            Segment end = getSegment(toMillisecond);
1604// if (start.getSegmentStart() != fromMillisecond
1605
// || end.getSegmentEnd() != toMillisecond) {
1606
// throw new IllegalArgumentException("Invalid Segment Range ["
1607
// + fromMillisecond + "," + toMillisecond + "]");
1608
// }
1609

1610            this.millisecond = fromMillisecond;
1611            this.segmentNumber = calculateSegmentNumber(fromMillisecond);
1612            this.segmentStart = start.segmentStart;
1613            this.segmentEnd = end.segmentEnd;
1614            this.segmentCount
1615                = (end.getSegmentNumber() - start.getSegmentNumber() + 1);
1616        }
1617
1618        /**
1619         * Returns the number of segments contained in this range.
1620         *
1621         * @return The segment count.
1622         */

1623        public long getSegmentCount() {
1624            return this.segmentCount;
1625        }
1626
1627        /**
1628         * Returns a segment that is the intersection of this segment and the
1629         * interval.
1630         *
1631         * @param from the start of the interval.
1632         * @param to the end of the interval.
1633         *
1634         * @return The intersection.
1635         */

1636        public Segment intersect(long from, long to) {
1637            
1638            // Segment fromSegment = getSegment(from);
1639
// fromSegment.inc();
1640
// Segment toSegment = getSegment(to);
1641
// toSegment.dec();
1642
long start = Math.max(from, this.segmentStart);
1643            long end = Math.min(to, this.segmentEnd);
1644            // long start = Math.max(
1645
// fromSegment.getSegmentStart(), this.segmentStart
1646
// );
1647
// long end = Math.min(toSegment.getSegmentEnd(), this.segmentEnd);
1648
if (start <= end) {
1649                return new SegmentRange(start, end);
1650            }
1651            else {
1652                return null;
1653            }
1654        }
1655
1656        /**
1657         * Returns true if all Segments of this SegmentRenge are an included
1658         * segment and are not an exception.
1659         *
1660         * @return <code>true</code> or </code>false</code>.
1661         */

1662        public boolean inIncludeSegments() {
1663            for (Segment segment = getSegment(this.segmentStart);
1664                segment.getSegmentStart() < this.segmentEnd;
1665                segment.inc()) {
1666                if (!segment.inIncludeSegments()) {
1667                    return (false);
1668                }
1669            }
1670            return true;
1671        }
1672
1673        /**
1674         * Returns true if we are an excluded segment.
1675         *
1676         * @return <code>true</code> or </code>false</code>.
1677         */

1678        public boolean inExcludeSegments() {
1679            for (Segment segment = getSegment(this.segmentStart);
1680                segment.getSegmentStart() < this.segmentEnd;
1681                segment.inc()) {
1682                if (!segment.inExceptionSegments()) {
1683                    return (false);
1684                }
1685            }
1686            return true;
1687        }
1688
1689        /**
1690         * Not implemented for SegmentRange. Always throws
1691         * IllegalArgumentException.
1692         *
1693         * @param n Number of segments to increment.
1694         */

1695        public void inc(long n) {
1696            throw new IllegalArgumentException JavaDoc(
1697                "Not implemented in SegmentRange"
1698            );
1699        }
1700
1701    }
1702
1703    /**
1704     * Special <code>SegmentRange</code> that came from the BaseTimeline.
1705     */

1706    protected class BaseTimelineSegmentRange extends SegmentRange {
1707
1708        /**
1709         * Constructor.
1710         *
1711         * @param fromDomainValue the start value.
1712         * @param toDomainValue the end value.
1713         */

1714        public BaseTimelineSegmentRange(long fromDomainValue,
1715                                        long toDomainValue) {
1716            super(fromDomainValue, toDomainValue);
1717        }
1718       
1719    }
1720
1721}
1722
Popular Tags