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

CompositeNode.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.Node;
import org.w3c.dom.DOMException;

/**
 * <code>CompositeNode</code> models <code>ModelNodes</code which 
 * can have children {@link ElementNode ElementNodes}.
 *
 * <p>A <code>CompositeNode</code> can have either <code>ModelNode</code>
 * children and text content children (see the 
 * {@link #appendTextChild appendTextChild} method).
 *
 * @version $Id: CompositeNode.java,v 1.9 2006/06/29 10:47:30 ln156897 Exp $
 */
public abstract class CompositeNode extends ModelNode {
    /**
     * The first child
     */
    protected ElementNode firstChild;

    /**
     * The last child
     */
    protected ElementNode lastChild;


    /**
     * 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(firstChild);
    }
    
    /**
     * @return this node's first child.
     */
    public ModelNode getFirstChildNode() {
        return firstChild;
    }

    /**
     * @return this node's last child.
     */
    public ModelNode getLastChildNode() {
        return lastChild;
    }

    /**
     * Utility method. Unhooks the children.
     */
    protected void unhookChildrenQuiet() {
        unhookQuiet(firstChild);
        firstChild = null;
        lastChild = null;
    }

    /**
     * Appends an element at the end of the list
     *
     * @param element the node to add to this <tt>CompositeNode</tt>
     * @throws NullPointerException if the input argument is null.
     */
    public void add(final ElementNode element) {
        if (element == null) {
            throw new NullPointerException();
        }

        element.preValidate();

        if (firstChild == null) {
            firstChild = element;
            lastChild = element;
            element.nextSibling = null;
            element.prevSibling = null;
        } else {
            lastChild.nextSibling = element;
            element.nextSibling = null;
            element.prevSibling = lastChild;
            lastChild = element;
            
        }

        // An insertion event is triggered from the setParent call.
        element.setParent(this);
    }

    // =========================================================================
    // Node interface implementation.
    // =========================================================================

    /**
     * @return the namespace URI of the Node.
     *
     * @throws SecurityException if the application does not have the necessary
     * privilege rights to access this (SVG) content.
     */
    public abstract String getNamespaceURI();

    /**
     * @return unprefixed node name. For an SVGElement, this returns the tag
     * name without a prefix.  In case of the Document node, string
     * <code>#document</code> is returned.
     * @throws SecurityException if the application does not have the necessary
     * privilege rights to access this (SVG) content.
     */
    public abstract String getLocalName();

    /**
     * Returns the parent <code>Node</code> of this <code>Node</code>.
     *
     * @return the parent node or null if there is no parent (i.e. if a node has
     * just been created and not yet added to the tree, or if it has been
     * removed from the tree, this is null).
     * @throws SecurityException if the application does not have the necessary
     * privilege rights to access this (SVG) content.
     */
    public abstract Node getParentNode();

    // JAVADOC COMMENT ELIDED
    public Node appendChild(final Node newChild) throws DOMException {
        return insertBefore(newChild, null);
    }

    // JAVADOC COMMENT ELIDED
    public Node removeChild(final Node oldChild) throws DOMException {
        // First, check if this is indeed a child of this node.
        if (!isChild(oldChild)) {
            if (oldChild == null) {
                throw new NullPointerException();
            }

            throw new DOMException(
                    DOMException.NOT_FOUND_ERR, 
                    Messages.formatMessage(Messages.ERROR_NOT_A_CHILD, null));
        }

        // Now, check if this is a supported operation
        if (!isRemoveChildSupported()) {
            throw new DOMException
                (DOMException.NOT_SUPPORTED_ERR,
                 Messages.formatMessage(
                        Messages.ERROR_REMOVE_CHILD_NOT_SUPPORTED,
                        new String[] {
                            getLocalName(),
                            getNamespaceURI()
                        }));
        }

        // Check if the removed child, or one of its descendants, has
        // an identifier.
        ElementNode oldNode = (ElementNode) oldChild;
        if (isIdBranch(oldNode)) {
            throw new DOMException(
                    DOMException.INVALID_ACCESS_ERR, 
                    Messages.formatMessage(
                        Messages.ERROR_CANNOT_REMOVE_NODE_WITH_ID,
                        null));
        }

        if (oldNode.nextSibling != null) {
            oldNode.nextSibling.prevSibling = oldNode.prevSibling;
        } else {
            lastChild = (ElementNode) oldNode.prevSibling;
        }

        if (oldNode.prevSibling != null) {
            oldNode.prevSibling.nextSibling = oldNode.nextSibling;
        } else {
            firstChild = (ElementNode) oldNode.nextSibling;
        }

        oldNode.nextSibling = null;
        oldNode.prevSibling = null;
        oldNode.setParent(null);
        
        return oldChild;
    }

