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

ModelNode.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 com.sun.perseus.j2d.Box;
import com.sun.perseus.j2d.Path;
import com.sun.perseus.j2d.RenderGraphics;
import com.sun.perseus.j2d.Tile;
import com.sun.perseus.j2d.Transform;

import org.w3c.dom.DOMException;

import org.w3c.dom.svg.SVGRect;
import org.w3c.dom.svg.SVGMatrix;

import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;

/**
 * The central structure manipulated in Perseus is called the <em>Model</em>. It
 * is used for the in-memory representation of an SVG Tiny document structure.
 * 
 * <p>The <code>ModelNode</code> abstraction represents an atomic node in the
 * <b><code>Model</code></b>'s tree structure. </p>
 * 
 * <p>A <code>ModelNode</code> may have regular children (which can be added on
 * <code>CompositeNode</code> implementations, for example), and expanded
 * children (which are automatically computed by the node, for example on
 * <code>Text</code> or on <code>Use</code>). In rendering order, expanded
 * content comes second.</p>
 *
 * <p>A <code>ModelNode</code> can have a parent, which may be null, but it
 * always belongs to a <code>DocumentNode</code> instance which is guaranteed to
 * no be null.</p>
 * 
 * <p>A <code>ModelNode</code> can be painted into a 
 * @link {com.sun.perseus.j2d.RenderGraphics RenderGraphics}. 
 *
 * <p>A <code>ModelNode</code> is subjet to interactivity and its
 * @link {#nodeHitAt nodeHitAt} method can be used to perform hit detection.</p>
 *
 * @version $Id: ModelNode.java,v 1.24 2006/06/29 10:47:33 ln156897 Exp $
 */
public abstract class ModelNode implements EventTarget {
    /**
     * Used in cases the parent node is null.
     */
    protected final Transform IDENTITY = new Transform(null);
    
    /**
     * The parent node
     */
    protected ModelNode parent;

    /**
     * The next sibling
     */
    protected ModelNode nextSibling;

    /**
     * The next sibling
     */
    protected ModelNode prevSibling;

    /**
     * Mask for the renderable bit.
     */
    public static final int CAN_RENDER_RENDERABLE_MASK = ~0x1;

    /**
     * Mask for the required features bit
     */
    public static final int CAN_RENDER_REQUIRED_FEATURES_MASK = ~(1 << 1);

    /**
     * Mask for the required extensions bit
     */
    public static final int CAN_RENDER_REQUIRED_EXTENSIONS_MASK = ~(1 << 2);

    /**
     * Mask for the required languages bit
     */
    public static final int CAN_RENDER_SYSTEM_LANGUAGE_MASK = ~(1 << 3);

    /**
     * Mask for conditions met, i.e., requiredFeatures, requiredExtensions,
     * and systemLanguage.
     */
    public static final int CAN_RENDER_CONDITIONS_MET_MASK =
        CAN_RENDER_REQUIRED_FEATURES_MASK 
        & CAN_RENDER_REQUIRED_EXTENSIONS_MASK
        & CAN_RENDER_SYSTEM_LANGUAGE_MASK;

    /**
     * Mask defining which bits from the canRenderState value are copied over
     * to newly created proxies. See the description of the canRenderState
     * bits below.
     */
    public static final int CAN_RENDER_PROXY_BITS_MASK = 0x7EE;
    /**
     * Mask for the non-invertible transform bit
     */
    public static final int CAN_RENDER_NON_INVERTIBLE_TXF_MASK = ~(1 << 4);

    /**
     * Mask for the display bit
     */
    public static final int CAN_RENDER_DISPLAY_MASK = ~(1 << 5);

    /**
     * Mask for the zero-width bit
     */
    public static final int CAN_RENDER_ZERO_WIDTH_MASK = ~(1 << 6);

    /**
     * Mask for the zero-height bit
     */
    public static final int CAN_RENDER_ZERO_HEIGHT_MASK = ~(1 << 7);

    /**
     * Mask for the empty viewbox bit
     */
    public static final int CAN_RENDER_EMPTY_VIEWBOX_MASK = ~(1 << 8);

    /**
     * Mask for the empty path bit
     */
    public static final int CAN_RENDER_EMPTY_PATH_MASK = ~(1 << 9);

    /**
     * Mask for the zero font-size bit
     */
    public static final int CAN_RENDER_ZERO_FONT_SIZE_MASK = ~(1 << 10);

    /**
     * Mask for the in-document-tree bit
     */
    public static final int CAN_RENDER_IN_DOCUMENT_TREE_MASK = ~(1 << 11);

    /**
     * Mask for the paren canRenderState bit
     */
    public static final int CAN_RENDER_PARENT_STATE_MASK = ~(1 << 12);


    /**
     * Mask for the renderable bit.
     */
    public static final int CAN_RENDER_RENDERABLE_BIT 
        = ~CAN_RENDER_RENDERABLE_MASK;

    /**
     * Mask for the required features bit
     */
    public static final int CAN_RENDER_REQUIRED_FEATURES_BIT 
        = ~CAN_RENDER_REQUIRED_FEATURES_MASK;

    /**
     * Mask for the required extensions bit
     */
    public static final int CAN_RENDER_REQUIRED_EXTENSIONS_BIT 
        = ~CAN_RENDER_REQUIRED_EXTENSIONS_MASK;

    /**
     * Mask for the system language bit
     */
    public static final int CAN_RENDER_SYSTEM_LANGUAGE_BIT 
        = ~CAN_RENDER_SYSTEM_LANGUAGE_MASK;

    /**
     * Mask for the conditionsMet bits
     */
    public static final int CAN_RENDER_CONDITIONS_MET_BITS 
        = ~CAN_RENDER_CONDITIONS_MET_MASK;

    /**
     * Mask for the non-invertible transform bit
     */
    public static final int CAN_RENDER_NON_INVERTIBLE_TXF_BIT 
        = ~CAN_RENDER_NON_INVERTIBLE_TXF_MASK;

    /**
     * Mask for the display bit
     */
    public static final int CAN_RENDER_DISPLAY_BIT 
        = ~CAN_RENDER_DISPLAY_MASK;

