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:
042: /*
043: * $Id$
044: */
045:
046: package com.sun.rave.faces.renderer;
047:
048: import java.io.IOException;
049: import java.util.Iterator;
050: import java.util.Map;
051:
052: import javax.faces.FactoryFinder;
053: import javax.faces.application.Application;
054: import javax.faces.application.ApplicationFactory;
055: import javax.faces.application.FacesMessage;
056: import javax.faces.component.EditableValueHolder;
057: import javax.faces.component.NamingContainer;
058: import javax.faces.component.UIComponent;
059: import javax.faces.component.ValueHolder;
060: import javax.faces.component.UIViewRoot;
061: import javax.faces.context.ExternalContext;
062: import javax.faces.context.FacesContext;
063: import javax.faces.context.ResponseWriter;
064: import javax.faces.convert.Converter;
065: import javax.faces.convert.ConverterException;
066: import javax.faces.el.ValueBinding;
067: import javax.faces.render.Renderer;
068:
069: /**
070: * <p>Abstract base class for concrete implementations of
071: * <code>javax.faces.render.Renderer</code> for the
072: * <em>Widgets Library</em>.</p>
073: */
074:
075: public abstract class AbstractRenderer extends Renderer {
076:
077: // ------------------------------------------------------ Manifest Constants
078:
079: /**
080: * <p>The list of attribute names in the HTML 4.01 Specification that
081: * correspond to the entity type <em>%events;</em>.</p>
082: */
083: public static final String EVENTS_ATTRIBUTES[] = { "onclick",
084: "ondblclick", //NOI18N
085: "onmousedown", "onmouseup", "onmouseover", //NOI18N
086: "onmousemove", "onmouseout", //NOI18N
087: "onkeypress", "onkeydown", "onkeyup", //NOI18N
088: };
089:
090: /**
091: * <p>The list of attribute names in the HTML 4.01 Specification that
092: * correspond to the entity type <em>%i18n;</em>.</p>
093: */
094: public static final String I18N_ATTRIBUTES[] = { "dir", "lang", }; //NOI18N
095:
096: // -------------------------------------------------------- Static Variables
097:
098: // ---------------------------------------------------------- Public Methods
099:
100: /**
101: * <p>Decode any new state of the specified <code>UIComponent</code>
102: * from the request contained in the specified <code>FacesContext</code>,
103: * and store that state on the <code>UIComponent</code>.</p>
104: *
105: * <p>The default implementation calls <code>setSubmittedValue()</code>
106: * unless this component has a boolean <code>disabled</code> or
107: * <code>readonly</code> attribute that is set to <code>true</code>.</p>
108: *
109: * @param context <code>FacesContext</code> for the current request
110: * @param component <code>UIComponent</code> to be decoded
111: *
112: * @exception NullPointerException if <code>context</code> or
113: * <code>component</code> is <code>null</code>
114: */
115: public void decode(FacesContext context, UIComponent component) {
116:
117: // Enforce NPE requirements in the Javadocs
118: if ((context == null) || (component == null)) {
119: throw new NullPointerException();
120: }
121:
122: // Disabled or readonly components are not decoded
123: if (isDisabled(component) || isReadOnly(component)) {
124: return;
125: }
126:
127: // Save submitted value on EditableValueHolder components
128: if (component instanceof EditableValueHolder) {
129: setSubmittedValue(context, component);
130: }
131:
132: }
133:
134: /**
135: * <p>Render the beginning of the specified <code>UIComponent</code>
136: * to the output stream or writer associated with the response we are
137: * creating.</p>
138: *
139: * <p>The default implementation calls <code>renderStart()</code> and
140: * <code>renderAttributes()</code>.</p>
141: *
142: * @param context <code>FacesContext</code> for the current request
143: * @param component <code>UIComponent</code> to be decoded
144: *
145: * @exception NullPointerException if <code>context</code> or
146: * <code>component</code> is <code>null</code>
147: *
148: * @exception IOException if an input/output error occurs
149: */
150: public void encodeBegin(FacesContext context, UIComponent component)
151: throws IOException {
152:
153: // Enforce NPE requirements in the Javadocs
154: if ((context == null) || (component == null)) {
155: throw new NullPointerException();
156: }
157:
158: // Render the element and attributes for this component
159: ResponseWriter writer = context.getResponseWriter();
160: renderStart(context, component, writer);
161: renderAttributes(context, component, writer);
162:
163: }
164:
165: /**
166: * <p>Render the children of the specified <code>UIComponent</code>
167: * to the output stream or writer associated with the response we are
168: * creating.</p>
169: *
170: * <p>The default implementation iterates through the children of
171: * this component and renders them.</p>
172: *
173: * @param context <code>FacesContext</code> for the current request
174: * @param component <code>UIComponent</code> to be decoded
175: *
176: * @exception NullPointerException if <code>context</code> or
177: * <code>component</code> is <code>null</code>
178: *
179: * @exception IOException if an input/output error occurs
180: */
181: public void encodeChildren(FacesContext context,
182: UIComponent component) throws IOException {
183:
184: if (context == null || component == null) {
185: throw new NullPointerException();
186: }
187:
188: Iterator kids = component.getChildren().iterator();
189: while (kids.hasNext()) {
190: UIComponent kid = (UIComponent) kids.next();
191: kid.encodeBegin(context);
192: if (kid.getRendersChildren()) {
193: kid.encodeChildren(context);
194: }
195: kid.encodeEnd(context);
196: }
197:
198: }
199:
200: /**
201: * <p>Render the ending of the specified <code>UIComponent</code>
202: * to the output stream or writer associated with the response we are
203: * creating.</p>
204: *
205: * <p>The default implementation calls <code>renderEnd()</code>.</p>
206: *
207: * @param context <code>FacesContext</code> for the current request
208: * @param component <code>UIComponent</code> to be decoded
209: *
210: * @exception NullPointerException if <code>context</code> or
211: * <code>component</code> is <code>null</code>
212: *
213: * @exception IOException if an input/output error occurs
214: */
215: public void encodeEnd(FacesContext context, UIComponent component)
216: throws IOException {
217:
218: // Enforce NPE requirements in the Javadocs
219: if ((context == null) || (component == null)) {
220: throw new NullPointerException();
221: }
222:
223: // Render the element closing for this component
224: ResponseWriter writer = context.getResponseWriter();
225: renderEnd(context, component, writer);
226:
227: }
228:
229: // --------------------------------------------------------- Package Methods
230:
231: // ------------------------------------------------------- Protected Methods
232:
233: /**
234: * <p>Render nested child components by invoking the encode methods
235: * on those components, but only when the <code>rendered</code>
236: * property is <code>true</code>.</p>
237: */
238: protected void encodeRecursive(FacesContext context,
239: UIComponent component) throws IOException {
240:
241: // suppress rendering if "rendered" property on the component is
242: // false.
243: if (!component.isRendered()) {
244: return;
245: }
246:
247: // Render this component and its children recursively
248: component.encodeBegin(context);
249: if (component.getRendersChildren()) {
250: component.encodeChildren(context);
251: } else {
252: Iterator kids = component.getChildren().iterator();
253: while (kids.hasNext()) {
254: UIComponent kid = (UIComponent) kids.next();
255: encodeRecursive(context, kid);
256: }
257: }
258: component.encodeEnd(context);
259:
260: }
261:
262: /**
263: * <p>Return the <code>Application</code> instance for this
264: * web application.</p>
265: */
266: protected Application getApplication() {
267:
268: return getFacesContext().getApplication();
269:
270: }
271:
272: /**
273: * <p>Return the value to be stored, as an Object that has been
274: * converted from the String representation (if necessary), or
275: * <code>null</code> if the String representation is null.</p>
276: *
277: * @param context FacesContext for the current request
278: * @param component Component whose value is being processed
279: * (must be a component that implements ValueHolder
280: * @param value String representation of the value
281: */
282: protected Object getAsObject(FacesContext context,
283: UIComponent component, String value) {
284:
285: if (value == null) {
286: return null;
287: }
288: Converter converter = ((ValueHolder) component).getConverter();
289: if (converter == null) {
290: ValueBinding vb = component.getValueBinding("value"); //NOI18N
291: if (vb != null) {
292: Class clazz = vb.getType(context);
293: if (clazz != null) {
294: converter = getApplication().createConverter(clazz);
295: }
296: }
297: }
298: if (converter != null) {
299: return converter.getAsObject(context, component, value);
300: } else {
301: return value;
302: }
303:
304: }
305:
306: /**
307: * <p>Return the value to be rendered, as a String (converted
308: * if necessary), or <code>null</code> if the value is null.</p>
309: *
310: * @param context FacesContext for the current request
311: * @param component Component whose value is to be retrieved (must be
312: * a component that implements ValueHolder)
313: */
314: protected String getAsString(FacesContext context,
315: UIComponent component) {
316: if (component instanceof EditableValueHolder) {
317: Object submittedValue = ((EditableValueHolder) component)
318: .getSubmittedValue();
319: if (submittedValue != null) {
320: return (String) submittedValue;
321: }
322: }
323: Object value = ((ValueHolder) component).getValue();
324: if (value == null) {
325: return null;
326: }
327: Converter converter = ((ValueHolder) component).getConverter();
328: if (converter == null) {
329: if (value instanceof String) {
330: return (String) value;
331: }
332: converter = getApplication().createConverter(
333: value.getClass());
334: }
335: if (converter != null) {
336: return converter.getAsString(context, component, value);
337: } else {
338: return value.toString();
339: }
340: }
341:
342: /**
343: * <p>Return the <code>ExternalContext</code> instance for the current
344: * request.</p>
345: */
346: protected ExternalContext getExternalContext() {
347:
348: return (FacesContext.getCurrentInstance().getExternalContext());
349:
350: }
351:
352: /**
353: * <p>Return the <code>FacesContext</code> instance for the current
354: * request.</p>
355: */
356: protected FacesContext getFacesContext() {
357:
358: return (FacesContext.getCurrentInstance());
359:
360: }
361:
362: /**
363: * <p>Return <code>true</code> if the specified component is disabled.</p>
364: *
365: * @param component <code>UIComponent</code> to be checked
366: */
367: protected boolean isDisabled(UIComponent component) {
368:
369: Object disabled = component.getAttributes().get("disabled"); //NOI18N
370: if (disabled == null) {
371: return (false);
372: }
373: if (disabled instanceof String) {
374: return (Boolean.valueOf((String) disabled).booleanValue());
375: } else {
376: return (disabled.equals(Boolean.TRUE));
377: }
378:
379: }
380:
381: /**
382: * <p>Return <code>true</code> if the specified component is read only.</p>
383: *
384: * @param component <code>UIComponent</code> to be checked
385: */
386: protected boolean isReadOnly(UIComponent component) {
387:
388: Object readonly = component.getAttributes().get("readonly"); //NOI18N
389: if (readonly == null) {
390: return (false);
391: }
392: if (readonly instanceof String) {
393: return (Boolean.valueOf((String) readonly).booleanValue());
394: } else {
395: return (readonly.equals(Boolean.TRUE));
396: }
397:
398: }
399:
400: /**
401: * <p>Render the element attributes for the generated markup related to this
402: * component. Simple renderers that create a single markup element
403: * for this component should override this method and include calls to
404: * to <code>writeAttribute()</code> and <code>writeURIAttribute</code>
405: * on the specified <code>ResponseWriter</code>.</p>
406: *
407: * <p>The default implementation does nothing.</p>
408: *
409: * @param context <code>FacesContext</code> for the current request
410: * @param component <code>EditableValueHolder</code> component whose
411: * submitted value is to be stored
412: * @param writer <code>ResponseWriter</code> to which the element
413: * start should be rendered
414: *
415: * @exception IOException if an input/output error occurs
416: */
417: protected void renderAttributes(FacesContext context,
418: UIComponent component, ResponseWriter writer)
419: throws IOException {
420:
421: }
422:
423: private static final String corePassThrough[] = { "style", "title" }; //NOI18N
424:
425: /**
426: * <p>Render the "core" set of attributes for this <code>UIComponent</code>.
427: * The default implementation conditionally generates the following
428: * attributes with values as specified.</p>
429: * <ul>
430: * <li><strong>id</strong> - If this component has a non-<code>null</code>
431: * <code>id</code> property, and the identifier does not start with
432: * <code>UIViewRoot.UNIQUE_ID_PREFIX</code>, render the
433: * <code>clientId</code>.</li>
434: * <li><strong>class</strong> - If this component has a
435: * non-<code>null</code> <code>styleClass</code> attribute, render its
436: * value, combined with the syles parameter (if any).</li>
437: * <li><strong>style</strong> - If this component has a
438: * non-<code>null</code> <code>style</code> attribute, render its
439: * value.</li>
440: * <li><strong>title</strong> - If this component has a
441: * non-<code>null</code> <code>title</code> attribute, render its
442: * value.</li>
443: * </ul>
444: *
445: * @param context <code>FacesContext</code> for the current request
446: * @param component <code>EditableValueHolder</code> component whose
447: * submitted value is to be stored
448: * @param writer <code>ResponseWriter</code> to which the element
449: * start should be rendered
450: * @param styles Space-separated list of CSS style classes to add
451: * to the <code>class</code> attribute, or <code>null</code> for none
452: *
453: * @exception IOException if an input/output error occurs
454: */
455: protected void renderCoreAttributes(FacesContext context,
456: UIComponent component, ResponseWriter writer, String styles)
457: throws IOException {
458:
459: String id = component.getId();
460: if ((id != null) && !id.startsWith(UIViewRoot.UNIQUE_ID_PREFIX)) {
461: writer.writeAttribute("id", component.getClientId(context),
462: "id"); //NOI18N
463: }
464: String classes = getStyleClasses(component, styles);
465: if (classes != null) {
466: writer.writeAttribute("class", classes, "styleClass"); //NOI18N
467: }
468: renderPassThrough(context, component, writer, corePassThrough);
469:
470: }
471:
472: /**
473: * <p>Render the element end for the generated markup related to this
474: * component. Simple renderers that create a single markup element
475: * for this component should override this method and include a call
476: * to <code>endElement()</code> on the specified
477: * <code>ResponseWriter</code>.</p>
478: *
479: * <p>The default implementation does nothing.</p>
480: *
481: * @param context <code>FacesContext</code> for the current request
482: * @param component <code>EditableValueHolder</code> component whose
483: * submitted value is to be stored
484: * @param writer <code>ResponseWriter</code> to which the element
485: * start should be rendered
486: *
487: * @exception IOException if an input/output error occurs
488: */
489: protected void renderEnd(FacesContext context,
490: UIComponent component, ResponseWriter writer)
491: throws IOException {
492:
493: }
494:
495: /**
496: * <p>Render any boolean attributes on the specified list that have
497: * <code>true</code> values on the corresponding attribute of the
498: * specified <code>UIComponent</code>.</p>
499: *
500: * @param context <code>FacesContext</code> for the current request
501: * @param component <code>EditableValueHolder</code> component whose
502: * submitted value is to be stored
503: * @param writer <code>ResponseWriter</code> to which the element
504: * start should be rendered
505: * @param names List of attribute names to be passed through
506: *
507: * @exception IOException if an input/output error occurs
508: */
509: protected void renderBoolean(FacesContext context,
510: UIComponent component, ResponseWriter writer,
511: String names[]) throws IOException {
512:
513: if (names == null) {
514: return;
515: }
516: Map attributes = component.getAttributes();
517: boolean flag;
518: Object value;
519: for (int i = 0; i < names.length; i++) {
520: value = attributes.get(names[i]);
521: if (value != null) {
522: if (value instanceof String) {
523: flag = Boolean.valueOf((String) value)
524: .booleanValue();
525: } else {
526: flag = Boolean.valueOf(value.toString())
527: .booleanValue();
528: }
529: if (flag) {
530: writer.writeAttribute(names[i], names[i], names[i]);
531: flag = false;
532: }
533: }
534: }
535:
536: }
537:
538: /**
539: * <p>Render any attributes on the specified list directly to the
540: * specified <code>ResponseWriter</code> for which the specified
541: * <code>UIComponent</code> has a non-<code>null</code> attribute value.
542: * This method may be used to "pass through" commonly used attribute
543: * name/value pairs with a minimum of code.</p>
544: *
545: * @param context <code>FacesContext</code> for the current request
546: * @param component <code>EditableValueHolder</code> component whose
547: * submitted value is to be stored
548: * @param writer <code>ResponseWriter</code> to which the element
549: * start should be rendered
550: * @param names List of attribute names to be passed through
551: *
552: * @exception IOException if an input/output error occurs
553: */
554: protected void renderPassThrough(FacesContext context,
555: UIComponent component, ResponseWriter writer,
556: String names[]) throws IOException {
557:
558: if (names == null) {
559: return;
560: }
561: Map attributes = component.getAttributes();
562: Object value;
563: for (int i = 0; i < names.length; i++) {
564: value = attributes.get(names[i]);
565: if (value != null) {
566: if (value instanceof String) {
567: writer.writeAttribute(names[i], (String) value,
568: names[i]);
569: } else {
570: writer.writeAttribute(names[i], value.toString(),
571: names[i]);
572: }
573: }
574: }
575:
576: }
577:
578: /**
579: * <p>Render the element start for the generated markup related to this
580: * component. Simple renderers that create a single markup element
581: * for this component should override this method and include a call
582: * to <code>startElement()</code> on the specified
583: * <code>ResponseWriter</code>.</p>
584: *
585: * <p>The default implementation does nothing.</p>
586: *
587: * @param context <code>FacesContext</code> for the current request
588: * @param component <code>EditableValueHolder</code> component whose
589: * submitted value is to be stored
590: * @param writer <code>ResponseWriter</code> to which the element
591: * start should be rendered
592: *
593: * @exception IOException if an input/output error occurs
594: */
595: protected void renderStart(FacesContext context,
596: UIComponent component, ResponseWriter writer)
597: throws IOException {
598:
599: }
600:
601: /**
602: * <p>If a submitted value was included on this request, store it in the
603: * component as appropriate.</p>
604: *
605: * <p>The default implementation determines whether this component
606: * implements <code>EditableValueHolder</code>. If so, it checks for a
607: * request parameter with the same name as the <code>clientId</code>
608: * of this <code>UIComponent</code>. If there is such a parameter, its
609: * value is passed (as a String) to the <code>setSubmittedValue()</code>
610: * method on the <code>EditableValueHolder</code> component.</p>
611: *
612: * @param context <code>FacesContext</code> for the current request
613: * @param component <code>EditableValueHolder</code> component whose
614: * submitted value is to be stored
615: */
616: protected void setSubmittedValue(FacesContext context,
617: UIComponent component) {
618:
619: if (!(component instanceof EditableValueHolder)) {
620: return;
621: }
622: String clientId = component.getClientId(context);
623: Map parameters = context.getExternalContext()
624: .getRequestParameterMap();
625: if (parameters.containsKey(clientId)) {
626: component.getAttributes().put("submittedValue", //NOI18N
627: (String) parameters.get(clientId));
628: }
629:
630: }
631:
632: // --------------------------------------------------------- Private Methods
633:
634: /**
635: * <p>Return a space-separated list of CSS style classes to render for
636: * this component, or <code>null</code> for none.</p>
637: *
638: * @param component <code>UIComponent</code> for which to calculate classes
639: * @param styles Additional styles specified by the renderer
640: */
641: private String getStyleClasses(UIComponent component, String styles) {
642: String styleClass = (String) component.getAttributes().get(
643: "styleClass"); //NOI18N
644: if (styleClass != null) {
645: if (styles != null) {
646: return styleClass + " " + styles; //NOI18N
647: } else {
648: return styleClass;
649: }
650: } else {
651: if (styles != null) {
652: return styles;
653: } else {
654: return null;
655: }
656: }
657: }
658:
659: }
|