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  
20  package org.apache.myfaces.tobago.util;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.component.Facets;
24  import org.apache.myfaces.tobago.component.RendererTypes;
25  import org.apache.myfaces.tobago.component.SupportsMarkup;
26  import org.apache.myfaces.tobago.context.Markup;
27  import org.apache.myfaces.tobago.context.TransientStateHolder;
28  import org.apache.myfaces.tobago.event.AbstractPopupActionListener;
29  import org.apache.myfaces.tobago.internal.component.AbstractUIForm;
30  import org.apache.myfaces.tobago.internal.component.AbstractUIInput;
31  import org.apache.myfaces.tobago.internal.component.AbstractUIPage;
32  import org.apache.myfaces.tobago.internal.component.AbstractUIPopup;
33  import org.apache.myfaces.tobago.internal.util.ArrayUtils;
34  import org.apache.myfaces.tobago.internal.util.Deprecation;
35  import org.apache.myfaces.tobago.internal.util.FindComponentUtils;
36  import org.apache.myfaces.tobago.internal.util.StringUtils;
37  import org.apache.myfaces.tobago.renderkit.RendererBase;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  import javax.faces.FactoryFinder;
42  import javax.faces.application.Application;
43  import javax.faces.application.FacesMessage;
44  import javax.faces.component.ActionSource;
45  import javax.faces.component.EditableValueHolder;
46  import javax.faces.component.NamingContainer;
47  import javax.faces.component.UICommand;
48  import javax.faces.component.UIComponent;
49  import javax.faces.component.UIGraphic;
50  import javax.faces.component.UIInput;
51  import javax.faces.component.UINamingContainer;
52  import javax.faces.component.UIOutput;
53  import javax.faces.component.UIParameter;
54  import javax.faces.component.UISelectMany;
55  import javax.faces.component.UIViewRoot;
56  import javax.faces.component.ValueHolder;
57  import javax.faces.context.FacesContext;
58  import javax.faces.convert.Converter;
59  import javax.faces.el.ValueBinding;
60  import javax.faces.event.ActionEvent;
61  import javax.faces.event.ActionListener;
62  import javax.faces.event.ValueChangeEvent;
63  import javax.faces.model.SelectItem;
64  import javax.faces.render.RenderKit;
65  import javax.faces.render.RenderKitFactory;
66  import javax.faces.render.Renderer;
67  import javax.faces.view.facelets.FaceletContext;
68  import javax.servlet.jsp.JspException;
69  import java.util.ArrayList;
70  import java.util.HashMap;
71  import java.util.Iterator;
72  import java.util.List;
73  import java.util.Map;
74  
75  public final class ComponentUtils {
76  
77    private static final Logger LOG = LoggerFactory.getLogger(ComponentUtils.class);
78  
79    public static final String SUB_SEPARATOR = "::";
80    
81    private static final String RENDER_KEY_PREFIX
82        = "org.apache.myfaces.tobago.util.ComponentUtils.RendererKeyPrefix_";
83  
84    private static final String PAGE_KEY = "org.apache.myfaces.tobago.Page.Key";
85  
86    public static final Class[] ACTION_ARGS = {};
87    public static final Class[] ACTION_LISTENER_ARGS = {ActionEvent.class};
88    public static final Class[] VALUE_CHANGE_LISTENER_ARGS = {ValueChangeEvent.class};
89    public static final Class[] VALIDATOR_ARGS = {FacesContext.class, UIComponent.class, Object.class};
90    public static final String LIST_SEPARATOR_CHARS = ", ";
91  
92    /**
93     * Name of the map for data attributes in components. New in JSF 2.2.
94     * @since 2.0.0
95     */
96    public static final String DATA_ATTRIBUTES_KEY = "javax.faces.component.DATA_ATTRIBUTES_KEY";
97  
98    private ComponentUtils() {
99    }
100 
101   public static boolean hasErrorMessages(FacesContext context) {
102     for (Iterator iter = context.getMessages(); iter.hasNext();) {
103       FacesMessage message = (FacesMessage) iter.next();
104       if (FacesMessage.SEVERITY_ERROR.compareTo(message.getSeverity()) <= 0) {
105         return true;
106       }
107     }
108     return false;
109   }
110 
111   public static boolean containsPopupActionListener(UICommand command) {
112     ActionListener[] actionListeners = command.getActionListeners();
113     for (ActionListener actionListener : actionListeners) {
114       if (actionListener instanceof AbstractPopupActionListener) {
115         return true;
116       }
117     }
118     return false;
119   }
120 
121   public static String getFacesMessageAsString(FacesContext facesContext, UIComponent component) {
122     Iterator messages = facesContext.getMessages(
123         component.getClientId(facesContext));
124     StringBuilder stringBuffer = new StringBuilder();
125     while (messages.hasNext()) {
126       FacesMessage message = (FacesMessage) messages.next();
127       stringBuffer.append(message.getDetail());
128     }
129     if (stringBuffer.length() > 0) {
130       return stringBuffer.toString();
131     } else {
132       return null;
133     }
134   }
135 
136   public static boolean isInPopup(UIComponent component) {
137     while (component != null) {
138       if (component instanceof AbstractUIPopup) {
139         return true;
140       }
141       component = component.getParent();
142     }
143     return false;
144   }
145 
146   public static void resetPage(FacesContext context) {
147     UIViewRoot view = context.getViewRoot();
148     if (view != null) {
149       view.getAttributes().remove(PAGE_KEY);
150     }
151   }
152 
153   /**
154    * Tries to walk up the parents to find the UIViewRoot, if not found, then go to FaceletContext's FacesContext for
155    * the view root.
156    */
157   public static UIViewRoot findViewRoot(FaceletContext faceletContext, UIComponent component) {
158     UIViewRoot viewRoot = findAncestor(component, UIViewRoot.class);
159     if (viewRoot != null) {
160       return viewRoot;
161     } else {
162       return faceletContext.getFacesContext().getViewRoot();
163     }
164   }
165 
166   public static AbstractUIPage findPage(FacesContext context, UIComponent component) {
167     UIViewRoot view = context.getViewRoot();
168     if (view != null) {
169       TransientStateHolder stateHolder = (TransientStateHolder) view.getAttributes().get(PAGE_KEY);
170       if (stateHolder == null || stateHolder.isEmpty()) {
171         AbstractUIPage page = findPage(component);
172         stateHolder = new TransientStateHolder(page);
173         context.getViewRoot().getAttributes().put(PAGE_KEY, stateHolder);
174       }
175       return (AbstractUIPage) stateHolder.get();
176     } else {
177       return findPage(component);
178     }
179   }
180 
181   public static AbstractUIPage findPage(UIComponent component) {
182     if (component instanceof UIViewRoot) {
183       return findPageBreadthFirst(component);
184     } else {
185       while (component != null) {
186         if (component instanceof AbstractUIPage) {
187           return (AbstractUIPage) component;
188         }
189         component = component.getParent();
190       }
191       return null;
192     }
193   }
194 
195   public static AbstractUIPage findPage(FacesContext facesContext) {
196     return findPageBreadthFirst(facesContext.getViewRoot());
197   }
198 
199   private static AbstractUIPage findPageBreadthFirst(UIComponent component) {
200     for (UIComponent child : component.getChildren()) {
201       if (child instanceof AbstractUIPage) {
202         return (AbstractUIPage) child;
203       }
204     }
205     for (UIComponent child : component.getChildren()) {
206       AbstractUIPage result = findPageBreadthFirst(child);
207       if (result != null) {
208         return result;
209       }
210     }
211     return null;
212   }
213 
214 
215   public static AbstractUIForm findForm(UIComponent component) {
216     while (component != null) {
217       if (component instanceof AbstractUIForm) {
218         return (AbstractUIForm) component;
219       }
220       component = component.getParent();
221     }
222     return null;
223   }
224 
225   public static <T> T findAncestor(UIComponent component, Class<T> type) {
226 
227     while (component != null) {
228       if (type.isAssignableFrom(component.getClass())) {
229         return (T) component;
230       }
231       component = component.getParent();
232     }
233     return null;
234   }
235 
236   /**
237    * Find all sub forms of a component, and collects it.
238    * It does not find sub forms of sub forms.
239    */
240   public static List<AbstractUIForm> findSubForms(UIComponent component) {
241     List<AbstractUIForm> collect = new ArrayList<AbstractUIForm>();
242     findSubForms(collect, component);
243     return collect;
244   }
245 
246   @SuppressWarnings("unchecked")
247   private static void findSubForms(List<AbstractUIForm> collect, UIComponent component) {
248     Iterator<UIComponent> kids = component.getFacetsAndChildren();
249     while (kids.hasNext()) {
250       UIComponent child = kids.next();
251       if (child instanceof AbstractUIForm) {
252         collect.add((AbstractUIForm) child);
253       } else {
254         findSubForms(collect, child);
255       }
256     }
257   }
258 
259   /**
260    * Searches the component tree beneath the component and return the first component matching the type.
261    */
262   public static <T extends UIComponent> T findDescendant(UIComponent component, Class<T> type) {
263 
264     for (UIComponent child : component.getChildren()) {
265       if (type.isAssignableFrom(child.getClass())) {
266         return (T) child;
267       }
268       final T descendant = findDescendant(child, type);
269       if (descendant != null) {
270         return descendant;
271       }
272     }
273     return null;
274   }
275 
276   /**
277    * Searches the component tree beneath the component and return all component matching the type.
278    */
279   public static <T extends UIComponent> List<T> findDescendantList(UIComponent component, Class<T> type) {
280 
281     List<T> result = new ArrayList<T>();
282     
283     for (UIComponent child : component.getChildren()) {
284       if (type.isAssignableFrom(child.getClass())) {
285         result.add((T) child);
286       }
287       result.addAll(findDescendantList(child, type));
288     }
289     return result;
290   }
291 
292   /**
293    * Looks for the attribute "for" in the component. If there is any
294    * search for the component which is referenced by the "for" attribute,
295    * and return their clientId.
296    * If there is no "for" attribute, return the "clientId" of the parent
297    * (if it has a parent). This is useful for labels.
298    */
299   public static String findClientIdFor(UIComponent component, FacesContext facesContext) {
300     UIComponent forComponent = findFor(component);
301     if (forComponent != null) {
302       String clientId = forComponent.getClientId(facesContext);
303       if (LOG.isDebugEnabled()) {
304         LOG.debug("found clientId: '" + clientId + "'");
305       }
306       return clientId;
307     }
308     if (LOG.isDebugEnabled()) {
309       LOG.debug("found no clientId");
310     }
311     return null;
312   }
313 
314   public static UIComponent findFor(UIComponent component) {
315     String forValue = (String) component.getAttributes().get(Attributes.FOR);
316     if (forValue == null) {
317       return component.getParent();
318     }
319     return ComponentUtils.findComponent(component, forValue);
320   }
321 
322   /**
323    * Looks for the attribute "for" of the component.
324    * In case that the value is equals to "@auto" the children of the parent will be
325    * checked if they are a UIInput. The "id" of the first one will be used to reset the "for"
326    * attribute of the component.
327    * @deprecated
328    */
329   @Deprecated
330   public static void evaluateAutoFor(UIComponent component) {
331     String forComponent = (String) component.getAttributes().get(Attributes.FOR);
332     if (LOG.isDebugEnabled()) {
333       LOG.debug("for = '" + forComponent + "'");
334     }
335     if ("@auto".equals(forComponent)) {
336       for (UIComponent child : component.getParent().getChildren()) {
337         if (setForToInput(component, child, AbstractUIInput.class, false)) {
338           break;
339         }
340       }
341     }
342   }
343 
344   /**
345    * Looks for the attribute "for" of the component.
346    * In case that the value is equals to "@auto" the children of the parent will be
347    * checked if they are of the type of the parameter clazz. The "id" of the first one will be used to reset the "for"
348    * attribute of the component.
349    */
350   public static void evaluateAutoFor(UIComponent component, Class<? extends UIComponent> clazz) {
351     String forComponent = (String) component.getAttributes().get(Attributes.FOR);
352     if (LOG.isDebugEnabled()) {
353       LOG.debug("for = '" + forComponent + "'");
354     }
355     if ("@auto".equals(forComponent)) {
356       // parent
357       for (UIComponent child : component.getParent().getChildren()) {
358         if (setForToInput(component, child, clazz, component instanceof NamingContainer)) {
359           return;
360         }
361       }
362       // grand parent
363       for (UIComponent child : component.getParent().getParent().getChildren()) {
364         if (setForToInput(component, child, clazz, component.getParent() instanceof NamingContainer)) {
365           return;
366         }
367       }
368     }
369   }
370 
371   private static boolean setForToInput(
372       UIComponent component, UIComponent child, Class<? extends UIComponent> clazz, boolean namingContainer) {
373     if (clazz.isAssignableFrom(child.getClass())) { // find the matching component
374       final String forComponent;
375       if (namingContainer) {
376         forComponent = ":::" + child.getId();
377       } else {
378         forComponent = child.getId();
379       }
380       component.getAttributes().put(Attributes.FOR, forComponent);
381       return true;
382     }
383     return false;
384   }
385 
386   public static boolean isInActiveForm(UIComponent component) {
387     while (component != null) {
388       if (component instanceof AbstractUIForm) {
389         AbstractUIForm form = (AbstractUIForm) component;
390         if (form.isSubmitted()) {
391           return true;
392         }
393       }
394       component = component.getParent();
395     }
396     return false;
397   }
398 
399   public static FacesMessage.Severity getMaximumSeverity(UIComponent component) {
400     final boolean invalid = component instanceof UIInput && !((UIInput) component).isValid();
401     FacesMessage.Severity max = invalid ? FacesMessage.SEVERITY_ERROR : null;
402     FacesContext facesContext = FacesContext.getCurrentInstance();
403     final Iterator messages = facesContext.getMessages(component.getClientId(facesContext));
404     while (messages.hasNext()) {
405       FacesMessage message = (FacesMessage) messages.next();
406       if (max == null || message.getSeverity().getOrdinal() > max.getOrdinal()) {
407         max = message.getSeverity();
408       }
409     }
410     return max;
411   }
412 
413   public static boolean isError(UIInput uiInput) {
414     FacesContext facesContext = FacesContext.getCurrentInstance();
415     return !uiInput.isValid()
416         || facesContext.getMessages(uiInput.getClientId(facesContext)).hasNext();
417   }
418 
419   public static boolean isError(UIComponent component) {
420     if (component instanceof AbstractUIInput) {
421       return isError((AbstractUIInput) component);
422     }
423     return false;
424   }
425 
426   public static boolean isOutputOnly(UIComponent component) {
427     return getBooleanAttribute(component, Attributes.DISABLED)
428         || getBooleanAttribute(component, Attributes.READONLY);
429   }
430 
431   public static boolean mayValidate(UIComponent component) {
432     return !isOutputOnly(component)
433         && ComponentUtils.isInActiveForm(component);
434   }
435 
436   public static boolean mayUpdateModel(UIComponent component) {
437     return mayValidate(component);
438   }
439 
440   public static boolean getBooleanAttribute(UIComponent component, String name) {
441 
442     Object bool = component.getAttributes().get(name);
443     if (bool == null) {
444       return false;
445     }
446     if (bool instanceof ValueBinding) {
447       bool = ((ValueBinding) bool).getValue(FacesContext.getCurrentInstance());
448     }
449     if (bool instanceof Boolean) {
450       return (Boolean) bool;
451     } else if (bool instanceof String) {
452       LOG.warn("Searching for a boolean, but find a String. Should not happen. "
453           + "attribute: '" + name + "' id: '" + component.getClientId(FacesContext.getCurrentInstance())
454           + "' comp: '" + component + "'");
455       return Boolean.valueOf((String) bool);
456     } else {
457       LOG.warn("Unknown type '" + bool.getClass().getName()
458           + "' for boolean attribute: " + name + " id: " + component.getClientId(FacesContext.getCurrentInstance())
459           + " comp: " + component);
460       return false;
461     }
462   }
463 
464 
465 
466   public static ValueBinding createValueBinding(String value) {
467     return FacesContext.getCurrentInstance().getApplication().createValueBinding(value);
468   }
469 
470   /**
471    * @deprecated since 1.5.0
472    * Please define a {@link Markup} and set it to the component with
473    * {@link SupportsMarkup#setMarkup(Markup markup)} before the rendering phase.
474    */
475   @Deprecated
476   public static void setStyleClasses(UIComponent component, String styleClasses) {
477     Deprecation.LOG.warn("style class " + styleClasses);
478   }
479 
480   /**
481    * @deprecated since 1.5.0
482    */
483   @Deprecated
484   public static void setMarkup(UIComponent markupComponent, String markup) {
485     Deprecation.LOG.error("markup=" + markup);
486   }
487 
488   public static Object getAttribute(UIComponent component, String name) {
489     Object value = component.getAttributes().get(name);
490     if (value instanceof ValueBinding) {
491       value = ((ValueBinding) value).getValue(FacesContext.getCurrentInstance());
492     }
493     return value;
494   }
495 
496   public static Object getObjectAttribute(UIComponent component, String name) {
497     return getAttribute(component, name);
498   }
499 
500   public static String getStringAttribute(UIComponent component, String name) {
501     return (String) getAttribute(component, name);
502   }
503 
504   public static int getIntAttribute(UIComponent component, String name) {
505     return getIntAttribute(component, name, 0);
506   }
507 
508   public static int getIntAttribute(UIComponent component, String name,
509       int defaultValue) {
510     Object integer = component.getAttributes().get(name);
511     if (integer instanceof Number) {
512       return ((Number) integer).intValue();
513     } else if (integer instanceof String) {
514       try {
515         return Integer.parseInt((String) integer);
516       } catch (NumberFormatException e) {
517         LOG.warn("Can't parse number from string : \"" + integer + "\"!");
518         return defaultValue;
519       }
520     } else if (integer == null) {
521       return defaultValue;
522     } else {
523       LOG.warn("Unknown type '" + integer.getClass().getName()
524           + "' for integer attribute: " + name + " comp: " + component);
525       return defaultValue;
526     }
527   }
528 
529   public static Character getCharacterAttribute(UIComponent component, String name) {
530     Object character = component.getAttributes().get(name);
531     if (character == null) {
532       return null;
533     } else if (character instanceof Character) {
534       return ((Character) character);
535     } else if (character instanceof String) {
536       String asString = ((String) character);
537       return asString.length() > 0 ? asString.charAt(0) : null;
538     } else {
539       LOG.warn("Unknown type '" + character.getClass().getName()
540           + "' for integer attribute: " + name + " comp: " + component);
541       return null;
542     }
543   }
544 
545   public static boolean isFacetOf(UIComponent component, UIComponent parent) {
546     for (Object o : parent.getFacets().keySet()) {
547       UIComponent facet = parent.getFacet((String) o);
548       if (component.equals(facet)) {
549         return true;
550       }
551     }
552     return false;
553   }
554 
555   public static RendererBase getRenderer(FacesContext facesContext, UIComponent component) {
556     return getRenderer(facesContext, component.getFamily(), component.getRendererType());
557   }
558 
559   public static RendererBase getRenderer(FacesContext facesContext, String family, String rendererType) {
560     if (rendererType == null) {
561       return null;
562     }
563 
564     Map<String, Object> requestMap = (Map<String, Object>) facesContext.getExternalContext().getRequestMap();
565     StringBuilder key = new StringBuilder(RENDER_KEY_PREFIX);
566     key.append(rendererType);
567     RendererBase renderer = (RendererBase) requestMap.get(key.toString());
568 
569     if (renderer == null) {
570       Renderer myRenderer = getRendererInternal(facesContext, family, rendererType);
571       if (myRenderer instanceof RendererBase) {
572         requestMap.put(key.toString(), myRenderer);
573         renderer = (RendererBase) myRenderer;
574       } else {
575         return null;
576       }
577     }
578     return renderer;
579   }
580 
581 
582   private static Renderer getRendererInternal(FacesContext facesContext, String family, String rendererType) {
583     RenderKitFactory rkFactory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
584     RenderKit renderKit = rkFactory.getRenderKit(facesContext, facesContext.getViewRoot().getRenderKitId());
585     Renderer myRenderer = renderKit.getRenderer(family, rendererType);
586     return myRenderer;
587   }
588 
589   public static Object findParameter(UIComponent component, String name) {
590     for (UIComponent child : component.getChildren()) {
591       if (child instanceof UIParameter) {
592         UIParameter parameter = (UIParameter) child;
593         if (LOG.isDebugEnabled()) {
594           LOG.debug("Select name='" + parameter.getName() + "'");
595           LOG.debug("Select value='" + parameter.getValue() + "'");
596         }
597         if (name.equals(parameter.getName())) {
598           return parameter.getValue();
599         }
600       }
601     }
602     return null;
603   }
604 
605   public static ActionListener createActionListener(String type)
606       throws JspException {
607     try {
608       ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
609       if (classLoader == null) {
610         classLoader = type.getClass().getClassLoader();
611       }
612       Class clazz = classLoader.loadClass(type);
613       return (ActionListener) clazz.newInstance();
614     } catch (Exception e) {
615       if (LOG.isDebugEnabled()) {
616         LOG.debug("type=" + type, e);
617       }
618       throw new JspException(e);
619     }
620   }
621 
622   public static UIGraphic getFirstGraphicChild(UIComponent component) {
623     UIGraphic graphic = null;
624     for (UIComponent child : component.getChildren()) {
625       if (child instanceof UIGraphic) {
626         graphic = (UIGraphic) child;
627         break;
628       }
629     }
630     return graphic;
631   }
632 
633   public static boolean isHoverEnabled(UIComponent component) {
634     return ComponentUtils.getBooleanAttribute(component, Attributes.HOVER);
635   }
636 
637   public static UIOutput getFirstNonGraphicChild(UIComponent component) {
638     for (UIComponent child : component.getChildren()) {
639       if (child instanceof UIOutput) {
640         return (UIOutput) child;
641       }
642     }
643     return null;
644   }
645 
646   /**
647    * @deprecated since 1.5.0
648    */
649   @Deprecated
650   public static void setIntegerSizeProperty(UIComponent component, String name, String value) {
651     Deprecation.LOG.error("name=" + name + " value=" + value);
652   }
653 
654   public static String removePx(String value) {
655     if (value != null && value.endsWith("px")) {
656       value = value.substring(0, value.length() - 2);
657     }
658     return value;
659   }
660 
661   public static void setValueForValueBinding(String name, Object value) {
662     FacesContext context = FacesContext.getCurrentInstance();
663     ValueBinding valueBinding = context.getApplication().createValueBinding(name);
664     valueBinding.setValue(context, value);
665   }
666 
667 
668   public static boolean hasSelectedValue(List<SelectItem> items, Object value) {
669     for (SelectItem item : items) {
670       if (item.getValue().equals(value)) {
671         return true;
672       }
673     }
674     return false;
675   }
676 
677   public static int getIntValue(ValueBinding valueBinding) {
678     return getAsInt(valueBinding.getValue(FacesContext.getCurrentInstance()));
679   }
680 
681   private static int getAsInt(Object value) {
682     int result;
683     if (value instanceof Number) {
684       result = ((Number) value).intValue();
685     } else if (value instanceof String) {
686       result = Integer.parseInt((String) value);
687     } else {
688       throw new IllegalArgumentException("Can't convert " + value + " to int!");
689     }
690     return result;
691   }
692 
693 
694   public static String createPickerId(FacesContext facesContext, UIComponent component, String postfix) {
695     //String id = component.getId();
696     String id = getComponentId(facesContext, component);
697     return id + "_picker" + postfix;
698   }
699 
700   public static String getComponentId(FacesContext facesContext, UIComponent component) {
701     String id = component.getId();
702     //if (id == null) {
703     // XXX What is this?
704     //  id = component.getClientId(facesContext).substring(id.lastIndexOf('_'));
705     //}
706     return id;
707   }
708 
709   /**
710    * Checks if the Component has a label facet and if not creates one with the label attribute.
711    *
712    * Todo: check if this method should be set to deprecated. 
713    */
714   public static UIComponent provideLabel(FacesContext facesContext, UIComponent component) {
715     UIComponent label = component.getFacet(Facets.LABEL);
716 
717 
718     if (label == null) {
719       final Map attributes = component.getAttributes();
720       Object labelText = component.getValueBinding(Attributes.LABEL);
721       if (labelText == null) {
722         labelText = attributes.get(Attributes.LABEL);
723       }
724 
725       if (labelText != null) {
726         Application application = FacesContext.getCurrentInstance().getApplication();
727         label = application.createComponent(UIOutput.COMPONENT_TYPE);
728         label.setRendererType(RendererTypes.LABEL);
729         String idprefix = ComponentUtils.getComponentId(facesContext, component);
730         label.setId(idprefix + "_" + Facets.LABEL);
731         label.setRendered(true);
732 
733         if (labelText instanceof ValueBinding) {
734           label.setValueBinding(Attributes.VALUE, (ValueBinding) labelText);
735         } else {
736           label.getAttributes().put(Attributes.VALUE, labelText);
737         }
738 
739         component.getFacets().put(Facets.LABEL, label);
740       }
741     }
742     return label;
743   }
744 
745   /**
746    * @deprecated since 1.5.0
747    */
748   @Deprecated
749   public static void setValidator(EditableValueHolder editableValueHolder, String validator) {
750     Deprecation.LOG.error("validator=" + validator);
751   }
752 
753   /**
754    * @deprecated since 1.5.0
755    */
756   @Deprecated
757   public static void setConverter(ValueHolder valueHolder, String converterId) {
758     Deprecation.LOG.error("converterId=" + converterId);
759   }
760 
761   /**
762    * @deprecated since 1.5.0
763    */
764   @Deprecated
765   public static void setAction(ActionSource component, String action) {
766     Deprecation.LOG.error("action=" + action);
767   }
768 
769   /**
770    * @deprecated since 1.5.0
771    */
772   @Deprecated
773   public static void setActionListener(ActionSource command, String actionListener) {
774     Deprecation.LOG.error("actionListener=" + actionListener);
775   }
776 
777   /**
778    * @deprecated since 1.5.0
779    */
780   @Deprecated
781   public static void setValueChangeListener(EditableValueHolder valueHolder, String valueChangeListener) {
782     Deprecation.LOG.error("valueChangeListener=" + valueChangeListener);
783   }
784 
785   /**
786    * @deprecated since 1.5.0
787    */
788   @Deprecated
789   public static void setValueBinding(UIComponent component, String name, String state) {
790     Deprecation.LOG.error("name=" + name + " state=" + state);
791   }
792 
793   /**
794    * @deprecated since 1.5
795    */
796   @Deprecated
797   public static String[] getMarkupBinding(FacesContext facesContext, SupportsMarkup component) {
798     ValueBinding vb = ((UIComponent) component).getValueBinding(Attributes.MARKUP);
799     if (vb != null) {
800       Object markups = vb.getValue(facesContext);
801       if (markups instanceof String[]) {
802         return (String[]) markups;
803       } else if (markups instanceof String) {
804         String[] strings = StringUtils.split((String) markups, ", ");
805         List<String> result = new ArrayList<String>(strings.length);
806         for (String string : strings) {
807           if (string.trim().length() != 0) {
808             result.add(string.trim());
809           }
810         }
811         return result.toArray(new String[result.size()]);
812       } else if (markups == null) {
813         return ArrayUtils.EMPTY_STRING_ARRAY;
814       } else {
815         return new String[]{markups.toString()};
816       }
817     }
818 
819     return ArrayUtils.EMPTY_STRING_ARRAY;
820   }
821 
822   /**
823    * The search depends on the number of colons in the relativeId:
824    * <dl>
825    *   <dd>colonCount == 0</dd>
826    *   <dt>fully relative</dt>
827    *   <dd>colonCount == 1</dd>
828    *   <dt>absolute (still normal findComponent syntax)</dt>
829    *   <dd>colonCount > 1</dd>
830    *   <dt>for each extra colon after 1, go up a naming container</dt>
831    * </dl>
832    * (to the view root, if naming containers run out)
833    */
834   public static UIComponent findComponent(UIComponent from, String relativeId) {
835     return FindComponentUtils.findComponent(from, relativeId);
836   }
837 
838   public static String[] splitList(String renderers) {
839     return StringUtils.split(renderers, LIST_SEPARATOR_CHARS);
840   }
841 
842   public static Object getConvertedValue(
843       FacesContext facesContext, UIComponent component, String stringValue) {
844     try {
845       Renderer renderer = getRenderer(facesContext, component);
846       if (renderer != null) {
847         if (component instanceof UISelectMany) {
848           final Object converted = renderer.getConvertedValue(facesContext, component, new String[]{stringValue});
849           return converted instanceof List ? ((List) converted).get(0) : ((Object[]) converted)[0];
850         } else {
851           return renderer.getConvertedValue(facesContext, component, stringValue);
852         }
853       } else if (component instanceof ValueHolder) {
854         Converter converter = ((ValueHolder) component).getConverter();
855         if (converter == null) {
856           //Try to find out by value binding
857           ValueBinding vb = component.getValueBinding("value");
858           if (vb != null) {
859             Class valueType = vb.getType(facesContext);
860             if (valueType != null) {
861               converter = facesContext.getApplication().createConverter(valueType);
862             }
863           }
864         }
865         if (converter != null) {
866           converter.getAsObject(facesContext, component, stringValue);
867         }
868       }
869     } catch (Exception e) {
870       LOG.warn("Can't convert string value '" + stringValue + "'", e);
871     }
872     return stringValue;
873   }
874 
875   public static Markup updateMarkup(UIComponent component, Markup markup) {
876     if (markup == null) {
877       markup = Markup.NULL;
878     }
879     if (ComponentUtils.getBooleanAttribute(component, Attributes.DISABLED)) {
880       markup = markup.add(Markup.DISABLED);
881     }
882     if (ComponentUtils.getBooleanAttribute(component, Attributes.READONLY)) {
883       markup = markup.add(Markup.READONLY);
884     }
885     if (component instanceof UIInput) {
886       UIInput input = (UIInput) component;
887 
888       final FacesMessage.Severity maximumSeverity = ComponentUtils.getMaximumSeverity(input);
889       markup = markup.add(markupOfSeverity(maximumSeverity));
890 
891       if (input.isRequired()) {
892         markup = markup.add(Markup.REQUIRED);
893       }
894     }
895     return markup;
896   }
897 
898   public static Markup markupOfSeverity(FacesMessage.Severity maximumSeverity) {
899     if (FacesMessage.SEVERITY_FATAL.equals(maximumSeverity)) {
900       return Markup.FATAL;
901     } else if (FacesMessage.SEVERITY_ERROR.equals(maximumSeverity)) {
902       return Markup.ERROR;
903     } else if (FacesMessage.SEVERITY_WARN.equals(maximumSeverity)) {
904       return Markup.WARN;
905     } else if (FacesMessage.SEVERITY_INFO.equals(maximumSeverity)) {
906       return Markup.INFO;
907     }
908     return null;
909   }
910 
911   public static void addCurrentMarkup(SupportsMarkup component, Markup markup) {
912     component.setCurrentMarkup(markup.add(component.getCurrentMarkup()));
913   }
914 
915   public static boolean hasChildrenWithMessages(FacesContext facesContext, NamingContainer  container) {
916     if (container instanceof UIComponent) {
917       String clientId = ((UIComponent) container).getClientId(facesContext);
918       for (Iterator ids = facesContext.getClientIdsWithMessages(); ids.hasNext();) {
919         String id = (String) ids.next();
920         if (id.startsWith(clientId)) {
921           return true;
922         }
923       }
924     }
925     return false;
926   }
927 
928   public static FacesMessage.Severity getMaximumSeverityOfChildrenMessages(
929       FacesContext facesContext, NamingContainer container) {
930     if (container instanceof UIComponent) {
931       String clientId = ((UIComponent) container).getClientId(facesContext);
932       FacesMessage.Severity max = null;
933       for (Iterator ids = facesContext.getClientIdsWithMessages(); ids.hasNext();) {
934         String id = (String) ids.next();
935         if (id != null && id.startsWith(clientId)) {
936           final Iterator messages = facesContext.getMessages(id);
937           while (messages.hasNext()) {
938             FacesMessage message = (FacesMessage) messages.next();
939             if (max == null || message.getSeverity().getOrdinal() > max.getOrdinal()) {
940               max = message.getSeverity();
941             }
942           }
943         }
944       }
945       return max;
946     }
947     return null;
948   }
949 
950   public static String[] getChildrenWithMessages(FacesContext facesContext, NamingContainer container) {
951     if (container instanceof UIComponent) {
952       List<String> clientIds = new ArrayList<String>();
953       String clientId = ((UIComponent) container).getClientId(facesContext);
954       for (Iterator ids = facesContext.getClientIdsWithMessages(); ids.hasNext();) {
955         String id = (String) ids.next();
956         if (id.startsWith(clientId)) {
957           clientIds.add(id);
958         }
959       }
960       return clientIds.toArray(new String[clientIds.size()]);
961     }
962     return ArrayUtils.EMPTY_STRING_ARRAY;
963   }
964 
965   /**
966    * Adding a data attribute to the component. 
967    * The name must start with "data-", e. g. "data-tobago-foo" or "data-bar"
968    */
969   public static void putDataAttributeWithPrefix(UIComponent component, String name, Object value) {
970     if (name.startsWith("data-")) {
971       putDataAttribute(component, name.substring(5), value);
972     } else {
973       LOG.error("The name must start with 'data-' but it doesn't: '" + name + "'");
974     }
975   }
976 
977   /**
978    * Adding a data attribute to the component.
979    * The name should not start with "data-", e. g. "tobago-foo" or "bar"
980    */
981   public static void putDataAttribute(UIComponent component, Object name, Object value) {
982     Map<Object, Object> map = getDataAttributes(component);
983     if (map == null) {
984       map = new HashMap<Object, Object>();
985       component.getAttributes().put(DATA_ATTRIBUTES_KEY, map);
986     }
987     map.put(name, value);
988   }
989 
990   @SuppressWarnings("unchecked")
991   public static Map<Object, Object> getDataAttributes(UIComponent component) {
992     return (Map<Object, Object>) component.getAttributes().get(DATA_ATTRIBUTES_KEY);
993   }
994 
995   public static boolean invokeOnComponent(
996       FacesContext context, UIComponent component, String clientId, javax.faces.component.ContextCallback callback) {
997     String thisClientId = component.getClientId(context);
998 
999     if (clientId.equals(thisClientId)) {
1000       callback.invokeContextCallback(context, component);
1001       return true;
1002     } else if (component instanceof NamingContainer) {
1003       // This component is a naming container. If the client id shows it's inside this naming container,
1004       // then process further.
1005       // Otherwise we know the client id we're looking for is not in this naming container,
1006       // so for improved performance short circuit and return false.
1007       if (clientId.startsWith(thisClientId)
1008           && (clientId.charAt(thisClientId.length()) == UINamingContainer.getSeparatorChar(context))) {
1009         if (invokeOnComponentFacetsAndChildren(context, component, clientId, callback)) {
1010           return true;
1011         }
1012       }
1013     } else {
1014       if (invokeOnComponentFacetsAndChildren(context, component, clientId, callback)) {
1015         return true;
1016       }
1017     }
1018 
1019     return false;
1020   }
1021 
1022   private static boolean invokeOnComponentFacetsAndChildren(
1023       FacesContext context, UIComponent component, String clientId, javax.faces.component.ContextCallback callback) {
1024     for (java.util.Iterator<UIComponent> it = component.getFacetsAndChildren(); it.hasNext();) {
1025       UIComponent child = it.next();
1026       if (child.invokeOnComponent(context, clientId, callback)) {
1027         return true;
1028       }
1029     }
1030     return false;
1031   }
1032 
1033 }