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