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}