    /**
     * Mask for the zero-width bit
     */
    public static final int CAN_RENDER_ZERO_WIDTH_BIT 
        = ~CAN_RENDER_ZERO_WIDTH_MASK;

    /**
     * Mask for the zero-height bit
     */
    public static final int CAN_RENDER_ZERO_HEIGHT_BIT 
        = ~CAN_RENDER_ZERO_HEIGHT_MASK;

    /**
     * Mask for the empty viewbox bit
     */
    public static final int CAN_RENDER_EMPTY_VIEWBOX_BIT 
        = ~CAN_RENDER_EMPTY_VIEWBOX_MASK;

    /**
     * Mask for the empty path bit
     */
    public static final int CAN_RENDER_EMPTY_PATH_BIT 
        = ~CAN_RENDER_EMPTY_PATH_MASK;

    /**
     * Mask for the zero font-size bit
     */
    public static final int CAN_RENDER_ZERO_FONT_SIZE_BIT 
        = ~CAN_RENDER_ZERO_FONT_SIZE_MASK;

    /**
     * Mask for the in document tree bit
     */
    public static final int CAN_RENDER_IN_DOCUMENT_TREE_BIT 
        = ~CAN_RENDER_IN_DOCUMENT_TREE_MASK;

    /**
     * Mask for the parent cansRenderState bit
     */
    public static final int CAN_RENDER_PARENT_STATE_BIT 
        = ~CAN_RENDER_PARENT_STATE_MASK;

    /**
     * Pre-computes whether or not this node can render. This is a bit-mask.
     *
     * 0  : renderable (i.e., initialized in the class constructor). All 
     *      classes.
     * 1  : required features. ElementNode
     * 2  : required extensions. ElementNode
     * 3  : required languages. ElementNode.
     * 4  : non-invertible transform. StructureNode, AbstractRenderingNode.
     * 5  : display. CompositeGraphicsNode.
     * 6  : width is zero. Ellipse, ImageNode, Rect, SVG, Viewport
     * 7  : height is zero. Ellipse, ImageNode, Rect, SVG, Viewport
     * 8  : viewBox width or height is zero or negative.
     * 9  : path is null or has no segments. ShapeNode.
     * 10 : font-size is negative or zero. Text.
     * 11 : node in document tree.
     * 12 : parent canRenderState. This 1 bit value is set if any of the bits
     *      in the parent node's canRenderState is set.
     *
     * Only bits 1/2/3 are special in that they need to be applied from a node
     * to its proxies. Other bits (e.g., bit 5 (display)) do not need to be
     * propagated because they are set when the property changes on the node and
     * there is already propagation of property changes to the proxies.
     *
     * By default, ModelNodes are not renderable and are not in the document 
     * tree.
     */
    protected int canRenderState 
        = CAN_RENDER_RENDERABLE_BIT | CAN_RENDER_IN_DOCUMENT_TREE_BIT;

    /**
     * Used to track if this node is loaded or not. A node becomes loaded after
     * its <code>markLoaded</code> method is called.  Note: the default is
     * <b>true</b> because when used through scripting, nodes are considered
     * fully loaded and operational.  When used by the builder module or a
     * parser, the flag needs to be first turned off (see the
     * <code>setLoaded</code> method to control the progressive rendering
     * behavior (see the <code>CanvasManager</code> class).
     */
    protected boolean loaded = true;

    /**
     * The owner <code>DocumentNode</code>
     */
    protected DocumentNode ownerDocument;
    
    /**
     * By default, a node's bounding box is not accounted for. This is 
     * overridden by children classes to specify when and under which condition
     * their bounding box should be accounted for.
     *
     * @return true if the node's bounding box should be accounted for.
     */
    protected boolean contributeBBox() {
        return false;
    }

    /**
     * @return the <code>UpdateListener</code> associated with the nearest
     *         <code>DocumentNode</code> ancestor or null if there is no
     *         <code>Viewporpt</code> ancestor
     */
    protected UpdateListener getUpdateListener() {
        //
        // It is important to _not_ use ownerDocument.getUpdateListener
        // because we do not report changes to elements which are not 
        // hooked into the document tree.
        //
        // May be that should be changed in the future: we could also
        // always report updates to the listener and let it figure out
        // whether or not the reporting node is in the Document tree
        // or not yet.
        //
        if (parent == null) {
            return null;
        } else {
            return parent.getUpdateListener();
        }
    }

    /**
     * Returns the next sibling object of this object, or
     * <code>null</code> if this is the last sibling.
     *
     * @return the next sibling object.
     */
    public ModelNode getNextSiblingNode() {
        return nextSibling;
    }

    /**
     * Returns the previous sibling object of this object, or
     * <code>null</code> if this is the first child node and there
     * is no previous sibling.
     *
     * @return the next sibling object.
     */
    public ModelNode getPreviousSiblingNode() {
        return prevSibling;
    }

    /**
     * @return a reference to the parent <code>ModelNode</code>
     */
    public ModelNode getParent() {
        return parent;
    }

    /**
     * @param newParent this node's new parent
     */
    protected void setParent(final ModelNode newParent) {
        modifyingNode();
        setParentQuiet(newParent);
        nodeInserted(this);
    }
    
    /**
     * Sets this node's parent but does not generate change 
     * events.
     * 
     * @param newParent the node's new parent node.
     */
    protected void setParentQuiet(final ModelNode newParent) {
        if (parent == newParent) {
            return;
        }

        // If the new parent is not null, check whether or not 
        // it is in the document tree. In all cases, we need 
        // to unhook and then hook again.
        if (parent != null) {
            onUnhookedFromDocumentTree();
        }
        
        parent = newParent;
        
        // If the parent is not null and in the document tree,
        // we process that condition again.
        if (parent != null) {
            if (parent.isInDocumentTree()) {
                onHookedInDocumentTree();
            }
        }
    }

    /**
     * Utility method. Unhooks a node and all its following siblings, 
     * quietly setting the parent and or proxy to null.
     *
     * @param node the root of the branch to unhook.
     */
    protected final void unhookQuiet(ModelNode node) {
        if (node != null) {
            if (node.prevSibling != null) {
                node.prevSibling.nextSibling = null;
            }
        }

        while (node != null) {
            node.setParentQuiet(null);
            if (node instanceof ElementNodeProxy) {
                ((ElementNodeProxy) node).setProxied(null);
            }
            node.unhookChildrenQuiet();
            node.unhookExpandedQuiet();
            node = node.nextSibling;
        }
    }

