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

Save This Page
Home » jcommon-1.0.13 » org.jfree » chart » axis » [javadoc | source]