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    }