    /**
     * Utilitty method. Returns true if the input node is part of the 
     * Document tree, i.e., if its top most ancestor is equal to its
     * ownerDocument.
     *
     * @return true if this node is a <code>DocumentNode</code> or if 
     *         one of its ancestors is a <code>DocumentNode</code>
     */
    protected boolean isInDocumentTree() {
        return ((canRenderState & CAN_RENDER_IN_DOCUMENT_TREE_BIT) == 0);
    }

    /**
     * Returns the <code>DocumentNode</code> this node is attached
     * to.
     *
     * @return the <code>DocumentNode</code> this node belongs to. The return
     *         value should never be null.
     */
    public DocumentNode getOwnerDocument() {
        return ownerDocument;
    }

    /**
     * By default, this node returns true if there are no children.
     * <code>ModelNode</code> implementations with expanded content
     * should override this method.
     *
     * @return true if this node has, or may have children or expanded
     *         children. A node may not know in advance if it has expanded
     *         children because in some cases, the computation of expanded
     *         content is done lazily. So this returns false if the node
     *         does not have children and will never have expanded content.
     */
    public boolean hasDescendants() {
        return getFirstChildNode() != null;
    }

    /**
     * Paints the input node and all its siblings into the  
     * <code>RenderGraphics</code>
     *
     * @param node the <code>ModelNode</code> to paint.
     * @param rg the <code>RenderGraphics</code> where the nodes should 
     *           be painted
     */
    static void paint(ModelNode node, final RenderGraphics rg) {
        while (node != null) {
            node.paint(rg);
            node = node.nextSibling;
        }
    }

    /**
     * @return the inherited value for the requested property.
     */
    protected final Object getInheritedPropertyState(final int propertyIndex) {
        if (parent == null) {
            return ownerDocument.getInitialPropertyState(propertyIndex);
        }

        return parent.getPropertyState(propertyIndex);
    }

    /**
     * @return the inherited value for the requested float property.
     */
    protected final float getInheritedFloatPropertyState(
            final int propertyIndex) {
        if (parent == null) {
            return ownerDocument.getInitialFloatPropertyState(propertyIndex);
        }

        return parent.getFloatPropertyState(propertyIndex);
    }

    /**
     * @return the inherited value for the requested packed property.
     */
    protected final int getInheritedPackedPropertyState(
            final int propertyIndex) {
        if (parent == null) {
            return ownerDocument.getInitialPackedPropertyState(propertyIndex);
        }

        return parent.getPackedPropertyState(propertyIndex);
    }

    /**
     * Returns the value for the given property.
     *
     * @return the value for the given property, null if the property is 
     *         unknown.
     */
    protected Object getPropertyState(final int propertyIndex) {
        return ownerDocument.getInitialPropertyState(propertyIndex);
    }
    
    /**
     * Returns the value for the given packed property.
     *
     * @return the value of the given property.
     */
    protected int getPackedPropertyState(final int propertyIndex) {
        return ownerDocument.getInitialPackedPropertyState(propertyIndex);
    }
    
    /**
     * Returns the value for the given float property.
     *
     * @return the value of the given property.
     */
    protected float getFloatPropertyState(final int propertyIndex) {
        return ownerDocument.getInitialFloatPropertyState(propertyIndex);
    }
    
    /**
     * Sets the computed value for the given property.
     *
     * @param propertyIndex the property index
     * @param propertyValue the computed value for the property.
     */
    protected void setPropertyState(final int propertyIndex,
                                    final Object propertyValue) {
    }

    /**
     * Sets the computed value for the given float property.
     *
     * @param propertyIndex the property index
     * @param propertyValue the computed value for the property.
     */
    protected void setFloatPropertyState(final int propertyIndex,
                                         final float propertyValue) {
    }

    /**
     * Sets the computed value for the given packed property.
     *
     * @param propertyIndex the property index
     * @param propertyValue the computed value for the property.
     */
    protected void setPackedPropertyState(final int propertyIndex,
                                          final int propertyValue) {
    }

    /**
     * Checks the state of the property value.
     *
     * @param propertyIndex the property index
     * @param propertyValue the computed value for the property.
     */
    protected boolean isPropertyState(final int propertyIndex,
                                      final Object propertyValue) {
        // By default, return true so that we don't bother setting an unknown 
        // property.
        return true;
    }

    /**
     * Checks the state of the float property value.
     *
     * @param propertyIndex the property index
     * @param propertyValue the computed value for the property.
     */
    protected boolean isFloatPropertyState(final int propertyIndex,
                                           final float propertyValue) {
        // By default, return true so that we don't bother setting an unknown 
        // property.
        return true;
    }

    /**
     * Checks the state of the packed property value.
     *
     * @param propertyIndex the property index
     * @param propertyValue the computed value for the property.
     */
    protected boolean isPackedPropertyState(final int propertyIndex,
                                            final int propertyValue) {
        // By default, return true so that we don't bother setting an unknown 
        // property.
        return true;
    }

    /**
     * Recomputes the given property's state given the new parent property.
     * By default, this simply propagates to children.
     *
     * @param propertyIndex index for the property whose value is changing.
     * @param parentPropertyValue the value that children of this node should 
     *        now inherit.
     * 
     */
    protected void recomputePropertyState(final int propertyIndex,
                                          final Object parentPropertyValue) {
        propagatePropertyState(propertyIndex, parentPropertyValue);
    }

    /**
     * Recomputes the given float property's state given the new parent
     * property. By default, this simply propagates to children.
     *
     * @param propertyIndex index for the property whose value is changing.
     * @param parentPropertyValue the value that children of this node should 
     *        now inherit.
     * 
     */
    protected void recomputeFloatPropertyState(
            final int propertyIndex,
            final float parentPropertyValue) {
        propagateFloatPropertyState(propertyIndex, parentPropertyValue);
    }

    /**
     * Recomputes the given packed property's state given the new parent 
     * property. By default, this simply propagates to children.
     *
     * @param propertyIndex index for the property whose value is changing.
     * @param parentPropertyValue the value that children of this node should 
     *        now inherit.
     * 
     */
    protected void recomputePackedPropertyState(final int propertyIndex,
                                                final int parentPropertyValue) {
        propagatePackedPropertyState(propertyIndex, parentPropertyValue);
    }

