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