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

ElementNodeProxy.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.RenderGraphics;

import com.sun.perseus.j2d.Box;
import com.sun.perseus.j2d.Tile;
import com.sun.perseus.j2d.Transform;

/**
 * A <code>ElementNodeProxy</code> delegates its rendering to a 
 * proxied <code>ElementNode</code> object. This class is used to 
 * model expanded content. SVG markup defines content that needs to 
 * be expanded before it is rendered. For example, the <code><use></code>
 * element is expanded with <code>ElementNodeProxy</code> children 
 * to represent the expansion expressed by the element. 
 *
 * @version $Id: ElementNodeProxy.java,v 1.14 2006/06/29 10:47:30 ln156897 Exp $
 */
public  class ElementNodeProxy extends ModelNode {
    /**
     * The proxied ModelNode
     */
    protected ElementNode proxied;

    /**
     * Controls whether content is expanded or not
     */
    protected boolean expanded;

    /**
     * The next proxy, if any.
     */
    protected ElementNodeProxy nextProxy;

    /**
     * The previous proxy, if any.
     */
    protected ElementNodeProxy prevProxy;

    /**
     * The first expanded child, if content has been expanded.
     */
    protected ModelNode firstExpandedChild;

    /**
     * The last expanded child, if content has been expanded
     */
    protected ModelNode lastExpandedChild;

    /**
     * @param proxiedNode <tt>ElementNode</tt> to proxy
     */
    protected ElementNodeProxy(final ElementNode proxiedNode) {
        super();
        this.proxied = proxiedNode;
        proxiedNode.addProxy(this);
        ownerDocument = proxiedNode.ownerDocument;
    }

    /**
     * When a CompositeNode is hooked into the document tree, by default,
     * it notifies its children and calls its own nodeHookedInDocumentTree
     * method.
     */
    final void onHookedInDocumentTree() {
        super.onHookedInDocumentTree();

        ModelNode c = firstExpandedChild;
        while (c != null) {
            c.onHookedInDocumentTree();
            c = c.nextSibling;
        }
    }

    /**
     * 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 void clearLayouts() {
        clearLayouts(firstExpandedChild);
    }

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

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

    /**
     * Does nothing, as there are no children.
     */
    protected void unhookChildrenQuiet() {
    }

    /**
     * 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.
     */
    public ModelNode getFirstExpandedChild() {
        expand();
        return firstExpandedChild;
    }

    /**
     * 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. 
     */
    public ModelNode getFirstComputedExpandedChild() {
        return firstExpandedChild;
    }

    /**
     * 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.
     */
    public ModelNode getLastExpandedChild() {
        expand();
        return lastExpandedChild;
    }

    /**
     * Utility method. Unhooks the expanded content.
     */
    protected void unhookExpandedQuiet() {
        unhookQuiet(firstExpandedChild);
        firstExpandedChild = null;
        lastExpandedChild = null;
        expanded = false;
    }

    /**
     * @return true if the ElementNodeProxy has children or if it has
     *         expanded content.
     */
    public boolean hasDescendants() {
        if (super.hasDescendants()) {
            return true;
        } else {
            expand();
            return firstExpandedChild != null;
        }
            
    }

    /**
     * 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 proxied.proxyNodeHitAt(pt, this);
    }

    /**
     * Appends the proxied node's transform, if there is a proxied node.
     *
     * @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(Transform tx,
                                        final Transform workTx) {
        if (proxied != null) {
            return proxied.appendTransform(tx, workTx);
        }

        return tx;
    }

    /**
     * @return a reference to the proxied <code>ModelNode</code>
     */
    public ElementNode getProxied() {
        return proxied;
    }

