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