    // JAVADOC COMMENT ELIDED
    public Node insertBefore(final Node newChild, 
                             final Node refChild) throws DOMException {
        if (newChild == null) {
            throw new NullPointerException();
        }

        if (newChild == ownerDocument) {
            throw new DOMException
                (DOMException.HIERARCHY_REQUEST_ERR, 
                 Messages.formatMessage
                 (Messages.ERROR_CANNOT_INSERT_DOCUMENT_NODE,
                                        null));
        }

        // The DocumentNode class can only create ElementNode instances.  If we
        // are dealing with an object which is not an ElementNode instance, we
        // know we are dealing with something in the wrong document.
        if (!(newChild instanceof ElementNode)) {
            throw new DOMException
                (DOMException.WRONG_DOCUMENT_ERR, 
                 Messages.formatMessage
                 (Messages.ERROR_CANNOT_INSERT_FROM_OTHER_DOCUMENT,
                                        null));
        }

        ElementNode newNode = (ElementNode) newChild;
        
        // Check if newNode belongs to the same document.
        if (newNode.ownerDocument != ownerDocument) {
            throw new DOMException
                (DOMException.WRONG_DOCUMENT_ERR,
                 Messages.formatMessage
                 (Messages.ERROR_CANNOT_INSERT_FROM_OTHER_DOCUMENT,
                                        null));
        }

        // Check if the newNode is of a type allowed by this Node
        // implementation.
        if (!isAllowedChild(newNode)) {
            throw new DOMException
                (DOMException.HIERARCHY_REQUEST_ERR, 
                 Messages.formatMessage
                 (Messages.ERROR_CHILD_NOT_ALLOWED,
                  new String[] {newNode.getLocalName(),
                                newNode.getNamespaceURI(),
                                getLocalName(),
                                getNamespaceURI()}));
        }

        // Check if the newNode is one of this node's ancestor, or the 
        // node itself.
        if (isAncestor(newNode)) {
            throw new DOMException
                (DOMException.HIERARCHY_REQUEST_ERR, 
                 Messages.formatMessage
                 (Messages.ERROR_INSERTING_ANCESTOR,
                  null));
        }

        // Check if this node is of type document and already has a child.
        if (this == ownerDocument) {
            if (firstChild != null) {
                throw new DOMException
                    (DOMException.HIERARCHY_REQUEST_ERR,
                     Messages.formatMessage
                     (Messages.ERROR_INSERTING_UNDER_DOCUMENT, null));
            }
        }

        // Check refChild if refChild is not null
        if (refChild != null) {
            if (!isChild(refChild)) {
                throw new DOMException
                    (DOMException.NOT_FOUND_ERR,
                     Messages.formatMessage
                     (Messages.ERROR_REF_NODE_NOT_A_CHILD, null));
            }
        }

        // Check that newChild is _not_ a child of the DocumentNode
        if (newNode.parent == ownerDocument) {
            throw new DOMException
                (DOMException.NOT_SUPPORTED_ERR, 
                 Messages.formatMessage
                 (Messages.ERROR_INSERTING_DOCUMENT_ELEMENT,
                  null));
        }

        // If the inserted node already has a parent, remove the node
        // from its current parent.
        if (newNode.parent != null) {
            ((CompositeNode) newNode.parent).removeChild(newNode);
        }

        if (refChild == null) {
	    // Performs the Use xlink:href attribute validation
            add(newNode);
        } else {
            // Because refChild's parent is this node, we know it has to 
            // be an ElementNode. So the following cast is safe. See 
            // isChild call above.
            ElementNode refNode = (ElementNode) refChild;
            newNode.prevSibling = refNode.prevSibling;
            newNode.nextSibling = refNode;
            if (refNode.prevSibling != null) {
                // refNode is _not_ the first node
                refNode.prevSibling.nextSibling = newNode;
            } else {
                // refNode _is_ the first node
                firstChild = newNode;
            }
            refNode.prevSibling = newNode;
            newNode.nextSibling = refNode;

            // An insertion event is triggered from the setParent call
            newNode.setParent(this);
        }

        return newChild;
    }

    /**
     * Implementation helper. By default, we only disallow SVG nodes under
     * all nodes except DocumentNode.
     *
     * @param node the candidate child node.
     * @return true if the input node can be inserted under this CompositeNode
     */
    protected boolean isAllowedChild(final ElementNode node) {
        if (node instanceof SVG) {
            return false;
        }
        return true;
    }

    /**
     * Implementation helper.
     *
     * @param node the node which may be an ancestor of this CompositeNode.
     * @return true if the input node is this node or if it is one of its 
     *         ancestors.
     */
    final boolean isAncestor(final ElementNode node) {
        if (node == this) {
            return true;
        } else if (parent != null) {
            return ((CompositeNode) parent).isAncestor(node);
        } else {
            return false;
        }
    }

    /**
     * Implementation helper.
     *
     * @param node the node which may be a child of this composite node.
     * @return true if the input node is one of this composite's children.
     */
    final boolean isChild(final Node node) {
        ElementNode c = firstChild;
        while (c != null) {
            if (c == node) {
                return true;
            } 
            c = (ElementNode) c.nextSibling;
        }
        return false;
    }

    /**
     * @return true if this node supports removing children. By default, this
     * returns true.
     */
    protected boolean isRemoveChildSupported() {
        return true;
    }

    /**
     * @param node the root of the branch to check for ids. Should not be null.
     * @return true if the input node, or any of its descendant, has an id.
     */
    protected static boolean isIdBranch(final ElementNode node) {
        if (node.id != null) {
            return true;
        } 

        ElementNode c = node.firstChild;
        while (c != null) {
            if (isIdBranch(c)) {
                return true;
            } else {
                c = (ElementNode) c.nextSibling;
            }
        }
        
        return false;
    }

    /**
     * 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();

        nodeHookedInDocumentTree();

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

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

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

        nodeUnhookedFromDocumentTree();

        ModelNode c = getFirstExpandedChild();
        while (c != null) {
            c.onUnhookedFromDocumentTree();
            c = c.nextSibling;
        }

        c = getFirstChildNode();
        while (c != null) {
            c.onUnhookedFromDocumentTree();
            c = c.nextSibling;
        }
    }

    /**
     * To be overriddent by derived classes, such as TimedElementNode,
     * if they need to do special operations when hooked into the 
     * document tree.
     */
    void nodeHookedInDocumentTree() {
    }

    /**
     * To be overriddent by derived classes, such as TimedElementNode,
     * if they need to do special operations when unhooked from the 
     * document tree.
     */
    void nodeUnhookedFromDocumentTree() {
    }

}