    /**
     * Modifies the node proxied by this proxy.
     *
     * @param newProxied this node's new proxied node
     */
    protected void setProxied(final ElementNode newProxied) {
        if (this.proxied == newProxied) {
            return;
        }

        // We call modifyingNode because this node's rendering
        // may change as a result of changing the proxy.
        modifyingNode();

        if (this.proxied != null) {
            this.proxied.removeProxy(this);
        }

        unhookQuiet(firstExpandedChild);

        this.proxied = newProxied;
        firstExpandedChild = null;
        lastExpandedChild = null;
        expanded = false;

        if (newProxied != null) {
            newProxied.addProxy(this);
        }

        // Initialize the requiredFeatures/requiredExtensions/systemLanguage
        // bits to the same value as the proxied node.
        int oldCanRenderState = canRenderState;
        canRenderState &= CAN_RENDER_REQUIRED_FEATURES_MASK;
        canRenderState &= CAN_RENDER_REQUIRED_EXTENSIONS_MASK;
        canRenderState &= CAN_RENDER_SYSTEM_LANGUAGE_MASK;
        
        if (newProxied != null) {
            canRenderState |= (newProxied.canRenderState 
                                & CAN_RENDER_REQUIRED_FEATURES_BIT);
            canRenderState |= (newProxied.canRenderState 
                                & CAN_RENDER_REQUIRED_EXTENSIONS_BIT);
            canRenderState |= (newProxied.canRenderState 
                                & CAN_RENDER_SYSTEM_LANGUAGE_BIT);        
        }

        propagateCanRenderState(oldCanRenderState, canRenderState);
        
        // We call modifiedNode because this node's rendering
        // may have changed as a result of changing the proxy.
        modifiedNode();
    }

    /**
     * Expand the content. This is done lazilly
     */
    protected void expand() {
        if (expanded) {
            return;
        }

        // Expand proxy tree. 
        //
        // NOTE: This implements the SVGElementInstance tree structure,
        // as described in the SVG 1.1 specification, section 5.17.
        //
        if (proxied != null) {
            firstExpandedChild = computeProxiesChain(
                    (ElementNode) proxied.getFirstChildNode());

            if (firstExpandedChild != null) {
                lastExpandedChild
                    = firstExpandedChild.prevSibling;

                // The prevSibling was set as a way to return both the first
                // and last element of the chain. We need to break the circular
                // reference.
                firstExpandedChild.prevSibling = null;
            } else {
                firstExpandedChild = null;
            }
        } 

        expanded = true;
    }

    /**
     * Proxied nodes should call this method when they are being modified.
     */
    public void modifyingProxied() {
        modifyingNode();
    }

    /**
     * Proxied nodes should call this method when they have been modified.
     */
    public void modifiedProxied() {
        modifiedNode();
    }

    /**
     * Proxied nodes should call this method they got a new added child.
     * This is an optimization of the more generic insertion
     * case. Appending a child is a recursive process which avoids
     * recomputing all the proxies recursively (a proxy referencing a proxy
     * referencing a proxy .... referencing a composite on which nodes are
     * appended). It might be advantageous to consider doing a generic 
     * optimized insertion into the children list.
     *
     * @param child the <code>ElementNode</code> which was just added under
     *        the proxied node.
     * @see #proxiedExpandedChildAdded
     */
    public void proxiedChildAdded(final ElementNode child) {
        // If this node is not expanded at all, expand it now
        if (!expanded) {
            expand();
        } else {
            ElementNodeProxy newChildProxy = child.buildProxy();

            if (firstExpandedChild == null) {
                firstExpandedChild = newChildProxy;
                lastExpandedChild = newChildProxy;
                newChildProxy.nextSibling = null;
                newChildProxy.prevSibling = null;
            } else {
                lastExpandedChild.nextSibling = newChildProxy;
                newChildProxy.nextSibling = null;
                newChildProxy.prevSibling = lastExpandedChild;
                lastExpandedChild = newChildProxy;
            }

            newChildProxy.setParentQuiet(this);
            newChildProxy.expand();
            nodeInserted(newChildProxy);
        }
    }

    /**
     * Implementation helper: computes the set of chained proxies
     * for the input node and return the head of the chain.
     *
     * @param proxiedChild the <code>ElementNode</code> for which the chain of 
     *        proxies should be computed.
     *
     * @return the head of the proxies chaing. <b>NOTE</b> that the prevSibling
     *         is set on the head to point to the last element of the chain,
     *         creating a circular list for the 'prevSibling' reference. This
     *         circular reference should be broken by the code using this 
     *         method.
     */
    protected ElementNodeProxy computeProxiesChain(ElementNode proxiedChild) {
        ElementNodeProxy firstProxy = null;
        ElementNodeProxy proxy = null;
        ElementNodeProxy previousProxy = null;
        while (proxiedChild != null) {
            proxy = proxiedChild.buildProxy();
            proxy.setParentQuiet(this);
            proxy.expand();
            if (previousProxy == null) {
                firstProxy = proxy;
            } else {
                previousProxy.nextSibling = proxy;
                proxy.prevSibling = previousProxy;
            }
            firstProxy.prevSibling = proxy;
            previousProxy = proxy;
            proxiedChild = (ElementNode) proxiedChild.nextSibling;
        }
        return firstProxy;
    }

}