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