001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.visualweb.jsfsupport.container;
042:
043: import java.io.IOException;
044: import java.io.Writer;
045:
046: import javax.faces.component.UIComponent;
047: import javax.faces.context.ResponseWriter;
048: import javax.faces.el.ValueBinding;
049:
050: import com.sun.faces.util.HtmlUtils;
051:
052: /**
053: * PrettyJspWriter provides a pretty-formated JSP writer needed for JSP generation
054: *
055: * @author Robert Brewin, Carl Quinn
056: * @version 1.0
057: */
058: public class PrettyJspWriter extends ResponseWriter {
059:
060: String encoding = "ISO-8859-1";
061:
062: /**
063: * Used to track the current indentation amount
064: */
065: private int currentIndent = 0;
066:
067: /**
068: * The indent amount
069: */
070: private int indentAmount = 4;
071:
072: /**
073: * Flag which indicates that we have an open element tag
074: */
075: private boolean closeStart;
076:
077: private boolean isEmptyElement;
078:
079: /** True when we shouldn't be escaping output (basically,
080: * inside of <script> and <style> elements).
081: */
082: private boolean dontEscape;
083:
084: /**
085: * Holds the <code>Writer</code> we will be emitting to
086: */
087: private ResettableStringWriter writer;
088:
089: private char[] buffer = new char[1028];
090: private char[] charHolder = new char[1];
091:
092: //--------------------------------------------------------------------------------- Construction
093:
094: /**
095: * Construct the PrettyJspWriter
096: */
097: public PrettyJspWriter() {
098: }
099:
100: /**
101: * Construct the PrettyJspWriter
102: */
103: public PrettyJspWriter(ResettableStringWriter outputWriter) {
104: this .writer = outputWriter;
105: }
106:
107: /**
108: * Set the output writer
109: *
110: * @param outputWriter -- the new <code>Writer</code> to use for emission
111: */
112: public void setOutputWriter(ResettableStringWriter outputWriter) {
113: this .writer = outputWriter;
114: }
115:
116: //---------------------------------------------------------------------------------- Indentation
117:
118: /**
119: * Get the current indentation amount
120: *
121: * @return the indent amount
122: */
123: public int getCurrentIndent() {
124: return currentIndent;
125: }
126:
127: /**
128: * Set the amount to indent
129: *
130: * @param indentAmount
131: */
132: public void setIndentAmount(int indentAmount) {
133: this .indentAmount = indentAmount;
134: }
135:
136: /**
137: * Increment the current indent
138: */
139: public void indent() {
140: currentIndent += indentAmount;
141: }
142:
143: /**
144: * Decrement the current indent
145: */
146: public void outdent() {
147: currentIndent -= indentAmount;
148: if (currentIndent < 0)
149: currentIndent = 0;
150: }
151:
152: /**
153: * Indent within the current stream
154: *
155: * @throws IOException
156: */
157: private void writeIndent() throws IOException {
158: for (int i = 0; i < currentIndent; i++)
159: writer.write(' ');
160: }
161:
162: //--------------------------------------------------------------------------------- zzz
163:
164: /**
165: * @return the content type, such as "text/html" for this
166: * ResponseWriter.
167: *
168: */
169: public String getContentType() {
170: return "text/html";
171: }
172:
173: /**
174: * @return the character encoding, such as "ISO-8859-1" for this
175: * ResponseWriter. Please see <a
176: * href="http://www.iana.org/assignments/character-sets">the
177: * IANA</a> for a list of character encodings.
178: *
179: */
180: public String getCharacterEncoding() {
181: return encoding;
182: }
183:
184: /**
185: * <p>Write whatever text should begin a response.</p>
186: *
187: * @exception java.io.IOException if an input/output error occurs
188: */
189: public void startDocument() throws IOException {
190: currentIndent = 0;
191: }
192:
193: /**
194: * <p>Write whatever text should end a response. If there is an open
195: * element that has been created by a call to <code>startElement()</code>,
196: * that element will be closed first.</p>
197: *
198: * @exception java.io.IOException if an input/output error occurs
199: */
200: public void endDocument() throws IOException {
201: //!CQ could error-check indent here...
202: currentIndent = 0;
203: writer.flush();
204: }
205:
206: /**
207: * Flush the stream. If the stream has saved any characters from the
208: * various write() methods in a buffer, write them immediately to their
209: * intended destination. Then, if that destination is another character or
210: * byte stream, flush it. Thus one flush() invocation will flush all the
211: * buffers in a chain of Writers and OutputStreams.
212: *
213: * @exception java.io.IOException If an I/O error occurs
214: */
215: public void flush() throws IOException {
216: closeStartIfNecessary();
217: writer.flush();
218: }
219:
220: /**
221: * <p>Write the start of an element, up to and including the
222: * element name. Once this method has been called, clients can
223: * call <code>writeAttribute()</code> or <code>writeURIAttribute()</code>
224: * method to add attributes and corresponding values. The starting
225: * element will be closed (that is, the trailing '>' character added)
226: * on any subsequent call to <code>startElement()</code>,
227: * <code>writeComment()</code>,
228: * <code>writeText()</code>, <code>endElement()</code>, or
229: * <code>endDocument()</code>.</p>
230: *
231: * @param name Name of the element to be started
232: *
233: * @param componentForElement May be <code>null</code>. If
234: * non-<code>null</code>, must be the UIComponent instance to which
235: * this element corresponds.
236: *
237: * @exception IOException if an input/output error occurs
238: * @exception NullPointerException if <code>name</code>
239: * is <code>null</code>
240: */
241: public void startElement(String name,
242: UIComponent componentForElement) throws IOException {
243:
244: char firstChar = name.charAt(0);
245: if (firstChar == 's'
246: || firstChar == 'S'
247: && (name.equalsIgnoreCase("script") || name
248: .equalsIgnoreCase("style")))
249: dontEscape = true;
250:
251: closeStartIfNecessary();
252: writeIndent();
253: writer.write("<" + name);
254: if (componentForElement != null) {
255: ValueBinding vb = componentForElement
256: .getValueBinding("binding");
257: if (vb != null)
258: writeAttribute("design-id", vb.getExpressionString(),
259: null);
260: }
261: closeStart = true;
262: isEmptyElement = true; // empty until proven otherwise
263: indent();
264: }
265:
266: /**
267: * This method automatically closes a previous element (if not
268: * already closed).
269: */
270: private void closeStartIfNecessary() throws IOException {
271: if (closeStart) {
272: writer.write(">\n");
273: closeStart = false;
274: }
275: }
276:
277: /**
278: * <p>Write the end of an element, after closing any open element
279: * created by a call to <code>startElement()</code>.
280: *
281: * @param name Name of the element to be ended
282: *
283: * @exception java.io.IOException if an input/output error occurs
284: * @exception java.lang.NullPointerException if <code>name</code>
285: * is <code>null</code>
286: */
287: public void endElement(String name) throws IOException {
288: // always turn escaping back on once an element ends
289: dontEscape = false;
290:
291: // See if we need to close the start of the last element
292: if (closeStart) {
293: //boolean isEmptyElement = HtmlUtils.isEmptyElement(name);
294: if (isEmptyElement) {
295: writer.write(" />");
296: closeStart = false;
297: outdent();
298: return;
299: }
300:
301: writer.write(">\n");
302: closeStart = false;
303: }
304:
305: outdent();
306:
307: writeIndent();
308: writer.write("</");
309: writer.write(name);
310: writer.write(">\n");
311: }
312:
313: /**
314: * <p>Force the closing of the start tag currently being
315: * written, if it has not already been closed.</p>
316: *
317: * @param component the {@link UIComponent} (if any) to which this
318: * tag corresponds.
319: *
320: * @exception IOException if an input/output error occurs
321: *
322: public void closeStartTag(UIComponent component) throws IOException {
323: if (closeStart) {
324: writer.write(">\n");
325: closeStart = false;
326: }
327: }*/
328:
329: /**
330: * <p>Write an attribute name and corresponding value (after converting
331: * that text to a String if necessary), after escaping it properly.
332: * This method may only be called after a call to
333: * <code>startElement()</code>, and before the opened element has been
334: * closed.</p>
335: *
336: * @param name Attribute name to be added
337: *
338: * @param value Attribute value to be added
339: *
340: * @param componentPropertyName May be <code>null</code>. If
341: * non-<code>null</code>, this must be the name of the property on
342: * the {@link UIComponent} passed in to a previous call to {@link
343: * #startElement} to which this attribute corresponds.
344: *
345: * @exception IllegalStateException if this method is called when there
346: * is no currently open element
347: * @exception IOException if an input/output error occurs
348: * @exception NullPointerException if <code>name</code> is
349: * <code>null</code>
350: */
351: public void writeAttribute(String name, Object value,
352: String componentPropertyName) throws IOException {
353:
354: writer.write(" ");
355: writer.write(name);
356: writer.write("=\"");
357:
358: // write the attribute value
359: HtmlUtils.writeAttribute(writer, buffer, value.toString());
360:
361: writer.write("\"");
362: }
363:
364: /**
365: * <p>Write a URI attribute name and corresponding value (after converting
366: * that text to a String if necessary), after encoding it properly
367: * (for example, '%' encoded for HTML).
368: * This method may only be called after a call to
369: * <code>startElement()</code>, and before the opened element has been
370: * closed.</p>
371: *
372: * @param name Attribute name to be added
373: *
374: * @param value Attribute value to be added
375: *
376: * @param componentPropertyName May be <code>null</code>. If
377: * non-<code>null</code>, this must be the name of the property on
378: * the {@link UIComponent} passed in to a previous call to {@link
379: * #startElement} to which this attribute corresponds.
380: *
381: * @exception IllegalStateException if this method is called when there
382: * is no currently open element
383: * @exception IOException if an input/output error occurs
384: * @exception NullPointerException if <code>name</code> is
385: * <code>null</code>
386: */
387: public void writeURIAttribute(String name, Object value,
388: String componentPropertyName) throws IOException {
389: //!CQ TODO: could throw exception or similar check if elementOpen is false
390:
391: writer.write(" ");
392: writer.write(name);
393: writer.write("=\"");
394:
395: String stringValue = value.toString();
396:
397: // Javascript URLs should not be URL-encoded
398: if (stringValue.startsWith("javascript:")) {
399: HtmlUtils.writeAttribute(writer, buffer, stringValue);
400: } else {
401: HtmlUtils.writeURL(writer, stringValue, encoding, null);
402: }
403:
404: writer.write("\"");
405: }
406:
407: /**
408: * <p>Write a comment containing the specified text, after converting
409: * that text to a String if necessary. If there is an open element
410: * that has been created by a call to <code>startElement()</code>,
411: * that element will be closed first.</p>
412: *
413: * @param comment Text content of the comment
414: *
415: * @exception java.io.IOException if an input/output error occurs
416: * @exception java.lang.NullPointerException if <code>comment</code>
417: * is <code>null</code>
418: */
419: public void writeComment(Object comment) throws IOException {
420: closeStartIfNecessary();
421: writer.write("<!-- ");
422: writer.write(comment.toString());
423: writer.write(" -->");
424: }
425:
426: /**
427: * <p>Write an object (after converting it to a String, if necessary),
428: * after escaping it properly. If there is an open element
429: * that has been created by a call to <code>startElement()</code>,
430: * that element will be closed first.</p>
431: *
432: * <p>All angle bracket occurrences in the argument must be escaped
433: * using the &gt; &lt; syntax.</p>
434: *
435: * @param text Text to be written
436: *
437: * @param componentPropertyName May be <code>null</code>. If
438: * non-<code>null</code>, this is the name of the property in the
439: * associated component to which this piece of text applies.
440: *
441: * @exception IOException if an input/output error occurs
442: * @exception NullPointerException if <code>text</code>
443: * is <code>null</code>
444: */
445: public void writeText(Object text, String componentPropertyName)
446: throws IOException {
447: isEmptyElement = false;
448: closeStartIfNecessary();
449: writeIndent();
450: if (dontEscape) {
451: writer.write(text.toString());
452: } else {
453: HtmlUtils.writeText(writer, buffer, text.toString());
454: }
455: }
456:
457: /**
458: * <p>Write a single character, after escaping it properly. If there
459: * is an open element that has been created by a call to
460: * <code>startElement()</code>, that element will be closed first.</p>
461: *
462: * @param text Text to be written
463: *
464: * @exception java.io.IOException if an input/output error occurs
465: */
466: public void writeText(char text) throws IOException {
467: isEmptyElement = false;
468: closeStartIfNecessary();
469: writeIndent();
470: if (dontEscape) {
471: writer.write(text);
472: } else {
473: charHolder[0] = text;
474: HtmlUtils.writeText(writer, buffer, charHolder);
475: }
476: }
477:
478: /**
479: * <p>Write text from a character array, after escaping it properly
480: * for this method. If there is an open element that has been
481: * created by a call to <code>startElement()</code>, that element
482: * will be closed first.</p>
483: *
484: * @param text Text to be written
485: * @param off Starting offset (zero-relative)
486: * @param len Number of characters to be written
487: *
488: * @exception java.lang.IndexOutOfBoundsException if the calculated starting or
489: * ending position is outside the bounds of the character array
490: * @exception java.io.IOException if an input/output error occurs
491: * @exception java.lang.NullPointerException if <code>text</code>
492: * is <code>null</code>
493: */
494: public void writeText(char[] text, int off, int len)
495: throws IOException {
496: isEmptyElement = false;
497: closeStartIfNecessary();
498: writeIndent();
499: if (dontEscape) {
500: writer.write(text);
501: } else {
502: HtmlUtils.writeText(writer, buffer, text);
503: }
504: }
505:
506: /**
507: * Creates a new instance of this ResponseWriter, using a different Writer.
508: */
509: public ResponseWriter cloneWithWriter(Writer writer) {
510: // How do we handle this? We need the writer to be resettable!
511: throw new RuntimeException(
512: "cloneWithWriter not supported by the Rave container!");
513: //return new PrettyJspWriter(writer);
514: }
515:
516: //--------------------------------------------------------------------------------------- Writer
517:
518: /**
519: * Close the stream, flushing it first. Once a stream has been closed, further write() or
520: * flush() invocations will cause an IOException to be thrown. Closing a previously-closed
521: * stream, however, has no effect.
522: *
523: * @exception java.io.IOException If an I/O error occurs
524: */
525: public void close() throws IOException {
526: closeStartIfNecessary();
527: writer.close();
528: }
529:
530: public void write(char cbuf) throws IOException {
531: isEmptyElement = false;
532: closeStartIfNecessary();
533: writer.write(cbuf);
534: }
535:
536: public void write(char[] cbuf, int off, int len) throws IOException {
537: isEmptyElement = false;
538: closeStartIfNecessary();
539: writer.write(cbuf, off, len);
540: }
541:
542: public void write(int c) throws IOException {
543: isEmptyElement = false;
544: closeStartIfNecessary();
545: writer.write(c);
546: }
547:
548: public void write(String str) throws IOException {
549: isEmptyElement = false;
550: closeStartIfNecessary();
551: writer.write(str);
552: }
553:
554: public void write(String str, int off, int len) throws IOException {
555: isEmptyElement = false;
556: closeStartIfNecessary();
557: writer.write(str, off, len);
558: }
559:
560: /*
561: * Returns the index of the next character to be written to the stream, or put another way, the
562: * number of characters since the writer was created.
563: *
564: * @returns character count (not including reset content) since writer was created.
565: */
566: public int getPosition() {
567: return writer.getPosition();
568: }
569:
570: /**
571: * Truncates the written content back to a particular position/index specified. <b>Note</b>:
572: * Should only be used to reset back to a position outside of a tag, since it will clear the
573: * "closeStart" flag.
574: *
575: * @param position The position to jump back to.
576: */
577: public void reset(int position) {
578: writer.reset(position);
579: closeStart = false;
580: }
581: }
|