    /**
     * Recomputes all inherited properties and propages the recomputation to 
     * children.
     */
    void recomputeInheritedProperties() {
        ModelNode c = getFirstChildNode();
        while (c != null) {
            c.recomputeInheritedProperties();
            c = c.nextSibling;
        }
    }
    
    /**
     * Called when the canRenderState changes. If both values are zero, or
     * if both values are non zero, then children need to recompute their
     * canRenderState and propagate if need be.
     *
     * @param oldCanRenderState the old value for canRenderState.
     * @param newCanRenderState the new value for canRenderState
     */
    protected void propagateCanRenderState(final int oldCanRenderState,
                                           final int newCanRenderState) {
        if (oldCanRenderState != newCanRenderState
            &&
            ((oldCanRenderState == 0) || (newCanRenderState == 0))) {
            if (newCanRenderState == 0) {
                // Clear the parent can render state bit and propagate.
                // Propagate to regular children.
                ModelNode node = getFirstChildNode();
                boolean nodeDisplay = false;
                int nodeOldState = 0;
                while (node != null) {                    
                    nodeOldState = node.canRenderState;
                    node.canRenderState &= CAN_RENDER_PARENT_STATE_MASK;
                    node.propagateCanRenderState(nodeOldState, 
                                                 node.canRenderState);
                    node = node.nextSibling;
                }
                
                // Propagate to expanded children.
                node = getFirstComputedExpandedChild();
                while (node != null) {
                    nodeOldState = node.canRenderState;
                    node.canRenderState &= CAN_RENDER_PARENT_STATE_MASK;
                    node.propagateCanRenderState(nodeOldState, 
                                                 node.canRenderState);
                    node = node.nextSibling;
                }

            } else {
                // Set the parent can render state bit and propagate.
                // Propagate to regular children.
                ModelNode node = getFirstChildNode();
                boolean nodeDisplay = false;
                int nodeOldState = 0;
                while (node != null) {                    
                    nodeOldState = node.canRenderState;
                    node.canRenderState |= CAN_RENDER_PARENT_STATE_BIT;
                    node.propagateCanRenderState(nodeOldState, 
                                                 node.canRenderState);
                    node = node.nextSibling;
                }
                
                // Propagate to expanded children.
                node = getFirstComputedExpandedChild();
                while (node != null) {
                    nodeOldState = node.canRenderState;
                    node.canRenderState |= CAN_RENDER_PARENT_STATE_BIT;
                    node.propagateCanRenderState(nodeOldState, 
                                                 node.canRenderState);
                    node = node.nextSibling;
                }

            }
        }
    }

    /**
     * Called when the computed value of the given property has changed.
     * By default, propagate both to regular content and expanded content.
     *
     * @param propertyIndex index for the property whose value has changed.
     * @param parentPropertyValue the value that children of this node should 
     *        now inherit.
     */
    protected void propagatePropertyState(final int propertyIndex,
                                          final Object parentPropertyValue) {
        // Propagate to regular children.
        ModelNode node = getFirstChildNode();
        while (node != null) {
            node.recomputePropertyState(propertyIndex, parentPropertyValue);
            node = node.nextSibling;
        }

        // Propagate to expanded children.
        node = getFirstExpandedChild();
        while (node != null) {
            node.recomputePropertyState(propertyIndex, parentPropertyValue);
            node = node.nextSibling;
        }
    }

    /**
     * Called when the computed value of the given float property has changed.
     * By default, propagate both to regular content and expanded content.
     *
     * @param propertyIndex index for the property whose value has changed.
     * @param parentPropertyValue the value that children of this node should 
     *        now inherit.
     */
    protected void propagateFloatPropertyState(
            final int propertyIndex,
            final float parentPropertyValue) {
        // Propagate to regular children.
        ModelNode node = getFirstChildNode();
        while (node != null) {
            node.recomputeFloatPropertyState(propertyIndex, 
                                             parentPropertyValue);
            node = node.nextSibling;
        }

        // Propagate to expanded children.
        node = getFirstExpandedChild();
        while (node != null) {
            node.recomputeFloatPropertyState(propertyIndex, 
                                             parentPropertyValue);
            node = node.nextSibling;
        }
    }

    /**
     * Called when the computed value of the given packed property has changed.
     * By default, propagate both to regular content and expanded content.
     *
     * @param propertyIndex index for the property whose value has changed.
     * @param parentPropertyValue the value that children of this node should 
     *        now inherit.
     */
    protected void propagatePackedPropertyState(final int propertyIndex,
                                                final int parentPropertyValue) {
        // Propagate to regular children.
        ModelNode node = getFirstChildNode();
        while (node != null) {
            node.recomputePackedPropertyState(propertyIndex, 
                                              parentPropertyValue);
            node = node.nextSibling;
        }

        // Propagate to expanded children.
        node = getFirstExpandedChild();
        while (node != null) {
            node.recomputePackedPropertyState(propertyIndex, 
                                              parentPropertyValue);
            node = node.nextSibling;
        }
    }

    /**
     * @return this node's cached transform. 
     */
    public Transform getTransformState() {
        // By default, a ModelNode does not add any new transform, so its
        // cached transform state is the same as its parent node.
        if (parent != null) {
            return parent.getTransformState();
        }
        
        return IDENTITY;
    }

    /**
     * @return this node's cached inverse transform. 
     */
    Transform getInverseTransformState() {
        // By default, a ModelNode does not add any new transform, so its
        // cached inverse transform state is the same as its parent node.
        if (parent != null) {
            return parent.getInverseTransformState();
        }
        
        return IDENTITY;
    }

    /**
     * Recomputes the transform cache, if one exists. This should recursively
     * call recomputeTransformState on children node or expanded content, if
     * any.
     *
     * By default, because a ModelNode has no transform and no cached transform,
     * this only does a pass down.
     */
    protected void recomputeTransformState() {
        if (parent == null) {
            recomputeTransformState(new Transform(null));
        } else {
            recomputeTransformState(parent.getTransformState());
        }
    }

