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