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