alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

jfreechart example source code file (SegmentedTimeline.java)

This example jfreechart source code file (SegmentedTimeline.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - jfreechart tags/keywords

basetimelinesegmentrange, calendar, date, date, gregoriancalendar, illegalargumentexception, io, segment, segment, segmentedtimeline, segmentedtimeline, segmentrange, segmentrange, simpletimezone, timezone, util

The jfreechart SegmentedTimeline.java source code

/* ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 *
 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jfreechart/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 *
 * -----------------------
 * SegmentedTimeline.java
 * -----------------------
 * (C) Copyright 2003-2008, by Bill Kelemen and Contributors.
 *
 * Original Author:  Bill Kelemen;
 * Contributor(s):   David Gilbert (for Object Refinery Limited);
 *
 * Changes
 * -------
 * 23-May-2003 : Version 1 (BK);
 * 15-Aug-2003 : Implemented Cloneable (DG);
 * 01-Jun-2004 : Modified to compile with JDK 1.2.2 (DG);
 * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG);
 * 04-Nov-2004 : Reverted change of 30-Sep-2004, won't work with JDK 1.3 (DG);
 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
 * ------------- JFREECHART 1.0.x ---------------------------------------------
 * 14-Nov-2006 : Fix in toTimelineValue(long) to avoid stack overflow (DG);
 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
 * 11-Jul-2007 : Fixed time zone bugs (DG);
 * 06-Jun-2008 : Performance enhancement posted in forum (DG);
 *
 */

package org.jfree.chart.axis;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

/**
 * A {@link Timeline} that implements a "segmented" timeline with included,
 * excluded and exception segments.
 * <P>
 * A Timeline will present a series of values to be used for an axis. Each
 * Timeline must provide transformation methods between domain values and
 * timeline values.
 * <P>
 * A timeline can be used as parameter to a
 * {@link org.jfree.chart.axis.DateAxis} to define the values that this axis
 * supports. This class implements a timeline formed by segments of equal
 * length (ex. days, hours, minutes) where some segments can be included in the
 * timeline and others excluded. Therefore timelines like "working days" or
 * "working hours" can be created where non-working days or non-working hours
 * respectively can be removed from the timeline, and therefore from the axis.
 * This creates a smooth plot with equal separation between all included
 * segments.
 * <P>
 * Because Timelines were created mainly for Date related axis, values are
 * represented as longs instead of doubles. In this case, the domain value is
 * just the number of milliseconds since January 1, 1970, 00:00:00 GMT as
 * defined by the getTime() method of {@link java.util.Date}.
 * <P>
 * In this class, a segment is defined as a unit of time of fixed length.
 * Examples of segments are: days, hours, minutes, etc. The size of a segment
 * is defined as the number of milliseconds in the segment. Some useful segment
 * sizes are defined as constants in this class: DAY_SEGMENT_SIZE,
 * HOUR_SEGMENT_SIZE, FIFTEEN_MINUTE_SEGMENT_SIZE and MINUTE_SEGMENT_SIZE.
 * <P>
 * Segments are group together to form a Segment Group. Each Segment Group will
 * contain a number of Segments included and a number of Segments excluded. This
 * Segment Group structure will repeat for the whole timeline.
 * <P>
 * For example, a working days SegmentedTimeline would be formed by a group of
 * 7 daily segments, where there are 5 included (Monday through Friday) and 2
 * excluded (Saturday and Sunday) segments.
 * <P>
 * Following is a diagram that explains the major attributes that define a
 * segment.  Each box is one segment and must be of fixed length (ms, second,
 * hour, day, etc).
 * <p>
 * <pre>
 * start time
 *   |
 *   v
 *   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 ...
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+...
 * |  |  |  |  |  |EE|EE|  |  |  |  |  |EE|EE|  |  |  |  |  |EE|EE|
 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+...
 *  \____________/ \___/            \_/
 *        \/         |               |
 *     included   excluded        segment
 *     segments   segments         size
 *  \_________  _______/
 *            \/
 *       segment group
 * </pre>
 * Legend:<br>
 * <space> = Included segment<br>
 * EE      = Excluded segments in the base timeline<br>
 * <p>
 * In the example, the following segment attributes are presented:
 * <ul>
 * <li>segment size: the size of each segment in ms.
 * <li>start time: the start of the first segment of the first segment group to
 *     consider.
 * <li>included segments: the number of segments to include in the group.
 * <li>excluded segments: the number of segments to exclude in the group.
 * </ul>
 * <p>
 * Exception Segments are allowed. These exception segments are defined as
 * segments that would have been in the included segments of the Segment Group,
 * but should be excluded for special reasons. In the previous working days
 * SegmentedTimeline example, holidays would be considered exceptions.
 * <P>
 * Additionally the <code>startTime, or start of the first Segment of
 * the smallest segment group needs to be defined. This startTime could be
 * relative to January 1, 1970, 00:00:00 GMT or any other date. This creates a
 * point of reference to start counting Segment Groups. For example, for the
 * working days SegmentedTimeline, the <code>startTime could be
 * 00:00:00 GMT of the first Monday after January 1, 1970. In this class, the
 * constant FIRST_MONDAY_AFTER_1900 refers to a reference point of the first
 * Monday of the last century.
 * <p>
 * A SegmentedTimeline can include a baseTimeline. This combination of
 * timelines allows the creation of more complex timelines. For example, in
 * order to implement a SegmentedTimeline for an intraday stock trading
 * application, where the trading period is defined as 9:00 AM through 4:00 PM
 * Monday through Friday, two SegmentedTimelines are used. The first one (the
 * baseTimeline) would be a working day SegmentedTimeline (daily timeline
 * Monday through Friday). On top of this baseTimeline, a second one is defined
 * that maps the 9:00 AM to 4:00 PM period. Because the baseTimeline defines a
 * timeline of Monday through Friday, the resulting (combined) timeline will
 * expose the period 9:00 AM through 4:00 PM only on Monday through Friday,
 * and will remove all other intermediate intervals.
 * <P>
 * Two factory methods newMondayThroughFridayTimeline() and
 * newFifteenMinuteTimeline() are provided as examples to create special
 * SegmentedTimelines.
 *
 * @see org.jfree.chart.axis.DateAxis
 */
public class SegmentedTimeline implements Timeline, Cloneable, Serializable {

    /** For serialization. */
    private static final long serialVersionUID = 1093779862539903110L;

    ////////////////////////////////////////////////////////////////////////////
    // predetermined segments sizes
    ////////////////////////////////////////////////////////////////////////////

    /** Defines a day segment size in ms. */
    public static final long DAY_SEGMENT_SIZE = 24 * 60 * 60 * 1000;

    /** Defines a one hour segment size in ms. */
    public static final long HOUR_SEGMENT_SIZE = 60 * 60 * 1000;

    /** Defines a 15-minute segment size in ms. */
    public static final long FIFTEEN_MINUTE_SEGMENT_SIZE = 15 * 60 * 1000;

    /** Defines a one-minute segment size in ms. */
    public static final long MINUTE_SEGMENT_SIZE = 60 * 1000;

    ////////////////////////////////////////////////////////////////////////////
    // other constants
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Utility constant that defines the startTime as the first monday after
     * 1/1/1970.  This should be used when creating a SegmentedTimeline for
     * Monday through Friday. See static block below for calculation of this
     * constant.
     *
     * @deprecated As of 1.0.7.  This field doesn't take into account changes
     *         to the default time zone.
     */
    public static long FIRST_MONDAY_AFTER_1900;

    /**
     * Utility TimeZone object that has no DST and an offset equal to the
     * default TimeZone. This allows easy arithmetic between days as each one
     * will have equal size.
     *
     * @deprecated As of 1.0.7.  This field is initialised based on the
     *         default time zone, and doesn't take into account subsequent
     *         changes to the default.
     */
    public static TimeZone NO_DST_TIME_ZONE;

    /**
     * This is the default time zone where the application is running. See
     * getTime() below where we make use of certain transformations between
     * times in the default time zone and the no-dst time zone used for our
     * calculations.
     *
     * @deprecated As of 1.0.7.  When the default time zone is required,
     *         just call <code>TimeZone.getDefault().
     */
    public static TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault();

    /**
     * This will be a utility calendar that has no DST but is shifted relative
     * to the default time zone's offset.
     */
    private Calendar workingCalendarNoDST;

    /**
     * This will be a utility calendar that used the default time zone.
     */
    private Calendar workingCalendar = Calendar.getInstance();

    ////////////////////////////////////////////////////////////////////////////
    // private attributes
    ////////////////////////////////////////////////////////////////////////////

    /** Segment size in ms. */
    private long segmentSize;

    /** Number of consecutive segments to include in a segment group. */
    private int segmentsIncluded;

    /** Number of consecutive segments to exclude in a segment group. */
    private int segmentsExcluded;

    /** Number of segments in a group (segmentsIncluded + segmentsExcluded). */
    private int groupSegmentCount;

    /**
     * Start of time reference from time zero (1/1/1970).
     * This is the start of segment #0.
     */
    private long startTime;

    /** Consecutive ms in segmentsIncluded (segmentsIncluded * segmentSize). */
    private long segmentsIncludedSize;

    /** Consecutive ms in segmentsExcluded (segmentsExcluded * segmentSize). */
    private long segmentsExcludedSize;

    /** ms in a segment group (segmentsIncludedSize + segmentsExcludedSize). */
    private long segmentsGroupSize;

    /**
     * List of exception segments (exceptions segments that would otherwise be
     * included based on the periodic (included, excluded) grouping).
     */
    private List exceptionSegments = new ArrayList();

    /**
     * This base timeline is used to specify exceptions at a higher level. For
     * example, if we are a intraday timeline and want to exclude holidays,
     * instead of having to exclude all intraday segments for the holiday,
     * segments from this base timeline can be excluded. This baseTimeline is
     * always optional and is only a convenience method.
     * <p>
     * Additionally, all excluded segments from this baseTimeline will be
     * considered exceptions at this level.
     */
    private SegmentedTimeline baseTimeline;

    /** A flag that controls whether or not to adjust for daylight saving. */
    private boolean adjustForDaylightSaving = false;

    ////////////////////////////////////////////////////////////////////////////
    // static block
    ////////////////////////////////////////////////////////////////////////////

    static {
        // make a time zone with no DST for our Calendar calculations
        int offset = TimeZone.getDefault().getRawOffset();
        NO_DST_TIME_ZONE = new SimpleTimeZone(offset, "UTC-" + offset);

        // calculate midnight of first monday after 1/1/1900 relative to
        // current locale
        Calendar cal = new GregorianCalendar(NO_DST_TIME_ZONE);
        cal.set(1900, 0, 1, 0, 0, 0);
        cal.set(Calendar.MILLISECOND, 0);
        while (cal.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
            cal.add(Calendar.DATE, 1);
        }
        // FIRST_MONDAY_AFTER_1900 = cal.getTime().getTime();
        // preceding code won't work with JDK 1.3
        FIRST_MONDAY_AFTER_1900 = cal.getTime().getTime();
    }

    ////////////////////////////////////////////////////////////////////////////
    // constructors and factory methods
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Constructs a new segmented timeline, optionaly using another segmented
     * timeline as its base. This chaining of SegmentedTimelines allows further
     * segmentation into smaller timelines.
     *
     * If a base
     *
     * @param segmentSize the size of a segment in ms. This time unit will be
     *        used to compute the included and excluded segments of the
     *        timeline.
     * @param segmentsIncluded Number of consecutive segments to include.
     * @param segmentsExcluded Number of consecutive segments to exclude.
     */
    public SegmentedTimeline(long segmentSize,
                             int segmentsIncluded,
                             int segmentsExcluded) {

        this.segmentSize = segmentSize;
        this.segmentsIncluded = segmentsIncluded;
        this.segmentsExcluded = segmentsExcluded;

        this.groupSegmentCount = this.segmentsIncluded + this.segmentsExcluded;
        this.segmentsIncludedSize = this.segmentsIncluded * this.segmentSize;
        this.segmentsExcludedSize = this.segmentsExcluded * this.segmentSize;
        this.segmentsGroupSize = this.segmentsIncludedSize
                                 + this.segmentsExcludedSize;
        int offset = TimeZone.getDefault().getRawOffset();
        TimeZone z = new SimpleTimeZone(offset, "UTC-" + offset);
        this.workingCalendarNoDST = new GregorianCalendar(z,
                Locale.getDefault());
    }

    /**
     * Returns the milliseconds for midnight of the first Monday after
     * 1-Jan-1900, ignoring daylight savings.
     *
     * @return The milliseconds.
     *
     * @since 1.0.7
     */
    public static long firstMondayAfter1900() {
        int offset = TimeZone.getDefault().getRawOffset();
        TimeZone z = new SimpleTimeZone(offset, "UTC-" + offset);

        // calculate midnight of first monday after 1/1/1900 relative to
        // current locale
        Calendar cal = new GregorianCalendar(z);
        cal.set(1900, 0, 1, 0, 0, 0);
        cal.set(Calendar.MILLISECOND, 0);
        while (cal.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
            cal.add(Calendar.DATE, 1);
        }
        //return cal.getTimeInMillis();
        // preceding code won't work with JDK 1.3
        return cal.getTime().getTime();
    }

    /**
     * Factory method to create a Monday through Friday SegmentedTimeline.
     * <P>
     * The <code>startTime of the resulting timeline will be midnight
     * of the first Monday after 1/1/1900.
     *
     * @return A fully initialized SegmentedTimeline.
     */
    public static SegmentedTimeline newMondayThroughFridayTimeline() {
        SegmentedTimeline timeline
            = new SegmentedTimeline(DAY_SEGMENT_SIZE, 5, 2);
        timeline.setStartTime(firstMondayAfter1900());
        return timeline;
    }

    /**
     * Factory method to create a 15-min, 9:00 AM thought 4:00 PM, Monday
     * through Friday SegmentedTimeline.
     * <P>
     * This timeline uses a segmentSize of FIFTEEN_MIN_SEGMENT_SIZE. The
     * segment group is defined as 28 included segments (9:00 AM through
     * 4:00 PM) and 68 excluded segments (4:00 PM through 9:00 AM the next day).
     * <P>
     * In order to exclude Saturdays and Sundays it uses a baseTimeline that
     * only includes Monday through Friday days.
     * <P>
     * The <code>startTime of the resulting timeline will be 9:00 AM
     * after the startTime of the baseTimeline. This will correspond to 9:00 AM
     * of the first Monday after 1/1/1900.
     *
     * @return A fully initialized SegmentedTimeline.
     */
    public static SegmentedTimeline newFifteenMinuteTimeline() {
        SegmentedTimeline timeline = new SegmentedTimeline(
                FIFTEEN_MINUTE_SEGMENT_SIZE, 28, 68);
        timeline.setStartTime(firstMondayAfter1900() + 36
                * timeline.getSegmentSize());
        timeline.setBaseTimeline(newMondayThroughFridayTimeline());
        return timeline;
    }

    /**
     * Returns the flag that controls whether or not the daylight saving
     * adjustment is applied.
     *
     * @return A boolean.
     */
    public boolean getAdjustForDaylightSaving() {
        return this.adjustForDaylightSaving;
    }

    /**
     * Sets the flag that controls whether or not the daylight saving adjustment
     * is applied.
     *
     * @param adjust  the flag.
     */
    public void setAdjustForDaylightSaving(boolean adjust) {
        this.adjustForDaylightSaving = adjust;
    }

    ////////////////////////////////////////////////////////////////////////////
    // operations
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Sets the start time for the timeline. This is the beginning of segment
     * zero.
     *
     * @param millisecond  the start time (encoded as in java.util.Date).
     */
    public void setStartTime(long millisecond) {
        this.startTime = millisecond;
    }

    /**
     * Returns the start time for the timeline. This is the beginning of
     * segment zero.
     *
     * @return The start time.
     */
    public long getStartTime() {
        return this.startTime;
    }

    /**
     * Returns the number of segments excluded per segment group.
     *
     * @return The number of segments excluded.
     */
    public int getSegmentsExcluded() {
        return this.segmentsExcluded;
    }

    /**
     * Returns the size in milliseconds of the segments excluded per segment
     * group.
     *
     * @return The size in milliseconds.
     */
    public long getSegmentsExcludedSize() {
        return this.segmentsExcludedSize;
    }

    /**
     * Returns the number of segments in a segment group. This will be equal to
     * segments included plus segments excluded.
     *
     * @return The number of segments.
     */
    public int getGroupSegmentCount() {
        return this.groupSegmentCount;
    }

    /**
     * Returns the size in milliseconds of a segment group. This will be equal
     * to size of the segments included plus the size of the segments excluded.
     *
     * @return The segment group size in milliseconds.
     */
    public long getSegmentsGroupSize() {
        return this.segmentsGroupSize;
    }

    /**
     * Returns the number of segments included per segment group.
     *
     * @return The number of segments.
     */
    public int getSegmentsIncluded() {
        return this.segmentsIncluded;
    }

    /**
     * Returns the size in ms of the segments included per segment group.
     *
     * @return The segment size in milliseconds.
     */
    public long getSegmentsIncludedSize() {
        return this.segmentsIncludedSize;
    }

    /**
     * Returns the size of one segment in ms.
     *
     * @return The segment size in milliseconds.
     */
    public long getSegmentSize() {
        return this.segmentSize;
    }

    /**
     * Returns a list of all the exception segments. This list is not
     * modifiable.
     *
     * @return The exception segments.
     */
    public List getExceptionSegments() {
        return Collections.unmodifiableList(this.exceptionSegments);
    }

    /**
     * Sets the exception segments list.
     *
     * @param exceptionSegments  the exception segments.
     */
    public void setExceptionSegments(List exceptionSegments) {
        this.exceptionSegments = exceptionSegments;
    }

    /**
     * Returns our baseTimeline, or <code>null if none.
     *
     * @return The base timeline.
     */
    public SegmentedTimeline getBaseTimeline() {
        return this.baseTimeline;
    }

    /**
     * Sets the base timeline.
     *
     * @param baseTimeline  the timeline.
     */
    public void setBaseTimeline(SegmentedTimeline baseTimeline) {

        // verify that baseTimeline is compatible with us
        if (baseTimeline != null) {
            if (baseTimeline.getSegmentSize() < this.segmentSize) {
                throw new IllegalArgumentException(
                        "baseTimeline.getSegmentSize() "
                        + "is smaller than segmentSize");
            }
            else if (baseTimeline.getStartTime() > this.startTime) {
                throw new IllegalArgumentException(
                        "baseTimeline.getStartTime() is after startTime");
            }
            else if ((baseTimeline.getSegmentSize() % this.segmentSize) != 0) {
                throw new IllegalArgumentException(
                        "baseTimeline.getSegmentSize() is not multiple of "
                        + "segmentSize");
            }
            else if (((this.startTime
                    - baseTimeline.getStartTime()) % this.segmentSize) != 0) {
                throw new IllegalArgumentException(
                        "baseTimeline is not aligned");
            }
        }

        this.baseTimeline = baseTimeline;
    }

    /**
     * Translates a value relative to the domain value (all Dates) into a value
     * relative to the segmented timeline. The values relative to the segmented
     * timeline are all consecutives starting at zero at the startTime.
     *
     * @param millisecond  the millisecond (as encoded by java.util.Date).
     *
     * @return The timeline value.
     */
    public long toTimelineValue(long millisecond) {

        long result;
        long rawMilliseconds = millisecond - this.startTime;
        long groupMilliseconds = rawMilliseconds % this.segmentsGroupSize;
        long groupIndex = rawMilliseconds / this.segmentsGroupSize;

        if (groupMilliseconds >= this.segmentsIncludedSize) {
            result = toTimelineValue(this.startTime + this.segmentsGroupSize
                    * (groupIndex + 1));
        }
        else {
            Segment segment = getSegment(millisecond);
            if (segment.inExceptionSegments()) {
                int p;
                while ((p = binarySearchExceptionSegments(segment)) >= 0) {
                    segment = getSegment(millisecond = ((Segment)
                            this.exceptionSegments.get(p)).getSegmentEnd() + 1);
                }
                result = toTimelineValue(millisecond);
            }
            else {
                long shiftedSegmentedValue = millisecond - this.startTime;
                long x = shiftedSegmentedValue % this.segmentsGroupSize;
                long y = shiftedSegmentedValue / this.segmentsGroupSize;

                long wholeExceptionsBeforeDomainValue =
                    getExceptionSegmentCount(this.startTime, millisecond - 1);

//                long partialTimeInException = 0;
//                Segment ss = getSegment(millisecond);
//                if (ss.inExceptionSegments()) {
//                    partialTimeInException = millisecond
                //     - ss.getSegmentStart();
//                }

                if (x < this.segmentsIncludedSize) {
                    result = this.segmentsIncludedSize * y
                             + x - wholeExceptionsBeforeDomainValue
                             * this.segmentSize;
                             // - partialTimeInException;
                }
                else {
                    result = this.segmentsIncludedSize * (y + 1)
                             - wholeExceptionsBeforeDomainValue
                             * this.segmentSize;
                             // - partialTimeInException;
                }
            }
        }

        return result;
    }

    /**
     * Translates a date into a value relative to the segmented timeline. The
     * values relative to the segmented timeline are all consecutives starting
     * at zero at the startTime.
     *
     * @param date  date relative to the domain.
     *
     * @return The timeline value (in milliseconds).
     */
    public long toTimelineValue(Date date) {
        return toTimelineValue(getTime(date));
        //return toTimelineValue(dateDomainValue.getTime());
    }

    /**
     * Translates a value relative to the timeline into a millisecond.
     *
     * @param timelineValue  the timeline value (in milliseconds).
     *
     * @return The domain value (in milliseconds).
     */
    public long toMillisecond(long timelineValue) {

        // calculate the result as if no exceptions
        Segment result = new Segment(this.startTime + timelineValue
                + (timelineValue / this.segmentsIncludedSize)
                * this.segmentsExcludedSize);

        long lastIndex = this.startTime;

        // adjust result for any exceptions in the result calculated
        while (lastIndex <= result.segmentStart) {

            // skip all whole exception segments in the range
            long exceptionSegmentCount;
            while ((exceptionSegmentCount = getExceptionSegmentCount(
                 lastIndex, (result.millisecond / this.segmentSize)
                 * this.segmentSize - 1)) > 0
            ) {
                lastIndex = result.segmentStart;
                // move forward exceptionSegmentCount segments skipping
                // excluded segments
                for (int i = 0; i < exceptionSegmentCount; i++) {
                    do {
                        result.inc();
                    }
                    while (result.inExcludeSegments());
                }
            }
            lastIndex = result.segmentStart;

            // skip exception or excluded segments we may fall on
            while (result.inExceptionSegments() || result.inExcludeSegments()) {
                result.inc();
                lastIndex += this.segmentSize;
            }

            lastIndex++;
        }

        return getTimeFromLong(result.millisecond);
    }

    /**
     * Converts a date/time value to take account of daylight savings time.
     *
     * @param date  the milliseconds.
     *
     * @return The milliseconds.
     */
    public long getTimeFromLong(long date) {
        long result = date;
        if (this.adjustForDaylightSaving) {
            this.workingCalendarNoDST.setTime(new Date(date));
            this.workingCalendar.set(
                this.workingCalendarNoDST.get(Calendar.YEAR),
                this.workingCalendarNoDST.get(Calendar.MONTH),
                this.workingCalendarNoDST.get(Calendar.DATE),
                this.workingCalendarNoDST.get(Calendar.HOUR_OF_DAY),
                this.workingCalendarNoDST.get(Calendar.MINUTE),
                this.workingCalendarNoDST.get(Calendar.SECOND)
            );
            this.workingCalendar.set(Calendar.MILLISECOND,
                    this.workingCalendarNoDST.get(Calendar.MILLISECOND));
            // result = this.workingCalendar.getTimeInMillis();
            // preceding code won't work with JDK 1.3
            result = this.workingCalendar.getTime().getTime();
        }
        return result;
    }

    /**
     * Returns <code>true if a value is contained in the timeline.
     *
     * @param millisecond  the value to verify.
     *
     * @return <code>true if value is contained in the timeline.
     */
    public boolean containsDomainValue(long millisecond) {
        Segment segment = getSegment(millisecond);
        return segment.inIncludeSegments();
    }

    /**
     * Returns <code>true if a value is contained in the timeline.
     *
     * @param date  date to verify
     *
     * @return <code>true if value is contained in the timeline
     */
    public boolean containsDomainValue(Date date) {
        return containsDomainValue(getTime(date));
    }

    /**
     * Returns <code>true if a range of values are contained in the
     * timeline. This is implemented verifying that all segments are in the
     * range.
     *
     * @param domainValueStart start of the range to verify
     * @param domainValueEnd end of the range to verify
     *
     * @return <code>true if the range is contained in the timeline
     */
    public boolean containsDomainRange(long domainValueStart,
                                       long domainValueEnd) {
        if (domainValueEnd < domainValueStart) {
            throw new IllegalArgumentException(
                    "domainValueEnd (" + domainValueEnd
                    + ") < domainValueStart (" + domainValueStart + ")");
        }
        Segment segment = getSegment(domainValueStart);
        boolean contains = true;
        do {
            contains = (segment.inIncludeSegments());
            if (segment.contains(domainValueEnd)) {
                break;
            }
            else {
                segment.inc();
            }
        }
        while (contains);
        return (contains);
    }

    /**
     * Returns <code>true if a range of values are contained in the
     * timeline. This is implemented verifying that all segments are in the
     * range.
     *
     * @param dateDomainValueStart start of the range to verify
     * @param dateDomainValueEnd end of the range to verify
     *
     * @return <code>true if the range is contained in the timeline
     */
    public boolean containsDomainRange(Date dateDomainValueStart,
                                       Date dateDomainValueEnd) {
        return containsDomainRange(getTime(dateDomainValueStart),
                getTime(dateDomainValueEnd));
    }

    /**
     * Adds a segment as an exception. An exception segment is defined as a
     * segment to exclude from what would otherwise be considered a valid
     * segment of the timeline.  An exception segment can not be contained
     * inside an already excluded segment.  If so, no action will occur (the
     * proposed exception segment will be discarded).
     * <p>
     * The segment is identified by a domainValue into any part of the segment.
     * Therefore the segmentStart <= domainValue <= segmentEnd.
     *
     * @param millisecond  domain value to treat as an exception
     */
    public void addException(long millisecond) {
        addException(new Segment(millisecond));
    }

    /**
     * Adds a segment range as an exception. An exception segment is defined as
     * a segment to exclude from what would otherwise be considered a valid
     * segment of the timeline.  An exception segment can not be contained
     * inside an already excluded segment.  If so, no action will occur (the
     * proposed exception segment will be discarded).
     * <p>
     * The segment range is identified by a domainValue that begins a valid
     * segment and ends with a domainValue that ends a valid segment.
     * Therefore the range will contain all segments whose segmentStart
     * <= domainValue and segmentEnd <= toDomainValue.
     *
     * @param fromDomainValue  start of domain range to treat as an exception
     * @param toDomainValue  end of domain range to treat as an exception
     */
    public void addException(long fromDomainValue, long toDomainValue) {
        addException(new SegmentRange(fromDomainValue, toDomainValue));
    }

    /**
     * Adds a segment as an exception. An exception segment is defined as a
     * segment to exclude from what would otherwise be considered a valid
     * segment of the timeline.  An exception segment can not be contained
     * inside an already excluded segment.  If so, no action will occur (the
     * proposed exception segment will be discarded).
     * <p>
     * The segment is identified by a Date into any part of the segment.
     *
     * @param exceptionDate  Date into the segment to exclude.
     */
    public void addException(Date exceptionDate) {
        addException(getTime(exceptionDate));
        //addException(exceptionDate.getTime());
    }

    /**
     * Adds a list of dates as segment exceptions. Each exception segment is
     * defined as a segment to exclude from what would otherwise be considered
     * a valid segment of the timeline.  An exception segment can not be
     * contained inside an already excluded segment.  If so, no action will
     * occur (the proposed exception segment will be discarded).
     * <p>
     * The segment is identified by a Date into any part of the segment.
     *
     * @param exceptionList  List of Date objects that identify the segments to
     *                       exclude.
     */
    public void addExceptions(List exceptionList) {
        for (Iterator iter = exceptionList.iterator(); iter.hasNext();) {
            addException((Date) iter.next());
        }
    }

    /**
     * Adds a segment as an exception. An exception segment is defined as a
     * segment to exclude from what would otherwise be considered a valid
     * segment of the timeline.  An exception segment can not be contained
     * inside an already excluded segment.  This is verified inside this
     * method, and if so, no action will occur (the proposed exception segment
     * will be discarded).
     *
     * @param segment  the segment to exclude.
     */
    private void addException(Segment segment) {
         if (segment.inIncludeSegments()) {
             int p = binarySearchExceptionSegments(segment);
             this.exceptionSegments.add(-(p + 1), segment);
         }
    }

    /**
     * Adds a segment relative to the baseTimeline as an exception. Because a
     * base segment is normally larger than our segments, this may add one or
     * more segment ranges to the exception list.
     * <p>
     * An exception segment is defined as a segment
     * to exclude from what would otherwise be considered a valid segment of
     * the timeline.  An exception segment can not be contained inside an
     * already excluded segment.  If so, no action will occur (the proposed
     * exception segment will be discarded).
     * <p>
     * The segment is identified by a domainValue into any part of the
     * baseTimeline segment.
     *
     * @param domainValue  domain value to teat as a baseTimeline exception.
     */
    public void addBaseTimelineException(long domainValue) {

        Segment baseSegment = this.baseTimeline.getSegment(domainValue);
        if (baseSegment.inIncludeSegments()) {

            // cycle through all the segments contained in the BaseTimeline
            // exception segment
            Segment segment = getSegment(baseSegment.getSegmentStart());
            while (segment.getSegmentStart() <= baseSegment.getSegmentEnd()) {
                if (segment.inIncludeSegments()) {

                    // find all consecutive included segments
                    long fromDomainValue = segment.getSegmentStart();
                    long toDomainValue;
                    do {
                        toDomainValue = segment.getSegmentEnd();
                        segment.inc();
                    }
                    while (segment.inIncludeSegments());

                    // add the interval as an exception
                    addException(fromDomainValue, toDomainValue);

                }
                else {
                    // this is not one of our included segment, skip it
                    segment.inc();
                }
            }
        }
    }

    /**
     * Adds a segment relative to the baseTimeline as an exception. An
     * exception segment is defined as a segment to exclude from what would
     * otherwise be considered a valid segment of the timeline.  An exception
     * segment can not be contained inside an already excluded segment. If so,
     * no action will occure (the proposed exception segment will be discarded).
     * <p>
     * The segment is identified by a domainValue into any part of the segment.
     * Therefore the segmentStart <= domainValue <= segmentEnd.
     *
     * @param date  date domain value to treat as a baseTimeline exception
     */
    public void addBaseTimelineException(Date date) {
        addBaseTimelineException(getTime(date));
    }

    /**
     * Adds all excluded segments from the BaseTimeline as exceptions to our
     * timeline. This allows us to combine two timelines for more complex
     * calculations.
     *
     * @param fromBaseDomainValue Start of the range where exclusions will be
     *                            extracted.
     * @param toBaseDomainValue End of the range to process.
     */
    public void addBaseTimelineExclusions(long fromBaseDomainValue,
                                          long toBaseDomainValue) {

        // find first excluded base segment starting fromDomainValue
        Segment baseSegment = this.baseTimeline.getSegment(fromBaseDomainValue);
        while (baseSegment.getSegmentStart() <= toBaseDomainValue
               && !baseSegment.inExcludeSegments()) {

            baseSegment.inc();

        }

        // cycle over all the base segments groups in the range
        while (baseSegment.getSegmentStart() <= toBaseDomainValue) {

            long baseExclusionRangeEnd = baseSegment.getSegmentStart()
                 + this.baseTimeline.getSegmentsExcluded()
                 * this.baseTimeline.getSegmentSize() - 1;

            // cycle through all the segments contained in the base exclusion
            // area
            Segment segment = getSegment(baseSegment.getSegmentStart());
            while (segment.getSegmentStart() <= baseExclusionRangeEnd) {
                if (segment.inIncludeSegments()) {

                    // find all consecutive included segments
                    long fromDomainValue = segment.getSegmentStart();
                    long toDomainValue;
                    do {
                        toDomainValue = segment.getSegmentEnd();
                        segment.inc();
                    }
                    while (segment.inIncludeSegments());

                    // add the interval as an exception
                    addException(new BaseTimelineSegmentRange(
                            fromDomainValue, toDomainValue));
                }
                else {
                    // this is not one of our included segment, skip it
                    segment.inc();
                }
            }

            // go to next base segment group
            baseSegment.inc(this.baseTimeline.getGroupSegmentCount());
        }
    }

    /**
     * Returns the number of exception segments wholly contained in the
     * (fromDomainValue, toDomainValue) interval.
     *
     * @param fromMillisecond  the beginning of the interval.
     * @param toMillisecond  the end of the interval.
     *
     * @return Number of exception segments contained in the interval.
     */
    public long getExceptionSegmentCount(long fromMillisecond,
                                         long toMillisecond) {
        if (toMillisecond < fromMillisecond) {
            return (0);
        }

        int n = 0;
        for (Iterator iter = this.exceptionSegments.iterator();
             iter.hasNext();) {
            Segment segment = (Segment) iter.next();
            Segment intersection = segment.intersect(fromMillisecond,
                    toMillisecond);
            if (intersection != null) {
                n += intersection.getSegmentCount();
            }
        }

        return (n);
    }

    /**
     * Returns a segment that contains a domainValue. If the domainValue is
     * not contained in the timeline (because it is not contained in the
     * baseTimeline), a Segment that contains
     * <code>index + segmentSize*m will be returned for the smallest
     * <code>m possible.
     *
     * @param millisecond  index into the segment
     *
     * @return A Segment that contains index, or the next possible Segment.
     */
    public Segment getSegment(long millisecond) {
        return new Segment(millisecond);
    }

    /**
     * Returns a segment that contains a date. For accurate calculations,
     * the calendar should use TIME_ZONE for its calculation (or any other
     * similar time zone).
     *
     * If the date is not contained in the timeline (because it is not
     * contained in the baseTimeline), a Segment that contains
     * <code>date + segmentSize*m will be returned for the smallest
     * <code>m possible.
     *
     * @param date date into the segment
     *
     * @return A Segment that contains date, or the next possible Segment.
     */
    public Segment getSegment(Date date) {
        return (getSegment(getTime(date)));
    }

    /**
     * Convenient method to test equality in two objects, taking into account
     * nulls.
     *
     * @param o first object to compare
     * @param p second object to compare
     *
     * @return <code>true if both objects are equal or both
     *         <code>null, false otherwise.
     */
    private boolean equals(Object o, Object p) {
        return (o == p || ((o != null) && o.equals(p)));
    }

    /**
     * Returns true if we are equal to the parameter
     *
     * @param o Object to verify with us
     *
     * @return <code>true or false
     */
    public boolean equals(Object o) {
        if (o instanceof SegmentedTimeline) {
            SegmentedTimeline other = (SegmentedTimeline) o;

            boolean b0 = (this.segmentSize == other.getSegmentSize());
            boolean b1 = (this.segmentsIncluded == other.getSegmentsIncluded());
            boolean b2 = (this.segmentsExcluded == other.getSegmentsExcluded());
            boolean b3 = (this.startTime == other.getStartTime());
            boolean b4 = equals(this.exceptionSegments,
                    other.getExceptionSegments());
            return b0 && b1 && b2 && b3 && b4;
        }
        else {
            return (false);
        }
    }

    /**
     * Returns a hash code for this object.
     *
     * @return A hash code.
     */
    public int hashCode() {
        int result = 19;
        result = 37 * result
                 + (int) (this.segmentSize ^ (this.segmentSize >>> 32));
        result = 37 * result + (int) (this.startTime ^ (this.startTime >>> 32));
        return result;
    }

    /**
     * Preforms a binary serach in the exceptionSegments sorted array. This
     * array can contain Segments or SegmentRange objects.
     *
     * @param  segment the key to be searched for.
     *
     * @return index of the search segment, if it is contained in the list;
     *         otherwise, <tt>(-(insertion point) - 1).  The
     *         <i>insertion point is defined as the point at which the
     *         segment would be inserted into the list: the index of the first
     *         element greater than the key, or <tt>list.size(), if all
     *         elements in the list are less than the specified segment.  Note
     *         that this guarantees that the return value will be >= 0 if
     *         and only if the key is found.
     */
    private int binarySearchExceptionSegments(Segment segment) {
        int low = 0;
        int high = this.exceptionSegments.size() - 1;

        while (low <= high) {
            int mid = (low + high) / 2;
            Segment midSegment = (Segment) this.exceptionSegments.get(mid);

            // first test for equality (contains or contained)
            if (segment.contains(midSegment) || midSegment.contains(segment)) {
                return mid;
            }

            if (midSegment.before(segment)) {
                low = mid + 1;
            }
            else if (midSegment.after(segment)) {
                high = mid - 1;
            }
            else {
                throw new IllegalStateException("Invalid condition.");
            }
        }
        return -(low + 1);  // key not found
    }

    /**
     * Special method that handles conversion between the Default Time Zone and
     * a UTC time zone with no DST. This is needed so all days have the same
     * size. This method is the prefered way of converting a Data into
     * milliseconds for usage in this class.
     *
     * @param date Date to convert to long.
     *
     * @return The milliseconds.
     */
    public long getTime(Date date) {
        long result = date.getTime();
        if (this.adjustForDaylightSaving) {
            this.workingCalendar.setTime(date);
            this.workingCalendarNoDST.set(
                    this.workingCalendar.get(Calendar.YEAR),
                    this.workingCalendar.get(Calendar.MONTH),
                    this.workingCalendar.get(Calendar.DATE),
                    this.workingCalendar.get(Calendar.HOUR_OF_DAY),
                    this.workingCalendar.get(Calendar.MINUTE),
                    this.workingCalendar.get(Calendar.SECOND));
            this.workingCalendarNoDST.set(Calendar.MILLISECOND,
                    this.workingCalendar.get(Calendar.MILLISECOND));
            Date revisedDate = this.workingCalendarNoDST.getTime();
            result = revisedDate.getTime();
        }

        return result;
    }

    /**
     * Converts a millisecond value into a {@link Date} object.
     *
     * @param value  the millisecond value.
     *
     * @return The date.
     */
    public Date getDate(long value) {
        this.workingCalendarNoDST.setTime(new Date(value));
        return (this.workingCalendarNoDST.getTime());
    }

    /**
     * Returns a clone of the timeline.
     *
     * @return A clone.
     *
     * @throws CloneNotSupportedException ??.
     */
    public Object clone() throws CloneNotSupportedException {
        SegmentedTimeline clone = (SegmentedTimeline) super.clone();
        return clone;
    }

    /**
     * Internal class to represent a valid segment for this timeline. A segment
     * is valid on a timeline if it is part of its included, excluded or
     * exception segments.
     * <p>
     * Each segment will know its segment number, segmentStart, segmentEnd and
     * index inside the segment.
     */
    public class Segment implements Comparable, Cloneable, Serializable {

        /** The segment number. */
        protected long segmentNumber;

        /** The segment start. */
        protected long segmentStart;

        /** The segment end. */
        protected long segmentEnd;

        /** A reference point within the segment. */
        protected long millisecond;

        /**
         * Protected constructor only used by sub-classes.
         */
        protected Segment() {
            // empty
        }

        /**
         * Creates a segment for a given point in time.
         *
         * @param millisecond  the millisecond (as encoded by java.util.Date).
         */
        protected Segment(long millisecond) {
            this.segmentNumber = calculateSegmentNumber(millisecond);
            this.segmentStart = SegmentedTimeline.this.startTime
                + this.segmentNumber * SegmentedTimeline.this.segmentSize;
            this.segmentEnd
                = this.segmentStart + SegmentedTimeline.this.segmentSize - 1;
            this.millisecond = millisecond;
        }

        /**
         * Calculates the segment number for a given millisecond.
         *
         * @param millis  the millisecond (as encoded by java.util.Date).
         *
         * @return The segment number.
         */
        public long calculateSegmentNumber(long millis) {
            if (millis >= SegmentedTimeline.this.startTime) {
                return (millis - SegmentedTimeline.this.startTime)
                    / SegmentedTimeline.this.segmentSize;
            }
            else {
                return ((millis - SegmentedTimeline.this.startTime)
                    / SegmentedTimeline.this.segmentSize) - 1;
            }
        }

        /**
         * Returns the segment number of this segment. Segments start at 0.
         *
         * @return The segment number.
         */
        public long getSegmentNumber() {
            return this.segmentNumber;
        }

        /**
         * Returns always one (the number of segments contained in this
         * segment).
         *
         * @return The segment count (always 1 for this class).
         */
        public long getSegmentCount() {
            return 1;
        }

        /**
         * Gets the start of this segment in ms.
         *
         * @return The segment start.
         */
        public long getSegmentStart() {
            return this.segmentStart;
        }

        /**
         * Gets the end of this segment in ms.
         *
         * @return The segment end.
         */
        public long getSegmentEnd() {
            return this.segmentEnd;
        }

        /**
         * Returns the millisecond used to reference this segment (always
         * between the segmentStart and segmentEnd).
         *
         * @return The millisecond.
         */
        public long getMillisecond() {
            return this.millisecond;
        }

        /**
         * Returns a {@link java.util.Date} that represents the reference point
         * for this segment.
         *
         * @return The date.
         */
        public Date getDate() {
            return SegmentedTimeline.this.getDate(this.millisecond);
        }

        /**
         * Returns true if a particular millisecond is contained in this
         * segment.
         *
         * @param millis  the millisecond to verify.
         *
         * @return <code>true if the millisecond is contained in the
         *         segment.
         */
        public boolean contains(long millis) {
            return (this.segmentStart <= millis && millis <= this.segmentEnd);
        }

        /**
         * Returns <code>true if an interval is contained in this
         * segment.
         *
         * @param from  the start of the interval.
         * @param to  the end of the interval.
         *
         * @return <code>true if the interval is contained in the
         *         segment.
         */
        public boolean contains(long from, long to) {
            return (this.segmentStart <= from && to <= this.segmentEnd);
        }

        /**
         * Returns <code>true if a segment is contained in this segment.
         *
         * @param segment  the segment to test for inclusion
         *
         * @return <code>true if the segment is contained in this
         *         segment.
         */
        public boolean contains(Segment segment) {
            return contains(segment.getSegmentStart(), segment.getSegmentEnd());
        }

        /**
         * Returns <code>true if this segment is contained in an
         * interval.
         *
         * @param from  the start of the interval.
         * @param to  the end of the interval.
         *
         * @return <code>true if this segment is contained in the
         *         interval.
         */
        public boolean contained(long from, long to) {
            return (from <= this.segmentStart && this.segmentEnd <= to);
        }

        /**
         * Returns a segment that is the intersection of this segment and the
         * interval.
         *
         * @param from  the start of the interval.
         * @param to  the end of the interval.
         *
         * @return A segment.
         */
        public Segment intersect(long from, long to) {
            if (from <= this.segmentStart && this.segmentEnd <= to) {
                return this;
            }
            else {
                return null;
            }
        }

        /**
         * Returns <code>true if this segment is wholly before another
         * segment.
         *
         * @param other  the other segment.
         *
         * @return A boolean.
         */
        public boolean before(Segment other) {
            return (this.segmentEnd < other.getSegmentStart());
        }

        /**
         * Returns <code>true if this segment is wholly after another
         * segment.
         *
         * @param other  the other segment.
         *
         * @return A boolean.
         */
        public boolean after(Segment other) {
            return (this.segmentStart > other.getSegmentEnd());
        }

        /**
         * Tests an object (usually another <code>Segment) for equality
         * with this segment.
         *
         * @param object The other segment to compare with us
         *
         * @return <code>true if we are the same segment
         */
        public boolean equals(Object object) {
            if (object instanceof Segment) {
                Segment other = (Segment) object;
                return (this.segmentNumber == other.getSegmentNumber()
                        && this.segmentStart == other.getSegmentStart()
                        && this.segmentEnd == other.getSegmentEnd()
                        && this.millisecond == other.getMillisecond());
            }
            else {
                return false;
            }
        }

        /**
         * Returns a copy of ourselves or <code>null if there was an
         * exception during cloning.
         *
         * @return A copy of this segment.
         */
        public Segment copy() {
            try {
                return (Segment) this.clone();
            }
            catch (CloneNotSupportedException e) {
                return null;
            }
        }

        /**
         * Will compare this Segment with another Segment (from Comparable
         * interface).
         *
         * @param object The other Segment to compare with
         *
         * @return -1: this < object, 0: this.equal(object) and
         *         +1: this > object
         */
        public int compareTo(Object object) {
            Segment other = (Segment) object;
            if (this.before(other)) {
                return -1;
            }
            else if (this.after(other)) {
                return +1;
            }
            else {
                return 0;
            }
        }

        /**
         * Returns true if we are an included segment and we are not an
         * exception.
         *
         * @return <code>true or false.
         */
        public boolean inIncludeSegments() {
            if (getSegmentNumberRelativeToGroup()
                    < SegmentedTimeline.this.segmentsIncluded) {
                return !inExceptionSegments();
            }
            else {
                return false;
            }
        }

        /**
         * Returns true if we are an excluded segment.
         *
         * @return <code>true or false.
         */
        public boolean inExcludeSegments() {
            return getSegmentNumberRelativeToGroup()
                    >= SegmentedTimeline.this.segmentsIncluded;
        }

        /**
         * Calculate the segment number relative to the segment group. This
         * will be a number between 0 and segmentsGroup-1. This value is
         * calculated from the segmentNumber. Special care is taken for
         * negative segmentNumbers.
         *
         * @return The segment number.
         */
        private long getSegmentNumberRelativeToGroup() {
            long p = (this.segmentNumber
                    % SegmentedTimeline.this.groupSegmentCount);
            if (p < 0) {
                p += SegmentedTimeline.this.groupSegmentCount;
            }
            return p;
        }

        /**
         * Returns true if we are an exception segment. This is implemented via
         * a binary search on the exceptionSegments sorted list.
         *
         * If the segment is not listed as an exception in our list and we have
         * a baseTimeline, a check is performed to see if the segment is inside
         * an excluded segment from our base. If so, it is also considered an
         * exception.
         *
         * @return <code>true if we are an exception segment.
         */
        public boolean inExceptionSegments() {
            return binarySearchExceptionSegments(this) >= 0;
        }

        /**
         * Increments the internal attributes of this segment by a number of
         * segments.
         *
         * @param n Number of segments to increment.
         */
        public void inc(long n) {
            this.segmentNumber += n;
            long m = n * SegmentedTimeline.this.segmentSize;
            this.segmentStart += m;
            this.segmentEnd += m;
            this.millisecond += m;
        }

        /**
         * Increments the internal attributes of this segment by one segment.
         * The exact time incremented is segmentSize.
         */
        public void inc() {
            inc(1);
        }

        /**
         * Decrements the internal attributes of this segment by a number of
         * segments.
         *
         * @param n Number of segments to decrement.
         */
        public void dec(long n) {
            this.segmentNumber -= n;
            long m = n * SegmentedTimeline.this.segmentSize;
            this.segmentStart -= m;
            this.segmentEnd -= m;
            this.millisecond -= m;
        }

        /**
         * Decrements the internal attributes of this segment by one segment.
         * The exact time decremented is segmentSize.
         */
        public void dec() {
            dec(1);
        }

        /**
         * Moves the index of this segment to the beginning if the segment.
         */
        public void moveIndexToStart() {
            this.millisecond = this.segmentStart;
        }

        /**
         * Moves the index of this segment to the end of the segment.
         */
        public void moveIndexToEnd() {
            this.millisecond = this.segmentEnd;
        }

    }

    /**
     * Private internal class to represent a range of segments. This class is
     * mainly used to store in one object a range of exception segments. This
     * optimizes certain timelines that use a small segment size (like an
     * intraday timeline) allowing them to express a day exception as one
     * SegmentRange instead of multi Segments.
     */
    protected class SegmentRange extends Segment {

        /** The number of segments in the range. */
        private long segmentCount;

        /**
         * Creates a SegmentRange between a start and end domain values.
         *
         * @param fromMillisecond  start of the range
         * @param toMillisecond  end of the range
         */
        public SegmentRange(long fromMillisecond, long toMillisecond) {

            Segment start = getSegment(fromMillisecond);
            Segment end = getSegment(toMillisecond);
//            if (start.getSegmentStart() != fromMillisecond
//                || end.getSegmentEnd() != toMillisecond) {
//                throw new IllegalArgumentException("Invalid Segment Range ["
//                    + fromMillisecond + "," + toMillisecond + "]");
//            }

            this.millisecond = fromMillisecond;
            this.segmentNumber = calculateSegmentNumber(fromMillisecond);
            this.segmentStart = start.segmentStart;
            this.segmentEnd = end.segmentEnd;
            this.segmentCount
                = (end.getSegmentNumber() - start.getSegmentNumber() + 1);
        }

        /**
         * Returns the number of segments contained in this range.
         *
         * @return The segment count.
         */
        public long getSegmentCount() {
            return this.segmentCount;
        }

        /**
         * Returns a segment that is the intersection of this segment and the
         * interval.
         *
         * @param from  the start of the interval.
         * @param to  the end of the interval.
         *
         * @return The intersection.
         */
        public Segment intersect(long from, long to) {

            // Segment fromSegment = getSegment(from);
            // fromSegment.inc();
            // Segment toSegment = getSegment(to);
            // toSegment.dec();
            long start = Math.max(from, this.segmentStart);
            long end = Math.min(to, this.segmentEnd);
            // long start = Math.max(
            //     fromSegment.getSegmentStart(), this.segmentStart
            // );
            // long end = Math.min(toSegment.getSegmentEnd(), this.segmentEnd);
            if (start <= end) {
                return new SegmentRange(start, end);
            }
            else {
                return null;
            }
        }

        /**
         * Returns true if all Segments of this SegmentRenge are an included
         * segment and are not an exception.
         *
         * @return <code>true or false.
         */
        public boolean inIncludeSegments() {
            for (Segment segment = getSegment(this.segmentStart);
                segment.getSegmentStart() < this.segmentEnd;
                segment.inc()) {
                if (!segment.inIncludeSegments()) {
                    return (false);
                }
            }
            return true;
        }

        /**
         * Returns true if we are an excluded segment.
         *
         * @return <code>true or false.
         */
        public boolean inExcludeSegments() {
            for (Segment segment = getSegment(this.segmentStart);
                segment.getSegmentStart() < this.segmentEnd;
                segment.inc()) {
                if (!segment.inExceptionSegments()) {
                    return (false);
                }
            }
            return true;
        }

        /**
         * Not implemented for SegmentRange. Always throws
         * IllegalArgumentException.
         *
         * @param n Number of segments to increment.
         */
        public void inc(long n) {
            throw new IllegalArgumentException(
                    "Not implemented in SegmentRange");
        }

    }

    /**
     * Special <code>SegmentRange that came from the BaseTimeline.
     */
    protected class BaseTimelineSegmentRange extends SegmentRange {

        /**
         * Constructor.
         *
         * @param fromDomainValue  the start value.
         * @param toDomainValue  the end value.
         */
        public BaseTimelineSegmentRange(long fromDomainValue,
                                        long toDomainValue) {
            super(fromDomainValue, toDomainValue);
        }

    }

}

Other jfreechart examples (source code examples)

Here is a short list of links related to this jfreechart SegmentedTimeline.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.