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