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