    /**
     * Recomputes the transform cache, if one exists. This should recursively
     * call recomputeTransformState on children node or expanded content, if
     * any.
     *
     * By default, because a ModelNode has no transform and no cached transform,
     * this only does a pass down.
     *
     * @param parentTransform the Transform applied to this node's parent.
     */
    protected void recomputeTransformState(final Transform parentTransform) {
        recomputeTransformState(getTransformState(), getFirstChildNode());
        recomputeTransformState(getTransformState(), getFirstExpandedChild());
        computeCanRenderTransformBit(getTransformState());
    }

    /**
     * Recomputes the transform cache of the input <code>ModelNode</code>
     * and all its siblings. Implementation helper.
     *
     * @param parentTransform the parent transform.
     * @param node first node whose transform cache should be 
     *        cleared.
     */
    void recomputeTransformState(final Transform parentTransform, 
                                 ModelNode node) {
        while (node != null) {
            node.recomputeTransformState(parentTransform);
            node = node.nextSibling;
        }
    }
    
    /**
     * @return true if this node is hooked to the document tree, i.e., if it top
     * most ancestor is the DocumentNode.
     */
    boolean inDocumentTree() {
        return parent != null && parent.inDocumentTree();
    }

    // JAVADOC COMMENT ELIDED
    public SVGMatrix getScreenCTM() {
        // The CTM is null if the element is not in the document tree.
        if (!inDocumentTree()) {
            return null;
        }

        return new Transform(getTransformState());
    }

    /**
     * Computes this node's rendering tile.
     *
     * @param tile the Tile instance whose bounds should be set.
     * @return the device space rendering tile.
     */
    protected void computeRenderingTile(final Tile tile) {
        // By default, there is no rendering and the tile is set to reflect
        // 'no rendering'
        tile.x = Integer.MIN_VALUE;
        tile.y = Integer.MIN_VALUE;
        tile.maxX = Integer.MIN_VALUE;
        tile.maxY = Integer.MIN_VALUE;
    }

    /**
     * @return the bounding box, in screen coordinate, which encompasses the
     * node's rendering. If this node's hasRendering method returns false, then
     * this method should return null. By default, this method returns null
     * because hasNodeRendering returns null by default.
     */
    protected Tile getRenderingTile() {
        return null;
    }

    /**
     * @return the tile which encompasses the node's last actual rendering. If
     * this node's hasRendering method returns false, then this method should
     * return null. By default, this method returns null because
     * hasNodeRendering returns null by default.
     */
    protected Tile getLastRenderedTile() {
        return null;
    }

    /**
     * After calling this method, getLastRenderedTile should always return null.
     */
    protected void clearLastRenderedTile() {
    }

    /**
     * Utility method to recycle a <code>Transform</code> instance when 
     * possible.
     *
     * @param tx the <code>Transform</code> to use to initialize the returned
     *        value.
     * @param workTx the candidate <code>Transform</code> instance which may be
     *        re-cycled. The instance can be recycled if it is different 
     *        (i.e., a different reference) from the input base transform 
     *        <code>tx</code>.
     */
    protected final Transform recycleTransform(final Transform tx,
                                               final Transform workTx) {
        if (workTx != null && workTx != tx) {
            // We recycle workTx
            workTx.setTransform(tx);
            return workTx;
        } else {
            // System.err.println(">>>>>>> creating new Transform instance...");
            // We cannot use workTx because it is null or 
            // it is the same as tx.
            return new Transform(tx);
        }        
    }
    
    /**
     * Should be overridden by derived classes to apply any node specific 
     * transform to the input transform.
     *
     * If the input transform is null and this node does not add any 
     * transform, the return value should be null.
     *
     * If the input transform is null and this node adds a transform, the
     * return value should be an object equal to this node's transform,
     * but not be the same instance.
     *
     * If the input transform is not null and this node does not add any
     * transform, the return value should be the same as the input transform.
     *
     * If the input transform is not null and this node adds a transform,
     * the return value should be a new (different) transform.
     *
     * The second parameter, workTxf can be re-used to hold the result 
     * only if it is different from tx. If it is referencing the same instance
     * as tx and a new Transform needs to be created, then it should not
     * be reused. 
     * IMPL NOTE : This is a coding style to recycle <code>Transform</code>
     * instances in animation loops.
     *
     * @param tx the <code>Transform</code> to apply additional node 
     *        transforms to. This may be null.
     * @param workTx a <code>Transform</code> which can be re-used if a 
     *        new <code>Transform</code> needs to be created and workTx
     *        is not the same instance as tx.
     * @return a transform with this node's transform added.
     */
    protected Transform appendTransform(final Transform tx, 
                                        final Transform workTx) {
        // Does nothing by default. See derived classes.
        return tx;
    }

    /**
     * @param bbox the bounding box to which this node's bounding box should be
     *        appended. That bounding box is in the target coordinate space. It 
     *        may be null, in which case this node should create a new one.
     * @param t the transform to apply from the node's coordinate space to the
     *        target coordinate space. May be null for the identity 
     *        transform.
     * @return the node's bounding box in the target coordinate space.
     */
    Box addBBox(Box bbox, final Transform t) {
        return bbox;
    }

    /**
     * Implementation helper. Adds the input shape's bounding box to the
     * input bounding box rect. If the shape is null, nothing is added.
     *
     * @param path the shape whose bounds, transformed through t, should be
     *        added to bbox.
     * @param t the transform to the target bounds space.
     * @param bbox the bounding box to which the shape's bounds should be
     *        added.
     * @return a rectangle that encompasses bbox and the path's bounding box,
     *         after transformation to the target coordinate space.
     */
    static Box addShapeBBox(Box bbox,
                            final Path path,
                            final Transform t) {
        if (path == null || path.getNumberOfSegments() == 0) {
            return bbox;
        }

        Box b = null;
        if (t == null) {
            b = path.getBounds();
        } else {
            b = path.getTransformedBounds(t);
        }

        return addBBox(bbox, 
                       b.getX(),
                       b.getY(),
                       b.getWidth(),
                       b.getHeight());
    }

