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