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.insync.faces;
042:
043: import com.sun.rave.designtime.markup.MarkupDesignBean;
044: import org.netbeans.modules.visualweb.insync.markup.MarkupUnit;
045: import org.netbeans.modules.visualweb.jsfsupport.container.FacesContainer;
046: import java.io.IOException;
047: import java.io.Writer;
048:
049: import javax.faces.component.UIComponent;
050: import javax.faces.context.ResponseWriter;
051: import org.openide.ErrorManager;
052:
053: import org.w3c.dom.Document;
054: import org.w3c.dom.DocumentFragment;
055: import org.w3c.dom.Element;
056: import org.w3c.dom.Node;
057:
058: import com.sun.rave.designtime.DesignBean;
059: import com.sun.rave.designtime.DesignContext;
060: import com.sun.rave.designtime.markup.MarkupDesignBean;
061:
062: // XXX Originally in jsfsupport, which was wrong location.
063: /**
064: * DocFragmentJspWriter provides a direct-to-DOM JSP writer for improved design-time DOM handling
065: *
066: * @author Carl Quinn
067: * @author Tor Norbye
068: * @version 1.1
069: */
070: class DocFragmentJspWriter extends ResponseWriter {
071:
072: String encoding = "ISO-8859-1";
073:
074: private FacesContainer container;
075: private Document doc;
076: private DocumentFragment frag;
077:
078: // <markup_separation> moved to designer/markup
079: // public interface ParsingDocument {
080: // /** Given a string of xhtml, parse it and append it to the given
081: // * parent node
082: // * @param parent The parent node; cannot be null
083: // * @param xhtml An xhtml fragment string; should be well formed but
084: // * may not be a complete xhtml document (e.g. no <body> tag;
085: // * may not be surrounded by an element, shouldn't have a DOCTYPE,
086: // * etc.)
087: // * @param bean The bean for which this markup is generated
088: // */
089: // public void appendParsedString(Node parent, String xhtml, MarkupDesignBean bean);
090: // }
091: // </markup_separation>
092:
093: private Node current;
094:
095: /**
096: * Flag which indicates that we have an open element tag
097: */
098: private boolean buildingStart;
099:
100: /** True when we shouldn't be escaping output (basically,
101: * inside of <script> and <style> elements).
102: */
103: private boolean dontEscape;
104:
105: private char[] charHolder = new char[1];
106:
107: //--------------------------------------------------------------------------------- Construction
108:
109: /**
110: * Construct the DocFragmentJspWriter
111: */
112: public DocFragmentJspWriter(FacesContainer container,
113: DocumentFragment frag) {
114: this .container = container;
115: this .frag = frag;
116: this .doc = frag.getOwnerDocument();
117: current = frag;
118: depth = 0;
119: skipDepth = -1;
120: }
121:
122: /**
123: * Import a node into the written dom tree. If deep is true just clone it in and continue at the
124: * same level. If close is false, then import one node & descend.
125: * @param node Node to be copied
126: * @param deep If true, copy recursively, e.g. include all children of node as well
127: * @return The imported node
128: */
129: public Node importNode(Node node, boolean deep) {
130: if (skipDepth != -1) {
131: return null;
132: }
133:
134: // Remove script tags (to lessen work that has to be done
135: // by the designer), remove f:subview and other "metatags" like
136: // that
137: if (node.getNodeType() == Node.ELEMENT_NODE) {
138: String tag = ((Element) node).getLocalName();
139: if (tag.charAt(0) == 's') {
140: if (tag.equals("subview")) {
141: return null;
142: //} else if (tag.equals("script")) {
143: // depth++;
144: // skipDepth = depth;
145: //
146: // return null;
147: } // else TODO - yank f:verbatim too (but keep its children)
148: }
149: } // else: note that I cannot remove comments because they often are
150: // interpreted by the browser: for example, a <style> element may
151: // contain a comment and browsers know to look inside the comment
152: // for the actual CSS styles
153:
154: Node newnode = doc.importNode(node, deep);
155: current.appendChild(newnode);
156: if (!deep) {
157: current = newnode;
158: depth++;
159: }
160: return newnode;
161: }
162:
163: public Node appendTextNode(String text) {
164: if (skipDepth != -1) {
165: return null;
166: }
167: Node newnode = doc.createTextNode(text);
168: current.appendChild(newnode);
169: return newnode;
170: }
171:
172: /**
173: * Pop up a level after doing a non-deep import
174: */
175: public void popNode() {
176: current = current.getParentNode();
177: }
178:
179: //------------------------------------------------------------------------------- ResponseWriter
180:
181: /**
182: * @return the content type, such as "text/html" for this
183: * ResponseWriter.
184: *
185: */
186: public String getContentType() {
187: return "text/html";
188: }
189:
190: /**
191: * @return the character encoding, such as "ISO-8859-1" for this
192: * ResponseWriter. Please see <a
193: * href="http://www.iana.org/assignments/character-sets">the
194: * IANA</a> for a list of character encodings.
195: *
196: */
197: public String getCharacterEncoding() {
198: return encoding;
199: }
200:
201: /**
202: * <p>Write whatever text should begin a response.</p>
203: *
204: * @exception java.io.IOException if an input/output error occurs
205: */
206: public void startDocument() throws IOException {
207: }
208:
209: /**
210: * <p>Write whatever text should end a response. If there is an open
211: * element that has been created by a call to <code>startElement()</code>,
212: * that element will be closed first.</p>
213: *
214: * @exception java.io.IOException if an input/output error occurs
215: */
216: public void endDocument() throws IOException {
217: flush();
218: }
219:
220: /**
221: * Flush the stream. If the stream has saved any characters from the
222: * various write() methods in a buffer, write them immediately to their
223: * intended destination. Then, if that destination is another character or
224: * byte stream, flush it. Thus one flush() invocation will flush all the
225: * buffers in a chain of Writers and OutputStreams.
226: *
227: * @exception java.io.IOException If an I/O error occurs
228: */
229: public void flush() throws IOException {
230: closeStartIfNecessary();
231: }
232:
233: /**
234: * <p>Write the start of an element, up to and including the
235: * element name. Once this method has been called, clients can
236: * call <code>writeAttribute()</code> or <code>writeURIAttribute()</code>
237: * method to add attributes and corresponding values. The starting
238: * element will be closed (that is, the trailing '>' character added)
239: * on any subsequent call to <code>startElement()</code>,
240: * <code>writeComment()</code>,
241: * <code>writeText()</code>, <code>endElement()</code>, or
242: * <code>endDocument()</code>.</p>
243: *
244: * @param name Name of the element to be started
245: *
246: * @param componentForElement May be <code>null</code>. If
247: * non-<code>null</code>, must be the UIComponent instance to which
248: * this element corresponds.
249: *
250: * @exception IOException if an input/output error occurs
251: * @exception NullPointerException if <code>name</code>
252: * is <code>null</code>
253: */
254: public void startElement(String name,
255: UIComponent componentForElement) throws IOException {
256: if (componentForElement == null) {
257: boolean assertionsEnabled = false;
258: assert assertionsEnabled = true;
259: if (assertionsEnabled) {
260: Throwable t = new Throwable();
261: t.fillInStackTrace();
262: StackTraceElement stack[] = t.getStackTrace();
263: StackTraceElement caller = stack[1];
264: String className = caller.getClassName();
265: className = className.substring(className
266: .lastIndexOf('.') + 1);
267: String methodName = caller.getMethodName();
268: if (!methodName.equals("renderHiddenField")) { // Known exception
269: System.err
270: .println("Warning: tag <"
271: + name
272: + "> rendered with null component parameter! Caller: "
273: + className + "." + methodName
274: + "():" + caller.getLineNumber());
275: }
276: }
277: }
278: if (skipDepth != -1) {
279: depth++;
280: return;
281: }
282: if (componentForElement != null
283: && componentForElement == preRendered) {
284: assert preRenderedFragment != null;
285: Node n = importNode(preRenderedFragment, true);
286: //XhtmlText.markJspxSource(n);
287: //XhtmlElement.setStyleParent(ec, elem);
288: depth++;
289: skipDepth = depth;
290: preRenderedFragment.getChildNodes().getLength();
291: return;
292: }
293:
294: closeStartIfNecessary();
295:
296: // If I ever support HTML instead of XHTML gotta do case insensitive searching
297: // here
298: // if ((firstChar == 's' || firstChar == 'S') &&
299: // (name.equalsIgnoreCase("script") || name.equalsIgnoreCase("style"))) {
300: // dontEscape = true;
301: //}
302: char firstChar = name.charAt(0);
303: if (firstChar == 's') {
304: if (name.equals("script")) {
305: //depth++;
306: //skipDepth = depth;
307: //return;
308: dontEscape = true;
309: } else if (name.equals("style")) {
310: dontEscape = true;
311: }
312: }
313:
314: Element e = doc.createElement(name);
315: current.appendChild(e);
316: current = e;
317: depth++;
318:
319: if (componentForElement != null) {
320: DesignContext ctx = container.getFacesContext()
321: .getDesignContext();
322: DesignBean bean = ctx
323: .getBeanForInstance(componentForElement);
324: if (bean == null) {
325: // If there is no design bean for this component, it is most likely a component
326: // created by the renderer itself. Search up the component tree for a suitable
327: // bean.
328: UIComponent ancestor = componentForElement.getParent();
329: bean = ctx.getBeanForInstance(ancestor);
330: while (bean == null && ancestor != null) {
331: ancestor = ancestor.getParent();
332: bean = ctx.getBeanForInstance(ancestor);
333: }
334: }
335: if ((current instanceof Element)
336: && (bean instanceof MarkupDesignBean)) {
337: // InSyncService.getProvider().setMarkupDesignBeanForElement((Element)current, (MarkupDesignBean)bean);
338: MarkupUnit.setMarkupDesignBeanForElement(
339: (Element) current, (MarkupDesignBean) bean);
340: }
341: }
342: buildingStart = true;
343: }
344:
345: /**
346: * This method automatically closes a previous element (if not
347: * already closed).
348: */
349: private void closeStartIfNecessary() throws IOException {
350: if (buildingStart) // XXX add a skip check here too?
351: buildingStart = false;
352: }
353:
354: /**
355: * <p>Write the end of an element, after closing any open element
356: * created by a call to <code>startElement()</code>.
357: *
358: * @param name Name of the element to be ended
359: *
360: * @exception java.io.IOException if an input/output error occurs
361: * @exception java.lang.NullPointerException if <code>name</code>
362: * is <code>null</code>
363: */
364: public void endElement(String name) throws IOException {
365: if (skipDepth != -1) {
366: if (depth == skipDepth) {
367: depth--;
368: skipDepth = -1;
369: return;
370: } else if (depth > skipDepth) {
371: depth--;
372: return;
373: }
374: }
375: // always turn escaping back on once an element ends
376: dontEscape = false;
377:
378: if (current instanceof Element) {
379: boolean assertionsEnabled = false;
380: assert assertionsEnabled = true;
381: if (assertionsEnabled) {
382: if (!current.getLocalName().equals(name)) {
383: String instanceName = MarkupUnit
384: .getMarkupDesignBeanForElement(
385: (Element) current)
386: .getInstanceName();
387: System.err.println("Renderer for " + instanceName
388: + " attempting to close markup element '"
389: + name + "', closing '"
390: + current.getLocalName() + "' instead");
391: }
392: }
393: current = current.getParentNode();
394: depth--;
395: }
396:
397: if (buildingStart)
398: buildingStart = false;
399: }
400:
401: /** Ensure that we're done with the given node. Called to "rollback"
402: * in case a child has aborted during render. */
403: public void setCurrent(Node current, int depth) {
404: this .current = current;
405: this .depth = depth;
406: buildingStart = false;
407: }
408:
409: /** Return the current target node being rendered to by the jsp writer */
410: public Node getCurrent() {
411: return current;
412: }
413:
414: /** Return the depth of the current target node being rendered by the
415: * jsp writer. The document fragment starts out at depth 0. */
416: public int getDepth() {
417: return depth;
418: }
419:
420: /**
421: * <p>Write an attribute name and corresponding value (after converting
422: * that text to a String if necessary), after escaping it properly.
423: * This method may only be called after a call to
424: * <code>startElement()</code>, and before the opened element has been
425: * closed.</p>
426: *
427: * @param name Attribute name to be added
428: *
429: * @param value Attribute value to be added
430: *
431: * @param componentPropertyName May be <code>null</code>. If
432: * non-<code>null</code>, this must be the name of the property on
433: * the {@link UIComponent} passed in to a previous call to {@link
434: * #startElement} to which this attribute corresponds.
435: *
436: * @exception IllegalStateException if this method is called when there
437: * is no currently open element
438: * @exception IOException if an input/output error occurs
439: * @exception NullPointerException if <code>name</code> is
440: * <code>null</code>
441: */
442: public void writeAttribute(String name, Object value,
443: String componentPropertyName) throws IOException {
444: if (skipDepth != -1) {
445: return;
446: }
447: if (value == null) {
448: ErrorManager.getDefault().log(
449: "ResponseWriter: writeAttribute " + name
450: + " called with null value!");
451: return;
452: }
453:
454: name = name.trim(); //work around bug 5017976
455: // assert current instanceof Element
456: if (current instanceof Element)
457: ((Element) current).setAttribute(name, value.toString());
458: }
459:
460: /**
461: * <p>Write a URI attribute name and corresponding value (after converting
462: * that text to a String if necessary), after encoding it properly
463: * (for example, '%' encoded for HTML).
464: * This method may only be called after a call to
465: * <code>startElement()</code>, and before the opened element has been
466: * closed.</p>
467: *
468: * @param name Attribute name to be added
469: *
470: * @param value Attribute value to be added
471: *
472: * @param componentPropertyName May be <code>null</code>. If
473: * non-<code>null</code>, this must be the name of the property on
474: * the {@link UIComponent} passed in to a previous call to {@link
475: * #startElement} to which this attribute corresponds.
476: *
477: * @exception IllegalStateException if this method is called when there
478: * is no currently open element
479: * @exception IOException if an input/output error occurs
480: * @exception NullPointerException if <code>name</code> is
481: * <code>null</code>
482: */
483: public void writeURIAttribute(String name, Object value,
484: String componentPropertyName) throws IOException {
485: if (skipDepth != -1) {
486: return;
487: }
488: // assert current instanceof Element
489: if (current instanceof Element)
490: ((Element) current).setAttribute(name, value.toString());
491: }
492:
493: /**
494: * <p>Write a comment containing the specified text, after converting
495: * that text to a String if necessary. If there is an open element
496: * that has been created by a call to <code>startElement()</code>,
497: * that element will be closed first.</p>
498: *
499: * @param comment Text content of the comment
500: *
501: * @exception java.io.IOException if an input/output error occurs
502: * @exception java.lang.NullPointerException if <code>comment</code>
503: * is <code>null</code>
504: */
505: public void writeComment(Object comment) throws IOException {
506: if (skipDepth != -1) {
507: return;
508: }
509: closeStartIfNecessary();
510: current.appendChild(doc.createComment(comment.toString()));
511: }
512:
513: /**
514: * <p>Write an object (after converting it to a String, if necessary),
515: * after escaping it properly. If there is an open element
516: * that has been created by a call to <code>startElement()</code>,
517: * that element will be closed first.</p>
518: *
519: * <p>All angle bracket occurrences in the argument must be escaped
520: * using the &gt; &lt; syntax.</p>
521: *
522: * @param text Text to be written
523: *
524: * @param componentPropertyName May be <code>null</code>. If
525: * non-<code>null</code>, this is the name of the property in the
526: * associated component to which this piece of text applies.
527: *
528: * @exception IOException if an input/output error occurs
529: * @exception NullPointerException if <code>text</code>
530: * is <code>null</code>
531: */
532: public void writeText(Object text, String componentPropertyName)
533: throws IOException {
534: if (skipDepth != -1) {
535: return;
536: }
537: closeStartIfNecessary();
538: if (text == null) {
539: return;
540: }
541: String s = text.toString();
542: /* This was necessary when we were rendering to JSPX. We now render to HTML.
543: int n = s.length();
544: StringBuffer sb = new StringBuffer(2*n);
545: for (int i = 0; i < n; i++) {
546: char c = s.charAt(i);
547: switch (c) {
548: case '&': sb.append("&"); break;
549: case '"': sb.append("""); break;
550: case '<': sb.append("<"); break;
551: case '>': sb.append(">"); break;
552: // apos missing, see BrowserPreview code
553: default: sb.append(c);
554: }
555: }
556: current.appendChild(doc.createTextNode(sb.toString()));
557: */
558: current.appendChild(doc.createTextNode(s));
559: }
560:
561: /**
562: * <p>Write a single character, after escaping it properly. If there
563: * is an open element that has been created by a call to
564: * <code>startElement()</code>, that element will be closed first.</p>
565: *
566: * @param text Text to be written
567: *
568: * @exception java.io.IOException if an input/output error occurs
569: */
570: public void writeText(char text) throws IOException {
571: if (skipDepth != -1) {
572: return;
573: }
574: charHolder[0] = text;
575: writeText(charHolder, null);
576: }
577:
578: /**
579: * <p>Write text from a character array, after escaping it properly
580: * for this method. If there is an open element that has been
581: * created by a call to <code>startElement()</code>, that element
582: * will be closed first.</p>
583: *
584: * @param text Text to be written
585: * @param off Starting offset (zero-relative)
586: * @param len Number of characters to be written
587: *
588: * @exception java.lang.IndexOutOfBoundsException if the calculated starting or
589: * ending position is outside the bounds of the character array
590: * @exception java.io.IOException if an input/output error occurs
591: * @exception java.lang.NullPointerException if <code>text</code>
592: * is <code>null</code>
593: */
594: public void writeText(char[] text, int off, int len)
595: throws IOException {
596: if (skipDepth != -1) {
597: return;
598: }
599: closeStartIfNecessary();
600: current.appendChild(doc.createTextNode(new String(text, off,
601: len)));
602: }
603:
604: /**
605: * Creates a new instance of this ResponseWriter, using a different Writer.
606: */
607: public ResponseWriter cloneWithWriter(Writer writer) {
608: // How do we handle this? We need the writer to be resettable!
609: throw new RuntimeException(
610: "cloneWithWriter not supported by the Creator container!");
611: //return new PrettyJspWriter(writer);
612: }
613:
614: //--------------------------------------------------------------------------------------- Writer
615:
616: /**
617: * Close the stream, flushing it first. Once a stream has been closed, further write() or
618: * flush() invocations will cause an IOException to be thrown. Closing a previously-closed
619: * stream, however, has no effect.
620: *
621: * @exception java.io.IOException If an I/O error occurs
622: */
623: public void close() throws IOException {
624: if (skipDepth != -1) {
625: return;
626: }
627: closeStartIfNecessary();
628: }
629:
630: public void write(char cbuf) throws IOException {
631: charHolder[0] = cbuf;
632: write(new String(charHolder));
633: }
634:
635: public void write(char[] cbuf, int off, int len) throws IOException {
636: write(new String(cbuf, off, len));
637: }
638:
639: public void write(int c) throws IOException {
640: write((char) c);
641: }
642:
643: public void write(String str) throws IOException {
644: if (skipDepth != -1 || str == null) {
645: return;
646: }
647: closeStartIfNecessary();
648: if (str.indexOf('<') != -1) {
649: // The string contains unescaped markup! We've gotta parse the string instead
650: MarkupDesignBean bean = null;
651: // if (current instanceof RaveElement) {
652: // bean = ((RaveElement)current).getDesignBean();
653: // }
654: if (current instanceof Element) {
655: // bean = InSyncService.getProvider().getMarkupDesignBeanForElement((Element)current);
656: bean = MarkupUnit
657: .getMarkupDesignBeanForElement((Element) current);
658: }
659: // ((ParsingDocument)doc).appendParsedString(current, str, bean);
660: // InSyncService.getProvider().appendParsedString(doc, current, str, bean);
661: MarkupUnit unit = MarkupUnit.getMarkupUnitForDocument(doc);
662: if (unit != null) {
663: unit.appendParsedString(current, str, bean);
664: }
665: } else if (str.indexOf('&') != -1) { // contains entities
666: // <markup_separation>
667: // String expanded = MarkupServiceProvider.getDefault().expandHtmlEntities(str);
668: // ====
669: String expanded = Entities.expandHtmlEntities(str);
670: // </markup_separation>
671: current.appendChild(doc.createTextNode(expanded));
672: } else {
673: current.appendChild(doc.createTextNode(str));
674: }
675: }
676:
677: public void write(String str, int off, int len) throws IOException {
678: write(str.substring(off, len));
679: }
680:
681: /** Return the DocumentFragment being constructed by the writer */
682: public DocumentFragment getFragment() {
683: return frag;
684: }
685:
686: /**
687: * Set the "pre rendered" DocumentFragment for a particular bean.
688: * Note: Only ONE bean can be pre-rendered at a time; this is not
689: * a per-bean assignment. When set, this will cause the given
690: * DocumentFragment to be inserted into the output fragment
691: * rather than calling the bean's renderer.
692: *
693: * This is intended to be used for for example having the ability
694: * to "inline edit" a particular component's value; in that case
695: * since we're not updating the value attribute during editing,
696: * we want to suppress the normal rendered portion from the component
697: * and instead substitute the inline-edited document fragment
698: * corresponding to the parsed text output of the component.
699: */
700: public void setPreRendered(UIComponent bean, DocumentFragment df) {
701: preRendered = bean;
702: preRenderedFragment = df;
703: }
704:
705: private UIComponent preRendered;
706: private DocumentFragment preRenderedFragment;
707: private int depth;
708: private int skipDepth;
709: }
|