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  import java.io.IOException;
22  import java.util.List;
23  
24  import javax.faces.component.NamingContainer;
25  import javax.faces.component.UIComponent;
26  import javax.faces.component.UIForm;
27  import javax.faces.context.FacesContext;
28  
29  import javax.faces.render.Renderer;
30  
31  import org.apache.myfaces.trinidad.component.UIXForm;
32  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
33  import org.apache.myfaces.trinidad.util.ComponentUtils;
34  
35  /**
36   * Generic utilities for rendering.
37   *
38   */
39  public class RenderUtils
40  {
41    private RenderUtils()
42    {
43    }
44  
45  
46    /**
47     * Encodes a component and all of its children.
48     */
49    @SuppressWarnings("unchecked")
50    static public void encodeRecursive(FacesContext context,
51                                       UIComponent component)
52      throws IOException
53    {
54      if (component.isRendered())
55      {
56        component.encodeBegin(context);
57        if (component.getRendersChildren())
58        {
59          component.encodeChildren(context);
60        }
61        else
62        {
63          if (component.getChildCount() > 0)
64          {
65            for(UIComponent child : (List<UIComponent>)component.getChildren())
66            {
67              encodeRecursive(context, child);
68            }
69          }
70        }
71  
72        component.encodeEnd(context);
73      }
74    }
75  
76    /**
77     *  Retrieves id of the form the component is contained in.
78     *
79     * @param context the faces context object
80     * @param component UIComponent whose container form id is to be retuirned
81     *
82     * @return String id of the form if one exists in component's hierarchy,
83     *                otherwise null
84     */
85    public static String getFormId(
86      FacesContext context,
87      UIComponent  component)
88    {
89      UIComponent form = null;
90      while (component != null)
91      {
92        if ((component instanceof UIForm) ||
93            (component instanceof UIXForm))
94        {
95          form = component;
96          break;
97        }
98  
99        component = component.getParent();
100     }
101 
102     if (form == null)
103       return null;
104 
105     return form.getClientId(context);
106   }
107 
108   /**
109    * Given a 'from' component and a relativeId, 
110    * return the clientId for use at rendering time that will identify the
111    * id of the component you are referencing on the client.
112    * This is used for attributes like e.g. "for" and "chooseId".
113    * 
114    * <p>
115    * e.g., given this hierarchy
116    * <br/>
117    *  &lt;f:subview id="aaa"&gt;  
118    *    &lt;f:subview id="xxx"&gt;<br/>
119            &lt;tr:chooseColor id="cp1" .../&gt;<br/>
120             &lt;f:subview id="yyy"><br/>
121                &lt;tr:inputColor id="sic1" chooseId="::cp1" .../&gt;<br/>
122             &lt;/f:subview&gt;<br/>
123          &lt;/f:subview&gt;   
124       &lt;/f:subview&gt;<br/>
125     </p>
126     <p>
127    * The 'from' component is the inputColor component.
128    * The 'relativeId' is "::cp1". ('::' pops up one naming container)
129    * The return value is 'aaa:xxx:cp1' when
130    * the clientId of the 'xxx' component is 'aaa:xxx'.
131    * 
132    * </p>
133    * <p>
134    * It does not assume that the target component can be located, although it does
135    * check. If it can't be found, it returns the correct relativeId anyway.
136    * </p>
137    * <p>
138    * A relativeId starting with
139    * NamingContainer.SEPARATOR_CHAR (that is, ':') will be
140    * treated as absolute (after dropping that character).
141    * A relativeId with no colons means it is within the same naming container
142    * as the 'from' component (this is within the 'from' component if 'from'
143    * is a naming container).
144    * A relativeId starting with '::' pops out of the 'from' component's
145    * naming container. If the 'from' component is a naming container, then
146    * '::' will pop out of the 'from' component itself. A relativeId with ':::' pops up two naming containers, etc.
147    * ComponentUtils.findRelativeComponent finds and returns the component, whereas
148    * this method returns a relativeId that can be used during renderering 
149    * so the component can be found in javascript on the client.
150    * </p>
151    * @param context
152    * @param from the component to search relative to
153    * @param scopedId the relative id path from the 'from' component to the
154    *                 component to find
155    * @return the clientId for the 'relative' component.
156    * @see ComponentUtils#findRelativeComponent
157    * @see javax.faces.component.UIComponent#findComponent
158 
159    */
160   public static String getRelativeId(
161     FacesContext context,
162     UIComponent  from,
163     String       scopedId)
164   {
165     if (from == null)
166         return null;
167     
168     if ((scopedId == null) || (scopedId.length() == 0))
169       return null;
170 
171     // Figure out how many colons
172     int colonCount = _getColonCount(scopedId);
173 
174     // colonCount == 0: fully relative
175     // colonCount == 1: absolute 
176     // colonCount > 1: for each extra colon after 1, pop out of
177     // the naming container (to the view root, if naming containers run out)
178     
179     if (colonCount == 1)
180       return scopedId.substring(1);
181     if (colonCount == 0 && !(from instanceof NamingContainer))
182     {
183       // we do it the fast way if there 
184       // are no colons and the from isn't a NamingContainer.
185       // the reason is this use case hasn't changed between the previous
186       // logic and the current logic for finding the component, so it
187       // is already backward compatible, and therefore we don't have to 
188       // call the findComponent code for backward compatibility.
189       return _getRelativeId(context, from, scopedId, colonCount);
190     }
191     
192     // 
193     // We need to make it backward compatible, and 
194     // the only way is to use the findRelativeComponent code.
195     // This way we'll have a hint that the syntax is 'old' if 
196     // it can't be found. Plus, findRelativeComponent code has 
197     // backward compatibilty built in.
198     UIComponent component = 
199       ComponentUtils.findRelativeComponent(from, scopedId);
200     if (component == null && from instanceof NamingContainer)
201     {
202       component = ComponentUtils.findRelativeComponent(from.getParent(), scopedId);
203       if (component != null)
204       {
205         _LOG.warning("DEPRECATED_RELATIVE_ID_SYNTAX", 
206           new Object[] {scopedId, from});
207       }
208     }
209     
210     // the component wasn't found, but go ahead and return something smart
211     if (component == null)
212     {
213       return _getRelativeId(context, from, scopedId, colonCount);
214     }
215     else
216     {
217       return component.getClientId(context);
218     }
219 
220   }
221 
222   /**
223    * Returns the clientId used by the renderer in place of the clientId used by the component.
224    * Certain renderers render their root element with a clientId that differs from the one
225    * used by the component.
226    * @param context FacesContent.
227    * @param component UIComponent.
228    * @return component clientId if the renderer clientId is null. Otherwise clientId used by 
229    * renderer.
230    */
231   public static String getRendererClientId(
232     FacesContext context, 
233     UIComponent component) 
234   {
235     String clientId = component.getClientId(context);
236     String family = component.getFamily();
237     String rendererType = component.getRendererType();
238     if (rendererType != null)
239     {
240       Renderer renderer = context.getRenderKit().getRenderer(family, rendererType);
241       if (renderer instanceof CoreRenderer)
242       {
243         CoreRenderer coreRenderer = (CoreRenderer) renderer;
244         String rendererClientId = coreRenderer.getClientId(context, component);
245         return rendererClientId == null ? clientId : rendererClientId;
246       }
247     }
248     return clientId;
249   }
250 
251   // This does NOT use findComponent
252   // ComponentUtils.findRelativeComponent finds the component, whereas
253   // this method returns a relativeId that can be used during renderering 
254   // so the component can be found in javascript on the client.
255   // This code is faster because it doesn't have to find the component.
256   // It is used when the getRelativeId's findRelativeComponent cannot find 
257   // the component. This way we can return the relativeId anyway.
258   private static String _getRelativeId(
259     FacesContext context,
260     UIComponent  from,
261     String       relativeId,
262     int          colonCount)
263   {
264 
265 
266     if (colonCount == 1)
267       return relativeId.substring(1);
268     else if (colonCount > 1)
269     {
270       relativeId = relativeId.substring(colonCount);
271     }
272       
273     // if the component is not a NamingContainer, then we need to 
274     // get the component's naming container and set this as the 'from'.
275 
276     if (!(from instanceof NamingContainer))
277     {
278       from = _getParentNamingContainer(from);
279     }
280     // pop out of the naming containers if there are multiple colons
281     // from will be null if there are no more naming containers
282     for (int j = 1; j < colonCount; j++)
283     {
284       from = _getParentNamingContainer(from);
285     }
286 
287     // assumption is no one but the parent naming container modifies its
288     // client id
289     if (from == null)
290       return relativeId;
291     else
292     {
293       return (from.getClientId(context) +
294               NamingContainer.SEPARATOR_CHAR + relativeId);
295     }
296 
297 
298   }
299 
300 
301   // Given a component, get its naming container. If the component
302   // is a naming container, it will get its naming container.
303   // This is different than the one in ComponentUtils. This one
304   // returns null if there are no NamingContainers. The other one
305   // returns the ViewRoot.
306   private static UIComponent _getParentNamingContainer (
307     UIComponent from)
308   {
309 
310     while (from != null && from.getParent() != null)
311     {
312       from = from.getParent();
313       if (from instanceof NamingContainer)
314         return from;
315     }
316 
317     return null;
318   }
319   
320   // Figure out how many colons
321   private static int _getColonCount(String relativeId)
322   {
323     int idLength = relativeId.length();
324     int colonCount = 0;
325     while (colonCount < idLength)
326     {
327       if (relativeId.charAt(colonCount) != NamingContainer.SEPARATOR_CHAR)
328         break;
329       colonCount++;
330     }
331     return colonCount;
332   }
333   
334   static private final TrinidadLogger _LOG =
335     TrinidadLogger.createTrinidadLogger(RenderUtils.class);
336 
337 }