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