    /**
     * Implementation helper. Adds the input rectangle to the input bounding box
     * rect.
     *
     * @param bbox the rectangle to which the new box should be added.
     * @param x the x-axis origin of the new rect to add
     * @param y the y-axis origin of the new rect to add
     * @param width the width of the rect to add
     * @param height the height of the rect to add
     * @return a bounding box that encompasses bbox and the (x, y, width, 
     *         height) rectangle.
     */
    static Box addBBox(Box bbox, 
                       final float x,
                       final float y,
                       final float width,
                       final float height) {
        if (bbox == null) {
            bbox = new Box(x, y, width, height);
            return bbox;
        }

        bbox.width = bbox.x + bbox.width;
        bbox.height = bbox.y + bbox.height;
        
        if (bbox.x > x) {
            bbox.x = x;
        }

        if (bbox.y > y) {
            bbox.y = y;
        }

        if (bbox.width < x + width) {
            bbox.width = x + width;
        }

        if (bbox.height < y + height) {
            bbox.height = y + height;
        }

        bbox.width -= bbox.x;
        bbox.height -= bbox.y;

        return bbox;
    }

    /**
     * Implementation helper. Computes the bounding box of the rectangle
     * transformed by the input matrix.
     *
     * @param bbox the bounding box to which this node's bounds should be 
     *        added.
     * @param x the rectangle's x-axis origin
     * @param y the rectangle's y-axis origin
     * @param width the rectangle's width
     * @param height the rectangle's height
     * @param m the matrix transforming to the target coordinate space
     *        into which the returned rectangle should be.
     * @return the matrix from the input rectangle's coordinate space to 
     *         the target coordinate space.
     */
    static Box addTransformedBBox(Box bbox,
                                  final float x,
                                  final float y,
                                  final float width,
                                  final float height,
                                  final Transform m) {
        
        if (m == null) {
            return addBBox(bbox, x, y, width, height);
        }

        float tx = (float) (m.getComponent(0) * x 
                            + m.getComponent(2) * y + m.getComponent(4));
        float ty = (float) (m.getComponent(1) * x 
                            + m.getComponent(3) * y + m.getComponent(5));

        float dx1 = (float) (m.getComponent(0) * width);
        float dy1 = (float) (m.getComponent(1) * width);

        float dx2 = (float) (m.getComponent(2) * height);
        float dy2 = (float) (m.getComponent(3) * height);

        if (dx1 < 0) {
            tx += dx1;
            dx1 = -dx1;
        }

        if (dx2 > 0) {
            dx1 += dx2;
        } else {
            dx1 -= dx2;
            tx += dx2;
        }

        if (dy1 < 0) {
            ty += dy1;
            dy1 = -dy1;
        }

        if (dy2 > 0) {
            dy1 += dy2;
        } else {
            dy1 -= dy2;
            ty += dy2;
        }

        return addBBox(bbox, tx, ty, dx1, dy1);
    }

    /**
     * @param bbox the bounding box to which this node's bounding box should be
     *        appended. That bounding box is in the target coordinate space. It 
     *        may be null, in which case this node should create a new one.
     * @param t the transform from the node coordinate system to the coordinate
     *        system into which the bounds should be computed.
     * @return the bounding box of this node, in the target coordinate space, 
     */
    Box addNodeBBox(final Box bbox, 
                    final Transform t) {
        return bbox;
    }

    /**
     * Returns the <code>ModelNode</code>, if any, hit by the
     * point at coordinate x/y. 
     * 
     * @param pt the x/y coordinate. Should never be null and be
     *        of size two. If not, the behavior is unspecified.
     *        The coordinates are in viewport space.
     * @return the <tt>ModelNode</tt> hit at the given point or null
     *         if none was hit.
     */
    public ModelNode nodeHitAt(final float[] pt) {
        return null;
    }

    /**
     * Returns the <code>ModelNode</code>, if any, hit by the
     * point at coordinate x/y, in viewport space. The input 
     * node, and all its siblings, are tested.
     *
     * @param node the first node on the set of nodes to test.
     * @param pt the point which is hit
     * @return the node hit at the given coordinate, or null if
     *         no node was hit.
     */
    protected final ModelNode nodeHitAt(ModelNode node, final float[] pt) {
        ModelNode hit = null;
        while (node != null) {
            hit = node.nodeHitAt(pt);
            if (hit != null) {
                return hit;
            }
            node = node.prevSibling;
        }
        return null;
    }

    /**
     * The node's URI base to use to resolve URI references
     * If a URI base value was set on this node, then that value
     * is returned. Otherwise, this method returns the parent's 
     * URI base. If there is not URI base on this node and if there
     * is not parent, then this method returns null.
     *
     * @return the node's URI base to use to resolve relative URI references.
     */
    protected String getURIBase() {
        if (parent != null) {
            return parent.getURIBase();
        }
        return null;
    }
    
    /**
     * Recomputes the requiredFeatures bit in the canRenderState bit mask.
     *
     * @param requiredFeatures if ConditionalProcessing.checkFeatures returns
     * true, the bit is cleared. Otherwise, the bit is set.
     */
    final void computeCanRenderRequiredFeaturesBit(
            final String[] requiredFeatures) {
        int oldCanRenderState = canRenderState;
        if (requiredFeatures == null
            ||
            ConditionalProcessing.checkFeatures(requiredFeatures)) {
            canRenderState &= CAN_RENDER_REQUIRED_FEATURES_MASK;
        } else {
            canRenderState |= CAN_RENDER_REQUIRED_FEATURES_BIT;
        }
        
        propagateCanRenderState(oldCanRenderState, canRenderState);
    }

    /**
     * Recomputes the requiredExtensions bit in the canRenderState bit mask.
     *
     * @param requiredExtensions if ConditionalProcessing.checkExtensions 
     *        returns true, the bit is cleared. Otherwise, the bit is set.
     */
    final void computeCanRenderRequiredExtensionsBit(
            final String[] requiredExtensions) {
        int oldCanRenderState = canRenderState;
        if (requiredExtensions == null
            ||
            ConditionalProcessing.checkExtensions(requiredExtensions)) {
            canRenderState &= CAN_RENDER_REQUIRED_EXTENSIONS_MASK;
        } else {
            canRenderState |= CAN_RENDER_REQUIRED_EXTENSIONS_BIT;
        }
        
        propagateCanRenderState(oldCanRenderState, canRenderState);
    }

