    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
    015    package org.apache.tapestry5.dom;
    016
    017    import java.io.PrintWriter;
    018    import java.util.Map;
    019
    020    import org.apache.tapestry5.internal.util.PrintOutCollector;
    021
    022    /**
    023     * A node within the DOM.
    024     */
    025    public 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    }