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.util;
20  
21  import java.util.Date;
22  import java.util.List;
23  import java.util.Locale;
24  import java.util.Map;
25  import java.util.TimeZone;
26  
27  import javax.faces.component.NamingContainer;
28  import javax.faces.component.UIComponent;
29  import javax.faces.component.UIViewRoot;
30  import javax.faces.component.visit.VisitContext;
31  import javax.faces.context.FacesContext;
32  
33  import org.apache.myfaces.trinidad.change.InsertedComponentFragmentLocator;
34  import org.apache.myfaces.trinidad.change.InsertingComponentFragmentLocator;
35  import org.apache.myfaces.trinidad.component.FlattenedComponent;
36  import org.apache.myfaces.trinidad.component.UIXComponent;
37  import org.apache.myfaces.trinidad.context.PageResolver;
38  import org.apache.myfaces.trinidad.context.RequestContext;
39  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
40  
41  /**
42   * Utility functions used by the Apache Trinidad components.
43   * <p>
44   * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/util/ComponentUtils.java#0 $) $Date: 10-nov-2005.19:08:37 $
45   */
46  public final class ComponentUtils
47  {
48    private ComponentUtils()
49    {
50    }
51  
52    /**
53     * Utility method for component code that resolves an Object,
54     * returning a default value if the value is null.
55     */
56    public static Object resolveObject(
57      Object value, 
58      Object defaultValue
59      )
60    {
61      return (value != null)
62               ? value
63               : defaultValue;
64    }
65  
66  
67    /**
68     * Utility method for component code that transforms Object->boolean.
69     */
70    public static boolean resolveBoolean(
71      Object  value, 
72      boolean defaultValue
73      )
74    {
75      if (defaultValue)
76        return !Boolean.FALSE.equals(value);
77      else
78        return Boolean.TRUE.equals(value);
79    }
80  
81    /**
82     * Utility method for component code that transforms Object->boolean.
83     */
84    public static boolean resolveBoolean(
85      Object value
86      )
87    {
88      return Boolean.TRUE.equals(value);
89    }
90  
91  
92    /**
93     * Utility method for component code that transforms an Object
94     * (which must be a java.lang.Number) into an int.
95     */
96    public static int resolveInteger(
97      Object value
98      )
99    {
100     return resolveInteger(value, 0);
101   }
102 
103   /**
104    * Utility method for component code that transforms an Object
105    * (which must be a java.lang.Number) into an int.
106    */
107   public static int resolveInteger(
108     Object value,
109     int     defaultValue
110     )
111   {
112     return (value != null)
113              ? ((Number)value).intValue()
114              : defaultValue;
115   }
116 
117 
118   /**
119    * Utility method for component code that transforms an Object
120    * (which must be a java.lang.Number) into a long.
121    */
122   public static long resolveLong(
123     Object value
124     )
125   {
126     return resolveLong(value, 0);
127   }
128 
129   /**
130    * Utility method for component code that transforms an Object
131    * (which must be a java.lang.Number) into a long.
132    */
133   public static long resolveLong(
134     Object value,
135     long   defaultValue
136     )
137   {
138     return (value != null)
139              ? ((Number)value).longValue()
140              : defaultValue;
141   }
142 
143   /**
144    * Utility method for component code that transforms an Object
145    * (which must be a java.lang.Number) into a double.
146    */
147   public static double resolveDouble(
148     Object value
149     )
150   {
151     return resolveDouble(value, 0);
152   }
153 
154   /**
155    * Utility method for component code that transforms an Object
156    * (which must be a java.lang.Number) into a double.
157    */
158   public static double resolveDouble(
159     Object value,
160     double   defaultValue
161     )
162   {
163     return (value != null)
164              ? ((Number)value).doubleValue()
165              : defaultValue;
166   }
167 
168 
169   /**
170    * Utility method for component code that transforms Character->character.
171    */
172   public static char resolveCharacter(
173     Character value
174     )
175   {
176     return resolveCharacter(value, '\u0000');
177   }
178 
179   /**
180    * Utility method for component code that transforms Character->character.
181    */
182   public static char resolveCharacter(
183     Character  value,
184     char       defaultValue
185     )
186   {
187     return (value != null)
188              ? value.charValue()
189              : defaultValue;
190   }
191 
192 
193   /**
194    * Utility method for component code that transforms Object->Number.
195    */
196   public static Number resolveNumber(
197     Object  value
198     )
199   {
200     return resolveNumber(value, null);
201   }
202 
203 
204   /**
205    * Utility method for component code that transforms Object->Number.
206    */
207   public static Number resolveNumber(
208     Object  value,
209     Number  defaultValue
210     )
211   {
212     return (value != null)
213              ? (Number) value
214              : defaultValue;
215   }
216   
217 
218   /**
219    * Utility method for component code that transforms Object->String.
220    */
221   public static String resolveString(
222     Object  value
223     )
224   {
225     return resolveString(value, false);
226   }
227 
228   /**
229    * Utility method for component code that transforms Object->String.
230    * If treatEmptyStringAsNull is true, null is returned for empty string.
231    */
232   public static String resolveString(
233     Object  value,
234     boolean treatEmptyStringAsNull
235     )
236   {
237     if (value == null)
238     {
239       return null;
240     }
241 
242     String strValue = value.toString();
243     if (treatEmptyStringAsNull && strValue.trim().isEmpty())
244     {
245       return null;
246     }
247 
248     return strValue;
249   }
250 
251   /**
252    * Utility method for component code that transforms Object->String.
253    */
254   public static String resolveString(
255     Object  value,
256     String  defaultValue
257     )
258   {
259     return (value != null)
260              ? value.toString()
261              : defaultValue;
262   }
263   
264   /**
265    * Utility method for code that transforms Object->String[]
266    */
267   public static String[] resolveStringArray(
268     Object value)
269   {
270     return resolveStringArray(value, null);
271   }
272   
273   /**
274    * Utility method for code that transforms Object->String[]
275    */
276   public static String[] resolveStringArray(
277     Object value,
278     String[] defaultValue)
279   {
280     return (value != null)
281              ? (String[]) value
282              : defaultValue;
283   }
284   
285   /**
286    * Utility method for code that transforms Object->Date
287    */
288   public static Date resolveDate(Object value)  
289   {
290     return resolveDate(value, null);
291   }
292   
293   /**
294    * Utility method for code that transforms Object->Date
295    */
296   public static Date resolveDate(
297     Object value,
298     Date defaultValue)
299   {
300     return (value != null)
301              ? (Date) value
302              : defaultValue;
303   }  
304   
305   public static TimeZone resolveTimeZone(
306     Object value
307     )
308   {
309     return resolveTimeZone(value, null);
310   }
311   
312   public static TimeZone resolveTimeZone(
313     Object value,
314     TimeZone defaultValue
315     )
316   {
317     return (value != null)
318              ? (TimeZone) value
319              : defaultValue;
320   }
321   
322   public static Locale resolveLocale(
323     Object value
324     )
325   {
326     return resolveLocale(value, null);
327   }
328   
329   public static Locale resolveLocale(
330     Object value,
331     Locale defaultValue
332     )
333   {
334     return (value != null)
335              ? (Locale) value
336              : defaultValue;
337   }
338   
339   /**
340    * @param visitContext
341    * @return <code>true</code> if this is a non-iterating visit.
342    */
343   public static boolean isSkipIterationVisit(VisitContext visitContext)
344   {
345     FacesContext context = visitContext.getFacesContext();
346     Map<Object, Object> attrs = context.getAttributes();
347     Object skipIteration = attrs.get("javax.faces.visit.SKIP_ITERATION");
348 
349     return Boolean.TRUE.equals(skipIteration);
350   }
351   
352   /**
353    * Gets the root cause of an exception.
354    * Keeps unwrapping the given throwable until the root cause is found.
355    */
356   public static Throwable unwrap(Throwable t)
357   {
358     while(true)
359     {
360       Throwable unwrap = t.getCause();
361       if (unwrap == null)
362         break;
363       t = unwrap;
364     }
365     return t;
366   }
367 
368 
369   /**
370    * Find a component relative to another.
371    * <p>
372    * The relative ID must account for NamingContainers. If the component is already inside
373    * of a naming container, you can use a single colon to start the search from the root, 
374    * or multiple colons to move up through the NamingContainers - "::" will 
375    * pop out of the current naming container, ":::" will pop out of two
376    * naming containers, etc.
377    * </p>
378    * 
379    * @param from the component to search relative to
380    * @param scopedId the relative id path from the 'from' component to the
381    *                 component to find
382    * @return the component if found, null otherwise
383    * @see org.apache.myfaces.trinidad.render.RenderUtils#getRelativeId
384    * @see javax.faces.component.UIComponent#findComponent
385    */
386   public static UIComponent findRelativeComponent(
387     UIComponent from,
388     String      scopedId)
389   {
390     if (from == null)
391         return null;
392     UIComponent originalFrom = from;
393     String originalRelativeId = scopedId;
394     
395     int idLength = scopedId.length();
396     // Figure out how many colons
397     int colonCount = 0;
398     while (colonCount < idLength)
399     {
400       if (scopedId.charAt(colonCount) != NamingContainer.SEPARATOR_CHAR)
401         break;
402       colonCount++;
403     }
404 
405     // colonCount == 0: fully relative
406     // colonCount == 1: absolute (still normal findComponent syntax)
407     // colonCount > 1: for each extra colon after 1, pop out of
408     // the naming container (to the view root, if naming containers run out)
409     if (colonCount > 1)
410     {
411       scopedId = scopedId.substring(colonCount);
412       
413       // if the component is not a NamingContainer, then we need to 
414       // get the component's naming container and set this as the 'from'.
415       // this way we'll pop out of the component's 
416       // naming container if there is are multiple colons.
417       if (!(from instanceof NamingContainer))
418       {
419         from = _getParentNamingContainerOrViewRoot(from);
420       }
421       
422       // pop out of the naming containers if there are multiple colons
423       for (int j = 1; j < colonCount; j++)
424       {
425         from = _getParentNamingContainerOrViewRoot(from);
426       }
427     }
428 
429     UIComponent found = from.findComponent(scopedId);
430     if (found != null)
431       return found;
432     else
433     {
434       // try the old way for backward compatability as far as it differed,
435       // which is only if the 'from' was not a NamingContainer.
436       if (!(originalFrom instanceof NamingContainer))
437         return _findRelativeComponentDeprecated(originalFrom, originalRelativeId);
438       else
439         return null;
440     }
441     
442   }
443   
444   /**
445    * Gets the location (context relative path) of the document that contains the tag definition 
446    *  corresponding to the supplied component. This implementation uses the 
447    *  InsertedComponentFragmentLocator and InsertingComponentFragmentLocator strategies to determine 
448    *  the document path for the supplied component.
449    * All registered InsertedComponentFragmentLocator services are first asked for location of the 
450    *  document for the supplied component first. If the locator could not be obtained, then this 
451    *  implementation will walk up the component tree and for every ancestor until the view root, 
452    *  call all the registered InsertedComponentFragmentLocator and InsertingComponentFragmentLocator 
453    *  services to obtain the location of the document. If in this process any non-null value is 
454    *  obtained for the document location, the ancestor processing is halted and the value is 
455    *  returned.
456    *  
457    * @param context   The FacesContext instance for the current request
458    * @param component The component for which the document path is to be determined
459    * @return Location of the document fragment that contains the tag corresponding to the supplied
460    *          component. Returns null if the path cannot be determined.
461    */
462   public static String getDocumentLocationForComponent(
463     FacesContext context,
464     UIComponent component)
465   {
466     UIViewRoot viewRoot = context.getViewRoot();
467     
468     // if we are dealing with view root, look no further
469     if (component == viewRoot)
470     {
471       return _getPhysicalURI(viewRoot.getViewId());
472     }
473     
474     String location = null;
475     
476     List<InsertedComponentFragmentLocator> insertedComponentFragmentLocators = 
477       ClassLoaderUtils.__getCachableServices(InsertedComponentFragmentLocator.class);
478     
479     // 1. try to get the path for the supplied component
480     location = _getInsertedComponentLocation(context,
481                                              insertedComponentFragmentLocators,
482                                              component,
483                                              component);
484     
485     // 2. try to get the path for the supplied component using its ancestors
486     if (location == null)
487     {
488       List<InsertingComponentFragmentLocator> insertingComponentFragmentLocators = 
489         ClassLoaderUtils.__getCachableServices(InsertingComponentFragmentLocator.class);
490       
491       UIComponent currAncestor = component.getParent();
492       
493       while (currAncestor != null)
494       {
495         if (currAncestor == viewRoot)
496         {
497           return _getPhysicalURI(viewRoot.getViewId());
498         }
499         
500         // 2a. try to get the location where the tag for ancestor is defined
501         location = _getInsertedComponentLocation(context,
502                                                  insertedComponentFragmentLocators,
503                                                  currAncestor,
504                                                  component);
505 
506         if (location != null)
507         {
508           break;
509         }
510         else
511         {
512           // 2b. try to get the location of the fragment that the ancestor could have inserted
513           location = _getInsertingComponentLocation(context,
514                                                    insertingComponentFragmentLocators,
515                                                    currAncestor,
516                                                    component);
517           
518           if (location != null)
519           {
520             break;
521           }
522         }
523         
524         currAncestor = currAncestor.getParent();
525       }
526     }
527     
528     return location;
529   }
530   
531   /**
532    * Gets the scoped identifier for the target component. The scoping will be
533    * within a subtree rooted by the supplied base component. If the supplied
534    * base component were to be the view root, the returned id will be the 
535    * absolute id and hence prefixed with NamingContainer.SEPARATOR_CHARACTER.
536    * 
537    * This algorithm reverse matches that of UIComponent.findComponent(). 
538    * In other words, the scoped id returned by this method can be safely used 
539    * in calls to findComponent() on the baseComponent, if it were to be
540    * enclosing the targetComponent.
541    * 
542    * This method assumes that the supplied baseComponent definitely encloses the
543    * targetComponent, return value is not reliable if this is not the case.
544    * 
545    * Examples of id returned: ':foo:bar:baz'/'foo:baz'/'foo'
546    * 
547    * @param targetComponent The component for which the scoped id needs to be
548    * determined.
549    * @param baseComponent The component relative to which the scoped id for the
550    * targetComponent needs to be determined.
551    * @return The scoped id for target component. Returns null if the supplied
552    * targetComponent was null or did not have an id.
553    */
554   public static String getScopedIdForComponent(
555     UIComponent targetComponent,
556     UIComponent baseComponent)
557   {
558     return _getScopedIdForComponentImpl(targetComponent, baseComponent, false);
559   }
560   
561   /**
562    * Gets the logical scoped identifier for the target component. The scoping will be
563    * within a subtree rooted by the supplied base component. The subtree will be computed in the
564    * context of the document or documents where the components were defined. If the supplied
565    * base component were to be the view root, the returned id will be the 
566    * absolute id and hence prefixed with NamingContainer.SEPARATOR_CHARACTER.
567    * 
568    * This algorithm reverse matches that of UIComponent.findComponent(). 
569    * In other words, the scoped id returned by this method can be safely used 
570    * in calls to findComponent() on the baseComponent, if it were to be
571    * enclosing the targetComponent.
572    * 
573    * This method assumes that the supplied baseComponent definitely encloses the
574    * targetComponent, return value is not reliable if this is not the case.
575    * 
576    * Examples of id returned: ':foo:bar:baz'/'foo:baz'/'foo'
577    * 
578    * @param targetComponent The component for which the scoped id needs to be
579    * determined.
580    * @param baseComponent The component relative to which the scoped id for the
581    * targetComponent needs to be determined.
582    * @return The scoped id for target component. Returns null if the supplied
583    * targetComponent was null or did not have an id.
584    */
585   public static String getLogicalScopedIdForComponent(
586     UIComponent targetComponent,
587     UIComponent baseComponent)
588   {
589     return _getScopedIdForComponentImpl(targetComponent, baseComponent, true);
590   }
591   
592   /**
593    * Provides the physical URI of the page with supplied logical view id. The implementation
594    *  uses any registered page resolver to do the conversion.
595    */
596   private static String _getPhysicalURI(String logicalViewId)
597   {
598     PageResolver pageResolver =  RequestContext.getCurrentInstance().getPageResolver();
599     return pageResolver.getPhysicalURI(logicalViewId);
600   }
601 
602   /**
603    * Returns scoped id for a component
604    * @param targetComponent The component for which the scoped id needs to be
605    * determined.
606    * @param baseComponent The component relative to which the scoped id for the
607    * targetComponent needs to be determined.
608    * @param isLogical true if a logical scoped id (the id in the context of the document where targetComponent was defined)
609    * should be returned, false otherwise
610    * @return The scoped id for target component. Returns null if the supplied
611    * targetComponent was null or did not have an id.
612    */
613   private static String _getScopedIdForComponentImpl(
614     UIComponent targetComponent,
615     UIComponent baseComponent,
616     boolean isLogical)
617   {
618     String targetComponentId = targetComponent.getId();
619     
620     if (targetComponent == null || 
621         targetComponentId == null ||
622         targetComponentId.length() == 0)
623       return null;
624     
625     // Optimize when both arguments are the same
626     if (targetComponent.equals(baseComponent))
627       return targetComponentId;
628 
629     StringBuilder builder = new StringBuilder(100);
630     
631     // Add a leading ':' if the baseComponent is the view root
632     if (baseComponent instanceof UIViewRoot)
633       builder.append(NamingContainer.SEPARATOR_CHAR);
634 
635     _buildScopedId(targetComponent, baseComponent, builder, isLogical);
636     
637     return builder.toString();
638   }
639 
640   /**
641    * Returns the nearest ancestor component, skipping over any
642    * flattening components.
643    * 
644    * @param context the FacesContext
645    * @param component the UIComponent
646    * @return the first ancestor component that is not a FlattenedComponent
647    *   that is actively flattening its children or null if no such ancestor
648    *   component is found.
649    * @see org.apache.myfaces.trinidad.component.FlattenedComponent
650    */
651   public static UIComponent getNonFlatteningAncestor(
652     FacesContext context,
653     UIComponent component)
654   {
655     UIComponent parent = component.getParent();
656     
657     while (parent != null)
658     { 
659       if (!_isFlattening(context, parent))
660         return parent;
661       
662       parent = parent.getParent();
663     }
664 
665     return null;
666   }
667 
668   /**
669    * Iterates through all the registered InsertedComponentFragmentLocator services and gets
670    *  the location of the fragment document where the tag corresponding to the supplied 
671    *  componentToTest is defined. Returns null if the location cannot be determined.
672    */
673   private static String _getInsertedComponentLocation(
674     FacesContext context,
675     List<InsertedComponentFragmentLocator> insertedComponentFragmentLocators,
676     UIComponent componentToTest,
677     UIComponent targetComponent)
678   {
679     for (InsertedComponentFragmentLocator 
680            insertedComponentFragmentLocator :  insertedComponentFragmentLocators)
681     {
682       String location = 
683         insertedComponentFragmentLocator.getFragmentUrlForInsertedComponent(context,
684                                                                             componentToTest,
685                                                                             targetComponent);
686       if (location != null)
687       {
688         return location;
689       }
690     }
691     return null;
692   }
693   
694   /**
695    * Iterates through all the registered InsertingComponentFragmentLocator services and gets
696    *  the location of the fragment document that the supplied componentToTest could have possibly
697    *  inserted. Returns null if the location cannot be determined.
698    */
699   private static String _getInsertingComponentLocation(
700     FacesContext context,
701     List<InsertingComponentFragmentLocator> insertingComponentFragmentLocators,
702     UIComponent componentToTest,
703     UIComponent targetComponent)
704   {
705     for (InsertingComponentFragmentLocator 
706            insertingComponentFragmentLocator :  insertingComponentFragmentLocators)
707     {
708       String location = 
709         insertingComponentFragmentLocator.getInsertedFragmentUrl(context,
710                                                                  componentToTest,
711                                                                  targetComponent);
712       if (location != null)
713       {
714         return location;
715       }
716     }
717     return null;
718   }
719   
720   private static boolean _isFlattening(FacesContext context, UIComponent component)
721   {
722     return ((component instanceof FlattenedComponent) &&
723       ((FlattenedComponent)component).isFlatteningChildren(context));
724   }
725   
726   /**
727    * Builds the scoped id. Adds the naming container's id and the separator char
728    * in a recursive fashion.
729    * @param targetComponent The component for which the scoped id needs to be
730    * built.
731    * @param baseComponent The component relative to which the scoped id for the
732    * targetComponent needs to be built.
733    * @param builder The StringBuilder which is to store the scoped id.
734    * @param isLogical true if the logical scoped id should be returned, false otherwise
735    * @return The String value of the scoped id
736    */
737   private static void _buildScopedId(
738     UIComponent  targetComponent,
739     UIComponent  baseComponent,
740     StringBuilder builder,
741     boolean isLogical)
742   {
743     UIComponent namingContainer = 
744       _getParentNamingContainer(targetComponent, baseComponent, isLogical);
745 
746     if (namingContainer != null)
747     {
748       _buildScopedId(namingContainer, baseComponent, builder, isLogical);
749       builder.append(NamingContainer.SEPARATOR_CHAR);
750     }
751       
752     builder.append(targetComponent.getId());
753   }
754 
755   /**
756    * Returns the naming container of the component. This method makes sure that
757    * we don't go beyond the a supplied base component. 
758    * @param component the UIComponent 
759    * @param baseComponent The component to limit the search up to.
760    * @param isLogical true if logical parent hierarchy should be used, false otherwise
761    * @return the naming container of the component which has to be in the 
762    * subtree rooted by the baseComponent. Returns null if no such ancestor 
763    * naming container component exists.
764    */
765   private static UIComponent _getParentNamingContainer(
766     UIComponent component,
767     UIComponent baseComponent,
768     boolean isLogical)
769   {
770     // Optimize when both arguments are the same - could happen due to recursion
771     //  in _buildScopedId()
772     if (component.equals(baseComponent))
773       return null;
774     
775     UIComponent checkedParent = component;
776     
777     do
778     {
779       checkedParent = isLogical ? UIXComponent.getLogicalParent(checkedParent) : checkedParent.getParent();
780     
781       if (checkedParent == null)
782         break;
783       
784       if (checkedParent instanceof NamingContainer)
785         break;
786       
787       // We hit the base component, abort.
788       if (checkedParent == baseComponent)
789         return null;
790       
791     } while (true);
792     
793     return checkedParent;
794   }
795   
796   // given a component, get its naming container. If the component
797   // is a naming container, it will get its naming container.
798   // if no parent naming containers exist, it stops at the ViewRoot.
799   private static UIComponent _getParentNamingContainerOrViewRoot (
800     UIComponent from)
801   {
802     while (from.getParent() != null)
803     {
804       from = from.getParent();
805       if (from instanceof NamingContainer)
806         break;
807     }
808     return from;
809   }
810 
811    /**
812     * Find a component relative to another.
813     * This method is the same as the 'old' public findRelativeComponent.
814    * This method is around so that the
815    * new findRelativeComponent method is backward compatibility.
816     * <p>
817     * The relative ID must account for NamingContainers. If the component is already inside
818     * of a naming container, you can use a single colon to start the search from the root, 
819     * or multiple colons to move up through the NamingContainers - "::" will search from 
820     * the parent naming container, ":::" will search from the grandparent 
821     * naming container, etc.
822     * </p>
823     * 
824     * @param from the component to search relative to
825     * @param relativeId the relative path to the component to find
826     * @return the component if found, null otherwise
827     */
828   private static UIComponent _findRelativeComponentDeprecated(
829     UIComponent from,
830     String      relativeId)
831   {  
832     UIComponent originalFrom = from;
833     String originalRelativeId = relativeId;
834 
835     int idLength = relativeId.length();
836     // Figure out how many colons
837     int colonCount = 0;
838     while (colonCount < idLength)
839     {
840       if (relativeId.charAt(colonCount) != NamingContainer.SEPARATOR_CHAR)
841         break;
842       colonCount++;
843     }
844 
845     // colonCount == 0: fully relative
846     // colonCount == 1: absolute (still normal findComponent syntax)
847     // colonCount > 1: for each extra colon after 1, go up a naming container
848     // (to the view root, if naming containers run out)
849     if (colonCount > 1)
850     {
851       relativeId = relativeId.substring(colonCount);
852       for (int j = 1; j < colonCount; j++)
853       {
854         while (from.getParent() != null)
855         {
856           from = from.getParent();
857           if (from instanceof NamingContainer)
858             break;
859         }
860       }
861     }
862 
863     UIComponent found = from.findComponent(relativeId);
864     if (found != null)
865     {
866       _LOG.warning("DEPRECATED_RELATIVE_ID_SYNTAX", 
867         new Object[] {originalRelativeId, originalFrom});
868     }
869     return found;
870   }
871   
872   static private final TrinidadLogger _LOG =
873     TrinidadLogger.createTrinidadLogger(ComponentUtils.class);
874 }