    001// Copyright 2006, 2007, 2008, 2009, 2010 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007//     http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.dom;
    016
    017import java.io.PrintWriter;
    018import java.util.Map;
    019
    020import org.apache.tapestry5.internal.util.PrintOutCollector;
    021
    022/**
    023 * A node within the DOM.
    024 */
    025public abstract class Node
    026{
    027    Element container;
    028
    029    /**
    030     * Next node within containing element.
    031     */
    032    Node nextSibling;
    033
    034    /**
    035     * Creates a new node, setting its container to the provided value. Container may also be null, but that is only
    036     * used for Document nodes (the topmost node of a DOM).
    037     *
    038     * @param container element containing this node
    039     */
    040    protected Node(Element container)
    041    {
    042        this.container = container;
    043    }
    044
    045    /**
    046     * Returns the containing {@link org.apache.tapestry5.dom.Element} for this node, or null if this node is the root
    047     * element of the document.
    048     */
    049    public Element getContainer()
    050    {
    051        return container;
    052    }
    053
    054    public Document getDocument()
    055    {
    056        return container.getDocument();
    057    }
    058
    059
    060    /**
    061     * Invokes {@link #toMarkup(PrintWriter)}, collecting output in a string, which is returned.
    062     */
    063    @Override
    064    public String toString()
    065    {
    066        PrintOutCollector collector = new PrintOutCollector();
    067
    068        toMarkup(collector.getPrintWriter());
    069
    070        return collector.getPrintOut();
    071    }
    072
    073
    074    /**
    075     * Writes the markup for this node to the writer.
    076     */
    077    public void toMarkup(PrintWriter writer)
    078    {
    079        toMarkup(getDocument(), writer, getNamespaceURIToPrefix());
    080    }
    081
    082    protected Map<String, String> getNamespaceURIToPrefix()
    083    {
    084        // For non-Elements, the container (which should be an Element) will provide the mapping.
    085
    086        return container.getNamespaceURIToPrefix();
    087    }
    088
    089    /**
    090     * Implemented by each subclass, with the document passed in for efficiency.
    091     */
    092    abstract void toMarkup(Document document, PrintWriter writer, Map<String, String> namespaceURIToPrefix);
    093
    094    /**
    095     * Moves this node so that it becomes a sibling of the element, ordered just before the element.
    096     *
    097     * @param element to move the node before
    098     * @return the node for further modification
    099     */
    100    public Node moveBefore(Element element)
    101    {
    102        validateElement(element);
    103
    104        remove();
    105
    106        element.container.insertChildBefore(element, this);
    107
    108        return this;
    109    }
    110
    111
    112    /**
    113     * Moves this node so that it becomes a sibling of the element, ordered just after the element.
    114     *
    115     * @param element to move the node after
    116     * @return the node for further modification
    117     */
    118    public Node moveAfter(Element element)
    119    {
    120        validateElement(element);
    121
    122        remove();
    123
    124        element.container.insertChildAfter(element, this);
    125
    126        return this;
    127    }
    128
    129    /**
    130     * Moves this node so that it becomes this first child of the element, shifting existing elements forward.
    131     *
    132     * @param element to move the node inside
    133     * @return the node for further modification
    134     */
    135    public Node moveToTop(Element element)
    136    {
    137        validateElement(element);
    138
    139        remove();
    140
    141        element.insertChildAt(0, this);
    142
    143        return this;
    144    }
    145
    146    /**
    147     * Moves this node so that it the last child of the element.
    148     *
    149     * @param element to move the node inside
    150     * @return the node for further modification
    151     */
    152    public Node moveToBottom(Element element)
    153    {
    154        validateElement(element);
    155
    156        remove();
    157
    158        element.addChild(this);
    159
    160        return this;
    161    }
    162
    163    private void validateElement(Element element)
    164    {
    165        assert element != null;
    166
    167        Node search = element;
    168        while (search != null)
    169        {
    170            if (search.equals(this))
    171            {
    172                throw new IllegalArgumentException("Unable to move a node relative to itself.");
    173            }
    174
    175            search = search.getContainer();
    176        }
    177    }
    178
    179    /**
    180     * Removes a node from its container, setting its container property to null, and removing it from its container's
    181     * list of children.
    182     */
    183    public void remove()
    184    {
    185        container.remove(this);
    186
    187        container = null;
    188    }
    189
    190    /**
    191     * Wraps a node inside a new element.  The new element is created before the node, then the node is moved inside the
    192     * new element.
    193     *
    194     * @param elementName    name of new element to create
    195     * @param namesAndValues to set attributes of new element
    196     * @return the created element
    197     */
    198    public Element wrap(String elementName, String... namesAndValues)
    199    {
    200        int index = container.indexOfNode(this);
    201
    202        // Insert the new element just before this node.
    203        Element element = container.elementAt(index, elementName, namesAndValues);
    204
    205        // Move this node inside the new element.
    206        moveToTop(element);
    207
    208        return element;
    209    }
    210}