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    }