0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026: package com.sun.perseus.model;
0027:
0028: import java.util.Vector;
0029:
0030: /**
0031: * The <code>TimedElementSupport</code> class is the main abstraction of the
0032: * SMIL timing model implementation.
0033: *
0034: * <p>It is responsible for computing the animation's state (inactive, active,
0035: * post-acitve, frozen) and, when the application is active, what the current
0036: * simple time is. That simple time is then used by the animation engine to
0037: * compute animated values. </p>
0038: *
0039: * <p>SMIL has a notion of time containers. A <code>TimedElementSupport</code>
0040: * is the child of a time container. The relationship is that the element's
0041: * current time is the container's simple time. In otherwords, sampling a
0042: * container for a given time (in its own container), yields a simple time for
0043: * the container. That simple time is the timed element's current time, used to
0044: * compute the timed element's own simple time. Refer to the SMIL 2
0045: * specification for an explanation of what the various time semantics are. </p>
0046: *
0047: * @version $Id: TimedElementSupport.java,v 1.7 2006/07/13 00:55:58 st125089 Exp $
0048: */
0049: public class TimedElementSupport {
0050: /**
0051: * Last Simple Duration End event type.
0052: */
0053: public static final String LAST_DUR_END_EVENT_TYPE = "lastDurEndEvent";
0054:
0055: /**
0056: * Seek End event type.
0057: */
0058: public static final String SEEK_END_EVENT_TYPE = "seekEndEvent";
0059:
0060: /**
0061: * Seek End event type.
0062: */
0063: public static final String SEEK_BEGIN_EVENT_TYPE = "seekBeginEvent";
0064:
0065: /**
0066: * End event type.
0067: */
0068: public static final String END_EVENT_TYPE = "endEvent";
0069:
0070: /**
0071: * Begin event type.
0072: */
0073: public static final String BEGIN_EVENT_TYPE = "beginEvent";
0074:
0075: /**
0076: * Repeat event type
0077: */
0078: public static final String REPEAT_EVENT_TYPE = "repeat";
0079:
0080: /**
0081: * Used when this <code>TimedElement</code> can be restarted under
0082: * any circumstance.
0083: */
0084: public static final int RESTART_ALWAYS = 1;
0085:
0086: /**
0087: * Used when this <code>TimedElement</code> can only be restarted when
0088: * it is not active (i.e., it does not have a current interval.
0089: */
0090: public static final int RESTART_WHEN_NOT_ACTIVE = 2;
0091:
0092: /**
0093: * Used when this <code>TimedElement</code> cannot be restarted under
0094: * any circumstance
0095: */
0096: public static final int RESTART_NEVER = 3;
0097:
0098: /**
0099: * Used when this <code>TimedElement</code> should not modify the
0100: * animated value when it is post active.
0101: */
0102: public static final int FILL_BEHAVIOR_REMOVE = 1;
0103:
0104: /**
0105: * Used when this <code>TimedElement</code> should maintain the
0106: * last value of its simple interval after it becomes inactive.
0107: */
0108: public static final int FILL_BEHAVIOR_FREEZE = 2;
0109:
0110: /**
0111: * State before the element has been initialized
0112: */
0113: protected static final int STATE_PRE_INIT = 1;
0114:
0115: /**
0116: * State where there has never been a current interval
0117: * (since the last init).
0118: */
0119: protected static final int STATE_NO_INTERVAL = 2;
0120:
0121: /**
0122: * State while the element is waiting to begin
0123: * the first interval.
0124: */
0125: protected static final int STATE_WAITING_INTERVAL_0 = 3;
0126:
0127: /**
0128: * State while the element is waiting to begin
0129: * an interval other than the first one
0130: */
0131: protected static final int STATE_WAITING_INTERVAL_N = 4;
0132:
0133: /**
0134: * State while the element is playing the current
0135: * interval.
0136: */
0137: protected static final int STATE_PLAYING = 5;
0138:
0139: /**
0140: * State when the element performs any fill on the previous
0141: * interval.
0142: */
0143: protected static final int STATE_FILL = 6;
0144:
0145: /**
0146: * This time value is used to avoid creating Time instances
0147: * over and over again in animation loops, when dispatching
0148: * events.
0149: */
0150: protected final Time eventTime = new Time(0);
0151:
0152: /**
0153: * The element's state. One of the STATE_XXX constants.
0154: */
0155: int state = STATE_PRE_INIT;
0156:
0157: /**
0158: * Controls whether the element is in this rare state
0159: * where it is playing but its last simple duration
0160: * is finished. In that state, the element is 'playing',
0161: * but acting as if frozen.
0162: *
0163: * There are situations where the min attribute may cause the
0164: * active duration to go beyond the time of the last simple
0165: * duration instance. In that case, the element is still
0166: * in the playing state, but we mark it as 'playFill', which
0167: * means that it should behave almost as if in the fill state.
0168: */
0169: boolean playFill = false;
0170:
0171: /**
0172: * The current iteration (only used while playing
0173: */
0174: int curIter = 0;
0175:
0176: /**
0177: * This element's simple duration. This corresponds to the 'dur'
0178: * attribute. Note that this is only one of the parameters that
0179: * defines the actual simple duration.
0180: *
0181: * @see #simpleDur
0182: * @see #computeSimpleDuration
0183: */
0184: Time dur = Time.INDEFINITE;
0185:
0186: /**
0187: * The number of times this element repeats the simple duration.
0188: * NaN means unspecified.
0189: */
0190: float repeatCount = Float.NaN;
0191:
0192: /**
0193: * repeatDur set a limit on active duration length.
0194: */
0195: Time repeatDur = null;
0196:
0197: /**
0198: * This element's implicit duration
0199: */
0200: Time implicitDuration = Time.UNRESOLVED;
0201:
0202: /**
0203: * min sets a lower limit on the active duration.
0204: */
0205: Time min = new Time(0);
0206:
0207: /**
0208: * max sets an upper limit on the active duration
0209: */
0210: Time max = Time.INDEFINITE;
0211:
0212: /**
0213: * Defines the restart behavior for this element. This should be
0214: * one of RESTART_ALWAYS, RESTART_WHEN_NOT_ACTIVE, RESTART_NEVER.
0215: */
0216: int restart = RESTART_ALWAYS;
0217:
0218: /**
0219: * Defines the behavior after this element is no longer active.
0220: * If FILL_BEHAVIOR_FILL, it means the element will keep sampling at the
0221: * last simple duration time. If FILL_BEHAVIOR_REMOVE (the default),
0222: * the effect of the animation is removed when the element is
0223: * inactive (i.e., when it no longer has a current interval.
0224: */
0225: int fillBehavior = FILL_BEHAVIOR_REMOVE;
0226:
0227: /**
0228: * A reference to this element's time container. This is initialized
0229: * anytime the parent is set. This element's current time is the
0230: * container's simple time.
0231: *
0232: * @see #setParent
0233: */
0234: protected TimeContainerSupport timeContainer;
0235:
0236: /**
0237: * Keeps a reference to the current <code>TimeInterval</code>
0238: */
0239: TimeInterval currentInterval;
0240:
0241: /**
0242: * Last local time (i.e., within the simple duration) this element
0243: * was sampled at.
0244: */
0245: long lastSampleTime = -1;
0246:
0247: /**
0248: * The simple duration for the current interval
0249: * @see #computeSimpleDuration
0250: */
0251: Time simpleDur = Time.UNRESOLVED;
0252:
0253: /**
0254: * Keeps a reference to the interval preceding the current one.
0255: */
0256: TimeInterval previousInterval;
0257:
0258: /**
0259: * Keeps are reference to the last interval that was actually
0260: * run.
0261: */
0262:
0263: /**
0264: * List of begin <code>TimeInstance</code>s.
0265: */
0266: Vector beginInstances = new Vector(1);
0267:
0268: /**
0269: * List of end <code>TimeInstance</code>s.
0270: */
0271: Vector endInstances = new Vector(1);
0272:
0273: /**
0274: * Begin <code>TimeCondition</code>s. Should _never_ be null.
0275: */
0276: Vector beginConditions = new Vector(1);
0277:
0278: /**
0279: * End <code>TimeCondition</code>s. Should _never_ be null.
0280: */
0281: Vector endConditions = new Vector(1);
0282:
0283: /**
0284: * List of <code>SyncBaseCondition</code>s depedent on this
0285: * element's begin condition.
0286: */
0287: Vector beginDependents;
0288:
0289: /**
0290: * List of <code>SyncBaseCondition</code>s dependent on this
0291: * element's end condition.
0292: */
0293: Vector endDependents;
0294:
0295: /**
0296: * The associated animation element.
0297: */
0298: ModelNode animationElement;
0299:
0300: /**
0301: * Time dependency cycles detector. This flag is set to true
0302: * when a timing update starts. There should only be one at
0303: * a time, and it should always be set back to true after
0304: * and timing update is complete.
0305: */
0306: boolean timingUpdate = false;
0307:
0308: /**
0309: * The seeking flag is used when seeking to a particular time
0310: * to prevent generation of beginEvent, endEvent and repeat
0311: * event.
0312: */
0313: boolean seeking = false;
0314:
0315: /**
0316: * Sets the repeat duration.
0317: *
0318: * @param repeatDur the new repeat duration.
0319: * @throws IllegalStateException if the element is not in the
0320: * STATE_PRE_INIT state.
0321: */
0322: public void setRepeatDur(final Time repeatDur) {
0323: checkPreInit();
0324: this .repeatDur = repeatDur;
0325: }
0326:
0327: /**
0328: * Sets the repeat count.
0329: *
0330: * @param repeatCount the new repeat count. Must be greater than
0331: * zero.
0332: * @throws IllegalStateException if the element is not in the
0333: * STATE_PRE_INIT state.
0334: */
0335: public void setRepeatCount(final float repeatCount) {
0336: checkPreInit();
0337:
0338: if (repeatCount <= 0) {
0339: throw new IllegalArgumentException();
0340: }
0341:
0342: this .repeatCount = repeatCount;
0343: }
0344:
0345: /**
0346: * Sets the restart strategy.
0347: *
0348: * @param restart the new restart strategy, one of
0349: * RESTART_ALWAYS, RESTART_NEVER or RESTART_WHEN_NOT_ACTIVE.
0350: * @throws IllegalStateException if the element is not in the
0351: * STATE_PRE_INIT state.
0352: */
0353: public void setRestart(final int restart) {
0354: checkPreInit();
0355: switch (restart) {
0356: case RESTART_ALWAYS:
0357: case RESTART_NEVER:
0358: case RESTART_WHEN_NOT_ACTIVE:
0359: this .restart = restart;
0360: break;
0361: default:
0362: throw new IllegalArgumentException();
0363: }
0364: }
0365:
0366: /**
0367: * Sets the maximun active duration for any timed element
0368: * interval.
0369: *
0370: * @param max the new maximum active duration. Should not be
0371: * null.
0372: * @throws IllegalStateException if the element is not in the
0373: * STATE_PRE_INIT state.
0374: */
0375: public void setMax(final Time max) {
0376: checkPreInit();
0377: if (max == null) {
0378: throw new IllegalArgumentException();
0379: }
0380: this .max = max;
0381: }
0382:
0383: /**
0384: * Sets the minimum active duration for any timed element interval.
0385: *
0386: * @param min the new minimum active duration. Should not be null.
0387: * @throws IllegalStateException if the element is not in the
0388: * STATE_PRE_INIT state.
0389: */
0390: public void setMin(final Time min) {
0391: checkPreInit();
0392: if (min == null) {
0393: throw new IllegalArgumentException();
0394: }
0395: this .min = min;
0396: }
0397:
0398: /**
0399: * Sets the duration for this timed element
0400: *
0401: * @param dur the new element duration.
0402: * @throws IllegalStateException if the element is not in the
0403: * STATE_PRE_INIT state.
0404: */
0405: public void setDur(final Time dur) {
0406: checkPreInit();
0407: this .dur = dur;
0408: }
0409:
0410: /**
0411: * Sets the fill state behavior.
0412: *
0413: * @param fillBehavior the new fill strategy, one of FILL_BEHAVIOR_FREEZE or
0414: * FILL_BEHAVIOR_REMOVE
0415: * @throws IllegalStateException if the element is not in the
0416: * STATE_PRE_INIT state.
0417: */
0418: public void setFillBehavior(final int fillBehavior) {
0419: checkPreInit();
0420: switch (fillBehavior) {
0421: case FILL_BEHAVIOR_FREEZE:
0422: case FILL_BEHAVIOR_REMOVE:
0423: this .fillBehavior = fillBehavior;
0424: break;
0425: default:
0426: throw new IllegalArgumentException();
0427: }
0428: }
0429:
0430: /**
0431: * Implementation helper. Throws an exception if the element is
0432: * not in the STATE_PRE_INIT state.
0433: *
0434: * @throws IllegalStateException if the element is not in the
0435: * STATE_PRE_INIT state.
0436: */
0437: public void checkPreInit() {
0438: if (state != STATE_PRE_INIT) {
0439: throw new IllegalStateException();
0440: }
0441: }
0442:
0443: /**
0444: * Calling this method causes a new <code>TimeInstance</code> to
0445: * be inserted in the begin instance list. The behavior depends
0446: * on the restart setting.
0447: */
0448: public void begin() {
0449: beginAt(0);
0450: }
0451:
0452: /**
0453: * Calling this method causes a new <code>TimeInstance</code> to
0454: * be inserted in the end instance list. This typically causes
0455: * the current interval (if any), to be ended.
0456: */
0457: public void end() {
0458: endAt(0);
0459: }
0460:
0461: /**
0462: * Calling this method causes a new <code>TimeInstance</code> to
0463: * be inserted in the begin instance list. The behavior depends
0464: * on the restart setting.
0465: *
0466: * @param offset the begin time inserted into the instance list
0467: * is offset by 'offset' milliseconds from the current
0468: * time.
0469: */
0470: public void beginAt(final long offset) {
0471: addInstance(true, offset);
0472: }
0473:
0474: /**
0475: * Calling this method causes a new <code>TimeInstance</code> to
0476: * be inserted in the end instance list. This typically causes
0477: * the current interval (if any), to be ended.
0478: *
0479: * @param offset the end time inserted into the instance list
0480: * is offset by 'offset' milliseconds from the current
0481: * time.
0482: */
0483: public void endAt(final long offset) {
0484: addInstance(false, offset);
0485: }
0486:
0487: /**
0488: * Adds a new <code>TimedInstance</code> with the specified offset
0489: * to the begin or end instance list (depending on <code>isBegin</code>
0490: *
0491: * @param isBegin if true, the instance is a begin instance
0492: * @param offset the instance offset from 0.
0493: */
0494: void addInstance(final boolean isBegin, final long offset) {
0495: Time currentTime = getCurrentTime();
0496: if (!currentTime.isResolved()) {
0497: // If the current time is not resolved, it means the time
0498: // container is not active. Therefore, there is no way
0499: // we can add a new time instance that would be meaningful.
0500: // So we do nothing.
0501: return;
0502: }
0503:
0504: Time newTime = new Time(currentTime.value + offset);
0505:
0506: TimeInstance newInstance = new TimeInstance(this , newTime,
0507: true, isBegin);
0508: }
0509:
0510: /**
0511: * Sets this timed element's time container
0512: *
0513: * @param timeContainer time container
0514: */
0515: protected void setTimeContainer(
0516: final TimeContainerSupport timeContainer) {
0517: if (this .timeContainer != null) {
0518: this .timeContainer.timedElementChildren.removeElement(this );
0519: }
0520:
0521: this .timeContainer = timeContainer;
0522: if (timeContainer != null) {
0523: timeContainer.timedElementChildren.addElement(this );
0524: if (timeContainer.state != STATE_PRE_INIT) {
0525: // This is a live addition of an animation to a container.
0526: // We need to initialize this timed element support.
0527: initialize();
0528: }
0529: }
0530: }
0531:
0532: /**
0533: * @return this TimedElement's container.
0534: */
0535: protected TimeContainerSupport getTimeContainer() {
0536: return timeContainer;
0537: }
0538:
0539: /**
0540: * This method is called by the element's time container when it
0541: * resets, i.e., when the container starts it's simple time again.
0542: *
0543: * This has the effect of:
0544: * 1. clearing all the instance times which have the clearOnReset
0545: * flag set to true.
0546: * 2. compute the first interval.
0547: */
0548: protected void initialize() {
0549: // Clear instances with clearOnReset == true
0550: reset();
0551:
0552: // Compute the first interval
0553: if (!checkNewInterval(computeFirstInterval())) {
0554: state = STATE_NO_INTERVAL;
0555: }
0556: }
0557:
0558: /**
0559: * Calls all the registered <code>TimeDependent</code>s so that they
0560: * are notified of a new TimeInterval creation.
0561: */
0562: void dispatchOnNewInterval() {
0563: int n = beginDependents == null ? 0 : beginDependents.size();
0564: for (int i = 0; i < n; i++) {
0565: ((TimeDependent) beginDependents.elementAt(i))
0566: .onNewInterval(this );
0567: }
0568:
0569: n = endDependents == null ? 0 : endDependents.size();
0570: for (int i = 0; i < n; i++) {
0571: ((TimeDependent) endDependents.elementAt(i))
0572: .onNewInterval(this );
0573: }
0574: }
0575:
0576: /**
0577: * @param after we are looking for a time greater than or equal
0578: * to after.
0579: * @param instances the Vector containing the TimeInstances to
0580: * look up.
0581: * @return the first value in the given instance list that
0582: * starts after the input time.
0583: */
0584: Time getTimeAfter(final Time after, final Vector instances) {
0585: int n = instances.size();
0586:
0587: // NOTE: The following _assumes_ that the instances vector
0588: // is sorted in increasing time order.
0589: for (int i = 0; i < n; i++) {
0590: TimeInstance timeInstance = (TimeInstance) instances
0591: .elementAt(i);
0592: if (timeInstance.time.greaterThan(after)) {
0593: return timeInstance.time;
0594: }
0595: }
0596: return null;
0597: }
0598:
0599: /**
0600: * @param after we are looking for a time greater than or equal
0601: * to after.
0602: * @param instances the Vector containing the TimeInstances to
0603: * look up.
0604: * @return the first value in the given instance list that
0605: * starts strictly after the input time.
0606: */
0607: Time getTimeAfterStrict(final Time after, final Vector instances) {
0608: int n = instances.size();
0609:
0610: // NOTE: The following _assumes_ that the instances vector
0611: // is sorted in increasing time order.
0612: for (int i = 0; i < n; i++) {
0613: TimeInstance timeInstance = (TimeInstance) instances
0614: .elementAt(i);
0615: if (!after.greaterThan(timeInstance.time)) {
0616: return timeInstance.time;
0617: }
0618: }
0619: return null;
0620: }
0621:
0622: /**
0623: * Computes the active end of this <code>TimedElement</code> for
0624: * the given begin and end time constraints. This accounts for
0625: * the other attributes which control or constrain the
0626: * active duration.
0627: *
0628: * @param begin the input begin time. Should not be unresolved or null.
0629: * @param end the proposed end time. May be null.
0630: * @return the computed, constrained end time.
0631: *
0632: * @see <a
0633: * href="http://www.w3.org/TR/smil20/smil-timing.html#Timing-ComputingActiveDur">
0634: * SMIL 2: Computing the active duration</a>
0635: */
0636: Time calculateActiveEnd(final Time begin, final Time end) {
0637: Time result = end;
0638: Time pad = null;
0639:
0640: if (begin == null) {
0641: throw new NullPointerException();
0642: }
0643:
0644: if (!begin.isResolved()) {
0645: throw new IllegalArgumentException();
0646: }
0647:
0648: //
0649: // In the context of this method, end and begin are the time instances
0650: // passed to this method, and _not_ the begin and end attributes.
0651: //
0652: // If endTime is not null, and none of dur, repeatDur, and repeatCount
0653: // are specified, then the simple duration is indefinite from the simple
0654: // duration table above (see SMIL 2 spec), and the active duration is
0655: // defined by the end value, according to the following cases:
0656: //
0657: // If end is resolved to a value, then pad = end - B,
0658: //
0659: // else, if end is indefinite, then pad = indefinite,
0660: //
0661: // else, if end is unresolved, then pad is unresolved, and needs to be
0662: // recomputed when more information becomes available.
0663: //
0664: if (end != null && dur == null && repeatDur == null
0665: && Float.isNaN(repeatCount)) {
0666: if (end.isResolved()) {
0667: pad = new Time(end.value - begin.value);
0668: } else if (end == Time.INDEFINITE) {
0669: pad = Time.INDEFINITE;
0670: } else {
0671: pad = Time.UNRESOLVED;
0672: }
0673: }
0674:
0675: // Else, if no end value is specified, or the end value is specified as
0676: // indefinite, then the active duration is determined from the
0677: // Intermediate Active Duration computation given below:
0678: //
0679: // pad = Result from Intermediate Active Duration Computation
0680: else if (end == null || end == Time.INDEFINITE) {
0681: pad = calculateIntermediateActiveDuration(computeSimpleDuration(end));
0682: }
0683:
0684: // Otherwise, an end value not equal to indefinite is specified along
0685: // with at least one of dur, repeatDur, and repeatCount. Then the pad is
0686: // the minimum of the result from the Intermediate Active Duration
0687: // Computation given below and duration between end and the element
0688: // begin:
0689: //
0690: // pad = MIN( Result from Intermediate Active Duration Computation,
0691: // end - B)
0692: else {
0693: pad = calculateIntermediateActiveDuration(computeSimpleDuration(end));
0694: Time pad2 = Time.UNRESOLVED;
0695: if (end.isResolved()) {
0696: pad2 = new Time(end.value - begin.value);
0697: }
0698: if (pad.greaterThan(pad2)) {
0699: pad = pad2;
0700: }
0701: }
0702:
0703: // Finally, the computed active duration ad is obtained by applying min
0704: // and max semantics to the preliminary active duration pad. In the
0705: // following expression, if there is no min value, substitute a value of
0706: // 0, and if there is no max value, substitute a value of "indefinite":
0707: //
0708: // ad = MIN( max, MAX( min, pad ))
0709:
0710: // Only do min/max constraint if min < max
0711: boolean doMinMax = true;
0712: if (min != null && max != null && !max.greaterThan(min)) {
0713: doMinMax = false;
0714: }
0715:
0716: Time ad = null;
0717: if (pad.isResolved()) {
0718: ad = new Time(pad.value);
0719: } else {
0720: ad = pad;
0721: }
0722:
0723: if (doMinMax) {
0724: // MAX( min, pad )
0725: if (min != null && min.greaterThan(ad)) {
0726: if (min.isResolved()) {
0727: ad.value = min.value;
0728: } else {
0729: ad = min;
0730: }
0731: }
0732:
0733: // MIN( max, MAX( min, pad ))
0734: if (max != null && !max.greaterThan(ad)) {
0735: if (max == Time.INDEFINITE) {
0736: ad = Time.INDEFINITE;
0737: } else {
0738: if (ad.isResolved()) {
0739: ad.value = max.value;
0740: } else {
0741: ad = new Time(max.value);
0742: }
0743: }
0744: }
0745: }
0746:
0747: // We have computed the active duration. Now, offset that
0748: // duration with the begin time to yield the computed end time.
0749: if (ad.isResolved()) {
0750: ad.value += begin.value;
0751: }
0752:
0753: // Finally, apply the restart behavior. If restart is always
0754: // we check if there is any end time that is before active
0755: // end time.
0756: if (restart == RESTART_ALWAYS) {
0757: Time restartBegin = getTimeAfterStrict(begin,
0758: beginInstances);
0759: if (restartBegin != null && ad.greaterThan(restartBegin)) {
0760: ad.value = restartBegin.value;
0761: }
0762: }
0763:
0764: return ad;
0765: }
0766:
0767: /**
0768: * Intermediate Active Duration Computation, as defined in the
0769: * SMIL 2 specifications.
0770: *
0771: * @param p0 the element's simple duration. Should not be null
0772: * @return the intermediate active duration.
0773: * @see <a href="http://www.w3.org/TR/smil20/smil-timing.html#q81">
0774: * Intermediate Active Duration Computation</a>
0775: */
0776: final Time calculateIntermediateActiveDuration(final Time p0) {
0777: if (p0 == null) {
0778: throw new NullPointerException();
0779: }
0780:
0781: if (p0.isResolved() && p0.value == 0) {
0782: return new Time(0);
0783: } else if (repeatDur == null && Float.isNaN(repeatCount)) {
0784: return p0;
0785: } else {
0786: Time p1 = Time.INDEFINITE;
0787: Time p2 = repeatDur;
0788: Time iad = Time.UNRESOLVED;
0789:
0790: if (!Float.isNaN(repeatCount)) {
0791: if (p0.isResolved()) {
0792: if (repeatCount == Float.MAX_VALUE) {
0793: p1 = Time.INDEFINITE;
0794: } else {
0795: p1 = new Time((long) (p0.value * repeatCount));
0796: }
0797: } else {
0798: p1 = p0; // INDEFINITE or UNRESOLVED
0799: }
0800: }
0801:
0802: if (p2 == null) {
0803: p2 = Time.INDEFINITE;
0804: }
0805:
0806: iad = Time.INDEFINITE;
0807: if (!p2.greaterThan(iad)) {
0808: iad = p2;
0809: }
0810: if (!p1.greaterThan(iad)) {
0811: iad = p1;
0812: }
0813: return iad;
0814: }
0815: }
0816:
0817: /**
0818: * Computes the element's simple duration, as defined in the
0819: * SMIL 2 specification except that the 'media' value is
0820: * not supported.
0821: *
0822: * For the purpose of computing the simple duration, an unspecified
0823: * dur or an UNRESOLVED dur yield the same computed simple duration.
0824: *
0825: * @param end the interval end time. Can be null or have any Time
0826: * value.
0827: * @return the computed simple duration.
0828: * @see <a
0829: * href="http://www.w3.org/TR/smil20/smil-timing.html#Timing-DefiningSimpleDur">
0830: * Defining the simple duration</a>
0831: */
0832: final Time computeSimpleDuration(final Time end) {
0833: Time implicitDur = getImplicitElementDuration();
0834:
0835: // dur | implicit | repeatDur and | simpleDuration
0836: // | duration | repeatCount |
0837: // -------------+-------------+----------------+--------------------
0838: // 1. unspecified | ignored | unspecified, | indefinite
0839: // | | end specified |
0840: // 2. Clock-value | ignored | ignored | dur or Clock-value
0841: // 3. indefinite | ignored | ignored | indefinite
0842: // 4. unspecified | resolved | ignored | implicit duration
0843: // 5. unspecified | unresolved | ignored | unresolved
0844: // -------------+-------------+----------------+--------------------
0845:
0846: // First line case
0847: if (dur == null && repeatDur == null
0848: && Float.isNaN(repeatCount) && end != Time.UNRESOLVED) {
0849: return Time.INDEFINITE;
0850: }
0851:
0852: // Second & Third line case
0853: if (dur != null && dur != Time.UNRESOLVED) {
0854: return dur;
0855: }
0856:
0857: // Fourth line case. If we got to this point, we know
0858: // dur is UNRESOLVED or null
0859: if (implicitDur != Time.UNRESOLVED) {
0860: return implicitDur;
0861: } else {
0862: // Fifth line
0863: return Time.UNRESOLVED;
0864: }
0865: }
0866:
0867: /**
0868: * @return this element's implicit duration. Can be used for media such
0869: * as audio and video where there is a natural, implicit duration.
0870: */
0871: protected Time getImplicitElementDuration() {
0872: return implicitDuration;
0873: }
0874:
0875: /**
0876: * @return true if there is at least one event end condition.
0877: */
0878: boolean endHasEventConditions() {
0879: for (int i = 0; i < endConditions.size(); i++) {
0880: if (endConditions.elementAt(i) instanceof EventBaseCondition) {
0881: return true;
0882: }
0883: }
0884: return false;
0885: }
0886:
0887: /**
0888: * Computes the time of the last simple duration instance
0889: * for the given time interval.
0890: *
0891: * @param ti the TimeInterval instance for which the time of the last
0892: * simple duration instance should be computed.
0893: *
0894: * @return the input TimeInterval instance.
0895: */
0896: TimeInterval computeLastDur(final TimeInterval ti) {
0897: Time simpleDur = computeSimpleDuration(ti.end);
0898: if (simpleDur != null) {
0899: Time iad = calculateIntermediateActiveDuration(simpleDur);
0900: if (iad.isResolved()) {
0901: ti.lastDur = new Time(ti.begin.value + iad.value);
0902: if (ti.lastDur.greaterThan(ti.end)) {
0903: ti.lastDur = ti.end;
0904: }
0905: } else {
0906: ti.lastDur = ti.end;
0907: }
0908: } else {
0909: ti.lastDur = ti.end;
0910: }
0911:
0912: return ti;
0913: }
0914:
0915: /**
0916: * The following computes the element's first interval, as defined
0917: * in the SMIL specification.
0918: *
0919: * @return the first time interval
0920: * @see <a
0921: * href="http://www.w3.org/TR/smil20/smil-timing.html#Timing-BeginEnd-LC-Start">
0922: * STARTUP - Getting the first interval</a>
0923: */
0924: TimeInterval computeFirstInterval() {
0925: Time beginAfter = new Time(Long.MIN_VALUE);
0926: Time parentSimpleEnd = getContainerSimpleDuration();
0927:
0928: while (true) { // loop till return
0929: Time tempBegin = getTimeAfter(beginAfter, beginInstances);
0930: if (tempBegin == null) {
0931: return null; // No interval
0932: }
0933:
0934: if (tempBegin.greaterThan(parentSimpleEnd)) {
0935: return null; // No interval
0936: }
0937:
0938: Time tempEnd = null;
0939: if (endConditions.size() == 0) {
0940: tempEnd = calculateActiveEnd(tempBegin, null);
0941: } else {
0942: tempEnd = getTimeAfter(tempBegin, endInstances);
0943: if (tempBegin.isSameTime(tempEnd)) {
0944: tempEnd = getTimeAfterStrict(tempEnd, endInstances);
0945: }
0946:
0947: if (tempEnd == null) {
0948: if (endHasEventConditions()
0949: || endInstances.size() == 0) {
0950: tempEnd = Time.UNRESOLVED;
0951: } else {
0952: return null; // No interval
0953: }
0954: }
0955:
0956: tempEnd = calculateActiveEnd(tempBegin, tempEnd);
0957: }
0958:
0959: // We have an end - is it after the parent simple begin?
0960: if (!tempEnd.isResolved() || tempEnd.value > 0) {
0961: return computeLastDur(new TimeInterval(tempBegin,
0962: tempEnd));
0963: } else {
0964: beginAfter = tempEnd;
0965: }
0966: }
0967: }
0968:
0969: /**
0970: * The following computes the element's next interval, as defined
0971: * in the SMIL specification.
0972: *
0973: * @return the next interval given the current begin and end time instances.
0974: * @see <a
0975: * href="http://www.w3.org/TR/smil20/smil-timing.html#Timing-BeginEnd-LC-End">
0976: * End of an interval</a>
0977: */
0978: TimeInterval computeNextInterval() {
0979: Time curEnd = previousInterval.end;
0980: Time beginAfter = curEnd;
0981: Time parentSimpleEnd = getContainerSimpleDuration();
0982: Time tempBegin = getTimeAfter(beginAfter, beginInstances);
0983:
0984: if (tempBegin == null) {
0985: return null;
0986: }
0987:
0988: if (tempBegin.greaterThan(parentSimpleEnd)) {
0989: return null;
0990: }
0991:
0992: Time tempEnd = null;
0993: if (endConditions.size() == 0) {
0994: tempEnd = calculateActiveEnd(tempBegin, null);
0995: if (endInstances.size() > 0) {
0996: // There is no end-attribute specified, but there are time
0997: // instances. The SMIL Animation specification says that if
0998: // there are times instances from DOM Events or DOM
0999: // beginElementAt calls, they short cut the active duration.
1000: //
1001: // See: http://www.w3.org/TR/2001/REC-smil-animation-20010904/
1002: // section 3.3.4.
1003: //
1004: Time tempEnd2 = getTimeAfter(tempBegin, endInstances);
1005: if (tempEnd2 != null && tempEnd2.isResolved()) {
1006: tempEnd = tempEnd2;
1007: }
1008: }
1009: } else {
1010: // We have a begin value - get an end
1011: tempEnd = getTimeAfter(tempBegin, endInstances);
1012:
1013: if (curEnd.isSameTime(tempEnd)) {
1014: tempEnd = getTimeAfterStrict(tempEnd, endInstances);
1015: }
1016:
1017: if (tempEnd == null) {
1018: if (endHasEventConditions() || endInstances.size() == 0) {
1019: tempEnd = Time.UNRESOLVED;
1020: } else {
1021: return null;
1022: }
1023: }
1024:
1025: tempEnd = calculateActiveEnd(tempBegin, tempEnd);
1026: }
1027:
1028: return computeLastDur(new TimeInterval(tempBegin, tempEnd));
1029: }
1030:
1031: /**
1032: * Computes the end time corresponding to the input begin time.
1033: *
1034: * @param tempBegin the begin time for which to search an end time.
1035: * @return the computed end time or null if there is no matching end.
1036: */
1037: Time computeEndTime(final Time tempBegin) {
1038: if (endInstances.size() == 0) {
1039: return calculateActiveEnd(tempBegin, null);
1040: } else {
1041: Time tempEnd = getTimeAfter(tempBegin, endInstances);
1042:
1043: if (tempBegin.isSameTime(tempEnd)) {
1044: // IMPL NOTE : Do this to get a strictly superior end time
1045: tempEnd = new Time(tempEnd.value + 1);
1046: tempEnd = getTimeAfter(tempEnd, endInstances);
1047: }
1048:
1049: if (tempEnd == null) {
1050: if (endHasEventConditions() || endInstances.size() == 0) {
1051: tempEnd = Time.UNRESOLVED;
1052: } else {
1053: return null;
1054: }
1055: }
1056:
1057: return calculateActiveEnd(tempBegin, tempEnd);
1058: }
1059: }
1060:
1061: /**
1062: * Resetting the element clears all of its 'clearOnReset' instances from the
1063: * begin and end list. This includes event based time instances, time
1064: * instances resulting from begin() or end() calls and resolved
1065: * IntervalTimeInstance.
1066: */
1067: void reset() {
1068: currentInterval = null;
1069: simpleDur = Time.UNRESOLVED;
1070: previousInterval = null;
1071: curIter = 0;
1072: state = STATE_PRE_INIT;
1073: lastSampleTime = -1;
1074:
1075: int n = beginInstances.size();
1076: for (int i = n - 1; i >= 0; i--) {
1077: TimeInstance timeInstance = (TimeInstance) beginInstances
1078: .elementAt(i);
1079: if (timeInstance.clearOnReset) {
1080: beginInstances.removeElementAt(i);
1081: } else if (timeInstance instanceof IntervalTimeInstance) {
1082: ((IntervalTimeInstance) timeInstance).syncTime();
1083: }
1084: }
1085: n = endInstances.size();
1086: for (int i = n - 1; i >= 0; i--) {
1087: TimeInstance timeInstance = (TimeInstance) endInstances
1088: .elementAt(i);
1089: if (timeInstance.clearOnReset) {
1090: endInstances.removeElementAt(i);
1091: } else if (timeInstance instanceof IntervalTimeInstance) {
1092: ((IntervalTimeInstance) timeInstance).syncTime();
1093: }
1094: }
1095: }
1096:
1097: /**
1098: * Removes all <code>IntervalTimeInstance</code>s in the begin and end
1099: * instance list if the syncBase is a descendant of syncTimeContainer
1100: *
1101: * @param syncTimeContainer the container under which
1102: * <code>IntervalTimeInstance</code> should be removed.
1103: */
1104: void removeSyncBaseTimesUnder(
1105: final TimeContainerSupport syncTimeContainer) {
1106: int n = beginInstances.size();
1107: for (int i = n - 1; i >= 0; i--) {
1108: TimeInstance timeInstance = (TimeInstance) beginInstances
1109: .elementAt(i);
1110: if (timeInstance instanceof IntervalTimeInstance) {
1111: IntervalTimeInstance iti = (IntervalTimeInstance) timeInstance;
1112: if (iti.syncBase.isDescendant(syncTimeContainer)) {
1113: beginInstances.removeElementAt(i);
1114: iti.dispose();
1115: }
1116: }
1117: }
1118: n = endInstances.size();
1119: for (int i = n - 1; i >= 0; i--) {
1120: TimeInstance timeInstance = (TimeInstance) endInstances
1121: .elementAt(i);
1122: if (timeInstance instanceof IntervalTimeInstance) {
1123: IntervalTimeInstance iti = (IntervalTimeInstance) timeInstance;
1124: if (iti.syncBase.isDescendant(syncTimeContainer)) {
1125: endInstances.removeElementAt(i);
1126: iti.dispose();
1127: }
1128: }
1129: }
1130: }
1131:
1132: /**
1133: * @param parent the time container which might be on the timed element's
1134: * parent hierarchy.
1135: * @return true if this timed element is equal to 'parent' or is a
1136: * descendant of 'parent'
1137: */
1138: boolean isDescendant(final TimeContainerSupport parent) {
1139: if (parent == this ) {
1140: return true;
1141: } else if (timeContainer != this ) {
1142: return timeContainer.isDescendant(parent);
1143: } else {
1144: return false;
1145: }
1146: }
1147:
1148: /**
1149: * Dispatches beginEvent. As per the SMIL 2 specification, this dispatches
1150: * a beginEvent for the resolved begin time, not the observed begin
1151: * time. It also dispatches any repeat event that might be needed.
1152: *
1153: * @param currentTime the current sampling time.
1154: *
1155: * @see <a
1156: * href="http://www.w3.org/TR/smil20/smil-timing.html#Timing-BeginValueSemantics">
1157: * SMIL2's Begin value semantics</a>
1158: */
1159: void dispatchBeginEvent(final Time currentTime) {
1160: if (animationElement != null) {
1161: ModelEvent beginEvent = null;
1162: if (seeking) {
1163: beginEvent = animationElement.ownerDocument
1164: .initEngineEvent(SEEK_BEGIN_EVENT_TYPE,
1165: animationElement);
1166: } else {
1167: beginEvent = animationElement.ownerDocument
1168: .initEngineEvent(BEGIN_EVENT_TYPE,
1169: animationElement);
1170: }
1171:
1172: eventTime.value = currentInterval.begin.value;
1173: beginEvent.eventTime = toRootContainerSimpleTimeClamp(eventTime);
1174: animationElement.dispatchEvent(beginEvent);
1175: }
1176:
1177: dispatchRepeatEvent(currentTime);
1178: }
1179:
1180: /**
1181: * Dispatches lastDurEndEvent.
1182: *
1183: * @see <a
1184: * href="http://www.w3.org/TR/smil20/smil-timing.html#smil-timing-Timing-BeginEnd-Restart
1185: * </a>
1186: */
1187: void dispatchLastDurEndEvent() {
1188: if (animationElement != null) {
1189: ModelEvent event = animationElement.ownerDocument
1190: .initEngineEvent(LAST_DUR_END_EVENT_TYPE,
1191: animationElement);
1192: eventTime.value = currentInterval.lastDur.value;
1193: event.eventTime = toRootContainerSimpleTimeClamp(eventTime);
1194: animationElement.dispatchEvent(event);
1195: }
1196: }
1197:
1198: /**
1199: * Dispatches seekeEndEvent.
1200: *
1201: * @see <a
1202: * href="http://www.w3.org/TR/smil20/smil-timing.html#smil-timing-Timing-BeginEnd-Restart
1203: * </a>
1204: */
1205: void dispatchSeekEndEvent() {
1206: if (animationElement != null) {
1207: ModelEvent event = animationElement.ownerDocument
1208: .initEngineEvent(SEEK_END_EVENT_TYPE,
1209: animationElement);
1210: animationElement.dispatchEvent(event);
1211: }
1212: }
1213:
1214: /**
1215: * Dispatches endEvent. As per the SMIL 2 specification, this dispatches an
1216: * endEvent for the resolved end time, not the observed end time.
1217: *
1218: * @param currentTime the current sampling time.
1219: *
1220: * @see <a
1221: * href="http://www.w3.org/TR/smil20/smil-timing.html#Timing-BeginValueSemantics">
1222: * SMIL2's Begin value semantics</a>
1223: */
1224: void dispatchEndEvent(final Time currentTime) {
1225: if ((!seeking || state == STATE_PLAYING)
1226: && animationElement != null) {
1227: ModelEvent endEvent = animationElement.ownerDocument
1228: .initEngineEvent(END_EVENT_TYPE, animationElement);
1229: if (seeking) {
1230: // We are seeking to a new currentTime and the interval
1231: // ends during the seek interval and the element was
1232: // active at the time of seek. The time for the end
1233: // event should be the time _before_ the seek
1234: eventTime.value = getRootContainer().lastSampleTime.value;
1235: endEvent.eventTime = eventTime;
1236: } else {
1237: eventTime.value = currentInterval.end.value;
1238: endEvent.eventTime = toRootContainerSimpleTimeClamp(eventTime);
1239: }
1240:
1241: animationElement.dispatchEvent(endEvent);
1242: }
1243: dispatchRepeatEvent(currentTime);
1244: }
1245:
1246: /**
1247: * Helper method.
1248: *
1249: * @return this timed element's root container.
1250: */
1251: TimeContainerRootSupport getRootContainer() {
1252: return timeContainer.getRootContainer();
1253: }
1254:
1255: /**
1256: * Dispatches repeatEvent. As per the SMIL 2 specification, this dispatches
1257: * a repeatEvent for the resolved end time, not the observed repeat
1258: * time(s).
1259: *
1260: * @see <a
1261: * href="http://www.w3.org/TR/smil20/smil-timing.html#Timing-BeginValueSemantics">
1262: * SMIL2's Begin value semantics</a>
1263: */
1264: void dispatchRepeatEvent(final Time currentTime) {
1265: if (!seeking && simpleDur.isResolved() && simpleDur.value > 0) {
1266: int prevIter = curIter;
1267:
1268: Time maxTime = currentTime;
1269: if (currentTime.greaterThan(currentInterval.end)) {
1270: maxTime = currentInterval.end;
1271: }
1272:
1273: curIter = (int) ((maxTime.value - currentInterval.begin.value) / simpleDur.value);
1274:
1275: if (curIter < 0) {
1276: curIter = 0;
1277: }
1278:
1279: if (maxTime.isSameTime(currentInterval.end)
1280: && ((maxTime.value - currentInterval.begin.value)
1281: % simpleDur.value == 0)) {
1282: curIter--;
1283: }
1284:
1285: if (animationElement != null) {
1286: for (int i = prevIter; i < curIter; i++) {
1287: ModelEvent repeatEvent = animationElement.ownerDocument
1288: .initEngineEvent(REPEAT_EVENT_TYPE,
1289: animationElement);
1290:
1291: // The event time is the time at which the repeat happened,
1292: // not the time at which it was detected (that would be
1293: // currentTime). So we have to compute the repeat time here
1294: Time repeatTime = eventTime;
1295: repeatTime.value = currentInterval.begin.value
1296: + (i + 1) * simpleDur.value;
1297: repeatTime = toRootContainerSimpleTimeClamp(repeatTime);
1298: repeatEvent.eventTime = repeatTime;
1299: repeatEvent.repeatCount = i + 1;
1300: animationElement.dispatchEvent(repeatEvent);
1301: }
1302: }
1303:
1304: if (prevIter != curIter) {
1305: onStartingRepeat(prevIter, curIter);
1306: }
1307: }
1308: }
1309:
1310: /**
1311: * To be overridden by extensions (TimeContainerSupport) to do specific
1312: * processing when a new iteration is started.
1313: *
1314: * @param prevIter the last iteration this element was playing.
1315: * @param curIter the new iteration this element is playing.
1316: */
1317: protected void onStartingRepeat(final int prevIter,
1318: final int curIter) {
1319: }
1320:
1321: /**
1322: * Checks if the input interval is non null. If it is, that interval
1323: * becomes the new currentInterval and the element sets its state
1324: * accordingly.
1325: *
1326: * @param interval the interval to check.
1327: * @return true if the candidate interval has become the current interval.
1328: */
1329: boolean checkNewInterval(final TimeInterval interval) {
1330: if (interval == null) {
1331: return false;
1332: } else {
1333: currentInterval = interval;
1334: simpleDur = computeSimpleDuration(currentInterval.end);
1335:
1336: // Transition to the current state
1337: // Note that we move to the 'playing' stage only in the
1338: // sample method, where event dispatching is done as well.
1339: if (previousInterval == null) {
1340: state = STATE_WAITING_INTERVAL_0;
1341: } else {
1342: state = STATE_WAITING_INTERVAL_N;
1343: }
1344:
1345: // Notify dependents of the new interval
1346: dispatchOnNewInterval();
1347: return true;
1348: }
1349: }
1350:
1351: /**
1352: * Helper method to find the active interval, or next interval, for
1353: * the requested time.
1354: *
1355: * @param seekToTime the time we are seeking the active or next interval
1356: * for.
1357: * @return the <code>TimeInterval</code> corresponding to
1358: * <code>seekToTime</code>
1359: */
1360: TimeInterval seekToInterval(final Time seekToTime) {
1361: previousInterval = null;
1362: TimeInterval interval = computeFirstInterval();
1363: if (interval == null) {
1364: return null;
1365: }
1366:
1367: while (interval != null && seekToTime.greaterThan(interval.end)) {
1368: previousInterval = interval;
1369: interval = computeNextInterval();
1370: }
1371:
1372: return interval;
1373: }
1374:
1375: /**
1376: * Helper method.
1377: *
1378: * @return true if the root time container is flagged as seeking back.
1379: */
1380: boolean isSeekingBack() {
1381: return getRootContainer().seekingBack;
1382: }
1383:
1384: /**
1385: * This method is typically called by this element's time container
1386: * when it samples.
1387: *
1388: * Note that if this element is not in the waiting or playing
1389: * state, this does nothing. This method assumes that successive
1390: * calls are made with increasing time values.
1391: *
1392: * @param currentTime the time at which this element should be
1393: * sampled.
1394: */
1395: void sample(final Time currentTime) {
1396: // Handle state transitions if there is a current interval
1397: boolean endOfInterval = false;
1398: boolean seekBack = false;
1399:
1400: switch (state) {
1401: default:
1402: return;
1403: case STATE_FILL:
1404: if (seeking && isSeekingBack()) {
1405: seekBack = true;
1406: }
1407: break;
1408: case STATE_WAITING_INTERVAL_0:
1409: if (currentTime.greaterThan(currentInterval.begin)) {
1410: // This element has begun between the previous sampling
1411: // and now. Dispatch the beginEvent.
1412: dispatchBeginEvent(currentTime);
1413:
1414: if (currentTime.greaterThan(currentInterval.end)) {
1415: // This element has also ended between the
1416: // previous sampling and now. Dispatch the endEvent
1417: dispatchLastDurEndEvent();
1418: dispatchEndEvent(currentTime);
1419: endOfInterval = true;
1420: }
1421:
1422: state = STATE_PLAYING;
1423: playFill = false;
1424: }
1425: break;
1426: case STATE_WAITING_INTERVAL_N:
1427: if (currentTime.greaterThan(currentInterval.begin)) {
1428: // This element has begun between the previous sampling
1429: // and now. Dispatch the beginEvent.
1430: dispatchBeginEvent(currentTime);
1431:
1432: if (currentTime.greaterThan(currentInterval.end)) {
1433: // This element has also ended between the
1434: // previous sampling and now. Dispatch the endEvent
1435: dispatchLastDurEndEvent();
1436: dispatchEndEvent(currentTime);
1437: endOfInterval = true;
1438: }
1439:
1440: state = STATE_PLAYING;
1441: playFill = false;
1442: } else {
1443: if (seeking && isSeekingBack()) {
1444: seekBack = true;
1445: }
1446: }
1447: break;
1448: case STATE_PLAYING:
1449: if (currentTime.greaterThan(currentInterval.lastDur)) {
1450: if (!playFill) {
1451: dispatchLastDurEndEvent();
1452: playFill = true;
1453: }
1454: }
1455:
1456: if (currentTime.greaterThan(currentInterval.end)) {
1457: // This element has also ended between the
1458: // previous sampling and now. Dispatch the endEvent
1459: dispatchEndEvent(currentTime);
1460: endOfInterval = true;
1461: } else {
1462: if (!seeking) {
1463: dispatchRepeatEvent(currentTime);
1464: } else if (currentInterval.begin
1465: .greaterThan(currentTime)) {
1466: // We are seeking backwards and the current interval
1467: // began during the seek interval. We need to
1468: // dispatch and end event at the time before the
1469: // seek.
1470: dispatchLastDurEndEvent();
1471: dispatchEndEvent(currentTime);
1472: endOfInterval = true;
1473: seekBack = true;
1474: }
1475: }
1476: break;
1477: }
1478:
1479: // If we just finished an interval,
1480: // we need to compute the new interval now
1481: if (endOfInterval) {
1482: previousInterval = currentInterval;
1483: currentInterval = null;
1484:
1485: // If we cannot restart, we just go to the fill state
1486: if (restart == RESTART_NEVER) {
1487: state = STATE_FILL;
1488: playFill = false;
1489: } else {
1490: TimeInterval nextInterval = null;
1491: if (!seekBack) {
1492: nextInterval = computeNextInterval();
1493: } else {
1494: nextInterval = seekToInterval(currentTime);
1495: }
1496:
1497: if (!checkNewInterval(nextInterval)) {
1498: // There is no next interval, we move to the
1499: // fill state.
1500: state = STATE_FILL;
1501: playFill = false;
1502: }
1503: }
1504:
1505: // This recursive call is needed in case we sample so far ahead
1506: // that we are skipping several intervals.
1507: // For example if the intervals are:
1508: // [0, 1000[
1509: // [2000, 3000[
1510: // [5000, 6500[
1511: // [8000, 1000[
1512: // and we sample at 0 and then 7000
1513: sample(currentTime);
1514: return;
1515: } else if (seekBack) {
1516: // If we are in the FILL state and we cannot restart, then we stay
1517: // in FILL state if seeking to after the previous interval's
1518: // end. Otherwise, we move to the STATE_NO_INTERVAL state.
1519: if (state == STATE_FILL && restart == RESTART_NEVER) {
1520: if (previousInterval.end.greaterThan(currentTime)) {
1521: dispatchSeekEndEvent();
1522: state = STATE_NO_INTERVAL;
1523: playFill = false;
1524: return;
1525: }
1526: } else {
1527: TimeInterval seekInterval = seekToInterval(currentTime);
1528:
1529: // If the seek interval is the same as the current interval, we
1530: // do not need to do further sampling because we were in, or
1531: // have already moved to, the right interval.
1532: if (seekInterval == null
1533: || currentInterval == null
1534: || !(seekInterval.begin
1535: .isSameTime(currentInterval.begin) && seekInterval.end
1536: .isSameTime(currentInterval.end))) {
1537:
1538: // We need to check if there is an interval active at the
1539: // time we are seeking to.
1540: if (!checkNewInterval(seekInterval)) {
1541: // There is no currentInterval.
1542: // Switch to the right state
1543: if (previousInterval != null) {
1544: state = STATE_FILL;
1545: playFill = false;
1546: } else {
1547: dispatchSeekEndEvent();
1548: state = STATE_NO_INTERVAL;
1549: playFill = false;
1550: return;
1551: }
1552: } else {
1553: // There is an interval at the time we are
1554: // seeking to. End the current interval (which
1555: // we know is different because of the previous if
1556: // statement) and sample again to have the new current
1557: // interval kick-in.
1558: dispatchSeekEndEvent();
1559: sample(currentTime);
1560: return;
1561: }
1562: }
1563: }
1564: }
1565:
1566: //
1567: // Different behaviors depending on the state. At this point, we
1568: // should only be in one of the following states:
1569: // - STATE_FILL
1570: // - STATE_PLAYING
1571: // - STATE_WAITING_INTERVAL_0
1572: // - STATE_WAITING_INTERVAL_N
1573: //
1574: long localTime = 0;
1575: switch (state) {
1576: default:
1577: // This should _never_ happen, given this method's code.
1578: // This is extremely hard to test (would require changing
1579: // state during the execution of this method) and does
1580: // not need to be covered by the test suite.
1581: throw new IllegalStateException();
1582: case STATE_WAITING_INTERVAL_N:
1583: case STATE_FILL:
1584: if (fillBehavior == FILL_BEHAVIOR_FREEZE) {
1585: // If we are in STATE_WAITING_INTERVAL_N or STATE_FILL,
1586: // it means that we had a previous interval. Therefore,
1587: // previousInterval is guaranteed to be not null.
1588: localTime = previousInterval.lastDur.value
1589: - previousInterval.begin.value;
1590: } else {
1591: return;
1592: }
1593: break;
1594: case STATE_WAITING_INTERVAL_0:
1595: return;
1596: case STATE_PLAYING:
1597: // If we are still in the PLAYING state but we are past the
1598: // end of the last simple duration end, make sure we sample at
1599: // that last simple duration time.
1600: if (!playFill) {
1601: localTime = currentTime.value
1602: - currentInterval.begin.value;
1603: } else {
1604: localTime = currentInterval.lastDur.value
1605: - currentInterval.begin.value;
1606: }
1607: break;
1608: }
1609:
1610: // If we get to this point, it means we were able to
1611: // compute a localTime we need to sample at. We are
1612: // either playing the animation or we are in a frozen
1613: // state. Compute the simple time from the localTime.
1614: if (simpleDur.isResolved()) {
1615: localTime = localTime % simpleDur.value;
1616: if (state != STATE_PLAYING || playFill) {
1617: // If we are not playing, it means we are frozen
1618: // (either STATE_WAITING_N or STATE_FILL).
1619: //
1620: // Make sure we sample on the end of the simple time
1621: // if we froze at the end of the interval.
1622: //
1623: // Note that we do not need to test if the value
1624: // was 0 before %. Because we are _not_ playing, it
1625: // means we are _not_ on the first value of the
1626: // interval, therefore, we never run into the condition
1627: // where localTime would be 0 before % and therefore,
1628: // we do not need to test for that condition
1629: //
1630: if (localTime == 0) {
1631: // Freeze on the last value in the simple duration
1632: // interval only when we freeze.
1633: localTime = simpleDur.value;
1634: }
1635: }
1636: }
1637:
1638: // At this point, we have computed our simple time.
1639: // Ready to connect with the animation engine.
1640: sampleAt(localTime);
1641:
1642: // Keep the last simpleTime this element was sampled at
1643: lastSampleTime = localTime;
1644: }
1645:
1646: /**
1647: * Samples this element at the given simple time.
1648: * Should be overridden for specific behaviors.
1649: *
1650: * @param simpleTime this timed element simple time.
1651: */
1652: void sampleAt(final long simpleTime) {
1653: }
1654:
1655: /**
1656: * Called when this element is the target of an hyperlink. The
1657: * behavior is that defined in the SMIL 2 specification.
1658: *
1659: * @see <a
1660: * href="http://www.w3.org/TR/smil20/smil-timing.html#Timing-HyperlinksAndTiming">
1661: * SMIL 2 Specification</a>
1662: */
1663: public void activate() {
1664: // 1. If the target element is active, seek the document time back to
1665: // the begin time of the current interval for the element.
1666: if (state == STATE_PLAYING) {
1667: Time seekToTime = new Time(currentInterval.begin.value);
1668: seekToTime = toRootContainerSimpleTimeClamp(seekToTime);
1669:
1670: if (seekToTime.isResolved()) {
1671: seekTo(seekToTime);
1672: return;
1673: }
1674: }
1675:
1676: //
1677: // 2. Else if the target element begin time is resolved (i.e. there is
1678: // at least one interval defined for the element), seek the document
1679: // time (forward or back, as needed) to the begin time of the first
1680: // interval for the target element. Note that the begin time may be
1681: // resolved as a result of an earlier hyperlink, DOM or event
1682: // activation. Once the begin time is resolved (and until the element is
1683: // reset, e.g. when the parent repeats), hyperlink traversal always
1684: // seeks. For a discussion of "reset", see Resetting element state. Note
1685: // also that for an element begin to be resolved, the begin time of all
1686: // ancestor elements must also be resolved.
1687: //
1688: TimeInterval firstInterval = computeFirstInterval();
1689: if (firstInterval != null) {
1690: Time seekToTime = new Time(firstInterval.begin.value);
1691:
1692: seekToTime = toRootContainerSimpleTimeClamp(seekToTime);
1693:
1694: if (seekToTime.isResolved()) {
1695: seekTo(seekToTime);
1696: return;
1697: }
1698: }
1699:
1700: // 3. Else (i.e. there are no defined intervals for the element), the
1701: // target element begin time must be resolved. This may require seeking
1702: // and/or resolving ancestor elements as well. This is done by recursing
1703: // from the target element up to the closest ancestor element that has a
1704: // resolved begin time (again noting that for an element to have a
1705: // resolved begin time, all of its ancestors must have resolved begin
1706: // times). Then, the recursion is "unwound", and for each ancestor in
1707: // turn (beneath the resolved ancestor) as well as the target element,
1708: // the following steps are performed:
1709: //
1710: // 1. If the element begin time is resolved, seek the document time
1711: // (forward or back, as needed) to the begin time of the first
1712: // interval for the target element.
1713: //
1714: // 2. Else (if the begin time is not resolved), just resolve the
1715: // element begin time at the current time on its parent time
1716: // container (given the current document position). Disregard the
1717: // sync-base or event base of the element, and do not
1718: // "back-propagate" any timing logic to resolve the element, but
1719: // rather treat it as though it were defined with begin="indefinite"
1720: // and just resolve begin time to the current parent time. This
1721: // should create an interval and propagate to time dependents.
1722: //
1723: seekTo(Time.UNRESOLVED);
1724: }
1725:
1726: /**
1727: * Implementation helper method for seekTo behavior.
1728: *
1729: * @param seekToTime the time to seek to.
1730: */
1731: void seekTo(final Time seekToTime) {
1732: if (seekToTime.isResolved()) {
1733: // Now, move up the container graph.
1734: timeContainer.seekTo(seekToTime);
1735: } else {
1736: // 3. Else (i.e. there are no defined intervals for the element),
1737: // the target element begin time must be resolved. This may require
1738: // seeking and/or resolving ancestor elements as well. This is done
1739: // by recursing from the target element up to the closest ancestor
1740: // element that has a resolved begin time (again noting that for an
1741: // element to have a resolved begin time, all of its ancestors must
1742: // have resolved begin times).
1743: if (timeContainer.state != STATE_PLAYING) {
1744: TimeInterval firstContainerInterval = timeContainer
1745: .computeFirstInterval();
1746: if (firstContainerInterval == null) {
1747: timeContainer.seekTo(Time.UNRESOLVED);
1748: } else {
1749: Time parentSeekTo = new Time(
1750: firstContainerInterval.begin.value);
1751: parentSeekTo = timeContainer
1752: .toRootContainerSimpleTimeClamp(parentSeekTo);
1753: timeContainer.seekTo(parentSeekTo);
1754: }
1755: }
1756:
1757: // Then, the recursion is "unwound", and for each ancestor in turn
1758: // (beneath the resolved ancestor) as well as the target element,
1759: // the following steps are performed:
1760: //
1761: // 1. If the element begin time is resolved, seek the document
1762: // time (forward or back, as needed) to the begin time of the
1763: // first interval for the target element.
1764: //
1765: // 2. Else (if the begin time is not resolved), just resolve the
1766: // element begin time at the current time on its parent time
1767: // container (given the current document position). Disregard
1768: // the sync-base or event base of the element, and do not
1769: // "back-propagate" any timing logic to resolve the element, but
1770: // rather treat it as though it were defined with
1771: // begin="indefinite" and just resolve begin time to the current
1772: // parent time. This should create an interval and propagate to
1773: // time dependents.
1774: //
1775: TimeInterval firstInterval = computeFirstInterval();
1776: if (firstInterval == null) {
1777: begin();
1778: firstInterval = computeFirstInterval();
1779: }
1780:
1781: Time goToTime = new Time(firstInterval.begin.value);
1782: if (goToTime.value < 0) {
1783: goToTime.value = 0;
1784: }
1785:
1786: timeContainer
1787: .seekTo(toRootContainerSimpleTimeClamp(goToTime));
1788: }
1789: }
1790:
1791: /**
1792: * Should be called whenever there is a change in the begin time instance
1793: * list.
1794: */
1795: private void reEvaluateBeginTime() {
1796: switch (state) {
1797: case STATE_WAITING_INTERVAL_0: {
1798: TimeInterval curInt = computeFirstInterval();
1799: if (curInt != null) {
1800: currentInterval.setBegin(curInt.begin);
1801: currentInterval.setEnd(curInt.end);
1802: computeLastDur(currentInterval);
1803: } else {
1804: // There no first interval any more, after
1805: // the begin instance has been changed.
1806: // We move back to the no interval state
1807: pruneCurrentInterval();
1808: }
1809: }
1810: break;
1811: case STATE_WAITING_INTERVAL_N: {
1812: TimeInterval curInt = computeNextInterval();
1813: if (curInt != null) {
1814: currentInterval.setBegin(curInt.begin);
1815: currentInterval.setEnd(curInt.end);
1816: computeLastDur(currentInterval);
1817: } else {
1818: pruneCurrentInterval();
1819: }
1820: }
1821:
1822: default:
1823: return;
1824: }
1825: }
1826:
1827: /**
1828: * Recomputes the end time. This only does something if the
1829: * element is playing or waiting to play, because it may update
1830: * the currentInterval's end time.
1831: */
1832: void reEvaluateEndTime() {
1833: switch (state) {
1834: case STATE_WAITING_INTERVAL_0:
1835: case STATE_WAITING_INTERVAL_N:
1836: case STATE_PLAYING:
1837: // We are dealing with a new end time and the animation is
1838: // playing. Re-evaluate the end time and update the
1839: // current interval.
1840: //
1841: // Note that we cannot simply do a computeNextInterval as this
1842: // might yield a begin time different from the one on the
1843: // playing interval.
1844: //
1845: Time newEnd = computeEndTime(currentInterval.begin);
1846:
1847: // It is unclear what should happen here if newEnd is null.
1848: // This could happen if a list of end times was empty and
1849: // a SyncBaseCondition, for example, inserts a new end instance
1850: // that is _before_ the currentInterval's begin time. In that
1851: // situation, should we just ignore newEnd or should we
1852: // prune the currentInterval (if WAITING_0 or WAITING_N) and
1853: // stop it (if PLAYING)?
1854: //
1855: // Pending further investigation of this corner case, we simply
1856: // ignore a null newEnd
1857: if (newEnd != null) {
1858: currentInterval.setEnd(newEnd);
1859: computeLastDur(currentInterval);
1860: }
1861: break;
1862: default:
1863: break;
1864: }
1865: }
1866:
1867: /**
1868: * Helper method invoked when the current interval becomes invalid
1869: * after re-evaluation of the begin and end instances.
1870: *
1871: * @see <a href="file:///D:/work/doc/specs/smil20.html#smil-timing-q86">
1872: * SMIL 2: Principles for building and pruning intervals</a>
1873: */
1874: private void pruneCurrentInterval() {
1875: TimeInterval prunedInterval = currentInterval;
1876: currentInterval = null;
1877: if (previousInterval != null) {
1878: state = STATE_FILL;
1879: } else {
1880: state = STATE_NO_INTERVAL;
1881: }
1882:
1883: prunedInterval.prune();
1884: }
1885:
1886: /**
1887: * This method inserts the input time instance in the instance list
1888: *
1889: * @param newInstance the instance to reposition in its instance list
1890: * @see #addTimeInstance
1891: * @see #onTimeInstanceUpdate
1892: */
1893: private void insertTimeInstance(final TimeInstance newInstance) {
1894: Vector instances = beginInstances;
1895: if (!newInstance.isBegin) {
1896: instances = endInstances;
1897: }
1898:
1899: int n = instances.size();
1900: int i = 0;
1901: Time newTime = newInstance.time;
1902: for (i = 0; i < n; i++) {
1903: TimeInstance timeInstance = (TimeInstance) instances
1904: .elementAt(i);
1905: if (!newTime.greaterThan(timeInstance.time)) {
1906: instances.insertElementAt(newInstance, i);
1907: break;
1908: }
1909: }
1910:
1911: if (i == n) {
1912: instances.addElement(newInstance);
1913: }
1914: }
1915:
1916: /**
1917: * Called to add a new <code>TimeInstance</code> to the begin or end
1918: * instance list (depending on the new instance's <code>isBegin</code>
1919: * setting).
1920: *
1921: * This is also called as a result of invoking beginAt or endAt.
1922: *
1923: * @param newInstance the new <code>TimeInstance</code> to add to the
1924: * list. Should not be null.
1925: * @see #beginAt
1926: * @see #endAt
1927: * @see <a
1928: * href="http://www.w3.org/TR/smil20/smil-timing.html#Timing-BeginEnd-Restart">
1929: * SMIL 2, Interaction with restart semantic</a>
1930: */
1931: void addTimeInstance(final TimeInstance newInstance) {
1932: if (timingUpdate) {
1933: // We are in a timing dependency cycle. Break now.
1934: return;
1935: }
1936:
1937: timingUpdate = true;
1938:
1939: try {
1940: insertTimeInstance(newInstance);
1941:
1942: // Do not handle instance time list changes if we are
1943: // initializing the time graph.
1944: if (state == STATE_PRE_INIT) {
1945: return;
1946: }
1947:
1948: if (!newInstance.isBegin) {
1949: reEvaluateEndTime();
1950: return;
1951: }
1952:
1953: // Impact the current interval if needed
1954: switch (state) {
1955: case STATE_NO_INTERVAL:
1956: checkNewInterval(computeFirstInterval());
1957: break;
1958: case STATE_FILL:
1959: if (restart != RESTART_NEVER) {
1960: checkNewInterval(computeNextInterval());
1961: }
1962: // Else, don't do anything: this element cannot
1963: // restart.
1964: break;
1965: case STATE_WAITING_INTERVAL_0: {
1966: TimeInterval curInt = computeFirstInterval();
1967: if (curInt != null) {
1968: currentInterval.setBegin(curInt.begin);
1969: currentInterval.setEnd(curInt.end);
1970: computeLastDur(currentInterval);
1971: }
1972: }
1973: break;
1974: case STATE_WAITING_INTERVAL_N: {
1975: TimeInterval curInt = computeNextInterval();
1976: // We just added a new time instance. It is not possible
1977: // that adding a time instance would make
1978: // computeNextInterval return a null interval here.
1979: // So we do not test for a null condition because it
1980: // can't happen. If it happens,
1981: // it means there is a bug in computeNextInterval or that
1982: // the beginInstance list was messed with, which should not
1983: // happen.
1984: currentInterval.setBegin(curInt.begin);
1985: currentInterval.setEnd(curInt.end);
1986: computeLastDur(currentInterval);
1987: }
1988: break;
1989: case STATE_PLAYING:
1990: if (newInstance.isBegin) {
1991: if (restart == RESTART_ALWAYS) {
1992: if (currentInterval.end
1993: .greaterThan(newInstance.time)
1994: && newInstance.time
1995: .greaterThan(currentInterval.begin)) {
1996: // Update the current interval end time with this
1997: // new begin time.
1998: currentInterval.setEnd(newInstance.time);
1999: computeLastDur(currentInterval);
2000:
2001: // Next interval will be computed in the next call
2002: // to sample();
2003: }
2004: // Ignore the new instance if it if falls after or
2005: // before the current interval
2006: }
2007: // Ignore the new instance if we cannot restart this
2008: // animation.
2009: }
2010: break;
2011: default:
2012: throw new IllegalStateException();
2013: }
2014: } finally {
2015: timingUpdate = false;
2016: }
2017: }
2018:
2019: /**
2020: * Called by <code>TimeInstance</code>s when they are updated.
2021: * In response, the TimedElement may recompute its current
2022: * interval.
2023: *
2024: * @param timeInstance the <code>TimeInstance</code> that was just
2025: * updated.
2026: *
2027: * @throws NullPointerException if timeInstance is null.
2028: *
2029: * @see <a
2030: * href="http://www.w3.org/TR/smil20/smil-timing.html#Timing-SemanticsOfTimingModel">
2031: * SMIL 2: Building the instance times lists</a>
2032: */
2033: void onTimeInstanceUpdate(final TimeInstance timeInstance) {
2034: if (timingUpdate) {
2035: // We are in a timing dependency cycle, break now.
2036: return;
2037: }
2038:
2039: timingUpdate = true;
2040:
2041: try {
2042: // First, reposition the instance in the instance list
2043: if (timeInstance.isBegin) {
2044: beginInstances.removeElement(timeInstance);
2045: } else {
2046: endInstances.removeElement(timeInstance);
2047: }
2048: insertTimeInstance(timeInstance);
2049:
2050: if (state == STATE_PRE_INIT) {
2051: return;
2052: }
2053:
2054: if (timeInstance.isBegin) {
2055: // We are dealing with a begin instance change.
2056: reEvaluateBeginTime();
2057: } else {
2058: // We are dealing with an end time instance.
2059: reEvaluateEndTime();
2060: }
2061: } finally {
2062: timingUpdate = false;
2063: }
2064: }
2065:
2066: /**
2067: * Called to remove a <code>TimeInstance</code> when a
2068: * <code>TimeInterval</code> is pruned. This causes a re-evaluation
2069: * of the current interval's begin or end time.
2070: *
2071: * @param timeInstance the instance to remove from its instance list.
2072: */
2073: void removeTimeInstance(final TimeInstance timeInstance) {
2074: if (timeInstance.isBegin) {
2075: beginInstances.removeElement(timeInstance);
2076: } else {
2077: endInstances.removeElement(timeInstance);
2078: }
2079:
2080: if (timingUpdate) {
2081: return;
2082: }
2083:
2084: timingUpdate = true;
2085: try {
2086: if (state != STATE_PRE_INIT) {
2087: if (timeInstance.isBegin) {
2088: reEvaluateBeginTime();
2089: } else {
2090: reEvaluateEndTime();
2091: }
2092: }
2093: } finally {
2094: timingUpdate = false;
2095: }
2096: }
2097:
2098: /**
2099: * Called by <code>TimeCondition</code>s when they are constructed.
2100: *
2101: * @param newCondition the new <code>TimeCondition</code> to add to the
2102: * list. Should not be null.
2103: */
2104: void addTimeCondition(final TimeCondition newCondition) {
2105: if (newCondition.isBegin) {
2106: beginConditions.addElement(newCondition);
2107: } else {
2108: endConditions.addElement(newCondition);
2109: }
2110: }
2111:
2112: /**
2113: * @param eventCondition the <code>EventBaseCondition</code> to check
2114: * against.
2115: * @return true if this <code>TimedElement</code> has a begin condition
2116: * corresponding to the input <code>EventBaseCondition</code>
2117: */
2118: boolean hasBeginCondition(final EventBaseCondition eventCondition) {
2119: int n = beginConditions.size();
2120: for (int i = 0; i < n; i++) {
2121: TimeCondition condition = (TimeCondition) beginConditions
2122: .elementAt(i);
2123: if (condition instanceof EventBaseCondition) {
2124: EventBaseCondition beginCondition = (EventBaseCondition) condition;
2125: if (beginCondition.eventType
2126: .equals(eventCondition.eventType)
2127: && beginCondition.eventBase == eventCondition.eventBase) {
2128: return true;
2129: }
2130:
2131: }
2132: }
2133:
2134: return false;
2135: }
2136:
2137: /**
2138: * @return this <code>TimedElement</code>'s current time.
2139: */
2140: Time getCurrentTime() {
2141: return timeContainer.getSimpleTime();
2142: }
2143:
2144: /**
2145: * Converts the input 'local' time to an absolute wallclock time.
2146: *
2147: * @param localTime the time to convert to wallclock time
2148: * @return the time converted to an absolute wallclock time.
2149: */
2150: long toWallClockTime(final long localTime) {
2151: return timeContainer.toWallClockTime(localTime);
2152: }
2153:
2154: /**
2155: * Converts the input simple time (i.e., a time in the parent container's
2156: * simple duration) to a root container simple time (i.e., a time
2157: * in the root time container's simple time interval). Note that this method
2158: * mutates the input time value. If the associated time container does not
2159: * have a current interval, this method returns Time.UNRESOLVED.
2160: *
2161: * @param simpleTime the time in the parent container's simple duration.
2162: * Should not be null. If simpleTime is Time.UNRESOLVED, the returned
2163: * time is UNRESOLVED. If simpleTime is Time.INDEFINITE, the returned
2164: * time is INDEFINITE.
2165: * @return a time in the root time container's simple duration (i.e., in
2166: * the root container's simple time interval).
2167: */
2168: Time toRootContainerSimpleTime(Time simpleTime) {
2169: if (timeContainer.currentInterval == null) {
2170: return Time.UNRESOLVED;
2171: }
2172:
2173: if (!simpleTime.isResolved()) {
2174: return simpleTime;
2175: }
2176:
2177: // Convert to the container's container simple time
2178:
2179: // Account for repeat behavior. This yields a time
2180: // in the [0, ActiveTime[ time system
2181: if (timeContainer.simpleDur.isResolved()) {
2182: simpleTime.value += timeContainer.curIter
2183: * timeContainer.simpleDur.value;
2184: }
2185:
2186: // By adding the timeContainer's begin value, we are
2187: // translating the simpleTime to a time in the container's
2188: // container simple duration [0, simpleDur[ interval.
2189: simpleTime.value += timeContainer.currentInterval.begin.value;
2190:
2191: // Now, move the conversion one level up.
2192: return timeContainer.toRootContainerSimpleTime(simpleTime);
2193: }
2194:
2195: /**
2196: * Same definition and behavior as toRootContainerSimpleTime, except that
2197: * this method clamps values to the container's simple duration. This
2198: * means that a value smaller than zero is converted to 0 and a value
2199: * greater than the simple duration is reduced to the simple duration.
2200: *
2201: * @param simpleTime the time in the parent container's simple duration.
2202: * Should not be null. If simpleTime is Time.UNRESOLVED, the returned
2203: * time is UNRESOLVED. If simpleTime is Time.INDEFINITE, the returned
2204: * time is INDEFINITE.
2205: * @return a time in the root time container's simple duration (i.e., in
2206: * the root container's simple time interval).
2207: */
2208: Time toRootContainerSimpleTimeClamp(final Time simpleTime) {
2209: if (timeContainer.currentInterval == null) {
2210: return Time.UNRESOLVED;
2211: }
2212:
2213: if (!simpleTime.isResolved()) {
2214: return simpleTime;
2215: }
2216:
2217: // Clamp to zero.
2218: if (simpleTime.value < 0) {
2219: simpleTime.value = 0;
2220: }
2221:
2222: // Convert to the container's _container_ simple time
2223:
2224: // Account for repeat behavior. This yields a time
2225: // in the [0, ActiveTime[ time system
2226: if (timeContainer.simpleDur.isResolved()) {
2227: // Clamp to the simple duration.
2228: if (simpleTime.value > timeContainer.simpleDur.value) {
2229: simpleTime.value = timeContainer.simpleDur.value;
2230: }
2231: simpleTime.value += timeContainer.curIter
2232: * timeContainer.simpleDur.value;
2233: } else {
2234: // Still clamp to the container's active duration.
2235: if (timeContainer.currentInterval.end.isResolved()) {
2236: long maxDur = timeContainer.currentInterval.end.value
2237: - timeContainer.currentInterval.begin.value;
2238: if (simpleTime.value > maxDur) {
2239: simpleTime.value = maxDur;
2240: }
2241: }
2242: }
2243:
2244: // By adding the timeContainer's begin value, we are
2245: // translating the simpleTime to a time in the container's
2246: // container simple duration [0, simpleDur[ interval.
2247: simpleTime.value += timeContainer.currentInterval.begin.value;
2248:
2249: // Now, move the conversion one level up.
2250: return timeContainer.toRootContainerSimpleTimeClamp(simpleTime);
2251: }
2252:
2253: /**
2254: * Converts the input root container simple time (i.e., a time in the root
2255: * container's simple time interval) to a time in this element's time
2256: * container simple duration. Note that this method mutates the input
2257: * argument. If the associated time container does not
2258: * have a current interval, this method returns Time.UNRESOLVED.
2259: *
2260: * @param simpleTime the time in the root container's simple duration. If
2261: * simpleTime is Time.UNRESOLVED, the returned time is UNRESOLVED. If
2262: * simpleTime is Time.INDEFINITE, the returned time is INDEFINITE.
2263: *
2264: * @return a simple time in the parent container's simple duration The
2265: * return value is in the [0, container simple duration] interval.
2266: */
2267: Time toContainerSimpleTime(Time simpleTime) {
2268: if (timeContainer.currentInterval == null) {
2269: return Time.UNRESOLVED;
2270: }
2271:
2272: if (!simpleTime.isResolved()) {
2273: return simpleTime;
2274: }
2275:
2276: // Convertion to the container's container simple time
2277: simpleTime = timeContainer.toContainerSimpleTime(simpleTime);
2278:
2279: // Account for the container's begin value
2280: simpleTime.value -= timeContainer.currentInterval.begin.value;
2281:
2282: // Account for repeat behavior
2283: if (timeContainer.simpleDur.isResolved()) {
2284: simpleTime.value -= timeContainer.curIter
2285: * timeContainer.simpleDur.value;
2286: }
2287:
2288: return simpleTime;
2289: }
2290:
2291: /**
2292: * @return the container's simple duration.
2293: */
2294: Time getContainerSimpleDuration() {
2295: return timeContainer.simpleDur;
2296: }
2297:
2298: /**
2299: * Debug helper.
2300: * @return a description of this object.
2301: */
2302: public String toString() {
2303: String str = "[Animation: " + animationElement + "] ["
2304: + getStateString() + "]";
2305: switch (state) {
2306: case STATE_WAITING_INTERVAL_N:
2307: case STATE_WAITING_INTERVAL_0:
2308: case STATE_PLAYING:
2309: str += "[" + currentInterval.begin + ", "
2310: + currentInterval.end
2311: /*
2312: + ", "
2313: + currentInterval.lastDur
2314: */
2315: + "]";
2316: break;
2317: default:
2318: break;
2319: }
2320: return str;
2321: }
2322:
2323: /**
2324: * Debug helper. Converts the state to a string.
2325: *
2326: * @return a string describing the element's state.
2327: */
2328: String getStateString() {
2329: switch (state) {
2330: case STATE_PRE_INIT:
2331: return "PRE_INIT";
2332: case STATE_WAITING_INTERVAL_0:
2333: return "WAITING_INTERVAL_0";
2334: case STATE_WAITING_INTERVAL_N:
2335: return "WAITING_INTERVAL_N";
2336: case STATE_NO_INTERVAL:
2337: return "NO_INTERVAL";
2338: case STATE_FILL:
2339: return "FILL";
2340: case STATE_PLAYING:
2341: return "PLAYING";
2342: default:
2343: return "UNKNOWN";
2344: }
2345: }
2346: }
|