0001: /* ===========================================================
0002: * JFreeChart : a free chart library for the Java(tm) platform
0003: * ===========================================================
0004: *
0005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
0006: *
0007: * Project Info: http://www.jfree.org/jfreechart/index.html
0008: *
0009: * This library is free software; you can redistribute it and/or modify it
0010: * under the terms of the GNU Lesser General Public License as published by
0011: * the Free Software Foundation; either version 2.1 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful, but
0015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
0016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
0017: * License for more details.
0018: *
0019: * You should have received a copy of the GNU Lesser General Public
0020: * License along with this library; if not, write to the Free Software
0021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
0022: * USA.
0023: *
0024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
0025: * in the United States and other countries.]
0026: *
0027: * -----------------------
0028: * SegmentedTimeline.java
0029: * -----------------------
0030: * (C) Copyright 2003-2007, by Bill Kelemen and Contributors.
0031: *
0032: * Original Author: Bill Kelemen;
0033: * Contributor(s): David Gilbert (for Object Refinery Limited);
0034: *
0035: * $Id: SegmentedTimeline.java,v 1.9.2.3 2007/02/02 14:32:42 mungady Exp $
0036: *
0037: * Changes
0038: * -------
0039: * 23-May-2003 : Version 1 (BK);
0040: * 15-Aug-2003 : Implemented Cloneable (DG);
0041: * 01-Jun-2004 : Modified to compile with JDK 1.2.2 (DG);
0042: * 30-Sep-2004 : Replaced getTime().getTime() with getTimeInMillis() (DG);
0043: * 04-Nov-2004 : Reverted change of 30-Sep-2004, won't work with JDK 1.3 (DG);
0044: * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
0045: * ------------- JFREECHART 1.0.x ---------------------------------------------
0046: * 14-Nov-2006 : Fix in toTimelineValue(long) to avoid stack overflow (DG);
0047: * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
0048: *
0049: */
0050:
0051: package org.jfree.chart.axis;
0052:
0053: import java.io.Serializable;
0054: import java.util.ArrayList;
0055: import java.util.Calendar;
0056: import java.util.Collections;
0057: import java.util.Date;
0058: import java.util.GregorianCalendar;
0059: import java.util.Iterator;
0060: import java.util.List;
0061: import java.util.SimpleTimeZone;
0062: import java.util.TimeZone;
0063:
0064: /**
0065: * A {@link Timeline} that implements a "segmented" timeline with included,
0066: * excluded and exception segments.
0067: * <P>
0068: * A Timeline will present a series of values to be used for an axis. Each
0069: * Timeline must provide transformation methods between domain values and
0070: * timeline values.
0071: * <P>
0072: * A timeline can be used as parameter to a
0073: * {@link org.jfree.chart.axis.DateAxis} to define the values that this axis
0074: * supports. This class implements a timeline formed by segments of equal
0075: * length (ex. days, hours, minutes) where some segments can be included in the
0076: * timeline and others excluded. Therefore timelines like "working days" or
0077: * "working hours" can be created where non-working days or non-working hours
0078: * respectively can be removed from the timeline, and therefore from the axis.
0079: * This creates a smooth plot with equal separation between all included
0080: * segments.
0081: * <P>
0082: * Because Timelines were created mainly for Date related axis, values are
0083: * represented as longs instead of doubles. In this case, the domain value is
0084: * just the number of milliseconds since January 1, 1970, 00:00:00 GMT as
0085: * defined by the getTime() method of {@link java.util.Date}.
0086: * <P>
0087: * In this class, a segment is defined as a unit of time of fixed length.
0088: * Examples of segments are: days, hours, minutes, etc. The size of a segment
0089: * is defined as the number of milliseconds in the segment. Some useful segment
0090: * sizes are defined as constants in this class: DAY_SEGMENT_SIZE,
0091: * HOUR_SEGMENT_SIZE, FIFTEEN_MINUTE_SEGMENT_SIZE and MINUTE_SEGMENT_SIZE.
0092: * <P>
0093: * Segments are group together to form a Segment Group. Each Segment Group will
0094: * contain a number of Segments included and a number of Segments excluded. This
0095: * Segment Group structure will repeat for the whole timeline.
0096: * <P>
0097: * For example, a working days SegmentedTimeline would be formed by a group of
0098: * 7 daily segments, where there are 5 included (Monday through Friday) and 2
0099: * excluded (Saturday and Sunday) segments.
0100: * <P>
0101: * Following is a diagram that explains the major attributes that define a
0102: * segment. Each box is one segment and must be of fixed length (ms, second,
0103: * hour, day, etc).
0104: * <p>
0105: * <pre>
0106: * start time
0107: * |
0108: * v
0109: * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
0110: * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+...
0111: * | | | | | |EE|EE| | | | | |EE|EE| | | | | |EE|EE|
0112: * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+...
0113: * \____________/ \___/ \_/
0114: * \/ | |
0115: * included excluded segment
0116: * segments segments size
0117: * \_________ _______/
0118: * \/
0119: * segment group
0120: * </pre>
0121: * Legend:<br>
0122: * <space> = Included segment<br>
0123: * EE = Excluded segments in the base timeline<br>
0124: * <p>
0125: * In the example, the following segment attributes are presented:
0126: * <ul>
0127: * <li>segment size: the size of each segment in ms.
0128: * <li>start time: the start of the first segment of the first segment group to
0129: * consider.
0130: * <li>included segments: the number of segments to include in the group.
0131: * <li>excluded segments: the number of segments to exclude in the group.
0132: * </ul>
0133: * <p>
0134: * Exception Segments are allowed. These exception segments are defined as
0135: * segments that would have been in the included segments of the Segment Group,
0136: * but should be excluded for special reasons. In the previous working days
0137: * SegmentedTimeline example, holidays would be considered exceptions.
0138: * <P>
0139: * Additionally the <code>startTime</code>, or start of the first Segment of
0140: * the smallest segment group needs to be defined. This startTime could be
0141: * relative to January 1, 1970, 00:00:00 GMT or any other date. This creates a
0142: * point of reference to start counting Segment Groups. For example, for the
0143: * working days SegmentedTimeline, the <code>startTime</code> could be
0144: * 00:00:00 GMT of the first Monday after January 1, 1970. In this class, the
0145: * constant FIRST_MONDAY_AFTER_1900 refers to a reference point of the first
0146: * Monday of the last century.
0147: * <p>
0148: * A SegmentedTimeline can include a baseTimeline. This combination of
0149: * timelines allows the creation of more complex timelines. For example, in
0150: * order to implement a SegmentedTimeline for an intraday stock trading
0151: * application, where the trading period is defined as 9:00 AM through 4:00 PM
0152: * Monday through Friday, two SegmentedTimelines are used. The first one (the
0153: * baseTimeline) would be a working day SegmentedTimeline (daily timeline
0154: * Monday through Friday). On top of this baseTimeline, a second one is defined
0155: * that maps the 9:00 AM to 4:00 PM period. Because the baseTimeline defines a
0156: * timeline of Monday through Friday, the resulting (combined) timeline will
0157: * expose the period 9:00 AM through 4:00 PM only on Monday through Friday,
0158: * and will remove all other intermediate intervals.
0159: * <P>
0160: * Two factory methods newMondayThroughFridayTimeline() and
0161: * newFifteenMinuteTimeline() are provided as examples to create special
0162: * SegmentedTimelines.
0163: *
0164: * @see org.jfree.chart.axis.DateAxis
0165: */
0166: public class SegmentedTimeline implements Timeline, Cloneable,
0167: Serializable {
0168:
0169: /** For serialization. */
0170: private static final long serialVersionUID = 1093779862539903110L;
0171:
0172: ////////////////////////////////////////////////////////////////////////////
0173: // predetermined segments sizes
0174: ////////////////////////////////////////////////////////////////////////////
0175:
0176: /** Defines a day segment size in ms. */
0177: public static final long DAY_SEGMENT_SIZE = 24 * 60 * 60 * 1000;
0178:
0179: /** Defines a one hour segment size in ms. */
0180: public static final long HOUR_SEGMENT_SIZE = 60 * 60 * 1000;
0181:
0182: /** Defines a 15-minute segment size in ms. */
0183: public static final long FIFTEEN_MINUTE_SEGMENT_SIZE = 15 * 60 * 1000;
0184:
0185: /** Defines a one-minute segment size in ms. */
0186: public static final long MINUTE_SEGMENT_SIZE = 60 * 1000;
0187:
0188: ////////////////////////////////////////////////////////////////////////////
0189: // other constants
0190: ////////////////////////////////////////////////////////////////////////////
0191:
0192: /**
0193: * Utility constant that defines the startTime as the first monday after
0194: * 1/1/1970. This should be used when creating a SegmentedTimeline for
0195: * Monday through Friday. See static block below for calculation of this
0196: * constant.
0197: */
0198: public static long FIRST_MONDAY_AFTER_1900;
0199:
0200: /**
0201: * Utility TimeZone object that has no DST and an offset equal to the
0202: * default TimeZone. This allows easy arithmetic between days as each one
0203: * will have equal size.
0204: */
0205: public static TimeZone NO_DST_TIME_ZONE;
0206:
0207: /**
0208: * This is the default time zone where the application is running. See
0209: * getTime() below where we make use of certain transformations between
0210: * times in the default time zone and the no-dst time zone used for our
0211: * calculations.
0212: */
0213: public static TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault();
0214:
0215: /**
0216: * This will be a utility calendar that has no DST but is shifted relative
0217: * to the default time zone's offset.
0218: */
0219: private Calendar workingCalendarNoDST = new GregorianCalendar(
0220: NO_DST_TIME_ZONE);
0221:
0222: /**
0223: * This will be a utility calendar that used the default time zone.
0224: */
0225: private Calendar workingCalendar = Calendar.getInstance();
0226:
0227: ////////////////////////////////////////////////////////////////////////////
0228: // private attributes
0229: ////////////////////////////////////////////////////////////////////////////
0230:
0231: /** Segment size in ms. */
0232: private long segmentSize;
0233:
0234: /** Number of consecutive segments to include in a segment group. */
0235: private int segmentsIncluded;
0236:
0237: /** Number of consecutive segments to exclude in a segment group. */
0238: private int segmentsExcluded;
0239:
0240: /** Number of segments in a group (segmentsIncluded + segmentsExcluded). */
0241: private int groupSegmentCount;
0242:
0243: /**
0244: * Start of time reference from time zero (1/1/1970).
0245: * This is the start of segment #0.
0246: */
0247: private long startTime;
0248:
0249: /** Consecutive ms in segmentsIncluded (segmentsIncluded * segmentSize). */
0250: private long segmentsIncludedSize;
0251:
0252: /** Consecutive ms in segmentsExcluded (segmentsExcluded * segmentSize). */
0253: private long segmentsExcludedSize;
0254:
0255: /** ms in a segment group (segmentsIncludedSize + segmentsExcludedSize). */
0256: private long segmentsGroupSize;
0257:
0258: /**
0259: * List of exception segments (exceptions segments that would otherwise be
0260: * included based on the periodic (included, excluded) grouping).
0261: */
0262: private List exceptionSegments = new ArrayList();
0263:
0264: /**
0265: * This base timeline is used to specify exceptions at a higher level. For
0266: * example, if we are a intraday timeline and want to exclude holidays,
0267: * instead of having to exclude all intraday segments for the holiday,
0268: * segments from this base timeline can be excluded. This baseTimeline is
0269: * always optional and is only a convenience method.
0270: * <p>
0271: * Additionally, all excluded segments from this baseTimeline will be
0272: * considered exceptions at this level.
0273: */
0274: private SegmentedTimeline baseTimeline;
0275:
0276: /** A flag that controls whether or not to adjust for daylight saving. */
0277: private boolean adjustForDaylightSaving = false;
0278:
0279: ////////////////////////////////////////////////////////////////////////////
0280: // static block
0281: ////////////////////////////////////////////////////////////////////////////
0282:
0283: static {
0284: // make a time zone with no DST for our Calendar calculations
0285: int offset = TimeZone.getDefault().getRawOffset();
0286: NO_DST_TIME_ZONE = new SimpleTimeZone(offset, "UTC-" + offset);
0287:
0288: // calculate midnight of first monday after 1/1/1900 relative to
0289: // current locale
0290: Calendar cal = new GregorianCalendar(NO_DST_TIME_ZONE);
0291: cal.set(1900, 0, 1, 0, 0, 0);
0292: cal.set(Calendar.MILLISECOND, 0);
0293: while (cal.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
0294: cal.add(Calendar.DATE, 1);
0295: }
0296: // FIRST_MONDAY_AFTER_1900 = cal.getTime().getTime();
0297: // preceding code won't work with JDK 1.3
0298: FIRST_MONDAY_AFTER_1900 = cal.getTime().getTime();
0299: }
0300:
0301: ////////////////////////////////////////////////////////////////////////////
0302: // constructors and factory methods
0303: ////////////////////////////////////////////////////////////////////////////
0304:
0305: /**
0306: * Constructs a new segmented timeline, optionaly using another segmented
0307: * timeline as its base. This chaining of SegmentedTimelines allows further
0308: * segmentation into smaller timelines.
0309: *
0310: * If a base
0311: *
0312: * @param segmentSize the size of a segment in ms. This time unit will be
0313: * used to compute the included and excluded segments of the
0314: * timeline.
0315: * @param segmentsIncluded Number of consecutive segments to include.
0316: * @param segmentsExcluded Number of consecutive segments to exclude.
0317: */
0318: public SegmentedTimeline(long segmentSize, int segmentsIncluded,
0319: int segmentsExcluded) {
0320:
0321: this .segmentSize = segmentSize;
0322: this .segmentsIncluded = segmentsIncluded;
0323: this .segmentsExcluded = segmentsExcluded;
0324:
0325: this .groupSegmentCount = this .segmentsIncluded
0326: + this .segmentsExcluded;
0327: this .segmentsIncludedSize = this .segmentsIncluded
0328: * this .segmentSize;
0329: this .segmentsExcludedSize = this .segmentsExcluded
0330: * this .segmentSize;
0331: this .segmentsGroupSize = this .segmentsIncludedSize
0332: + this .segmentsExcludedSize;
0333:
0334: }
0335:
0336: /**
0337: * Factory method to create a Monday through Friday SegmentedTimeline.
0338: * <P>
0339: * The <code>startTime</code> of the resulting timeline will be midnight
0340: * of the first Monday after 1/1/1900.
0341: *
0342: * @return A fully initialized SegmentedTimeline.
0343: */
0344: public static SegmentedTimeline newMondayThroughFridayTimeline() {
0345: SegmentedTimeline timeline = new SegmentedTimeline(
0346: DAY_SEGMENT_SIZE, 5, 2);
0347: timeline.setStartTime(FIRST_MONDAY_AFTER_1900);
0348: return timeline;
0349: }
0350:
0351: /**
0352: * Factory method to create a 15-min, 9:00 AM thought 4:00 PM, Monday
0353: * through Friday SegmentedTimeline.
0354: * <P>
0355: * This timeline uses a segmentSize of FIFTEEN_MIN_SEGMENT_SIZE. The
0356: * segment group is defined as 28 included segments (9:00 AM through
0357: * 4:00 PM) and 68 excluded segments (4:00 PM through 9:00 AM the next day).
0358: * <P>
0359: * In order to exclude Saturdays and Sundays it uses a baseTimeline that
0360: * only includes Monday through Friday days.
0361: * <P>
0362: * The <code>startTime</code> of the resulting timeline will be 9:00 AM
0363: * after the startTime of the baseTimeline. This will correspond to 9:00 AM
0364: * of the first Monday after 1/1/1900.
0365: *
0366: * @return A fully initialized SegmentedTimeline.
0367: */
0368: public static SegmentedTimeline newFifteenMinuteTimeline() {
0369: SegmentedTimeline timeline = new SegmentedTimeline(
0370: FIFTEEN_MINUTE_SEGMENT_SIZE, 28, 68);
0371: timeline.setStartTime(FIRST_MONDAY_AFTER_1900 + 36
0372: * timeline.getSegmentSize());
0373: timeline.setBaseTimeline(newMondayThroughFridayTimeline());
0374: return timeline;
0375: }
0376:
0377: /**
0378: * Returns the flag that controls whether or not the daylight saving
0379: * adjustment is applied.
0380: *
0381: * @return A boolean.
0382: */
0383: public boolean getAdjustForDaylightSaving() {
0384: return this .adjustForDaylightSaving;
0385: }
0386:
0387: /**
0388: * Sets the flag that controls whether or not the daylight saving adjustment
0389: * is applied.
0390: *
0391: * @param adjust the flag.
0392: */
0393: public void setAdjustForDaylightSaving(boolean adjust) {
0394: this .adjustForDaylightSaving = adjust;
0395: }
0396:
0397: ////////////////////////////////////////////////////////////////////////////
0398: // operations
0399: ////////////////////////////////////////////////////////////////////////////
0400:
0401: /**
0402: * Sets the start time for the timeline. This is the beginning of segment
0403: * zero.
0404: *
0405: * @param millisecond the start time (encoded as in java.util.Date).
0406: */
0407: public void setStartTime(long millisecond) {
0408: this .startTime = millisecond;
0409: }
0410:
0411: /**
0412: * Returns the start time for the timeline. This is the beginning of
0413: * segment zero.
0414: *
0415: * @return The start time.
0416: */
0417: public long getStartTime() {
0418: return this .startTime;
0419: }
0420:
0421: /**
0422: * Returns the number of segments excluded per segment group.
0423: *
0424: * @return The number of segments excluded.
0425: */
0426: public int getSegmentsExcluded() {
0427: return this .segmentsExcluded;
0428: }
0429:
0430: /**
0431: * Returns the size in milliseconds of the segments excluded per segment
0432: * group.
0433: *
0434: * @return The size in milliseconds.
0435: */
0436: public long getSegmentsExcludedSize() {
0437: return this .segmentsExcludedSize;
0438: }
0439:
0440: /**
0441: * Returns the number of segments in a segment group. This will be equal to
0442: * segments included plus segments excluded.
0443: *
0444: * @return The number of segments.
0445: */
0446: public int getGroupSegmentCount() {
0447: return this .groupSegmentCount;
0448: }
0449:
0450: /**
0451: * Returns the size in milliseconds of a segment group. This will be equal
0452: * to size of the segments included plus the size of the segments excluded.
0453: *
0454: * @return The segment group size in milliseconds.
0455: */
0456: public long getSegmentsGroupSize() {
0457: return this .segmentsGroupSize;
0458: }
0459:
0460: /**
0461: * Returns the number of segments included per segment group.
0462: *
0463: * @return The number of segments.
0464: */
0465: public int getSegmentsIncluded() {
0466: return this .segmentsIncluded;
0467: }
0468:
0469: /**
0470: * Returns the size in ms of the segments included per segment group.
0471: *
0472: * @return The segment size in milliseconds.
0473: */
0474: public long getSegmentsIncludedSize() {
0475: return this .segmentsIncludedSize;
0476: }
0477:
0478: /**
0479: * Returns the size of one segment in ms.
0480: *
0481: * @return The segment size in milliseconds.
0482: */
0483: public long getSegmentSize() {
0484: return this .segmentSize;
0485: }
0486:
0487: /**
0488: * Returns a list of all the exception segments. This list is not
0489: * modifiable.
0490: *
0491: * @return The exception segments.
0492: */
0493: public List getExceptionSegments() {
0494: return Collections.unmodifiableList(this .exceptionSegments);
0495: }
0496:
0497: /**
0498: * Sets the exception segments list.
0499: *
0500: * @param exceptionSegments the exception segments.
0501: */
0502: public void setExceptionSegments(List exceptionSegments) {
0503: this .exceptionSegments = exceptionSegments;
0504: }
0505:
0506: /**
0507: * Returns our baseTimeline, or <code>null</code> if none.
0508: *
0509: * @return The base timeline.
0510: */
0511: public SegmentedTimeline getBaseTimeline() {
0512: return this .baseTimeline;
0513: }
0514:
0515: /**
0516: * Sets the base timeline.
0517: *
0518: * @param baseTimeline the timeline.
0519: */
0520: public void setBaseTimeline(SegmentedTimeline baseTimeline) {
0521:
0522: // verify that baseTimeline is compatible with us
0523: if (baseTimeline != null) {
0524: if (baseTimeline.getSegmentSize() < this .segmentSize) {
0525: throw new IllegalArgumentException(
0526: "baseTimeline.getSegmentSize() is smaller than segmentSize");
0527: } else if (baseTimeline.getStartTime() > this .startTime) {
0528: throw new IllegalArgumentException(
0529: "baseTimeline.getStartTime() is after startTime");
0530: } else if ((baseTimeline.getSegmentSize() % this .segmentSize) != 0) {
0531: throw new IllegalArgumentException(
0532: "baseTimeline.getSegmentSize() is not multiple of "
0533: + "segmentSize");
0534: } else if (((this .startTime - baseTimeline.getStartTime()) % this .segmentSize) != 0) {
0535: throw new IllegalArgumentException(
0536: "baseTimeline is not aligned");
0537: }
0538: }
0539:
0540: this .baseTimeline = baseTimeline;
0541: }
0542:
0543: /**
0544: * Translates a value relative to the domain value (all Dates) into a value
0545: * relative to the segmented timeline. The values relative to the segmented
0546: * timeline are all consecutives starting at zero at the startTime.
0547: *
0548: * @param millisecond the millisecond (as encoded by java.util.Date).
0549: *
0550: * @return The timeline value.
0551: */
0552: public long toTimelineValue(long millisecond) {
0553:
0554: long result;
0555: long rawMilliseconds = millisecond - this .startTime;
0556: long groupMilliseconds = rawMilliseconds
0557: % this .segmentsGroupSize;
0558: long groupIndex = rawMilliseconds / this .segmentsGroupSize;
0559:
0560: if (groupMilliseconds >= this .segmentsIncludedSize) {
0561: result = toTimelineValue(this .startTime
0562: + this .segmentsGroupSize * (groupIndex + 1));
0563: } else {
0564: Segment segment = getSegment(millisecond);
0565: if (segment.inExceptionSegments()) {
0566: do {
0567: segment = getSegment(millisecond = segment
0568: .getSegmentEnd() + 1);
0569: } while (segment.inExceptionSegments());
0570: result = toTimelineValue(millisecond);
0571: } else {
0572: long shiftedSegmentedValue = millisecond
0573: - this .startTime;
0574: long x = shiftedSegmentedValue % this .segmentsGroupSize;
0575: long y = shiftedSegmentedValue / this .segmentsGroupSize;
0576:
0577: long wholeExceptionsBeforeDomainValue = getExceptionSegmentCount(
0578: this .startTime, millisecond - 1);
0579:
0580: // long partialTimeInException = 0;
0581: // Segment ss = getSegment(millisecond);
0582: // if (ss.inExceptionSegments()) {
0583: // partialTimeInException = millisecond
0584: // - ss.getSegmentStart();
0585: // }
0586:
0587: if (x < this .segmentsIncludedSize) {
0588: result = this .segmentsIncludedSize * y + x
0589: - wholeExceptionsBeforeDomainValue
0590: * this .segmentSize;
0591: // - partialTimeInException;;
0592: } else {
0593: result = this .segmentsIncludedSize * (y + 1)
0594: - wholeExceptionsBeforeDomainValue
0595: * this .segmentSize;
0596: // - partialTimeInException;
0597: }
0598: }
0599: }
0600:
0601: return result;
0602: }
0603:
0604: /**
0605: * Translates a date into a value relative to the segmented timeline. The
0606: * values relative to the segmented timeline are all consecutives starting
0607: * at zero at the startTime.
0608: *
0609: * @param date date relative to the domain.
0610: *
0611: * @return The timeline value (in milliseconds).
0612: */
0613: public long toTimelineValue(Date date) {
0614: return toTimelineValue(getTime(date));
0615: //return toTimelineValue(dateDomainValue.getTime());
0616: }
0617:
0618: /**
0619: * Translates a value relative to the timeline into a millisecond.
0620: *
0621: * @param timelineValue the timeline value (in milliseconds).
0622: *
0623: * @return The domain value (in milliseconds).
0624: */
0625: public long toMillisecond(long timelineValue) {
0626:
0627: // calculate the result as if no exceptions
0628: Segment result = new Segment(this .startTime + timelineValue
0629: + (timelineValue / this .segmentsIncludedSize)
0630: * this .segmentsExcludedSize);
0631:
0632: long lastIndex = this .startTime;
0633:
0634: // adjust result for any exceptions in the result calculated
0635: while (lastIndex <= result.segmentStart) {
0636:
0637: // skip all whole exception segments in the range
0638: long exceptionSegmentCount;
0639: while ((exceptionSegmentCount = getExceptionSegmentCount(
0640: lastIndex, (result.millisecond / this .segmentSize)
0641: * this .segmentSize - 1)) > 0) {
0642: lastIndex = result.segmentStart;
0643: // move forward exceptionSegmentCount segments skipping
0644: // excluded segments
0645: for (int i = 0; i < exceptionSegmentCount; i++) {
0646: do {
0647: result.inc();
0648: } while (result.inExcludeSegments());
0649: }
0650: }
0651: lastIndex = result.segmentStart;
0652:
0653: // skip exception or excluded segments we may fall on
0654: while (result.inExceptionSegments()
0655: || result.inExcludeSegments()) {
0656: result.inc();
0657: lastIndex += this .segmentSize;
0658: }
0659:
0660: lastIndex++;
0661: }
0662:
0663: return getTimeFromLong(result.millisecond);
0664: }
0665:
0666: /**
0667: * Converts a date/time value to take account of daylight savings time.
0668: *
0669: * @param date the milliseconds.
0670: *
0671: * @return The milliseconds.
0672: */
0673: public long getTimeFromLong(long date) {
0674: long result = date;
0675: if (this .adjustForDaylightSaving) {
0676: this .workingCalendarNoDST.setTime(new Date(date));
0677: this .workingCalendar.set(this .workingCalendarNoDST
0678: .get(Calendar.YEAR), this .workingCalendarNoDST
0679: .get(Calendar.MONTH), this .workingCalendarNoDST
0680: .get(Calendar.DATE), this .workingCalendarNoDST
0681: .get(Calendar.HOUR_OF_DAY),
0682: this .workingCalendarNoDST.get(Calendar.MINUTE),
0683: this .workingCalendarNoDST.get(Calendar.SECOND));
0684: this .workingCalendar
0685: .set(Calendar.MILLISECOND,
0686: this .workingCalendarNoDST
0687: .get(Calendar.MILLISECOND));
0688: // result = this.workingCalendar.getTimeInMillis();
0689: // preceding code won't work with JDK 1.3
0690: result = this .workingCalendar.getTime().getTime();
0691: }
0692: return result;
0693: }
0694:
0695: /**
0696: * Returns <code>true</code> if a value is contained in the timeline.
0697: *
0698: * @param millisecond the value to verify.
0699: *
0700: * @return <code>true</code> if value is contained in the timeline.
0701: */
0702: public boolean containsDomainValue(long millisecond) {
0703: Segment segment = getSegment(millisecond);
0704: return segment.inIncludeSegments();
0705: }
0706:
0707: /**
0708: * Returns <code>true</code> if a value is contained in the timeline.
0709: *
0710: * @param date date to verify
0711: *
0712: * @return <code>true</code> if value is contained in the timeline
0713: */
0714: public boolean containsDomainValue(Date date) {
0715: return containsDomainValue(getTime(date));
0716: }
0717:
0718: /**
0719: * Returns <code>true</code> if a range of values are contained in the
0720: * timeline. This is implemented verifying that all segments are in the
0721: * range.
0722: *
0723: * @param domainValueStart start of the range to verify
0724: * @param domainValueEnd end of the range to verify
0725: *
0726: * @return <code>true</code> if the range is contained in the timeline
0727: */
0728: public boolean containsDomainRange(long domainValueStart,
0729: long domainValueEnd) {
0730: if (domainValueEnd < domainValueStart) {
0731: throw new IllegalArgumentException("domainValueEnd ("
0732: + domainValueEnd + ") < domainValueStart ("
0733: + domainValueStart + ")");
0734: }
0735: Segment segment = getSegment(domainValueStart);
0736: boolean contains = true;
0737: do {
0738: contains = (segment.inIncludeSegments());
0739: if (segment.contains(domainValueEnd)) {
0740: break;
0741: } else {
0742: segment.inc();
0743: }
0744: } while (contains);
0745: return (contains);
0746: }
0747:
0748: /**
0749: * Returns <code>true</code> if a range of values are contained in the
0750: * timeline. This is implemented verifying that all segments are in the
0751: * range.
0752: *
0753: * @param dateDomainValueStart start of the range to verify
0754: * @param dateDomainValueEnd end of the range to verify
0755: *
0756: * @return <code>true</code> if the range is contained in the timeline
0757: */
0758: public boolean containsDomainRange(Date dateDomainValueStart,
0759: Date dateDomainValueEnd) {
0760: return containsDomainRange(getTime(dateDomainValueStart),
0761: getTime(dateDomainValueEnd));
0762: }
0763:
0764: /**
0765: * Adds a segment as an exception. An exception segment is defined as a
0766: * segment to exclude from what would otherwise be considered a valid
0767: * segment of the timeline. An exception segment can not be contained
0768: * inside an already excluded segment. If so, no action will occur (the
0769: * proposed exception segment will be discarded).
0770: * <p>
0771: * The segment is identified by a domainValue into any part of the segment.
0772: * Therefore the segmentStart <= domainValue <= segmentEnd.
0773: *
0774: * @param millisecond domain value to treat as an exception
0775: */
0776: public void addException(long millisecond) {
0777: addException(new Segment(millisecond));
0778: }
0779:
0780: /**
0781: * Adds a segment range as an exception. An exception segment is defined as
0782: * a segment to exclude from what would otherwise be considered a valid
0783: * segment of the timeline. An exception segment can not be contained
0784: * inside an already excluded segment. If so, no action will occur (the
0785: * proposed exception segment will be discarded).
0786: * <p>
0787: * The segment range is identified by a domainValue that begins a valid
0788: * segment and ends with a domainValue that ends a valid segment.
0789: * Therefore the range will contain all segments whose segmentStart
0790: * <= domainValue and segmentEnd <= toDomainValue.
0791: *
0792: * @param fromDomainValue start of domain range to treat as an exception
0793: * @param toDomainValue end of domain range to treat as an exception
0794: */
0795: public void addException(long fromDomainValue, long toDomainValue) {
0796: addException(new SegmentRange(fromDomainValue, toDomainValue));
0797: }
0798:
0799: /**
0800: * Adds a segment as an exception. An exception segment is defined as a
0801: * segment to exclude from what would otherwise be considered a valid
0802: * segment of the timeline. An exception segment can not be contained
0803: * inside an already excluded segment. If so, no action will occur (the
0804: * proposed exception segment will be discarded).
0805: * <p>
0806: * The segment is identified by a Date into any part of the segment.
0807: *
0808: * @param exceptionDate Date into the segment to exclude.
0809: */
0810: public void addException(Date exceptionDate) {
0811: addException(getTime(exceptionDate));
0812: //addException(exceptionDate.getTime());
0813: }
0814:
0815: /**
0816: * Adds a list of dates as segment exceptions. Each exception segment is
0817: * defined as a segment to exclude from what would otherwise be considered
0818: * a valid segment of the timeline. An exception segment can not be
0819: * contained inside an already excluded segment. If so, no action will
0820: * occur (the proposed exception segment will be discarded).
0821: * <p>
0822: * The segment is identified by a Date into any part of the segment.
0823: *
0824: * @param exceptionList List of Date objects that identify the segments to
0825: * exclude.
0826: */
0827: public void addExceptions(List exceptionList) {
0828: for (Iterator iter = exceptionList.iterator(); iter.hasNext();) {
0829: addException((Date) iter.next());
0830: }
0831: }
0832:
0833: /**
0834: * Adds a segment as an exception. An exception segment is defined as a
0835: * segment to exclude from what would otherwise be considered a valid
0836: * segment of the timeline. An exception segment can not be contained
0837: * inside an already excluded segment. This is verified inside this
0838: * method, and if so, no action will occur (the proposed exception segment
0839: * will be discarded).
0840: *
0841: * @param segment the segment to exclude.
0842: */
0843: private void addException(Segment segment) {
0844: if (segment.inIncludeSegments()) {
0845: int p = binarySearchExceptionSegments(segment);
0846: this .exceptionSegments.add(-(p + 1), segment);
0847: }
0848: }
0849:
0850: /**
0851: * Adds a segment relative to the baseTimeline as an exception. Because a
0852: * base segment is normally larger than our segments, this may add one or
0853: * more segment ranges to the exception list.
0854: * <p>
0855: * An exception segment is defined as a segment
0856: * to exclude from what would otherwise be considered a valid segment of
0857: * the timeline. An exception segment can not be contained inside an
0858: * already excluded segment. If so, no action will occur (the proposed
0859: * exception segment will be discarded).
0860: * <p>
0861: * The segment is identified by a domainValue into any part of the
0862: * baseTimeline segment.
0863: *
0864: * @param domainValue domain value to teat as a baseTimeline exception.
0865: */
0866: public void addBaseTimelineException(long domainValue) {
0867:
0868: Segment baseSegment = this .baseTimeline.getSegment(domainValue);
0869: if (baseSegment.inIncludeSegments()) {
0870:
0871: // cycle through all the segments contained in the BaseTimeline
0872: // exception segment
0873: Segment segment = getSegment(baseSegment.getSegmentStart());
0874: while (segment.getSegmentStart() <= baseSegment
0875: .getSegmentEnd()) {
0876: if (segment.inIncludeSegments()) {
0877:
0878: // find all consecutive included segments
0879: long fromDomainValue = segment.getSegmentStart();
0880: long toDomainValue;
0881: do {
0882: toDomainValue = segment.getSegmentEnd();
0883: segment.inc();
0884: } while (segment.inIncludeSegments());
0885:
0886: // add the interval as an exception
0887: addException(fromDomainValue, toDomainValue);
0888:
0889: } else {
0890: // this is not one of our included segment, skip it
0891: segment.inc();
0892: }
0893: }
0894: }
0895: }
0896:
0897: /**
0898: * Adds a segment relative to the baseTimeline as an exception. An
0899: * exception segment is defined as a segment to exclude from what would
0900: * otherwise be considered a valid segment of the timeline. An exception
0901: * segment can not be contained inside an already excluded segment. If so,
0902: * no action will occure (the proposed exception segment will be discarded).
0903: * <p>
0904: * The segment is identified by a domainValue into any part of the segment.
0905: * Therefore the segmentStart <= domainValue <= segmentEnd.
0906: *
0907: * @param date date domain value to treat as a baseTimeline exception
0908: */
0909: public void addBaseTimelineException(Date date) {
0910: addBaseTimelineException(getTime(date));
0911: }
0912:
0913: /**
0914: * Adds all excluded segments from the BaseTimeline as exceptions to our
0915: * timeline. This allows us to combine two timelines for more complex
0916: * calculations.
0917: *
0918: * @param fromBaseDomainValue Start of the range where exclusions will be
0919: * extracted.
0920: * @param toBaseDomainValue End of the range to process.
0921: */
0922: public void addBaseTimelineExclusions(long fromBaseDomainValue,
0923: long toBaseDomainValue) {
0924:
0925: // find first excluded base segment starting fromDomainValue
0926: Segment baseSegment = this .baseTimeline
0927: .getSegment(fromBaseDomainValue);
0928: while (baseSegment.getSegmentStart() <= toBaseDomainValue
0929: && !baseSegment.inExcludeSegments()) {
0930:
0931: baseSegment.inc();
0932:
0933: }
0934:
0935: // cycle over all the base segments groups in the range
0936: while (baseSegment.getSegmentStart() <= toBaseDomainValue) {
0937:
0938: long baseExclusionRangeEnd = baseSegment.getSegmentStart()
0939: + this .baseTimeline.getSegmentsExcluded()
0940: * this .baseTimeline.getSegmentSize() - 1;
0941:
0942: // cycle through all the segments contained in the base exclusion
0943: // area
0944: Segment segment = getSegment(baseSegment.getSegmentStart());
0945: while (segment.getSegmentStart() <= baseExclusionRangeEnd) {
0946: if (segment.inIncludeSegments()) {
0947:
0948: // find all consecutive included segments
0949: long fromDomainValue = segment.getSegmentStart();
0950: long toDomainValue;
0951: do {
0952: toDomainValue = segment.getSegmentEnd();
0953: segment.inc();
0954: } while (segment.inIncludeSegments());
0955:
0956: // add the interval as an exception
0957: addException(new BaseTimelineSegmentRange(
0958: fromDomainValue, toDomainValue));
0959: } else {
0960: // this is not one of our included segment, skip it
0961: segment.inc();
0962: }
0963: }
0964:
0965: // go to next base segment group
0966: baseSegment.inc(this .baseTimeline.getGroupSegmentCount());
0967: }
0968: }
0969:
0970: /**
0971: * Returns the number of exception segments wholly contained in the
0972: * (fromDomainValue, toDomainValue) interval.
0973: *
0974: * @param fromMillisecond the beginning of the interval.
0975: * @param toMillisecond the end of the interval.
0976: *
0977: * @return Number of exception segments contained in the interval.
0978: */
0979: public long getExceptionSegmentCount(long fromMillisecond,
0980: long toMillisecond) {
0981: if (toMillisecond < fromMillisecond) {
0982: return (0);
0983: }
0984:
0985: int n = 0;
0986: for (Iterator iter = this .exceptionSegments.iterator(); iter
0987: .hasNext();) {
0988: Segment segment = (Segment) iter.next();
0989: Segment intersection = segment.intersect(fromMillisecond,
0990: toMillisecond);
0991: if (intersection != null) {
0992: n += intersection.getSegmentCount();
0993: }
0994: }
0995:
0996: return (n);
0997: }
0998:
0999: /**
1000: * Returns a segment that contains a domainValue. If the domainValue is
1001: * not contained in the timeline (because it is not contained in the
1002: * baseTimeline), a Segment that contains
1003: * <code>index + segmentSize*m</code> will be returned for the smallest
1004: * <code>m</code> possible.
1005: *
1006: * @param millisecond index into the segment
1007: *
1008: * @return A Segment that contains index, or the next possible Segment.
1009: */
1010: public Segment getSegment(long millisecond) {
1011: return new Segment(millisecond);
1012: }
1013:
1014: /**
1015: * Returns a segment that contains a date. For accurate calculations,
1016: * the calendar should use TIME_ZONE for its calculation (or any other
1017: * similar time zone).
1018: *
1019: * If the date is not contained in the timeline (because it is not
1020: * contained in the baseTimeline), a Segment that contains
1021: * <code>date + segmentSize*m</code> will be returned for the smallest
1022: * <code>m</code> possible.
1023: *
1024: * @param date date into the segment
1025: *
1026: * @return A Segment that contains date, or the next possible Segment.
1027: */
1028: public Segment getSegment(Date date) {
1029: return (getSegment(getTime(date)));
1030: }
1031:
1032: /**
1033: * Convenient method to test equality in two objects, taking into account
1034: * nulls.
1035: *
1036: * @param o first object to compare
1037: * @param p second object to compare
1038: *
1039: * @return <code>true</code> if both objects are equal or both
1040: * <code>null</code>, <code>false</code> otherwise.
1041: */
1042: private boolean equals(Object o, Object p) {
1043: return (o == p || ((o != null) && o.equals(p)));
1044: }
1045:
1046: /**
1047: * Returns true if we are equal to the parameter
1048: *
1049: * @param o Object to verify with us
1050: *
1051: * @return <code>true</code> or <code>false</code>
1052: */
1053: public boolean equals(Object o) {
1054: if (o instanceof SegmentedTimeline) {
1055: SegmentedTimeline other = (SegmentedTimeline) o;
1056:
1057: boolean b0 = (this .segmentSize == other.getSegmentSize());
1058: boolean b1 = (this .segmentsIncluded == other
1059: .getSegmentsIncluded());
1060: boolean b2 = (this .segmentsExcluded == other
1061: .getSegmentsExcluded());
1062: boolean b3 = (this .startTime == other.getStartTime());
1063: boolean b4 = equals(this .exceptionSegments, other
1064: .getExceptionSegments());
1065: return b0 && b1 && b2 && b3 && b4;
1066: } else {
1067: return (false);
1068: }
1069: }
1070:
1071: /**
1072: * Returns a hash code for this object.
1073: *
1074: * @return A hash code.
1075: */
1076: public int hashCode() {
1077: int result = 19;
1078: result = 37 * result
1079: + (int) (this .segmentSize ^ (this .segmentSize >>> 32));
1080: result = 37 * result
1081: + (int) (this .startTime ^ (this .startTime >>> 32));
1082: return result;
1083: }
1084:
1085: /**
1086: * Preforms a binary serach in the exceptionSegments sorted array. This
1087: * array can contain Segments or SegmentRange objects.
1088: *
1089: * @param segment the key to be searched for.
1090: *
1091: * @return index of the search segment, if it is contained in the list;
1092: * otherwise, <tt>(-(<i>insertion point</i>) - 1)</tt>. The
1093: * <i>insertion point</i> is defined as the point at which the
1094: * segment would be inserted into the list: the index of the first
1095: * element greater than the key, or <tt>list.size()</tt>, if all
1096: * elements in the list are less than the specified segment. Note
1097: * that this guarantees that the return value will be >= 0 if
1098: * and only if the key is found.
1099: */
1100: private int binarySearchExceptionSegments(Segment segment) {
1101: int low = 0;
1102: int high = this .exceptionSegments.size() - 1;
1103:
1104: while (low <= high) {
1105: int mid = (low + high) / 2;
1106: Segment midSegment = (Segment) this .exceptionSegments
1107: .get(mid);
1108:
1109: // first test for equality (contains or contained)
1110: if (segment.contains(midSegment)
1111: || midSegment.contains(segment)) {
1112: return mid;
1113: }
1114:
1115: if (midSegment.before(segment)) {
1116: low = mid + 1;
1117: } else if (midSegment.after(segment)) {
1118: high = mid - 1;
1119: } else {
1120: throw new IllegalStateException("Invalid condition.");
1121: }
1122: }
1123: return -(low + 1); // key not found
1124: }
1125:
1126: /**
1127: * Special method that handles conversion between the Default Time Zone and
1128: * a UTC time zone with no DST. This is needed so all days have the same
1129: * size. This method is the prefered way of converting a Data into
1130: * milliseconds for usage in this class.
1131: *
1132: * @param date Date to convert to long.
1133: *
1134: * @return The milliseconds.
1135: */
1136: public long getTime(Date date) {
1137: long result = date.getTime();
1138: if (this .adjustForDaylightSaving) {
1139: this .workingCalendar.setTime(date);
1140: this .workingCalendarNoDST.set(this .workingCalendar
1141: .get(Calendar.YEAR), this .workingCalendar
1142: .get(Calendar.MONTH), this .workingCalendar
1143: .get(Calendar.DATE), this .workingCalendar
1144: .get(Calendar.HOUR_OF_DAY), this .workingCalendar
1145: .get(Calendar.MINUTE), this .workingCalendar
1146: .get(Calendar.SECOND));
1147: this .workingCalendarNoDST.set(Calendar.MILLISECOND,
1148: this .workingCalendar.get(Calendar.MILLISECOND));
1149: Date revisedDate = this .workingCalendarNoDST.getTime();
1150: result = revisedDate.getTime();
1151: }
1152:
1153: return result;
1154: }
1155:
1156: /**
1157: * Converts a millisecond value into a {@link Date} object.
1158: *
1159: * @param value the millisecond value.
1160: *
1161: * @return The date.
1162: */
1163: public Date getDate(long value) {
1164: this .workingCalendarNoDST.setTime(new Date(value));
1165: return (this .workingCalendarNoDST.getTime());
1166: }
1167:
1168: /**
1169: * Returns a clone of the timeline.
1170: *
1171: * @return A clone.
1172: *
1173: * @throws CloneNotSupportedException ??.
1174: */
1175: public Object clone() throws CloneNotSupportedException {
1176: SegmentedTimeline clone = (SegmentedTimeline) super .clone();
1177: return clone;
1178: }
1179:
1180: /**
1181: * Internal class to represent a valid segment for this timeline. A segment
1182: * is valid on a timeline if it is part of its included, excluded or
1183: * exception segments.
1184: * <p>
1185: * Each segment will know its segment number, segmentStart, segmentEnd and
1186: * index inside the segment.
1187: */
1188: public class Segment implements Comparable, Cloneable, Serializable {
1189:
1190: /** The segment number. */
1191: protected long segmentNumber;
1192:
1193: /** The segment start. */
1194: protected long segmentStart;
1195:
1196: /** The segment end. */
1197: protected long segmentEnd;
1198:
1199: /** A reference point within the segment. */
1200: protected long millisecond;
1201:
1202: /**
1203: * Protected constructor only used by sub-classes.
1204: */
1205: protected Segment() {
1206: // empty
1207: }
1208:
1209: /**
1210: * Creates a segment for a given point in time.
1211: *
1212: * @param millisecond the millisecond (as encoded by java.util.Date).
1213: */
1214: protected Segment(long millisecond) {
1215: this .segmentNumber = calculateSegmentNumber(millisecond);
1216: this .segmentStart = SegmentedTimeline.this .startTime
1217: + this .segmentNumber
1218: * SegmentedTimeline.this .segmentSize;
1219: this .segmentEnd = this .segmentStart
1220: + SegmentedTimeline.this .segmentSize - 1;
1221: this .millisecond = millisecond;
1222: }
1223:
1224: /**
1225: * Calculates the segment number for a given millisecond.
1226: *
1227: * @param millis the millisecond (as encoded by java.util.Date).
1228: *
1229: * @return The segment number.
1230: */
1231: public long calculateSegmentNumber(long millis) {
1232: if (millis >= SegmentedTimeline.this .startTime) {
1233: return (millis - SegmentedTimeline.this .startTime)
1234: / SegmentedTimeline.this .segmentSize;
1235: } else {
1236: return ((millis - SegmentedTimeline.this .startTime) / SegmentedTimeline.this .segmentSize) - 1;
1237: }
1238: }
1239:
1240: /**
1241: * Returns the segment number of this segment. Segments start at 0.
1242: *
1243: * @return The segment number.
1244: */
1245: public long getSegmentNumber() {
1246: return this .segmentNumber;
1247: }
1248:
1249: /**
1250: * Returns always one (the number of segments contained in this
1251: * segment).
1252: *
1253: * @return The segment count (always 1 for this class).
1254: */
1255: public long getSegmentCount() {
1256: return 1;
1257: }
1258:
1259: /**
1260: * Gets the start of this segment in ms.
1261: *
1262: * @return The segment start.
1263: */
1264: public long getSegmentStart() {
1265: return this .segmentStart;
1266: }
1267:
1268: /**
1269: * Gets the end of this segment in ms.
1270: *
1271: * @return The segment end.
1272: */
1273: public long getSegmentEnd() {
1274: return this .segmentEnd;
1275: }
1276:
1277: /**
1278: * Returns the millisecond used to reference this segment (always
1279: * between the segmentStart and segmentEnd).
1280: *
1281: * @return The millisecond.
1282: */
1283: public long getMillisecond() {
1284: return this .millisecond;
1285: }
1286:
1287: /**
1288: * Returns a {@link java.util.Date} that represents the reference point
1289: * for this segment.
1290: *
1291: * @return The date.
1292: */
1293: public Date getDate() {
1294: return SegmentedTimeline.this .getDate(this .millisecond);
1295: }
1296:
1297: /**
1298: * Returns true if a particular millisecond is contained in this
1299: * segment.
1300: *
1301: * @param millis the millisecond to verify.
1302: *
1303: * @return <code>true</code> if the millisecond is contained in the
1304: * segment.
1305: */
1306: public boolean contains(long millis) {
1307: return (this .segmentStart <= millis && millis <= this .segmentEnd);
1308: }
1309:
1310: /**
1311: * Returns <code>true</code> if an interval is contained in this
1312: * segment.
1313: *
1314: * @param from the start of the interval.
1315: * @param to the end of the interval.
1316: *
1317: * @return <code>true</code> if the interval is contained in the
1318: * segment.
1319: */
1320: public boolean contains(long from, long to) {
1321: return (this .segmentStart <= from && to <= this .segmentEnd);
1322: }
1323:
1324: /**
1325: * Returns <code>true</code> if a segment is contained in this segment.
1326: *
1327: * @param segment the segment to test for inclusion
1328: *
1329: * @return <code>true</code> if the segment is contained in this
1330: * segment.
1331: */
1332: public boolean contains(Segment segment) {
1333: return contains(segment.getSegmentStart(), segment
1334: .getSegmentEnd());
1335: }
1336:
1337: /**
1338: * Returns <code>true</code> if this segment is contained in an
1339: * interval.
1340: *
1341: * @param from the start of the interval.
1342: * @param to the end of the interval.
1343: *
1344: * @return <code>true</code> if this segment is contained in the
1345: * interval.
1346: */
1347: public boolean contained(long from, long to) {
1348: return (from <= this .segmentStart && this .segmentEnd <= to);
1349: }
1350:
1351: /**
1352: * Returns a segment that is the intersection of this segment and the
1353: * interval.
1354: *
1355: * @param from the start of the interval.
1356: * @param to the end of the interval.
1357: *
1358: * @return A segment.
1359: */
1360: public Segment intersect(long from, long to) {
1361: if (from <= this .segmentStart && this .segmentEnd <= to) {
1362: return this ;
1363: } else {
1364: return null;
1365: }
1366: }
1367:
1368: /**
1369: * Returns <code>true</code> if this segment is wholly before another
1370: * segment.
1371: *
1372: * @param other the other segment.
1373: *
1374: * @return A boolean.
1375: */
1376: public boolean before(Segment other) {
1377: return (this .segmentEnd < other.getSegmentStart());
1378: }
1379:
1380: /**
1381: * Returns <code>true</code> if this segment is wholly after another
1382: * segment.
1383: *
1384: * @param other the other segment.
1385: *
1386: * @return A boolean.
1387: */
1388: public boolean after(Segment other) {
1389: return (this .segmentStart > other.getSegmentEnd());
1390: }
1391:
1392: /**
1393: * Tests an object (usually another <code>Segment</code>) for equality
1394: * with this segment.
1395: *
1396: * @param object The other segment to compare with us
1397: *
1398: * @return <code>true</code> if we are the same segment
1399: */
1400: public boolean equals(Object object) {
1401: if (object instanceof Segment) {
1402: Segment other = (Segment) object;
1403: return (this .segmentNumber == other.getSegmentNumber()
1404: && this .segmentStart == other.getSegmentStart()
1405: && this .segmentEnd == other.getSegmentEnd() && this .millisecond == other
1406: .getMillisecond());
1407: } else {
1408: return false;
1409: }
1410: }
1411:
1412: /**
1413: * Returns a copy of ourselves or <code>null</code> if there was an
1414: * exception during cloning.
1415: *
1416: * @return A copy of this segment.
1417: */
1418: public Segment copy() {
1419: try {
1420: return (Segment) this .clone();
1421: } catch (CloneNotSupportedException e) {
1422: return null;
1423: }
1424: }
1425:
1426: /**
1427: * Will compare this Segment with another Segment (from Comparable
1428: * interface).
1429: *
1430: * @param object The other Segment to compare with
1431: *
1432: * @return -1: this < object, 0: this.equal(object) and
1433: * +1: this > object
1434: */
1435: public int compareTo(Object object) {
1436: Segment other = (Segment) object;
1437: if (this .before(other)) {
1438: return -1;
1439: } else if (this .after(other)) {
1440: return +1;
1441: } else {
1442: return 0;
1443: }
1444: }
1445:
1446: /**
1447: * Returns true if we are an included segment and we are not an
1448: * exception.
1449: *
1450: * @return <code>true</code> or <code>false</code>.
1451: */
1452: public boolean inIncludeSegments() {
1453: if (getSegmentNumberRelativeToGroup() < SegmentedTimeline.this .segmentsIncluded) {
1454: return !inExceptionSegments();
1455: } else {
1456: return false;
1457: }
1458: }
1459:
1460: /**
1461: * Returns true if we are an excluded segment.
1462: *
1463: * @return <code>true</code> or <code>false</code>.
1464: */
1465: public boolean inExcludeSegments() {
1466: return getSegmentNumberRelativeToGroup() >= SegmentedTimeline.this .segmentsIncluded;
1467: }
1468:
1469: /**
1470: * Calculate the segment number relative to the segment group. This
1471: * will be a number between 0 and segmentsGroup-1. This value is
1472: * calculated from the segmentNumber. Special care is taken for
1473: * negative segmentNumbers.
1474: *
1475: * @return The segment number.
1476: */
1477: private long getSegmentNumberRelativeToGroup() {
1478: long p = (this .segmentNumber % SegmentedTimeline.this .groupSegmentCount);
1479: if (p < 0) {
1480: p += SegmentedTimeline.this .groupSegmentCount;
1481: }
1482: return p;
1483: }
1484:
1485: /**
1486: * Returns true if we are an exception segment. This is implemented via
1487: * a binary search on the exceptionSegments sorted list.
1488: *
1489: * If the segment is not listed as an exception in our list and we have
1490: * a baseTimeline, a check is performed to see if the segment is inside
1491: * an excluded segment from our base. If so, it is also considered an
1492: * exception.
1493: *
1494: * @return <code>true</code> if we are an exception segment.
1495: */
1496: public boolean inExceptionSegments() {
1497: return binarySearchExceptionSegments(this ) >= 0;
1498: }
1499:
1500: /**
1501: * Increments the internal attributes of this segment by a number of
1502: * segments.
1503: *
1504: * @param n Number of segments to increment.
1505: */
1506: public void inc(long n) {
1507: this .segmentNumber += n;
1508: long m = n * SegmentedTimeline.this .segmentSize;
1509: this .segmentStart += m;
1510: this .segmentEnd += m;
1511: this .millisecond += m;
1512: }
1513:
1514: /**
1515: * Increments the internal attributes of this segment by one segment.
1516: * The exact time incremented is segmentSize.
1517: */
1518: public void inc() {
1519: inc(1);
1520: }
1521:
1522: /**
1523: * Decrements the internal attributes of this segment by a number of
1524: * segments.
1525: *
1526: * @param n Number of segments to decrement.
1527: */
1528: public void dec(long n) {
1529: this .segmentNumber -= n;
1530: long m = n * SegmentedTimeline.this .segmentSize;
1531: this .segmentStart -= m;
1532: this .segmentEnd -= m;
1533: this .millisecond -= m;
1534: }
1535:
1536: /**
1537: * Decrements the internal attributes of this segment by one segment.
1538: * The exact time decremented is segmentSize.
1539: */
1540: public void dec() {
1541: dec(1);
1542: }
1543:
1544: /**
1545: * Moves the index of this segment to the beginning if the segment.
1546: */
1547: public void moveIndexToStart() {
1548: this .millisecond = this .segmentStart;
1549: }
1550:
1551: /**
1552: * Moves the index of this segment to the end of the segment.
1553: */
1554: public void moveIndexToEnd() {
1555: this .millisecond = this .segmentEnd;
1556: }
1557:
1558: }
1559:
1560: /**
1561: * Private internal class to represent a range of segments. This class is
1562: * mainly used to store in one object a range of exception segments. This
1563: * optimizes certain timelines that use a small segment size (like an
1564: * intraday timeline) allowing them to express a day exception as one
1565: * SegmentRange instead of multi Segments.
1566: */
1567: protected class SegmentRange extends Segment {
1568:
1569: /** The number of segments in the range. */
1570: private long segmentCount;
1571:
1572: /**
1573: * Creates a SegmentRange between a start and end domain values.
1574: *
1575: * @param fromMillisecond start of the range
1576: * @param toMillisecond end of the range
1577: */
1578: public SegmentRange(long fromMillisecond, long toMillisecond) {
1579:
1580: Segment start = getSegment(fromMillisecond);
1581: Segment end = getSegment(toMillisecond);
1582: // if (start.getSegmentStart() != fromMillisecond
1583: // || end.getSegmentEnd() != toMillisecond) {
1584: // throw new IllegalArgumentException("Invalid Segment Range ["
1585: // + fromMillisecond + "," + toMillisecond + "]");
1586: // }
1587:
1588: this .millisecond = fromMillisecond;
1589: this .segmentNumber = calculateSegmentNumber(fromMillisecond);
1590: this .segmentStart = start.segmentStart;
1591: this .segmentEnd = end.segmentEnd;
1592: this .segmentCount = (end.getSegmentNumber()
1593: - start.getSegmentNumber() + 1);
1594: }
1595:
1596: /**
1597: * Returns the number of segments contained in this range.
1598: *
1599: * @return The segment count.
1600: */
1601: public long getSegmentCount() {
1602: return this .segmentCount;
1603: }
1604:
1605: /**
1606: * Returns a segment that is the intersection of this segment and the
1607: * interval.
1608: *
1609: * @param from the start of the interval.
1610: * @param to the end of the interval.
1611: *
1612: * @return The intersection.
1613: */
1614: public Segment intersect(long from, long to) {
1615:
1616: // Segment fromSegment = getSegment(from);
1617: // fromSegment.inc();
1618: // Segment toSegment = getSegment(to);
1619: // toSegment.dec();
1620: long start = Math.max(from, this .segmentStart);
1621: long end = Math.min(to, this .segmentEnd);
1622: // long start = Math.max(
1623: // fromSegment.getSegmentStart(), this.segmentStart
1624: // );
1625: // long end = Math.min(toSegment.getSegmentEnd(), this.segmentEnd);
1626: if (start <= end) {
1627: return new SegmentRange(start, end);
1628: } else {
1629: return null;
1630: }
1631: }
1632:
1633: /**
1634: * Returns true if all Segments of this SegmentRenge are an included
1635: * segment and are not an exception.
1636: *
1637: * @return <code>true</code> or </code>false</code>.
1638: */
1639: public boolean inIncludeSegments() {
1640: for (Segment segment = getSegment(this .segmentStart); segment
1641: .getSegmentStart() < this .segmentEnd; segment.inc()) {
1642: if (!segment.inIncludeSegments()) {
1643: return (false);
1644: }
1645: }
1646: return true;
1647: }
1648:
1649: /**
1650: * Returns true if we are an excluded segment.
1651: *
1652: * @return <code>true</code> or </code>false</code>.
1653: */
1654: public boolean inExcludeSegments() {
1655: for (Segment segment = getSegment(this .segmentStart); segment
1656: .getSegmentStart() < this .segmentEnd; segment.inc()) {
1657: if (!segment.inExceptionSegments()) {
1658: return (false);
1659: }
1660: }
1661: return true;
1662: }
1663:
1664: /**
1665: * Not implemented for SegmentRange. Always throws
1666: * IllegalArgumentException.
1667: *
1668: * @param n Number of segments to increment.
1669: */
1670: public void inc(long n) {
1671: throw new IllegalArgumentException(
1672: "Not implemented in SegmentRange");
1673: }
1674:
1675: }
1676:
1677: /**
1678: * Special <code>SegmentRange</code> that came from the BaseTimeline.
1679: */
1680: protected class BaseTimelineSegmentRange extends SegmentRange {
1681:
1682: /**
1683: * Constructor.
1684: *
1685: * @param fromDomainValue the start value.
1686: * @param toDomainValue the end value.
1687: */
1688: public BaseTimelineSegmentRange(long fromDomainValue,
1689: long toDomainValue) {
1690: super(fromDomainValue, toDomainValue);
1691: }
1692:
1693: }
1694:
1695: }
|