    /**
     * Recomputes the systemLanguage bit in the canRenderState bit mask.
     *
     * @param systemLanguage if ConditionalProcessing.checkLanguage returns
     * true, the bit is cleared. Otherwise, the bit is set.
     */
    final void computeCanRenderSystemLanguageBit(
            final String[] systemLanguage) {
        int oldCanRenderState = canRenderState;
        if (systemLanguage == null
            ||
            ConditionalProcessing.checkLanguage(systemLanguage)) {
            canRenderState &= CAN_RENDER_SYSTEM_LANGUAGE_MASK;
        } else {
            canRenderState |= CAN_RENDER_SYSTEM_LANGUAGE_BIT;
        }
        
        propagateCanRenderState(oldCanRenderState, canRenderState);
    }

    /**
     * Recomputes the non-invertible transform bit in the canRenderState 
     * bit mask.
     *
     * @param transform the transform which drives the non-invertible 
     * transform bit. If non invertible, the bit is set. Otherwise, the 
     * bit is cleared.
     */
    final void computeCanRenderTransformBit(final Transform transform) {
        int oldCanRenderState = canRenderState;
        if (transform == null || transform.isInvertible()) {
            canRenderState &= CAN_RENDER_NON_INVERTIBLE_TXF_MASK;
        } else {
            canRenderState |= CAN_RENDER_NON_INVERTIBLE_TXF_BIT;
        }
        
        propagateCanRenderState(oldCanRenderState, canRenderState);
    }

    /**
     * Recomputes the empty viewBox bit in the canRenderState 
     * bit mask.
     *
     * @param viewBox the viewBox value which drives the empty viewBox
     * bit. If null or if positive width/height, the bit is cleared. 
     * Otherwise, the bit is set.
     */
    final void computeCanRenderEmptyViewBoxBit(final float[][] viewBox) {
        int oldCanRenderState = canRenderState;
        if (viewBox == null || (viewBox[1][0] > 0 && viewBox[2][0] > 0)) {
            canRenderState &= CAN_RENDER_EMPTY_VIEWBOX_MASK;
        } else {
            canRenderState |= CAN_RENDER_EMPTY_VIEWBOX_BIT;
        }
        
        propagateCanRenderState(oldCanRenderState, canRenderState);
    }

    /**
     * Recomputes the display bit in the canRenderState bit mask.
     *
     * @param display the display value. If true, the bit is cleared. If
     * false, the bit is set.
     */
    final void computeCanRenderDisplayBit(final boolean display) {
        int oldCanRenderState = canRenderState;
        if (display) {
            canRenderState &= CAN_RENDER_DISPLAY_MASK;
        } else {
            canRenderState |= CAN_RENDER_DISPLAY_BIT;
        }
        
        propagateCanRenderState(oldCanRenderState, canRenderState);
    }
    
    /**
     * Recomputes the zero-width bit in the canRenderState bit mask.
     *
     * @param width the new width value. If zero or negative, the bit
     * is set. Otherwise, the bit is cleared.
     */
    final void computeCanRenderWidthBit(final float width) {
        int oldCanRenderState = canRenderState;
        if (width > 0) {
            canRenderState &= CAN_RENDER_ZERO_WIDTH_MASK;
        } else {
            canRenderState |= CAN_RENDER_ZERO_WIDTH_BIT;
        }
        
        propagateCanRenderState(oldCanRenderState, canRenderState);
    }

    /**
     * Recomputes the zero-height bit in the canRenderState bit mask.
     *
     * @param height the new height value. If zero or negative, the bit
     * is set. Otherwise, the bit is cleared.
     */
    final void computeCanRenderHeightBit(final float height) {
        int oldCanRenderState = canRenderState;
        if (height > 0) {
            canRenderState &= CAN_RENDER_ZERO_HEIGHT_MASK;
        } else {
            canRenderState |= CAN_RENDER_ZERO_HEIGHT_BIT;
        }
        
        propagateCanRenderState(oldCanRenderState, canRenderState);
    }

    /**
     * Recomputes the zero-fontSize bit in the canRenderState bit mask.
     *
     * @param fontSize the new fontSize value. If zero or negative, the bit
     * is set. Otherwise, the bit is cleared.
     */
    final void computeCanRenderFontSizeBit(final float fontSize) {
        int oldCanRenderState = canRenderState;
        if (fontSize > 0) {
            canRenderState &= CAN_RENDER_ZERO_FONT_SIZE_MASK;
        } else {
            canRenderState |= CAN_RENDER_ZERO_FONT_SIZE_BIT;
        }
        
        propagateCanRenderState(oldCanRenderState, canRenderState);
    }

    /**
     * Recomputes the empty path bit in the canRenderStaet bit mask.
     *
     * @param path the value that determines the bit state. If null or
     * if no segments, the bit is set. Otherwise, the bit is cleared.
     */
    final void computeCanRenderEmptyPathBit(final Path path) {
        int oldCanRenderState = canRenderState;
        if (path == null || path.getNumberOfSegments() == 0) {
            canRenderState |= CAN_RENDER_EMPTY_PATH_BIT;
        } else {
            canRenderState &= CAN_RENDER_EMPTY_PATH_MASK;
        }
        
        propagateCanRenderState(oldCanRenderState, canRenderState);
    }

    /**
     * By default, there is no node rendering.
     *
     * @return true if the node has some rendering of its own,
     *         i.e., rendering beyond what its children or
     *         expanded content render.
     */
    protected boolean hasNodeRendering() {
        return false;
    }

    /**
     * Clears the text layouts, if any exist. This is typically
     * called when the font selection has changed and nodes such
     * as <code>Text</code> should recompute their layouts.
     * This should recursively call clearLayouts on children 
     * node or expanded content, if any.
     */
    protected abstract void clearLayouts();

    /**
     * Clears the text layouts in the input node and all its
     * siblings. Implementation helper.
     *
     * @param node the first node whose text layouts should be cleared.
     */
    void clearLayouts(ModelNode node) {
        while (node != null) {
            node.clearLayouts();
            node = node.nextSibling;
        }
    }
    
    /**
     * @return true if this node is considered loaded. This is used
     *         in support of progressive rendering.
     */
    public final boolean isLoaded() {
        return loaded;
    }
    
