001 // Copyright 2005 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.tapestry.markup;
016
017 import java.io.PrintWriter;
018 import java.util.ArrayList;
019 import java.util.List;
020
021 import org.apache.hivemind.ApplicationRuntimeException;
022 import org.apache.hivemind.util.Defense;
023 import org.apache.tapestry.IMarkupWriter;
024 import org.apache.tapestry.NestedMarkupWriter;
025
026 /**
027 * Completely revised (for 4.0) implementation of {@link org.apache.tapestry.IMarkupWriter}. No
028 * longer does internal buffering (since the servlet/portlet APIs support that natively) and wraps
029 * around a {@link java.io.PrintWriter} (rather than an {@link java.io.OutputStream}).
030 *
031 * @author Howard M. Lewis Ship
032 * @since 4.0
033 */
034 public class MarkupWriterImpl implements IMarkupWriter
035 {
036 /**
037 * The underlying {@link PrintWriter}that output is sent to.
038 */
039
040 private PrintWriter _writer;
041
042 /**
043 * Filter used to "escape" characters that need any kind of special encoding for the output
044 * content type.
045 */
046
047 private MarkupFilter _filter;
048
049 /**
050 * Indicates whether a tag is open or not. A tag is opened by {@link #begin(String)}or
051 * {@link #beginEmpty(String)}. It stays open while calls to the <code>attribute()</code>
052 * methods are made. It is closed (the '>' is written) when any other method is invoked.
053 */
054
055 private boolean _openTag = false;
056
057 /**
058 * Indicates that the tag was opened with {@link #beginEmpty(String)}, which affects how the
059 * tag is closed (a slash is added to indicate the lack of a body). This is compatible with
060 * HTML, but reflects an XML/XHTML leaning.
061 */
062
063 private boolean _emptyTag = false;
064
065 private String _contentType;
066
067 /**
068 * A Stack of Strings used to track the active tag elements. Elements are active until the
069 * corresponding close tag is written. The {@link #push(String)}method adds elements to the
070 * stack, {@link #pop()}removes them.
071 */
072
073 private List _activeElementStack;
074
075 public MarkupWriterImpl(String contentType, PrintWriter writer, MarkupFilter filter)
076 {
077 Defense.notNull(contentType, "contentType");
078 Defense.notNull(writer, "writer");
079 Defense.notNull(filter, "filter");
080
081 _contentType = contentType;
082 _writer = writer;
083 _filter = filter;
084 }
085
086 public void attribute(String name, int value)
087 {
088 checkTagOpen();
089
090 _writer.print(' ');
091 _writer.print(name);
092 _writer.print("=\"");
093 _writer.print(value);
094 _writer.print('"');
095 }
096
097 public void attribute(String name, boolean value)
098 {
099 checkTagOpen();
100
101 _writer.print(' ');
102 _writer.print(name);
103 _writer.print("=\"");
104 _writer.print(value);
105 _writer.print('"');
106 }
107
108 public void attribute(String name, String value)
109 {
110 attribute(name, value, false);
111 }
112
113 public void attribute(String name, String value, boolean raw)
114 {
115 checkTagOpen();
116
117 _writer.print(' ');
118
119 // Could use a check here that name contains only valid characters
120
121 _writer.print(name);
122 _writer.print("=\"");
123
124 if (value != null)
125 {
126 char[] data = value.toCharArray();
127 maybePrintFiltered(data, 0, data.length, raw, true);
128 }
129
130 _writer.print('"');
131 }
132
133 /**
134 * Prints the value, if non-null. May pass it through the filter, unless raw is true.
135 */
136
137 private void maybePrintFiltered(char[] data, int offset, int length, boolean raw,
138 boolean isAttribute)
139 {
140 if (data == null || length <= 0)
141 return;
142
143 if (raw)
144 {
145 _writer.write(data, offset, length);
146 return;
147 }
148
149 _filter.print(_writer, data, offset, length, isAttribute);
150 }
151
152 public void attributeRaw(String name, String value)
153 {
154 attribute(name, value, true);
155 }
156
157 public void begin(String name)
158 {
159 if (_openTag)
160 closeTag();
161
162 push(name);
163
164 _writer.print('<');
165 _writer.print(name);
166
167 _openTag = true;
168 _emptyTag = false;
169 }
170
171 public void beginEmpty(String name)
172 {
173 if (_openTag)
174 closeTag();
175
176 _writer.print('<');
177 _writer.print(name);
178
179 _openTag = true;
180 _emptyTag = true;
181 }
182
183 public boolean checkError()
184 {
185 return _writer.checkError();
186 }
187
188 public void close()
189 {
190 if (_openTag)
191 closeTag();
192
193 // Close any active elements.
194
195 while (!stackEmpty())
196 {
197 _writer.print("</");
198 _writer.print(pop());
199 _writer.print('>');
200 }
201
202 _writer.close();
203
204 _writer = null;
205 _filter = null;
206 _activeElementStack = null;
207 }
208
209 public void closeTag()
210 {
211 if (_emptyTag)
212 _writer.print('/');
213
214 _writer.print('>');
215
216 _openTag = false;
217 _emptyTag = false;
218 }
219
220 public void comment(String value)
221 {
222 if (_openTag)
223 closeTag();
224
225 _writer.print("<!-- ");
226 _writer.print(value);
227 _writer.println(" -->");
228 }
229
230 public void end()
231 {
232 if (_openTag)
233 closeTag();
234
235 if (stackEmpty())
236 throw new ApplicationRuntimeException(MarkupMessages.endWithEmptyStack());
237
238 _writer.print("</");
239 _writer.print(pop());
240 _writer.print('>');
241 }
242
243 public void end(String name)
244 {
245 if (_openTag)
246 closeTag();
247
248 if (_activeElementStack == null || !_activeElementStack.contains(name))
249 throw new ApplicationRuntimeException(MarkupMessages.elementNotOnStack(
250 name,
251 _activeElementStack));
252
253 while (true)
254 {
255 String tagName = pop();
256
257 _writer.print("</");
258 _writer.print(tagName);
259 _writer.print('>');
260
261 if (tagName.equals(name))
262 break;
263 }
264 }
265
266 public void flush()
267 {
268 _writer.flush();
269 }
270
271 public NestedMarkupWriter getNestedWriter()
272 {
273 return new NestedMarkupWriterImpl(this, _filter);
274 }
275
276 public void print(char[] data, int offset, int length)
277 {
278 print(data, offset, length, false);
279 }
280
281 public void printRaw(char[] buffer, int offset, int length)
282 {
283 print(buffer, offset, length, true);
284 }
285
286 public void print(char[] buffer, int offset, int length, boolean raw)
287 {
288 if (_openTag)
289 closeTag();
290
291 maybePrintFiltered(buffer, offset, length, raw, false);
292 }
293
294 public void print(String value)
295 {
296 print(value, false);
297 }
298
299 public void printRaw(String value)
300 {
301 print(value, true);
302 }
303
304 public void print(String value, boolean raw)
305 {
306 if (value == null || value.length() == 0)
307 {
308 print(null, 0, 0, raw);
309 return;
310 }
311
312 char[] buffer = value.toCharArray();
313
314 print(buffer, 0, buffer.length, raw);
315 }
316
317 public void print(char value)
318 {
319 char[] data = new char[]
320 { value };
321
322 print(data, 0, 1);
323 }
324
325 public void print(int value)
326 {
327 if (_openTag)
328 closeTag();
329
330 _writer.print(value);
331 }
332
333 public void println()
334 {
335 if (_openTag)
336 closeTag();
337
338 _writer.println();
339 }
340
341 public String getContentType()
342 {
343 return _contentType;
344 }
345
346 private void checkTagOpen()
347 {
348 if (!_openTag)
349 throw new IllegalStateException(MarkupMessages.tagNotOpen());
350 }
351
352 private void push(String name)
353 {
354 if (_activeElementStack == null)
355 _activeElementStack = new ArrayList();
356
357 _activeElementStack.add(name);
358 }
359
360 private String pop()
361 {
362 int lastIndex = _activeElementStack.size() - 1;
363
364 return (String) _activeElementStack.remove(lastIndex);
365 }
366
367 private boolean stackEmpty()
368 {
369 return _activeElementStack == null || _activeElementStack.isEmpty();
370 }
371 }