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.commons.collections.CollectionUtils;
23  import org.apache.myfaces.tobago.component.Attributes;
24  import org.apache.myfaces.tobago.component.Facets;
25  import org.apache.myfaces.tobago.context.Markup;
26  import org.apache.myfaces.tobago.context.TransientStateHolder;
27  import org.apache.myfaces.tobago.event.AbstractPopupActionListener;
28  import org.apache.myfaces.tobago.internal.component.AbstractUIForm;
29  import org.apache.myfaces.tobago.internal.component.AbstractUIInput;
30  import org.apache.myfaces.tobago.internal.component.AbstractUIPage;
31  import org.apache.myfaces.tobago.internal.component.AbstractUIPopup;
32  import org.apache.myfaces.tobago.internal.component.AbstractUISheet;
33  import org.apache.myfaces.tobago.internal.util.StringUtils;
34  import org.apache.myfaces.tobago.renderkit.RendererBase;
35  import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  import javax.faces.FactoryFinder;
40  import javax.faces.application.FacesMessage;
41  import javax.faces.component.NamingContainer;
42  import javax.faces.component.UICommand;
43  import javax.faces.component.UIComponent;
44  import javax.faces.component.UIInput;
45  import javax.faces.component.UINamingContainer;
46  import javax.faces.component.UIParameter;
47  import javax.faces.component.UISelectMany;
48  import javax.faces.component.UIViewRoot;
49  import javax.faces.component.ValueHolder;
50  import javax.faces.context.FacesContext;
51  import javax.faces.convert.Converter;
52  import javax.faces.el.ValueBinding;
53  import javax.faces.event.ActionEvent;
54  import javax.faces.event.ActionListener;
55  import javax.faces.event.ValueChangeEvent;
56  import javax.faces.render.RenderKit;
57  import javax.faces.render.RenderKitFactory;
58  import javax.faces.render.Renderer;
59  import javax.faces.view.facelets.FaceletContext;
60  import java.util.ArrayList;
61  import java.util.Collection;
62  import java.util.HashMap;
63  import java.util.Iterator;
64  import java.util.List;
65  import java.util.Map;
66  
67  public final class ComponentUtils {
68  
69    private static final Logger LOG = LoggerFactory.getLogger(ComponentUtils.class);
70  
71    public static final String SUB_SEPARATOR = "::";
72    
73    private static final String RENDER_KEY_PREFIX
74        = "org.apache.myfaces.tobago.util.ComponentUtils.RendererKeyPrefix_";
75  
76    private static final String PAGE_KEY = "org.apache.myfaces.tobago.Page.Key";
77  
78    public static final Class[] ACTION_ARGS = {};
79    public static final Class[] ACTION_LISTENER_ARGS = {ActionEvent.class};
80    public static final Class[] VALUE_CHANGE_LISTENER_ARGS = {ValueChangeEvent.class};
81    public static final Class[] VALIDATOR_ARGS = {FacesContext.class, UIComponent.class, Object.class};
82    public static final String LIST_SEPARATOR_CHARS = ", ";
83  
84    /**
85     * Name of the map for data attributes in components. New in JSF 2.2.
86     * @since 2.0.0
87     */
88    public static final String DATA_ATTRIBUTES_KEY = "javax.faces.component.DATA_ATTRIBUTES_KEY";
89  
90    private ComponentUtils() {
91    }
92  
93    public static boolean hasErrorMessages(final FacesContext context) {
94      for (final Iterator iter = context.getMessages(); iter.hasNext();) {
95        final FacesMessage message = (FacesMessage) iter.next();
96        if (FacesMessage.SEVERITY_ERROR.compareTo(message.getSeverity()) <= 0) {
97          return true;
98        }
99      }
100     return false;
101   }
102 
103   public static boolean containsPopupActionListener(final UICommand command) {
104     final ActionListener[] actionListeners = command.getActionListeners();
105     for (final ActionListener actionListener : actionListeners) {
106       if (actionListener instanceof AbstractPopupActionListener) {
107         return true;
108       }
109     }
110     return false;
111   }
112 
113   public static String getFacesMessageAsString(final FacesContext facesContext, final UIComponent component) {
114     final Iterator messages = facesContext.getMessages(
115         component.getClientId(facesContext));
116     final StringBuilder stringBuffer = new StringBuilder();
117     while (messages.hasNext()) {
118       final FacesMessage message = (FacesMessage) messages.next();
119       stringBuffer.append(message.getDetail());
120     }
121     if (stringBuffer.length() > 0) {
122       return stringBuffer.toString();
123     } else {
124       return null;
125     }
126   }
127 
128   public static boolean isInPopup(UIComponent component) {
129     while (component != null) {
130       if (component instanceof AbstractUIPopup) {
131         return true;
132       }
133       component = component.getParent();
134     }
135     return false;
136   }
137 
138   public static void resetPage(final FacesContext context) {
139     final UIViewRoot view = context.getViewRoot();
140     if (view != null) {
141       view.getAttributes().remove(PAGE_KEY);
142     }
143   }
144 
145   /**
146    * Tries to walk up the parents to find the UIViewRoot, if not found, then go to FaceletContext's FacesContext for
147    * the view root.
148    */
149   public static UIViewRoot findViewRoot(final FaceletContext faceletContext, final UIComponent component) {
150     final UIViewRoot viewRoot = findAncestor(component, UIViewRoot.class);
151     if (viewRoot != null) {
152       return viewRoot;
153     } else {
154       return faceletContext.getFacesContext().getViewRoot();
155     }
156   }
157 
158   public static AbstractUIPage findPage(final FacesContext context, final UIComponent component) {
159     final UIViewRoot view = context.getViewRoot();
160     if (view != null) {
161       TransientStateHolder stateHolder = (TransientStateHolder) view.getAttributes().get(PAGE_KEY);
162       if (stateHolder == null || stateHolder.isEmpty()) {
163         final AbstractUIPage page = findPage(component);
164         stateHolder = new TransientStateHolder(page);
165         context.getViewRoot().getAttributes().put(PAGE_KEY, stateHolder);
166       }
167       return (AbstractUIPage) stateHolder.get();
168     } else {
169       return findPage(component);
170     }
171   }
172 
173   public static AbstractUIPage findPage(UIComponent component) {
174     if (component instanceof UIViewRoot) {
175       return findPageBreadthFirst(component);
176     } else {
177       while (component != null) {
178         if (component instanceof AbstractUIPage) {
179           return (AbstractUIPage) component;
180         }
181         component = component.getParent();
182       }
183       return null;
184     }
185   }
186 
187   public static AbstractUIPage findPage(final FacesContext facesContext) {
188     return findPageBreadthFirst(facesContext.getViewRoot());
189   }
190 
191   private static AbstractUIPage findPageBreadthFirst(final UIComponent component) {
192     for (final UIComponent child : component.getChildren()) {
193       if (child instanceof AbstractUIPage) {
194         return (AbstractUIPage) child;
195       }
196     }
197     for (final UIComponent child : component.getChildren()) {
198       final AbstractUIPage result = findPageBreadthFirst(child);
199       if (result != null) {
200         return result;
201       }
202     }
203     return null;
204   }
205 
206 
207   public static AbstractUIForm findForm(UIComponent component) {
208     while (component != null) {
209       if (component instanceof AbstractUIForm) {
210         return (AbstractUIForm) component;
211       }
212       component = component.getParent();
213     }
214     return null;
215   }
216 
217   public static <T> T findAncestor(UIComponent component, final Class<T> type) {
218 
219     while (component != null) {
220       if (type.isAssignableFrom(component.getClass())) {
221         return (T) component;
222       }
223       component = component.getParent();
224     }
225     return null;
226   }
227 
228   /**
229    * Find all sub forms of a component, and collects it.
230    * It does not find sub forms of sub forms.
231    */
232   public static List<AbstractUIForm> findSubForms(final UIComponent component) {
233     final List<AbstractUIForm> collect = new ArrayList<AbstractUIForm>();
234     findSubForms(collect, component);
235     return collect;
236   }
237 
238   @SuppressWarnings("unchecked")
239   private static void findSubForms(final List<AbstractUIForm> collect, final UIComponent component) {
240     final Iterator<UIComponent> kids = component.getFacetsAndChildren();
241     while (kids.hasNext()) {
242       final UIComponent child = kids.next();
243       if (child instanceof AbstractUIForm) {
244         collect.add((AbstractUIForm) child);
245       } else {
246         findSubForms(collect, child);
247       }
248     }
249   }
250 
251   /**
252    * Searches the component tree beneath the component and return the first component matching the type.
253    */
254   public static <T extends UIComponent> T findDescendant(final UIComponent component, final Class<T> type) {
255 
256     for (final UIComponent child : component.getChildren()) {
257       if (type.isAssignableFrom(child.getClass())) {
258         return (T) child;
259       }
260       final T descendant = findDescendant(child, type);
261       if (descendant != null) {
262         return descendant;
263       }
264     }
265     return null;
266   }
267 
268   /**
269    * Searches the component tree beneath the component and return the first component matching the type.
270    */
271   public static <T extends UIComponent> T findFacetDescendant(
272       final UIComponent component, final Facets facet, final Class<T> type) {
273 
274     final UIComponent facetComponent = component.getFacet(facet.name());
275     if (facetComponent != null) {
276       if (type.isAssignableFrom(facetComponent.getClass())) {
277         return (T) facetComponent;
278       } else {
279         return findDescendant(facetComponent, type);
280       }
281     } else {
282       return null;
283     }
284   }
285 
286   /**
287    * Searches the children beneath the component and return the first component matching the type.
288    */
289   public static <T extends UIComponent> T findChild(final UIComponent component, final Class<T> type) {
290 
291     for (final UIComponent child : component.getChildren()) {
292       if (type.isAssignableFrom(child.getClass())) {
293         return (T) child;
294       }
295     }
296     return null;
297   }
298 
299   /**
300    * Searches the component tree beneath the component and return all component matching the type.
301    */
302   public static <T extends UIComponent> List<T> findDescendantList(final UIComponent component, final Class<T> type) {
303 
304     final List<T> result = new ArrayList<T>();
305     
306     for (final UIComponent child : component.getChildren()) {
307       if (type.isAssignableFrom(child.getClass())) {
308         result.add((T) child);
309       }
310       result.addAll(findDescendantList(child, type));
311     }
312     return result;
313   }
314 
315   /**
316    * Looks for the attribute "for" in the component. If there is any
317    * search for the component which is referenced by the "for" attribute,
318    * and return their clientId.
319    * If there is no "for" attribute, return the "clientId" of the parent
320    * (if it has a parent). This is useful for labels.
321    */
322   public static String findClientIdFor(final UIComponent component, final FacesContext facesContext) {
323     final UIComponent forComponent = findFor(component);
324     if (forComponent != null) {
325       final String clientId = forComponent.getClientId(facesContext);
326       if (LOG.isDebugEnabled()) {
327         LOG.debug("found clientId: '" + clientId + "'");
328       }
329       return clientId;
330     }
331     if (LOG.isDebugEnabled()) {
332       LOG.debug("found no clientId");
333     }
334     return null;
335   }
336 
337   public static UIComponent findFor(final UIComponent component) {
338     final String forValue = getStringAttribute(component, Attributes.forValue);
339     if (forValue == null) {
340       return component.getParent();
341     }
342     return ComponentUtils.findComponent(component, forValue);
343   }
344 
345   /**
346    * Looks for the attribute "for" of the component.
347    * In case that the value is equals to "@auto" the children of the parent will be
348    * checked if they are of the type of the parameter clazz. The "id" of the first one will be used to reset the "for"
349    * attribute of the component.
350    */
351   public static void evaluateAutoFor(final UIComponent component, final Class<? extends UIComponent> clazz) {
352     final String forComponent = getStringAttribute(component, Attributes.forValue);
353     if (LOG.isDebugEnabled()) {
354       LOG.debug("for = '" + forComponent + "'");
355     }
356     if ("@auto".equals(forComponent)) {
357       // parent
358       for (final UIComponent child : component.getParent().getChildren()) {
359         if (setForToInput(component, child, clazz, component instanceof NamingContainer)) {
360           return;
361         }
362       }
363       // grand parent
364       for (final UIComponent child : component.getParent().getParent().getChildren()) {
365         if (setForToInput(component, child, clazz, component.getParent() instanceof NamingContainer)) {
366           return;
367         }
368       }
369     }
370   }
371 
372   private static boolean setForToInput(
373       final UIComponent component, final UIComponent child, final Class<? extends UIComponent> clazz,
374       final boolean namingContainer) {
375     if (clazz.isAssignableFrom(child.getClass())) { // find the matching component
376       final String forComponent;
377       if (namingContainer) {
378         forComponent = ":::" + child.getId();
379       } else {
380         forComponent = child.getId();
381       }
382       ComponentUtils.setAttribute(component, Attributes.forValue, forComponent);
383       return true;
384     }
385     return false;
386   }
387 
388   public static boolean isInActiveForm(UIComponent component) {
389     while (component != null) {
390       if (component instanceof AbstractUIForm) {
391         final AbstractUIForm form = (AbstractUIForm) component;
392         if (form.isSubmitted()) {
393           return true;
394         }
395       }
396       component = component.getParent();
397     }
398     return false;
399   }
400 
401   public static FacesMessage.Severity getMaximumSeverity(final UIComponent component) {
402     final boolean invalid = component instanceof UIInput && !((UIInput) component).isValid();
403     FacesMessage.Severity max = invalid ? FacesMessage.SEVERITY_ERROR : null;
404     final FacesContext facesContext = FacesContext.getCurrentInstance();
405     final Iterator messages = facesContext.getMessages(component.getClientId(facesContext));
406     while (messages.hasNext()) {
407       final FacesMessage message = (FacesMessage) messages.next();
408       if (max == null || message.getSeverity().getOrdinal() > max.getOrdinal()) {
409         max = message.getSeverity();
410       }
411     }
412     return max;
413   }
414 
415   public static boolean isError(final UIInput uiInput) {
416     final FacesContext facesContext = FacesContext.getCurrentInstance();
417     return !uiInput.isValid()
418         || facesContext.getMessages(uiInput.getClientId(facesContext)).hasNext();
419   }
420 
421   public static boolean isError(final UIComponent component) {
422     if (component instanceof AbstractUIInput) {
423       return isError((AbstractUIInput) component);
424     }
425     return false;
426   }
427 
428   public static boolean isOutputOnly(final UIComponent component) {
429     return getBooleanAttribute(component, Attributes.disabled)
430         || getBooleanAttribute(component, Attributes.readonly);
431   }
432 
433   public static boolean mayValidate(final UIComponent component) {
434     return !isOutputOnly(component)
435         && ComponentUtils.isInActiveForm(component);
436   }
437 
438   public static boolean mayUpdateModel(final UIComponent component) {
439     return mayValidate(component);
440   }
441 
442   public static Object getAttribute(final UIComponent component, final Attributes name) {
443     return component.getAttributes().get(name.getName());
444   }
445 
446   public static boolean getBooleanAttribute(final UIComponent component, final Attributes name) {
447     return getBooleanAttribute(component, name, false);
448   }
449 
450   public static boolean getBooleanAttribute(
451       final UIComponent component, final Attributes name, final boolean defaultValue) {
452 
453     Object bool = component.getAttributes().get(name.getName());
454     if (bool == null) {
455       return defaultValue;
456     } else if (bool instanceof Boolean) {
457       return (Boolean) bool;
458     } else {
459       return Boolean.valueOf(bool.toString());
460     }
461   }
462 
463   public static String getStringAttribute(final UIComponent component, final Attributes name) {
464     return getStringAttribute(component, name, null);
465   }
466 
467   public static String getStringAttribute(
468       final UIComponent component, final Attributes name, final String defaultValue) {
469     final String result = (String) getAttribute(component, name);
470     return result != null ? result : defaultValue;
471   }
472 
473   public static int getIntAttribute(final UIComponent component, final Attributes name) {
474     return getIntAttribute(component, name, 0);
475   }
476 
477   public static int getIntAttribute(final UIComponent component, final Attributes name, final int defaultValue) {
478     final Object integer = component.getAttributes().get(name.getName());
479     if (integer instanceof Number) {
480       return ((Number) integer).intValue();
481     } else if (integer instanceof String) {
482       try {
483         return Integer.parseInt((String) integer);
484       } catch (final NumberFormatException e) {
485         LOG.warn("Can't parse number from string : \"" + integer + "\"!");
486         return defaultValue;
487       }
488     } else if (integer == null) {
489       return defaultValue;
490     } else {
491       LOG.warn("Unknown type '" + integer.getClass().getName()
492           + "' for integer attribute: " + name + " comp: " + component);
493       return defaultValue;
494     }
495   }
496 
497   public static Character getCharacterAttribute(final UIComponent component, final Attributes name) {
498     final Object character = getAttribute(component, name);
499     if (character == null) {
500       return null;
501     } else if (character instanceof Character) {
502       return ((Character) character);
503     } else if (character instanceof String) {
504       final String asString = ((String) character);
505       return asString.length() > 0 ? asString.charAt(0) : null;
506     } else {
507       LOG.warn("Unknown type '" + character.getClass().getName()
508           + "' for integer attribute: " + name + " comp: " + component);
509       return null;
510     }
511   }
512 
513   public static void setAttribute(final UIComponent component, final Attributes name, final Object value) {
514     component.getAttributes().put(name.getName(), value);
515   }
516 
517   public static void removeAttribute(final UIComponent component, final Attributes name) {
518     component.getAttributes().remove(name.getName());
519   }
520 
521   public static UIComponent getFacet(final UIComponent component, final Facets facet) {
522     return component.getFacet(facet.name());
523   }
524 
525   public static void setFacet(final UIComponent component, final Facets facet, final UIComponent child) {
526     component.getFacets().put(facet.name(), child);
527   }
528 
529   public static boolean isFacetOf(final UIComponent component, final UIComponent parent) {
530     for (final Object o : parent.getFacets().keySet()) {
531       final UIComponent facet = parent.getFacet((String) o);
532       if (component.equals(facet)) {
533         return true;
534       }
535     }
536     return false;
537   }
538 
539   public static RendererBase getRenderer(final FacesContext facesContext, final UIComponent component) {
540     return getRenderer(facesContext, component.getFamily(), component.getRendererType());
541   }
542 
543   public static RendererBase getRenderer(final FacesContext facesContext, final String family,
544                                          final String rendererType) {
545     if (rendererType == null) {
546       return null;
547     }
548 
549     final Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
550     final StringBuilder key = new StringBuilder(RENDER_KEY_PREFIX);
551     key.append(rendererType);
552     RendererBase renderer = (RendererBase) requestMap.get(key.toString());
553 
554     if (renderer == null) {
555       final Renderer myRenderer = getRendererInternal(facesContext, family, rendererType);
556       if (myRenderer instanceof RendererBase) {
557         requestMap.put(key.toString(), myRenderer);
558         renderer = (RendererBase) myRenderer;
559       } else {
560         return null;
561       }
562     }
563     return renderer;
564   }
565 
566 
567   private static Renderer getRendererInternal(
568       final FacesContext facesContext, final String family, final String rendererType) {
569     final RenderKitFactory rkFactory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
570     final RenderKit renderKit = rkFactory.getRenderKit(facesContext, facesContext.getViewRoot().getRenderKitId());
571     final Renderer myRenderer = renderKit.getRenderer(family, rendererType);
572     return myRenderer;
573   }
574 
575   public static Object findParameter(final UIComponent component, final String name) {
576     for (final UIComponent child : component.getChildren()) {
577       if (child instanceof UIParameter) {
578         final UIParameter parameter = (UIParameter) child;
579         if (LOG.isDebugEnabled()) {
580           LOG.debug("Select name='" + parameter.getName() + "'");
581           LOG.debug("Select value='" + parameter.getValue() + "'");
582         }
583         if (name.equals(parameter.getName())) {
584           return parameter.getValue();
585         }
586       }
587     }
588     return null;
589   }
590 
591   /**
592    * <p>
593    * The search depends on the number of prefixed colons in the relativeId:
594    * </p>
595    * <dl>
596    *   <dd>number of prefixed colons == 0</dd>
597    *   <dt>fully relative</dt>
598    *   <dd>number of prefixed colons == 1</dd>
599    *   <dt>absolute (still normal findComponent syntax)</dt>
600    *   <dd>number of prefixed colons == 2</dd>
601    *   <dt>search in the current naming container (same as 0 colons)</dt>
602    *   <dd>number of prefixed colons == 3</dd>
603    *   <dt>search in the parent naming container of the current naming container</dt>
604    *   <dd>number of prefixed colons &gt; 3</dd>
605    *   <dt>go to the next parent naming container for each additional colon</dt>
606    * </dl>
607    * <p>
608    * If a literal is specified: to use more than one identifier the identifiers must be space delimited.
609    * </p>
610    */
611   public static UIComponent findComponent(final UIComponent from, final String relativeId) {
612     UIComponent from1 = from;
613     String relativeId1 = relativeId;
614     final int idLength = relativeId1.length();
615     if (idLength > 0 && relativeId1.charAt(0) == '@') {
616       if (relativeId1.equals("@this")) {
617         return from1;
618       }
619     }
620 
621     // Figure out how many colons
622     int colonCount = 0;
623     while (colonCount < idLength) {
624       if (relativeId1.charAt(colonCount) != UINamingContainer.getSeparatorChar(FacesContext.getCurrentInstance())) {
625         break;
626       }
627       colonCount++;
628     }
629 
630     // colonCount == 0: fully relative
631     // colonCount == 1: absolute (still normal findComponent syntax)
632     // colonCount > 1: for each extra colon after 1, go up a naming container
633     // (to the view root, if naming containers run out)
634     if (colonCount > 1) {
635       relativeId1 = relativeId1.substring(colonCount);
636       for (int j = 1; j < colonCount; j++) {
637         while (from1.getParent() != null) {
638           from1 = from1.getParent();
639           if (from1 instanceof NamingContainer) {
640             break;
641           }
642         }
643       }
644     }
645     return from1.findComponent(relativeId1);
646   }
647 
648   /**
649    * Resolves the real clientIds.
650    */
651   public static String evaluateClientIds(
652       final FacesContext context, final UIComponent component, final String[] componentIds) {
653     final List<String> result = new ArrayList<String>(componentIds.length);
654     for (final String id : componentIds) {
655       if (!StringUtils.isBlank(id)) {
656         final String clientId = evaluateClientId(context, component, id);
657         if (clientId != null) {
658           result.add(clientId);
659         }
660       }
661     }
662     if (result.isEmpty()) {
663       return null;
664     } else {
665       return StringUtils.join(result, ' ');
666     }
667   }
668 
669   /**
670    * Resolves the real clientId.
671    */
672   public static String evaluateClientId(
673       final FacesContext context, final UIComponent component, final String componentId) {
674     final UIComponent partiallyComponent = ComponentUtils.findComponent(component, componentId);
675     if (partiallyComponent != null) {
676       final String clientId = partiallyComponent.getClientId(context);
677       if (partiallyComponent instanceof AbstractUISheet) {
678         final int rowIndex = ((AbstractUISheet) partiallyComponent).getRowIndex();
679         if (rowIndex >= 0 && clientId.endsWith(Integer.toString(rowIndex))) {
680           return clientId.substring(0, clientId.lastIndexOf(UINamingContainer.getSeparatorChar(context)));
681         }
682       }
683       return clientId;
684     }
685     LOG.error("No component found for id='" + componentId + "', "
686         + "search base component is '" + component.getClientId(context) + "'");
687     return null;
688   }
689 
690   public static String[] splitList(final String renderers) {
691     return StringUtils.split(renderers, LIST_SEPARATOR_CHARS);
692   }
693 
694   public static Object getConvertedValue(
695       final FacesContext facesContext, final UIComponent component, final String stringValue) {
696     try {
697       final Renderer renderer = getRenderer(facesContext, component);
698       if (renderer != null) {
699         if (component instanceof UISelectMany) {
700           final Object converted = renderer.getConvertedValue(facesContext, component, new String[]{stringValue});
701           return converted instanceof Collection ? CollectionUtils.get(converted, 0) : ((Object[]) converted)[0];
702         } else {
703           return renderer.getConvertedValue(facesContext, component, stringValue);
704         }
705       } else if (component instanceof ValueHolder) {
706         Converter converter = ((ValueHolder) component).getConverter();
707         if (converter == null) {
708           //Try to find out by value binding
709           final ValueBinding vb = component.getValueBinding("value");
710           if (vb != null) {
711             final Class valueType = vb.getType(facesContext);
712             if (valueType != null) {
713               converter = facesContext.getApplication().createConverter(valueType);
714             }
715           }
716         }
717         if (converter != null) {
718           converter.getAsObject(facesContext, component, stringValue);
719         }
720       }
721     } catch (final Exception e) {
722       LOG.warn("Can't convert string value '" + stringValue + "'", e);
723     }
724     return stringValue;
725   }
726 
727   public static Markup updateMarkup(final UIComponent component, Markup markup) {
728     if (markup == null) {
729       markup = Markup.NULL;
730     }
731     if (ComponentUtils.getBooleanAttribute(component, Attributes.disabled)) {
732       markup = markup.add(Markup.DISABLED);
733     }
734     if (ComponentUtils.getBooleanAttribute(component, Attributes.readonly)) {
735       markup = markup.add(Markup.READONLY);
736     }
737     if (component instanceof UIInput) {
738       final UIInput input = (UIInput) component;
739 
740       final FacesMessage.Severity maximumSeverity = ComponentUtils.getMaximumSeverity(input);
741       markup = markup.add(markupOfSeverity(maximumSeverity));
742 
743       if (input.isRequired()) {
744         markup = markup.add(Markup.REQUIRED);
745       }
746     }
747     return markup;
748   }
749 
750   public static Markup markupOfSeverity(final FacesMessage.Severity maximumSeverity) {
751     if (FacesMessage.SEVERITY_FATAL.equals(maximumSeverity)) {
752       return Markup.FATAL;
753     } else if (FacesMessage.SEVERITY_ERROR.equals(maximumSeverity)) {
754       return Markup.ERROR;
755     } else if (FacesMessage.SEVERITY_WARN.equals(maximumSeverity)) {
756       return Markup.WARN;
757     } else if (FacesMessage.SEVERITY_INFO.equals(maximumSeverity)) {
758       return Markup.INFO;
759     }
760     return null;
761   }
762 
763   public static FacesMessage.Severity getMaximumSeverityOfChildrenMessages(
764       final FacesContext facesContext, final NamingContainer container) {
765     if (container instanceof UIComponent) {
766       final String clientId = ((UIComponent) container).getClientId(facesContext);
767       FacesMessage.Severity max = null;
768       for (final Iterator ids = facesContext.getClientIdsWithMessages(); ids.hasNext();) {
769         final String id = (String) ids.next();
770         if (id != null && id.startsWith(clientId)) {
771           final Iterator messages = facesContext.getMessages(id);
772           while (messages.hasNext()) {
773             final FacesMessage message = (FacesMessage) messages.next();
774             if (max == null || message.getSeverity().getOrdinal() > max.getOrdinal()) {
775               max = message.getSeverity();
776             }
777           }
778         }
779       }
780       return max;
781     }
782     return null;
783   }
784 
785   /**
786    * Adding a data attribute to the component. 
787    * The name must start with "data-", e. g. "data-tobago-foo" or "data-bar"
788    */
789   public static void putDataAttributeWithPrefix(
790       final UIComponent component, final DataAttributes name, final Object value) {
791     if (name.getValue().startsWith("data-")) {
792       putDataAttribute(component, name.getValue().substring(5), value);
793     } else {
794       LOG.error("The name must start with 'data-' but it doesn't: '" + name + "'");
795     }
796   }
797 
798   /**
799    * Adding a data attribute to the component.
800    * The name should not start with "data-", e. g. "tobago-foo" or "bar"
801    */
802   public static void putDataAttribute(final UIComponent component, final Object name, final Object value) {
803     Map<Object, Object> map = getDataAttributes(component);
804     if (map == null) {
805       map = new HashMap<Object, Object>();
806       component.getAttributes().put(DATA_ATTRIBUTES_KEY, map);
807     }
808     map.put(name, value);
809   }
810 
811   @SuppressWarnings("unchecked")
812   public static Map<Object, Object> getDataAttributes(final UIComponent component) {
813     return (Map<Object, Object>) component.getAttributes().get(DATA_ATTRIBUTES_KEY);
814   }
815 
816   public static Object getDataAttribute(final UIComponent component, final String name) {
817     Map<Object, Object> map = getDataAttributes(component);
818     return map != null ? map.get(name) : null;
819   }
820 }