FileDocCategorySizeDatePackage
TraitAnim.javaAPI DocphoneME MR2 API (J2ME)13583Wed May 02 18:00:36 BST 2007com.sun.perseus.model

TraitAnim.java

/*
 *
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

package com.sun.perseus.model;

import org.w3c.dom.DOMException;

/**
 * The <code>TraitAnim</code> class is the link between animation targets
 * (i.e., traits on elements) and animation elements.
 * 
 * <p>When an animation becomes active on a target (i.e., on a trait or pseudo
 * trait for an element), it invokes getAnimTrait(traitName) on the
 * corresponding element to get the trait's TraitAnim. One is created if the
 * trait is not currently animated. Then, the animation adds itself to the
 * TraitAnim through a call to addAnimation(). As an animation adds itself, it
 * becomes the TraitAnim's rootAnim. If there was no animation, the new
 * animation's baseVal becomes the TraitAnim itself. If there was already an
 * animation, the new Animation's baseVal becomes the previous rootAnim.</p>
 * 
 * <p>When an animation becomes inactive, it removes itself from the TraitAnim
 * by calling the removeAnimation method. When the last active animation removes
 * itself from the TraitAnim, the TraitAnim removes itself from the
 * ElementNode's TraitAnim list and it restores the trait's original base value
 * (stored as baseValue).</p>
 *
 * <p>This achieves the sandwich model behavior described in the SMIL Animation
 * specification (section 3.5). In particular, because the tree is sampled in
 * document order in Perseus, the animation that appears first in document order
 * will have lower priority (i.e., it will be added first to the TraitAnim). If
 * an animation is a time dependent of another one, then it will become active
 * after its time sync and will have higher priority than its time sync.</p>
 *
 * @version $Id: TraitAnim.java,v 1.4 2006/06/29 10:47:36 ln156897 Exp $
 */
abstract class TraitAnim implements BaseValue {
    /**
     * The TraitAnim underlying type. One of ElementNode.TRAIT_TYPE 
     * constants.
     */
    String traitType;

    /**
     * This animation's root.
     */
    Animation rootAnim;

    /**
     * The target element.
     */
    ElementNode targetElement;

    /**
     * The target trait name.
     */
    String traitName;

    /**
     * The target trait namespace.
     */
    String traitNamespace;

    /**
     * The trait's specified value, as a String.
     */
    String specifiedTraitValue;
    
    /**
     * True when the TraitAnim has at least one active animation.
     */
    boolean active;

    /**
     * Constructs a new TraitAnim for a given ElementNode trait
     * in the given namespace.
     *
     * @param targetElement the ElementNode whose trait is animated.
     *        Should not be null.
     * @param targetNamespace the target trait's namespace. Should not 
     *        be null. The per-element partition namespace should be 
     *        represented by the ElementNode.NULL_NS value.
     * @param targetTrait the name of the animated trait. Should not
     *        be null.
     */
    TraitAnim(final ElementNode targetElement,
              final String traitNamespace,
              final String traitName,
              final String traitType) {
        if (targetElement == null 
            || 
            traitName == null
            || 
            traitNamespace == null
            ||
            traitType == null) {
            throw new NullPointerException();
        }

        this.targetElement = targetElement;
        this.traitNamespace = traitNamespace;
        this.traitName = traitName;
        this.traitType = traitType;
    } 

    /**
     * @return the trait's specified base value, as a String.
     */
    public String getSpecifiedTraitNS() {
        if (specifiedTraitValue == null) {
            specifiedTraitValue = targetElement.getSpecifiedTraitNSImpl(
                    traitNamespace, traitName);
        }

        return specifiedTraitValue;
    }

    /**
     * Restores the base value. This is invoked when there are not more 
     * animations and the original base value needs to be restored.
     */
    final void restore() {
        // Now, restore the specified trait value
        if (traitNamespace == ElementNode.NULL_NS) {
            targetElement.setTraitImpl(traitName, specifiedTraitValue);
        } else {
            targetElement.setTraitNSImpl(traitNamespace, traitName, 
                                         specifiedTraitValue);
        }
    }

    /**
     * Adds a new animation to this TraitAnim. The new animation
     * becomes the highest priority animation. If this is the 
     * first animation added to the TraitAnim, the new animation's
     * base value becomes the TraitAnim itself and the TraitAnim
     * registers with the DocumentNode. If there is already
     * one or more animation in the TraitAnim, the baseValue for the
     * new animation becomes the previous animation root. In all
     * cases, the new animation becomes the rootAnim.
     *
     * @param newAnim the new highest priority animation for this TraitAnim.
     *        Should not be null.
     */
    void addAnimation(final Animation newAnim) {
        // Reject null values
        if (newAnim == null) {
            throw new NullPointerException();
        }

        if (rootAnim == null) {
            // This is the first animation.
            // Set the animation as the root animation and set its
            // baseValue. 
            rootAnim = newAnim;
            newAnim.baseVal = this;
            targetElement.ownerDocument.activeTraitAnims.addElement(this);

            // We need to recompute the specifiedTraitAnim at this point
            // Otherwise, the specifiedTrait value may be off (i.e., different
            // from what it was set to originally, when the TraitAnim was 
            // created.
            this.specifiedTraitValue = targetElement.getSpecifiedTraitNSImpl(
                    traitNamespace, traitName);
        } else {
            // This is a new animation in the sandwich.
            // The new animation becomes the highest priority animation and
            // its baseValue is the previous rootAnim.
            newAnim.baseVal = rootAnim;
            rootAnim = newAnim;
        }

        active = true;
    }

