View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.trinidad.render;
20  
21  
22  import java.beans.Beans;
23  
24  import java.io.IOException;
25  
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  
33  import javax.faces.application.ResourceHandler;
34  import javax.faces.component.UIComponent;
35  import javax.faces.component.UIParameter;
36  import javax.faces.component.UIViewRoot;
37  import javax.faces.component.behavior.ClientBehavior;
38  import javax.faces.component.behavior.ClientBehaviorContext;
39  import javax.faces.component.behavior.ClientBehaviorHolder;
40  import javax.faces.component.visit.VisitCallback;
41  import javax.faces.component.visit.VisitContext;
42  import javax.faces.component.visit.VisitResult;
43  import javax.faces.context.FacesContext;
44  import javax.faces.render.Renderer;
45  
46  import org.apache.myfaces.trinidad.bean.FacesBean;
47  import org.apache.myfaces.trinidad.component.UIXComponent;
48  import org.apache.myfaces.trinidad.context.Agent;
49  import org.apache.myfaces.trinidad.context.PartialPageContext;
50  import org.apache.myfaces.trinidad.context.RenderingContext;
51  import org.apache.myfaces.trinidad.context.RequestContext;
52  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
53  
54  
55  /**
56   * Basic implementation of the core rendering functionality
57   * across render kits.
58   */
59  public class CoreRenderer extends Renderer
60  {
61    // TODO Move elsewhere?
62    static public final char CHAR_UNDEFINED = (char) -1;
63    static public final int NO_CHILD_INDEX = -1;
64  
65    protected CoreRenderer()
66    {
67    }
68  
69    /**
70     * Allows the rendered to specify what components should be involved with rendered children
71     * life-cycle operations and methods.
72     *
73     * @param facesContext the faces context
74     * @param component the component from which to get the rendered facets and children
75     * @see UIXComponentBase#getRenderedFacetsAndChildren(FacesContext)
76     * @return A list of components to process as rendered components. Defaults to all facets and
77     * children of a component
78     */
79    public Iterator<UIComponent> getRenderedFacetsAndChildren(
80      FacesContext facesContext,
81      UIComponent  component)
82    {
83      return component.getFacetsAndChildren();
84    }
85  
86    /**
87     * <p>
88     * Called when visiting the CoreRenderer's component during optimized partial page encoding so
89     * that the CoreRenderer can modify what is actually encoded.  For example tab controls often
90     * render the tabs for the ShowDetailItems in the tab bar before delegating to the
91     * disclosed ShowDetailItem to render the tab content.  As a result, the tab control
92     * needs to encode its tab bar if any of its ShowDetailItems are partial targets so that
93     * the tab labels, for example, are up-to-date.
94     * </p>
95     * <p>
96     * The default implementation calls the VisitCallback and returns its result if this UIXComponent
97     * is a partial target of the current encoding.
98     * </p>
99     * @param visitContext VisitContext to pass to the VisitCallback
100    * @param partialContext PartialPageContext for the current partial encoding
101    * @param component The component for the CoreRenderer to visit
102    * @param callback VisitCallback to call if this component is a partial target
103    * @return The VisitResult controlling continued iteration of the visit.
104    */
105   public VisitResult partialEncodeVisit(
106     VisitContext       visitContext,
107     PartialPageContext partialContext,
108     UIComponent        component,
109     VisitCallback      callback)
110   {
111     if (partialContext.isPossiblePartialTarget(component.getId()) &&
112         partialContext.isPartialTarget(component.getClientId(visitContext.getFacesContext())))
113     {
114       // visit the component instance
115       return callback.visit(visitContext, component);
116     }
117     else
118     {
119       // Not visiting this component, but allow visit to
120       // continue into this subtree in case we've got
121       // visit targets there.
122       return VisitResult.ACCEPT;
123     }
124   }
125 
126   /**
127    * <p>
128    * Called before rendering the current comopnent's children in order to set
129    * up any special context.
130    * </p>
131    * <p>If <code>setupEncodingContext</code> succeeds then
132    * <code>tearDownEncodingContext</code> will be called for the same component.
133    * </p>
134    * <p>The default implementation does nothing</p>
135    * @param context FacesContext for this request
136    * @param rc RenderingContext for this encoding pass
137    * @ param component Component to encode using this Renderer
138    * @see #tearDownEncodingContext
139    */
140   public void setupEncodingContext(
141     FacesContext     context,
142     RenderingContext rc,
143     UIComponent      component)
144   {
145     // TODO Remove after one release
146     if (component instanceof UIXComponent)
147       setupEncodingContext(context, rc, (UIXComponent)component);
148 
149   }
150 
151   // TODO Remove after one release
152   @Deprecated
153   public void setupEncodingContext(
154     @SuppressWarnings("unused") FacesContext context,
155     @SuppressWarnings("unused") RenderingContext rc,
156     @SuppressWarnings("unused") UIXComponent component)
157   {
158   }
159 
160   /**
161    * <p>
162    * Called before rendering the current component's children in order to set
163    * up any special context.
164    * </p>
165    * <p>If <code>setupChildrenEncodingContext</code> succeeds then
166    * <code>tearDownChildrenEncodingContext</code> will be called for the same component.
167    * </p>
168    * <p>The default implementation does nothing</p>
169    * @param context FacesContext for this request
170    * @param rc RenderingContext for this encoding pass
171    * @param component Component to encode using this Renderer
172    * @see #tearDownChildrenEncodingContext
173    */
174   public void setupChildrenEncodingContext(
175     @SuppressWarnings("unused") FacesContext context,
176     @SuppressWarnings("unused") RenderingContext rc,
177     @SuppressWarnings("unused") UIComponent component)
178   {
179   }
180 
181   /**
182    * <p>
183    * Called after rendering the current component in order to tear
184    * down any special context.
185    * </p>
186    * <p>
187    * <code>tearDownEncodingContext</code> will be called on the component if
188    * <code>setupEncodingContext</code> succeeded.
189    * </p>
190    * <p>The default implementation does nothing</p>
191    * @param context FacesContext for this request
192    * @param rc RenderingContext for this encoding pass
193    * @param component Component to encode using this Renderer
194    * @see #setupEncodingContext
195    */
196   public void tearDownEncodingContext(
197     FacesContext     context,
198     RenderingContext rc,
199     UIComponent     component)
200   {
201     // TODO Remove after one release
202     if (component instanceof UIXComponent)
203       tearDownEncodingContext(context, rc, (UIXComponent)component);
204   }
205 
206   /**
207    * Hook to allow the renderer to customize the visitation of the children components
208    * of a component during the visitation of a component during rendering.
209    *
210    * @param component the component which owns the children to visit
211    * @param visitContext the visitation context
212    * @param callback the visit callback
213    * @return <code>true</code> if the visit is complete.
214    * @see UIXComponent#visitChildren(VisitContext, VisitCallback)
215    */
216   public boolean visitChildrenForEncoding(
217     UIXComponent  component,
218     VisitContext  visitContext,
219     VisitCallback callback)
220   {
221     // visit the children of the component
222     Iterator<UIComponent> kids = getRenderedFacetsAndChildren(
223                                    visitContext.getFacesContext(), component);
224 
225     while (kids.hasNext())
226     {
227       // If any kid visit returns true, we are done.
228       if (kids.next().visitTree(visitContext, callback))
229       {
230         return true;
231       }
232     }
233 
234     return false;
235   }
236 
237   // TODO Remove after one release
238   @Deprecated
239   public void tearDownEncodingContext(
240     @SuppressWarnings("unused") FacesContext context,
241     @SuppressWarnings("unused") RenderingContext rc,
242     @SuppressWarnings("unused") UIXComponent     component)
243   {
244   }
245 
246   /**
247    * <p>
248    * Called after rendering the current component's children in order to tear
249    * down any special context.
250    * </p>
251    * <p>
252    * <code>tearDownChildrenEncodingContext</code> will be called on the component if
253    * <code>setupChildrenEncodingContext</code> succeeded.
254    * </p>
255    * <p>The default implementation does nothing</p>
256    * @param context FacesContext for this request
257    * @param rc RenderingContext for this encoding pass
258    * @param component Component to encode using this Renderer
259    * @see #setupChildrenEncodingContext
260    */
261   public void tearDownChildrenEncodingContext(
262     @SuppressWarnings("unused") FacesContext context,
263     @SuppressWarnings("unused") RenderingContext rc,
264     @SuppressWarnings("unused") UIComponent component)
265   {
266   }
267 
268   // Note this should probably be made final, but since it is new, doing so could
269   // break compatibility with present sub-classes
270 
271   /**
272    * {@inheritDoc}
273    * <p>
274    * Sub-classes should override the
275    * {@link #decode(FacesContext, UIComponent, FacesBean, String)} method
276    * to perform their own decoding logic
277    * </p>
278    *
279    * @see #decode(FacesContext, UIComponent, FacesBean, String)
280    */
281   @Override
282   public final void decode(
283     FacesContext facesContext,
284     UIComponent  component)
285   {
286     FacesBean facesBean = getFacesBean(component);
287     String clientId = null;
288     if (facesBean != null)
289     {
290       clientId = decodeBehaviors(facesContext, component, facesBean);
291     }
292     decode(facesContext, component, facesBean, clientId);
293   }
294 
295   /**
296    * Hook for sub-classes to perform their own decode logic
297    * @param facesContext the faces context
298    * @param component the component to decode
299    * @param facesBean the faces bean for the component
300    * @param clientId the client ID if it has been retrieved already
301    * during decoding, otherwise it will be null. Passed in for performance
302    * reasons, so that if it has already been retrieved it will not need to be
303    * retrieved again
304    */
305   protected void decode(
306     @SuppressWarnings("unused") FacesContext facesContext,
307     @SuppressWarnings("unused") UIComponent  component,
308     @SuppressWarnings("unused") FacesBean    facesBean,
309     @SuppressWarnings("unused") String       clientId)
310   {
311     // No-op
312   }
313 
314   //
315   // COERCION HELPERS
316   //
317 
318   /**
319    * Coerces an object into a String.
320    */
321   static public String toString(Object o)
322   {
323     if (o == null)
324       return null;
325     return o.toString();
326   }
327 
328   /**
329    * Coerces an object into a resource URI, calling the view-handler.
330    */
331   static public String toResourceUri(FacesContext fc, Object o)
332   {
333     if (o == null)
334       return null;
335 
336     String uri = o.toString();
337 
338     // *** EL Coercion problem ***
339     // If icon or image attribute was declared with #{resource[]} and that expression
340     // evaluates to null (it means ResourceHandler.createResource returns null because requested resource does not exist)
341     // EL implementation turns null into ""
342     // see http://www.irian.at/blog/blogid/unifiedElCoercion/#unifiedElCoercion
343     if (uri.length() == 0)
344     {
345       return null;
346     }
347 
348 
349     // With JSF 2.0 url for resources can be done with EL like #{resource['resourcename']}
350     // and such EL after evalution contains context path for the current web application already,
351     // -> we dont want call viewHandler.getResourceURL()
352     if (uri.contains(ResourceHandler.RESOURCE_IDENTIFIER))
353     {
354       return uri;
355     }
356 
357     // Treat two slashes as server-relative
358     if (uri.startsWith("//"))
359     {
360       return uri.substring(1);
361     }
362     else
363     {
364       // If the specified path starts with a "/",
365       // following method will prefix it with the context path for the current web application,
366       // and return the result
367       return fc.getApplication().getViewHandler().getResourceURL(fc, uri);
368     }
369   }
370 
371   /**
372    * Coerces an object into an action URI, calling the view-handler.
373    */
374   static public String toActionUri(FacesContext fc, Object o)
375   {
376     if (o == null)
377       return null;
378 
379     String uri = o.toString();
380 
381     // Treat two slashes as server-relative
382     if (uri.startsWith("//"))
383     {
384       return uri.substring(1);
385     }
386     else
387     {
388       return fc.getApplication().getViewHandler().getActionURL(fc, uri);
389     }
390   }
391 
392   /**
393    * Coerces an object into a resource URI, calling the view-handler.
394    * @deprecated use toResourceUri
395    */
396   @Deprecated
397   static public String toUri(Object o)
398   {
399     return toResourceUri(FacesContext.getCurrentInstance(),o);
400   }
401 
402   /**
403    * Returns the integer value of an object;  this does
404    * not support null (which must be substituted with a default
405    * before calling).
406    */
407   static public int toInt(Object o)
408   {
409     return ((Number) o).intValue();
410   }
411 
412   /**
413    * Returns the integer value of an object;  this does
414    * not support null (which must be substituted with a default
415    * before calling).
416    */
417   static public long toLong(Object o)
418   {
419     return ((Number) o).longValue();
420   }
421 
422   /**
423    * Returns the character value of an object, XhtmlConstants.CHAR_UNDEFINED
424    * if there is none.
425    */
426   static public char toChar(Object o)
427   {
428     if (o == null)
429       return CHAR_UNDEFINED;
430 
431     char c;
432     if (o instanceof Character)
433     {
434       c = ((Character) o).charValue();
435     }
436     else
437     {
438       // If it's not a Character object, then let's turn it into
439       // a CharSequence and grab the first character
440       CharSequence cs;
441       if (o instanceof CharSequence)
442       {
443         cs = (CharSequence) o;
444       }
445       else
446       {
447         cs = o.toString();
448       }
449 
450       if (cs.length() == 0)
451         c = CHAR_UNDEFINED;
452       else
453         c = cs.charAt(0);
454     }
455 
456     // Handle the occasional odd bit of code that likes
457     // returning null, and treat it identically to UNDEFINED.
458     if (c == '\u0000')
459       c = CHAR_UNDEFINED;
460 
461     return c;
462   }
463 
464   @Override
465   public final void encodeBegin(
466     FacesContext context,
467     UIComponent  component
468     ) throws IOException
469   {
470     if (!getRendersChildren())
471     {
472       RenderingContext rc = RenderingContext.getCurrentInstance();
473       if (rc == null)
474         throw new IllegalStateException(_LOG.getMessage(
475           "NO_RENDERINGCONTEXT"));
476 
477       FacesBean bean = getFacesBean(component);
478 
479       beforeEncode(context, rc, component, bean);
480       encodeBegin(context, rc, component, bean);
481     }
482   }
483 
484   @Override
485   public final void encodeChildren(
486     FacesContext context,
487     UIComponent  component)
488   {
489     // encodeChildren() is fairly useless - it's simpler to just
490     // put the output in encodeEnd(), or use the encodeAll() hook
491   }
492 
493   @Override
494   public final void encodeEnd(
495     FacesContext context,
496     UIComponent  component
497     ) throws IOException
498   {
499     RenderingContext rc = RenderingContext.getCurrentInstance();
500     if (rc == null)
501       throw new IllegalStateException(_LOG.getMessage(
502         "NO_RENDERINGCONTEXT"));
503 
504     FacesBean bean = getFacesBean(component);
505     RuntimeException re = null;
506     try
507     {
508       if (getRendersChildren())
509       {
510         beforeEncode(context, rc, component, bean);
511         encodeAll(context, rc, component, bean);
512       }
513       else
514       {
515         encodeEnd(context, rc, component, bean);
516       }
517     }
518     catch (RuntimeException ex)
519     {
520       re = ex;
521     }
522     finally
523     {
524       try
525       {
526         afterEncode(context, rc, component, bean);
527       }
528       catch (RuntimeException ex)
529       {
530         if (re == null)
531         {
532           throw ex;
533         }
534         _LOG.warning(ex);
535       }
536 
537       if (re != null)
538       {
539         throw re;
540       }
541     }
542   }
543 
544   /**
545    * Hook for rendering the start of a component;  only
546    * called if getRendersChildren() is <em>false</em>.
547    */
548   protected void encodeBegin(
549     FacesContext     context,
550     RenderingContext rc,
551     UIComponent      component,
552     FacesBean        bean
553     ) throws IOException
554   {
555     if (getRendersChildren())
556       throw new IllegalStateException();
557   }
558 
559   /**
560    * Hook for rendering the component resources for the <code>target</code>.
561    * @param context Current <code>FacesContext</code> object for this request.
562    * @param target The target for the resources (e.g. head/body/form)
563    *
564    * @throws IOException
565    */
566   protected final void encodeComponentResources(
567     FacesContext context,
568     String       target
569     ) throws IOException
570   {
571     if(target != null)
572     {
573       UIViewRoot viewRoot = context.getViewRoot();
574       for(UIComponent componentResource : viewRoot.getComponentResources(context, target))
575       {
576         componentResource.encodeAll(context);
577       }
578     }
579   }
580 
581   /**
582    * Hook for rendering the end of a component;  only
583    * called if getRendersChildren() is <em>false</em>.
584    */
585   protected void encodeEnd(
586     FacesContext     context,
587     RenderingContext rc,
588     UIComponent      component,
589     FacesBean        bean
590     ) throws IOException
591   {
592     if (getRendersChildren())
593       throw new IllegalStateException();
594   }
595 
596   /**
597    * Hook for rendering all of a component;  only
598    * called if getRendersChildren() is <em>true</em>.
599    */
600   protected void encodeAll(
601     FacesContext     context,
602     RenderingContext rc,
603     UIComponent      component,
604     FacesBean        bean
605     ) throws IOException
606   {
607     if (!getRendersChildren())
608       throw new IllegalStateException();
609   }
610 
611   /**
612    * Hook for encoding a child;  this assumes that isRendered()
613    * has already been called. (RenderUtils.encodeRecursive()
614    * can be used if you don't need that check.)
615    * =-=AEW Ugh.
616    */
617   @SuppressWarnings("unchecked")
618   protected void encodeChild(
619     FacesContext context,
620     UIComponent  child
621     ) throws IOException
622   {
623     assert(child.isRendered());
624     child.encodeAll(context);
625   }
626 
627   @SuppressWarnings("unchecked")
628   protected void encodeAllChildren(
629     FacesContext context,
630     UIComponent  component
631     ) throws IOException
632   {
633     int childCount = component.getChildCount();
634     if (childCount == 0)
635       return;
636 
637     for (UIComponent child : (List<UIComponent>)component.getChildren())
638     {
639       if (child.isRendered())
640       {
641         encodeChild(context, child);
642       }
643     }
644   }
645 
646   protected void delegateRenderer(
647     FacesContext     context,
648     RenderingContext rc,
649     UIComponent      component,
650     FacesBean        bean,
651     CoreRenderer     renderer
652     ) throws IOException
653   {
654     if (renderer.getRendersChildren())
655     {
656       renderer.encodeAll(context, rc, component, bean);
657     }
658     else
659     {
660       throw new IllegalStateException();
661     }
662   }
663 
664   protected void delegateRendererBegin(
665     FacesContext     context,
666     RenderingContext rc,
667     UIComponent      component,
668     FacesBean        bean,
669     CoreRenderer     renderer
670     ) throws IOException
671   {
672     if (renderer.getRendersChildren())
673     {
674       throw new IllegalStateException();
675     }
676     else
677     {
678       renderer.encodeBegin(context, rc, component, bean);
679     }
680   }
681 
682   protected void delegateRendererEnd(
683     FacesContext     context,
684     RenderingContext rc,
685     UIComponent      component,
686     FacesBean        bean,
687     CoreRenderer     renderer
688     ) throws IOException
689   {
690     if (renderer.getRendersChildren())
691     {
692       throw new IllegalStateException();
693     }
694     else
695     {
696       renderer.encodeEnd(context, rc, component, bean);
697     }
698   }
699 
700   /**
701    * Renders the client ID as an "id".
702    */
703   protected void renderId(
704     FacesContext context,
705     UIComponent component
706     ) throws IOException
707   {
708     if (shouldRenderId(context, component))
709     {
710       String clientId = getClientId(context, component);
711       context.getResponseWriter().writeAttribute("id", clientId, "id");
712     }
713   }
714 
715   /**
716    * Returns the client ID that should be used for rendering (if
717    * {@link #shouldRenderId} returns true).
718    */
719   protected String getClientId(
720     FacesContext context,
721     UIComponent  component)
722   {
723     return component.getClientId(context);
724   }
725 
726   /**
727    * Returns true if the component should render an ID.  Components
728    * that deliver events should always return "true".
729    */
730   // TODO Is this a bottleneck?  If so, optimize!
731   protected boolean shouldRenderId(
732     FacesContext context,
733     UIComponent component)
734   {
735     String id = component.getId();
736 
737     // Otherwise, if ID isn't set, don't bother
738     if (id == null)
739       return false;
740 
741     // ... or if the ID was generated, don't bother
742     if (id.startsWith(UIViewRoot.UNIQUE_ID_PREFIX))
743       return false;
744 
745     return true;
746   }
747 
748   protected boolean skipDecode(
749     FacesContext context)
750   {
751     return false;
752   }
753 
754   protected FacesBean getFacesBean(
755     UIComponent component)
756   {
757     return ((UIXComponent) component).getFacesBean();
758   }
759 
760   static protected final Object getRenderingProperty(
761     RenderingContext rc,
762     Object           key)
763   {
764     return rc.getProperties().get(key);
765   }
766 
767   static protected final Object setRenderingProperty(
768     RenderingContext rc,
769     Object           key,
770     Object           value)
771   {
772     return rc.getProperties().put(key, value);
773   }
774 
775   /**
776    * Gets a facet, verifying that the facet should be rendered.
777    */
778   static public UIComponent getFacet(
779     UIComponent component,
780     String      name)
781   {
782     UIComponent facet = component.getFacet(name);
783     if ((facet == null) || !facet.isRendered())
784       return null;
785 
786     return facet;
787   }
788 
789   /**
790    * Returns true if the component has children and at least
791    * one has rendered=="true".
792    */
793   @SuppressWarnings("unchecked")
794   static public boolean hasRenderedChildren(
795     UIComponent component)
796   {
797     int count = component.getChildCount();
798     if (count == 0)
799       return false;
800 
801     for(UIComponent child : (List<UIComponent>)component.getChildren())
802     {
803       if (child.isRendered())
804       {
805         return true;
806       }
807     }
808 
809     return false;
810   }
811 
812   /**
813    * Returns the total number of children with rendered=="true".
814    */
815   @SuppressWarnings("unchecked")
816   static public int getRenderedChildCount(
817     UIComponent component)
818   {
819     int count = component.getChildCount();
820     if (count == 0)
821       return 0;
822 
823     int total = 0;
824     for(UIComponent child : (List<UIComponent>)component.getChildren())
825     {
826       if (child.isRendered())
827       {
828         total++;
829       }
830     }
831 
832     return total;
833   }
834 
835  /**
836    * @param afterChildIndex The children coming after this index, will
837    * be considered.
838    * @return the index of the next child that must be rendered, or
839    * {@link #NO_CHILD_INDEX} if there is none.
840    */
841   public static int getNextRenderedChildIndex(
842     List<UIComponent> components,
843     int               afterChildIndex
844     )
845   {
846     int childIndex = afterChildIndex + 1;
847     Iterator<UIComponent> iter = components.listIterator(childIndex);
848     for(; iter.hasNext(); childIndex++)
849     {
850       if(iter.next().isRendered())
851       {
852         return childIndex;
853       }
854     }
855 
856     return NO_CHILD_INDEX;
857   }
858 
859   //
860   // AGENT CAPABILITY CONVENIENCE METHODS
861   //
862 
863   static public boolean isDesktop(RenderingContext rc)
864   {
865     return (Agent.TYPE_DESKTOP.equals(rc.getAgent().getType()));
866   }
867 
868   static public boolean isPDA(RenderingContext rc)
869   {
870     return (Agent.TYPE_PDA.equals(rc.getAgent().getType()));
871   }
872 
873   static public boolean isIE(RenderingContext rc)
874   {
875     return (Agent.AGENT_IE.equals(rc.getAgent().getAgentName()));
876   }
877 
878   static public boolean isKonqueror(RenderingContext rc)
879   {
880     return (Agent.AGENT_KONQUEROR.equals(rc.getAgent().getAgentName()));
881   }
882 
883   static public boolean isGecko(RenderingContext rc)
884   {
885     return (Agent.AGENT_GECKO.equals(rc.getAgent().getAgentName()));
886   }
887 
888   static public boolean isWebKit(RenderingContext rc)
889   {
890     return (Agent.AGENT_WEBKIT.equals(rc.getAgent().getAgentName()));
891   }
892 
893   static public boolean isOpera(RenderingContext rc)
894   {
895     return (Agent.AGENT_OPERA.equals(rc.getAgent().getAgentName()));
896   }
897 
898   static public boolean isIPhone(RenderingContext rc)
899   {
900     return (Agent.PLATFORM_IPHONE.equals(rc.getAgent().getPlatformName()));
901   }
902 
903   static public boolean isGenericPDA(RenderingContext rc)
904   {
905     return (Agent.PLATFORM_GENERICPDA.equals(rc.getAgent().getPlatformName()));
906   }
907 
908   /**
909    * This method returns true if a user-agent's platform is NokiaS60
910    * @param arc - RenderingContext of a request
911    * @return boolean
912    */
913   static public boolean isNokiaS60(RenderingContext rc)
914   {
915     return (Agent.PLATFORM_NOKIA_S60.equals(rc.getAgent().getPlatformName()));
916   }
917 
918   static public boolean isInaccessibleMode(RenderingContext rc)
919   {
920     return (rc.getAccessibilityMode() ==
921             RequestContext.Accessibility.INACCESSIBLE);
922   }
923 
924   static public boolean isScreenReaderMode(RenderingContext rc)
925   {
926     return (rc.getAccessibilityMode() ==
927             RequestContext.Accessibility.SCREEN_READER);
928   }
929 
930   //
931   // Encoding hook methods for sub-classes
932   //
933 
934   /**
935    * Hook method that gets invoked before the component is encoded
936    *
937    * @see #encodeBegin(FacesContext, RederingContext, UIComponent, FacesBean)
938    * @see #encodeAll(FacesContext, RederingContext, UIComponent, FacesBean)
939    */
940   protected void beforeEncode(
941     FacesContext     context,
942     RenderingContext rc,
943     UIComponent      component,
944     FacesBean        bean)
945   {
946     setupEncodingContext(context, rc, component);
947   }
948 
949   /**
950    * Hook method that gets invoked after the component is encoded
951    *
952    * @see #encodeEnd(FacesContext, RederingContext, UIComponent, FacesBean)
953    * @see #encodeAll(FacesContext, RederingContext, UIComponent, FacesBean)
954    */
955   protected void afterEncode(
956     FacesContext     context,
957     RenderingContext rc,
958     UIComponent      component,
959     FacesBean        bean)
960   {
961     tearDownEncodingContext(context, rc, component);
962   }
963 
964   //
965   // Rendering convenience methods.
966   //
967 
968   /**
969    * Decodes the behaviors of this component, if it is the component that is the source
970    * of the call to the server and the event matches behaviors that are attached to
971    * the component
972    *
973    * @param facesContext the faces context
974    * @param component the component
975    * @param bean the faces bean
976    * @return the client ID if it was retrieved, null otherwise
977    */
978   protected final String decodeBehaviors(
979     FacesContext facesContext,
980     UIComponent  component,
981     FacesBean    bean)
982   {
983     if (!(component instanceof ClientBehaviorHolder))
984     {
985       return null;
986     }
987 
988     // Check if there are client behaviors first as it should be faster to access then
989     // getting the behavior event from the request parameter map (fewer method calls)
990     Map<String, List<ClientBehavior>> behaviorsMap = ((ClientBehaviorHolder)component).getClientBehaviors();
991     if (behaviorsMap.isEmpty())
992     {
993       return null;
994     }
995 
996     // Get the behavior event sent by the client, if any
997     Map<String, String> requestParams = facesContext.getExternalContext().getRequestParameterMap();
998     String event = requestParams.get(_BEHAVIOR_EVENT_KEY);
999     if (event == null)
1000     {
1001       return null;
1002     }
1003 
1004     // Does the component have behaviors for this event type?
1005     List<ClientBehavior> behaviors = behaviorsMap.get(event);
1006     if (behaviors == null || behaviors.isEmpty())
1007     {
1008       return null;
1009     }
1010 
1011     // See if this is the submitting component
1012     String clientId = component.getClientId(facesContext);
1013     String sourceClientId = requestParams.get("javax.faces.source");
1014     if (clientId.equals(sourceClientId))
1015     {
1016       // Decode the behaviors
1017       for (ClientBehavior behavior: behaviors)
1018       {
1019         behavior.decode(facesContext, component);
1020       }
1021     }
1022 
1023     return clientId;
1024   }
1025 
1026   /**
1027    * Get a collection of all the parameters that are children of the current component as
1028    * client behavior parameters.
1029    * @param component The component
1030    * @return Collection of parameters (will be non-null)
1031    */
1032   public static Collection<ClientBehaviorContext.Parameter> getBehaviorParameters(
1033     UIComponent component)
1034   {
1035     int childCount = component.getChildCount();
1036     if (childCount > 0)
1037     {
1038       List<ClientBehaviorContext.Parameter> list = null;
1039       for (UIComponent child : component.getChildren())
1040       {
1041         if (!(child instanceof UIParameter)) { continue; }
1042 
1043         if (list == null)
1044         {
1045           // leave plenty of room to hold the parameters
1046           list = new ArrayList<ClientBehaviorContext.Parameter>(childCount);
1047         }
1048         UIParameter param = (UIParameter) child;
1049         list.add(new ClientBehaviorContext.Parameter(param.getName(), param.getValue()));
1050       }
1051 
1052       if (list != null)
1053       {
1054         return list;
1055       }
1056     }
1057 
1058     return Collections.<ClientBehaviorContext.Parameter>emptyList();
1059   }
1060 
1061   protected void renderEncodedActionURI(
1062    FacesContext context,
1063    String       name,
1064    Object       value) throws IOException
1065   {
1066     if (value != null)
1067     {
1068       value = context.getExternalContext().encodeActionURL(value.toString());
1069       context.getResponseWriter().writeURIAttribute(name, value, null);
1070     }
1071   }
1072 
1073   protected void renderEncodedResourceURI(
1074    FacesContext context,
1075    String       name,
1076    Object       value) throws IOException
1077   {
1078     if (value != null)
1079     {
1080       value = context.getExternalContext().encodeResourceURL(value.toString());
1081       context.getResponseWriter().writeURIAttribute(name, value, null);
1082     }
1083   }
1084 
1085   /**
1086    * Render a generic CSS styleClass (not one derived from an attribute).
1087    * The styleclass will be passed through the RenderingContext
1088    * getStyleClass() API.
1089    * @param context  the FacesContext
1090    * @param styleClass the style class
1091    */
1092   static public void renderStyleClass(
1093     FacesContext     context,
1094     RenderingContext rc,
1095     String           styleClass) throws IOException
1096   {
1097     if (styleClass != null)
1098     {
1099       String compressedStyleClass = rc.getStyleClass(styleClass);
1100       context.getResponseWriter().writeAttribute("class", compressedStyleClass, null);
1101 
1102       if (rc.isDesignTime())
1103         context.getResponseWriter().writeAttribute("rawClass", styleClass, null);
1104     }
1105   }
1106 
1107   /**
1108    * Render an array of CSS styleClasses as space-separated values.
1109    * @param context  the FacesContext
1110    * @param styleClasses the style classes
1111    */
1112   static public void renderStyleClasses(
1113     FacesContext     context,
1114     RenderingContext rc,
1115     String[]         styleClasses) throws IOException
1116   {
1117     int length = styleClasses.length;
1118     if (length == 0)
1119       return;
1120 
1121     String value;
1122     // Optimize one-element arrays
1123     if (length == 1)
1124     {
1125       value = rc.getStyleClass(styleClasses[0]);
1126     }
1127     // Otherwise, build up the array of mutated style classes.
1128     else
1129     {
1130       // Assume that styleclass compression is active in terms of sizing
1131       // this buffer - this is not true for portlets, but this isn't a
1132       // huge optimizations, and the relatively smaller content delivered
1133       // to portlets makes this still less important
1134       StringBuilder builder =
1135         new StringBuilder((_COMPRESSED_LENGTH + 1) * length);
1136       for (int i = 0; i < length; i++)
1137       {
1138         if (styleClasses[i] != null)
1139         {
1140           String styleClass = rc.getStyleClass(styleClasses[i]);
1141           if (styleClass != null)
1142           {
1143             if (builder.length() != 0)
1144               builder.append(' ');
1145             builder.append(styleClass);
1146           }
1147         }
1148       }
1149 
1150       if (builder.length() == 0)
1151         value = null;
1152       else
1153         value = builder.toString();
1154     }
1155 
1156     context.getResponseWriter().writeAttribute("class", value, null);
1157 
1158     if (rc.isDesignTime())
1159     {
1160       StringBuilder builder = new StringBuilder();
1161       for (int i = 0; i < length; i++)
1162       {
1163         if (styleClasses[i] != null)
1164         {
1165           String styleClass = styleClasses[i];
1166           if (styleClass != null)
1167           {
1168             if (builder.length() != 0)
1169               builder.append(' ');
1170             builder.append(styleClass);
1171           }
1172         }
1173       }
1174 
1175       if (builder.length() > 0)
1176         context.getResponseWriter().writeAttribute("rawClass", builder.toString(), null);
1177     }
1178 
1179   }
1180 
1181   // Heuristic guess of the maximum length of a typical compressed style
1182   private static final int _COMPRESSED_LENGTH = 4;
1183 
1184   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
1185     CoreRenderer.class);
1186   private static final String _BEHAVIOR_EVENT_KEY = "javax.faces.behavior.event";
1187 }