001 // Copyright 2006, 2007, 2008, 2009, 2010, 2011 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.internal.services;
016
017 import org.apache.tapestry5.MarkupWriter;
018 import org.apache.tapestry5.MarkupWriterListener;
019 import org.apache.tapestry5.dom.*;
020
021 import java.io.PrintWriter;
022 import java.util.Collection;
023 import java.util.List;
024 import java.util.concurrent.CopyOnWriteArrayList;
025
026 public class MarkupWriterImpl implements MarkupWriter
027 {
028 private final Document document;
029
030 private Element current;
031
032 private Text currentText;
033
034 private List<MarkupWriterListener> listeners;
035
036 /**
037 * Creates a new instance of the MarkupWriter with a {@link org.apache.tapestry5.dom.DefaultMarkupModel}.
038 */
039 public MarkupWriterImpl()
040 {
041 this(new DefaultMarkupModel());
042 }
043
044 public MarkupWriterImpl(MarkupModel model)
045 {
046 this(model, null);
047 }
048
049 public MarkupWriterImpl(MarkupModel model, String encoding)
050 {
051 document = new Document(model, encoding);
052 }
053
054 public void toMarkup(PrintWriter writer)
055 {
056 document.toMarkup(writer);
057 }
058
059 @Override
060 public String toString()
061 {
062 return document.toString();
063 }
064
065 public Document getDocument()
066 {
067 return document;
068 }
069
070 public Element getElement()
071 {
072 return current;
073 }
074
075 public void cdata(String content)
076 {
077 currentText = null;
078
079 if (current == null)
080 {
081 document.cdata(content);
082 } else
083 {
084 current.cdata(content);
085 }
086 }
087
088 public void write(String text)
089 {
090 if (text == null) return;
091
092 if (currentText == null)
093 {
094 currentText =
095 current == null
096 ? document.text(text)
097 : current.text(text);
098
099 return;
100 }
101
102 currentText.write(text);
103 }
104
105 public void writef(String format, Object... args)
106 {
107 // A bit of a cheat:
108
109 write("");
110 currentText.writef(format, args);
111 }
112
113 public void attributes(Object... namesAndValues)
114 {
115 ensureCurrentElement();
116
117 int i = 0;
118
119 int length = namesAndValues.length;
120
121 if (length % 2 != 0)
122 throw new IllegalArgumentException(ServicesMessages.markupWriterAttributeNameOrValueOmitted(current.getName(), namesAndValues));
123
124 while (i < length)
125 {
126 // name should never be null.
127
128 String name = namesAndValues[i++].toString();
129 Object value = namesAndValues[i++];
130
131 if (value == null) continue;
132
133 current.attribute(name, value.toString());
134 }
135 }
136
137 private void ensureCurrentElement()
138 {
139 if (current == null)
140 throw new IllegalStateException(ServicesMessages.markupWriterNoCurrentElement());
141 }
142
143 public Element element(String name, Object... namesAndValues)
144 {
145 if (current == null)
146 {
147 Element existingRootElement = document.getRootElement();
148
149 if (existingRootElement != null)
150 throw new IllegalStateException(String.format(
151 "A document must have exactly one root element. Element <%s> is already the root element.",
152 existingRootElement.getName()));
153
154 current = document.newRootElement(name);
155 } else
156 {
157 current = current.element(name);
158 }
159
160 attributes(namesAndValues);
161
162 currentText = null;
163
164 fireElementDidStart();
165
166 return current;
167 }
168
169 public void writeRaw(String text)
170 {
171 currentText = null;
172
173 if (current == null)
174 {
175 document.raw(text);
176 } else
177 {
178 current.raw(text);
179 }
180 }
181
182 public Element end()
183 {
184 ensureCurrentElement();
185
186 fireElementDidEnd();
187
188 current = current.getContainer();
189
190 currentText = null;
191
192 return current;
193 }
194
195 public void comment(String text)
196 {
197 currentText = null;
198
199 if (current == null)
200 {
201 document.comment(text);
202 } else
203 {
204 current.comment(text);
205 }
206 }
207
208 public Element attributeNS(String namespace, String attributeName, String attributeValue)
209 {
210 ensureCurrentElement();
211
212 current.attribute(namespace, attributeName, attributeValue);
213
214 return current;
215 }
216
217 public Element defineNamespace(String namespace, String namespacePrefix)
218 {
219 ensureCurrentElement();
220
221 current.defineNamespace(namespace, namespacePrefix);
222
223 return current;
224 }
225
226 public Element elementNS(String namespace, String elementName)
227 {
228 if (current == null) current = document.newRootElement(namespace, elementName);
229 else current = current.elementNS(namespace, elementName);
230
231 currentText = null;
232
233 fireElementDidStart();
234
235 return current;
236 }
237
238 public void addListener(MarkupWriterListener listener)
239 {
240 assert listener != null;
241
242 if (listeners == null)
243 {
244 // TAP5-XXX: Using a copy-on-write list means we don't have to make defensive copies
245 // while iterating the listeners (to protect against listeners that add or remove listeners).
246 listeners = new CopyOnWriteArrayList<MarkupWriterListener>();
247 }
248
249 listeners.add(listener);
250 }
251
252 public void removeListener(MarkupWriterListener listener)
253 {
254 if (listeners != null)
255 listeners.remove(listener);
256 }
257
258 private void fireElementDidStart()
259 {
260 if (isEmpty(listeners)) return;
261
262 for (MarkupWriterListener l : listeners)
263 {
264 l.elementDidStart(current);
265 }
266 }
267
268 private static boolean isEmpty(Collection<?> collection)
269 {
270 return collection == null || collection.isEmpty();
271 }
272
273 private void fireElementDidEnd()
274 {
275 if (isEmpty(listeners)) return;
276
277 for (MarkupWriterListener l : listeners)
278 {
279 l.elementDidEnd(current);
280 }
281 }
282 }
283