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 }