    /**
     * Removes the input animation from this TraitAnim. If the removed 
     * animation's baseValue is the TraitAnim itself, it means this is 
     * the last active animation on the trait and the TraitAnim will 
     * mark itself as inactive. If this is not the last animation,
     * then this animation's baseValue becomes the rootAnim.
     *
     * If removedAnim is not part of this TraitAnim, this method
     * does nothing.
     *
     * @param removedAnim the animation to remove from the TraitAnim.
     *        should not be null. 
     */
    void removeAnimation(final Animation removedAnim) {
        // Reject null values.
        if (removedAnim == null) {
            throw new NullPointerException();
        }

        if (removedAnim == rootAnim) {
            // Removing the root animatoin
            if (removedAnim.baseVal == this) {
                // This is the last animation in the TraitAnim.
                // Unregister from the Document.
                targetElement.ownerDocument.activeTraitAnims.removeElement(
                        this);
                rootAnim = null;

                // Mark the animation as inactive.
                active = false;
                restore();
            } else {
                rootAnim = (Animation) removedAnim.baseVal;
            }
        } else {
            if (rootAnim != null) {
                // Removing an animation other than the root.
                // Find the preceding animation.
                Animation prevAnim = null;
                Animation curAnim = rootAnim;
                while (curAnim.baseVal != this) {
                    if (curAnim.baseVal == removedAnim) {
                        prevAnim = curAnim;
                        break;
                    }
                    curAnim = (Animation) curAnim.baseVal;
                }
                
                // If removedAnimat was indeed part of the sandwich.
                if (prevAnim != null) {
                    prevAnim.baseVal = removedAnim.baseVal;
                }
            }
        }
    }

    /**
     * @param traitType the expected type for this trait. One of the 
     *        ElementNode.TRAIT_TYPE_.... constants (e.g., TRAIT_TYPE_STRING).
     *        All TraitAnim implementations must support TRAIT_TYPE_STRING.
     *        They may support additional types (for example, FloatTraitAnim
     *        may support TRAIT_TYPE_FLOAT).
     * @return the trait's computed value, as a String.
     *
     * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
     * trait's value cannot be specified as a requested type value.
     */
    final String getTrait(final String traitType) throws DOMException {
        if (ElementNode.TRAIT_TYPE_STRING.equals(traitType)
            ||
            this.traitType.equals(traitType)) {
            return getTraitImpl();
        } else {
            throw targetElement.unsupportedTraitTypeNS(traitName, 
                                                       traitNamespace, 
                                                       traitType);
        }
    }

    /**
     * @return the trait's value, as a String.
     */
    abstract protected String getTraitImpl();

    /**
     * Sets the trait's base value, as a String.
     * 
     * @param value the new trait base value.
     * @param traitType the requested trait type.
     *
     * @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
     * trait's value cannot be specified as the requested traitType.
     * @throws DOMException with error code INVALID_ACCESS_ERR if the input
     * value is an invalid value for the given trait or null.
     */
    final void setTrait(String value, String traitType) throws DOMException {
        if (ElementNode.TRAIT_TYPE_STRING.equals(traitType)
            ||
            this.traitType.equals(traitType)) {
            setTraitImpl(value);
        } else {
            throw targetElement.unsupportedTraitTypeNS(traitName, 
                                                       traitNamespace, 
                                                       traitType);
        }
    }

    /**
     * Sets the trait's base value, as a String.
     * 
     * @param value the new trait base value.
     *
     * @throws DOMException with error code INVALID_ACCESS_ERR if the input
     * value is an invalid value for the given trait or null.
     */
    abstract void setTraitImpl(String value) throws DOMException;

    /**
     * Applies the animation effect. The implementation makes sure it 
     * implements the sandwich model by 'pulling' values from the 
     * root animation (i.e., the animation with the highest priority).
     */
    abstract void apply();

    /**
     * Converts the input values set to a RefValues object.
     *
     * @param anim the <code>Animation</code> for which the values should be
     *        converted.
     * @param values a semi-colon seperated list of values which need to be
     * validated.
     * @param reqTraitNamespace the namespace of the trait which has the values
     *        value on the requesting element.
     * @param reqTraitName the name of the trait which has the values value on 
     *        the requesting element.
     * @throws DOMException with error code INVALID_ACCESS_ERR if the input
     * value is incompatible with the given trait.
     */
    abstract RefValues toRefValues(
            final Animation anim,
            final String[] values,
            final String reqTraitNamespace,
            final String reqTraitName) throws DOMException;

    /**
     * Used to sum two animated trait values.
     *
     * @param valueA the base value. May be null.
     * @param valueB the value to add to the base value. If the baseValue 
     * @return the sum result.
     */
    abstract Object[] sum(Object[] valueA, Object[] valueB);

    /**
     * Used to multiply an animated trait value by a number of iterations.
     *
     * @param value the animated trait value to multiply.
     * @param iter the number of iteration to account for.
     * @return the multiply result.
     */
    abstract Object[] multiply(Object[] value, int iter);

    /**
     * @return true if this trait supports interpolation. false otherwise.
     */
    boolean supportsInterpolation() {
        return false;
    }
}