    /**
     * @param isLoaded the new loaded state
     */
    public void setLoaded(final boolean isLoaded) {
        loaded = isLoaded;
    }

    /**
     * @return true if the node needs to be fully loaded before it
     *         can be painted
     */
    boolean getPaintNeedsLoad() {
        return false;
    }

    /**
     * Used to notify the <code>UpdateListener</code>, if any, of
     * an upcoming node modification
     *
     */
    protected void modifyingNode() {
        UpdateListener updateListener = getUpdateListener();
        if (updateListener != null) {
            updateListener.modifyingNode(this);
        }
    }

    /**
     * Used to notify the <code>UpdateListener</code>, if any, of
     * an  change in a node's rendering
     *
     */
    protected void modifyingNodeRendering() {
        UpdateListener updateListener = getUpdateListener();
        if (updateListener != null) {
            updateListener.modifyingNodeRendering(this);
        }
    }

    /**
     * Used to notify the <code>UpdateListener</code>, if any, of
     * a completed node modification
     *
     */
    protected void modifiedNode() {
        UpdateListener updateListener = getUpdateListener();
        if (updateListener != null) {
            updateListener.modifiedNode(this);
        }
    }

    /**
     * Used to notify the <code>UpdateListener</code>, if any, of
     * a node insertion
     *
     * @param node the node which was just inserted
     */
    protected static void nodeInserted(final ModelNode node) {
        UpdateListener updateListener = node.getUpdateListener();
        if (updateListener != null) {
            updateListener.nodeInserted(node);
        }
    }

    // JAVADOC COMMENT ELIDED
    public void addEventListener(final String type, 
                                 final EventListener listener, 
                                 final boolean useCapture) throws DOMException {
        ownerDocument.eventSupport
            .addEventListener(this, 
                              type, 
                              useCapture 
                              ? 
                              EventSupport.CAPTURE_PHASE : 
                              EventSupport.BUBBLE_PHASE,
                              listener);
        
    }

    // JAVADOC COMMENT ELIDED
    public void removeEventListener(
            final String type, 
            final EventListener listener, 
            final boolean useCapture) throws DOMException {
        ownerDocument.eventSupport
            .removeEventListener(this, 
                                 type, 
                                 useCapture 
                                 ? 
                                 EventSupport.CAPTURE_PHASE : 
                                 EventSupport.BUBBLE_PHASE,
                                 listener);
    }

    /**
     * Delegates to the associated <code>EventSupport</code> class.
     *
     * @param evt the event to dispatch
     */
    public void dispatchEvent(final ModelEvent evt) {
        ownerDocument.eventSupport.dispatchEvent(evt);
    }

    // =========================================================================
    // Methods with default implementations which are typically overridden in
    // extension.
    // =========================================================================

    /**
     * To be overriddent by derived classes, such as TimedElementNode,
     * if they need to perform special operations when hooked into the 
     * document tree.
     */
    void onHookedInDocumentTree() {
        // Clear the 'in document tree' can render bit
        int oldCanRenderState = canRenderState;
        canRenderState &= CAN_RENDER_IN_DOCUMENT_TREE_MASK;
        
        // Set the parent's canRenderState bit
        if (parent.canRenderState == 0) {
            canRenderState &= CAN_RENDER_PARENT_STATE_MASK;
        } else {
            canRenderState |= CAN_RENDER_PARENT_STATE_BIT;
        }
        propagateCanRenderState(oldCanRenderState, canRenderState);
        
        recomputeTransformState();
        recomputeInheritedProperties();
    }

    /**
     * To be overriddent by derived classes, such as TimedElementNode,
     * if they need to perform special operations when unhooked from the 
     * document tree.
     */
    void onUnhookedFromDocumentTree() {
        // Set the 'in document tree' can render bit
        int oldCanRenderState = canRenderState;
        canRenderState |= CAN_RENDER_IN_DOCUMENT_TREE_BIT;
        
        // Clear the parent's canRenderState bit
        canRenderState &= CAN_RENDER_PARENT_STATE_MASK;
        
        propagateCanRenderState(oldCanRenderState, canRenderState);

        recomputeTransformState();
        recomputeInheritedProperties();        
    }

    /**
     * Paints this node into the input <code>RenderGraphics</code>.
     *
     * @param rg the <tt>RenderGraphics</tt> where the node should paint itself
     */
    public void paint(final RenderGraphics rg) {
        // By default, do not paint anything.
    }

    /**
     * To be overridden by nodes which may have a rendering if they need
     * to do something specific (e.g., notify their RenderingManager) when
     * they have been rendered.
     */
    protected void nodeRendered() {
        // By default, do nothing.
    }


    // =========================================================================
    // Abstract method to override in extensions.
    // =========================================================================
 
    /**
     * Utility method. Unhooks the children.
     */
    protected abstract void unhookChildrenQuiet();

    /**
     * Utility method. Unhooks the expanded content.
     */
    protected abstract void unhookExpandedQuiet();

    /**
     * @return a reference to the node's first child, or null if there
     *         are no children.
     */
    public abstract ModelNode getFirstChildNode();

    /**
     * @return a reference to the node's last child, or null if there
     *         are no children.
     */
    public abstract ModelNode getLastChildNode();

    /**
     * Some node types (such as <code>ElementNodeProxy</code>) have
     * expanded children that they compute in some specific
     * way depending on the implementation.     
     *
     * @return a reference to the node's first expanded child, or null if there
     *         are no expanded children. This forces the computation of expanded
     *         content if needed.
     */
    abstract ModelNode getFirstExpandedChild();

    /**
     * Some node types (such as <code>ElementNodeProxy</code>) have
     * expanded children that they compute in some specific
     * way depending on the implementation.     
     *
     * @return a reference to the node's first expanded child, or null if there
     *         are no expanded children. This forces the computation of expanded
     *         content if needed.
     */
    abstract ModelNode getFirstComputedExpandedChild();

    /**
     * Some node types (such as <code>ElementNodeProxy</code>) have
     * expanded children that they compute in some specific
     * way depending on the implementation.     
     *
     * @return a reference to the node's last expanded child, or null if there
     *         are no expanded children. This forces the computation of expanded
     *         content if needed.
     */
    abstract ModelNode getLastExpandedChild();


}