001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.myfaces.tobago.component;
021    
022    import org.apache.commons.lang.ObjectUtils;
023    import org.apache.commons.lang.StringUtils;
024    import org.apache.commons.lang.math.NumberUtils;
025    import org.apache.commons.logging.Log;
026    import org.apache.commons.logging.LogFactory;
027    import org.apache.myfaces.tobago.TobagoConstants;
028    import org.apache.myfaces.tobago.context.TransientStateHolder;
029    import org.apache.myfaces.tobago.el.ConstantMethodBinding;
030    import org.apache.myfaces.tobago.event.PopupActionListener;
031    import org.apache.myfaces.tobago.event.SheetStateChangeEvent;
032    import org.apache.myfaces.tobago.renderkit.LayoutableRendererBase;
033    import org.apache.myfaces.tobago.renderkit.html.StyleClasses;
034    import org.apache.myfaces.tobago.util.Callback;
035    import org.apache.myfaces.tobago.util.RangeParser;
036    import org.apache.myfaces.tobago.util.TobagoCallback;
037    
038    import javax.faces.FactoryFinder;
039    import javax.faces.application.Application;
040    import javax.faces.application.FacesMessage;
041    import javax.faces.component.ActionSource;
042    import javax.faces.component.EditableValueHolder;
043    import javax.faces.component.NamingContainer;
044    import javax.faces.component.UICommand;
045    import javax.faces.component.UIComponent;
046    import javax.faces.component.UIGraphic;
047    import javax.faces.component.UIOutput;
048    import javax.faces.component.UIParameter;
049    import javax.faces.component.UISelectItem;
050    import javax.faces.component.UISelectItems;
051    import javax.faces.component.ValueHolder;
052    import javax.faces.context.FacesContext;
053    import javax.faces.convert.Converter;
054    import javax.faces.el.MethodBinding;
055    import javax.faces.el.ValueBinding;
056    import javax.faces.event.ActionEvent;
057    import javax.faces.event.ActionListener;
058    import javax.faces.event.PhaseId;
059    import javax.faces.event.ValueChangeEvent;
060    import javax.faces.model.SelectItem;
061    import javax.faces.render.RenderKit;
062    import javax.faces.render.RenderKitFactory;
063    import javax.faces.render.Renderer;
064    import javax.faces.webapp.UIComponentTag;
065    import javax.servlet.jsp.JspException;
066    import java.util.ArrayList;
067    import java.util.Arrays;
068    import java.util.Collection;
069    import java.util.Collections;
070    import java.util.Iterator;
071    import java.util.List;
072    import java.util.Map;
073    import java.util.Set;
074    
075    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ACTION_LINK;
076    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ACTION_ONCLICK;
077    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ALIGN;
078    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_CONVERTER;
079    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_CREATE_SPAN;
080    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_DISABLED;
081    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ESCAPE;
082    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_FOR;
083    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_HOVER;
084    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LABEL;
085    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_MARKUP;
086    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_READONLY;
087    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_RENDERED_PARTIALLY;
088    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_RENDER_RANGE;
089    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_RENDER_RANGE_EXTERN;
090    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SORTABLE;
091    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE_CLASS;
092    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_VALUE;
093    import static org.apache.myfaces.tobago.TobagoConstants.COMMAND_TYPE_NAVIGATE;
094    import static org.apache.myfaces.tobago.TobagoConstants.COMMAND_TYPE_RESET;
095    import static org.apache.myfaces.tobago.TobagoConstants.COMMAND_TYPE_SCRIPT;
096    import static org.apache.myfaces.tobago.TobagoConstants.FACET_ITEMS;
097    import static org.apache.myfaces.tobago.TobagoConstants.FACET_LABEL;
098    import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_LABEL;
099    import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_OUT;
100    import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_SELECT_BOOLEAN_CHECKBOX;
101    import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_SELECT_ONE_RADIO;
102    
103    public class ComponentUtil {
104    
105      private static final Log LOG = LogFactory.getLog(ComponentUtil.class);
106    
107      private static final String RENDER_KEY_PREFIX
108          = "org.apache.myfaces.tobago.component.ComponentUtil.RendererKeyPrefix_";
109    
110      public static final Class[] ACTION_ARGS = {};
111      public static final Class[] ACTION_LISTENER_ARGS = {ActionEvent.class};
112      public static final Class[] VALUE_CHANGE_LISTENER_ARGS = {ValueChangeEvent.class};
113      public static final Class[] VALIDATOR_ARGS = {FacesContext.class, UIComponent.class, Object.class};
114    
115      private ComponentUtil() {
116      }
117    
118    
119      public static boolean hasErrorMessages(FacesContext context) {
120        for (Iterator iter = context.getMessages(); iter.hasNext();) {
121          FacesMessage message = (FacesMessage) iter.next();
122          if (FacesMessage.SEVERITY_ERROR.compareTo(message.getSeverity()) <= 0) {
123            return true;
124          }
125        }
126        return false;
127      }
128    
129      public static boolean containsPopupActionListener(UICommand command) {
130        ActionListener[] actionListeners = command.getActionListeners();
131        for (ActionListener actionListener : actionListeners) {
132          if (actionListener instanceof PopupActionListener) {
133            return true;
134          }
135        }
136        return false;
137      }
138    
139      public static String getFacesMessageAsString(FacesContext facesContext, UIComponent component) {
140        Iterator messages = facesContext.getMessages(
141            component.getClientId(facesContext));
142        StringBuilder stringBuffer = new StringBuilder();
143        while (messages.hasNext()) {
144          FacesMessage message = (FacesMessage) messages.next();
145          stringBuffer.append(message.getDetail());
146        }
147        if (stringBuffer.length() > 0) {
148          return stringBuffer.toString();
149        } else {
150          return null;
151        }
152      }
153    
154      public static boolean isInPopup(UIComponent component) {
155        while (component != null) {
156          if (component instanceof UIPopup) {
157            return true;
158          }
159          component = component.getParent();
160        }
161        return false;
162      }
163    
164      public static void resetPage(FacesContext context) {
165        javax.faces.component.UIViewRoot view = context.getViewRoot();
166        if (view != null) {
167          view.getAttributes().remove(UIPage.COMPONENT_TYPE);
168        }
169      }
170    
171      @SuppressWarnings(value = "unchecked")
172      public static UIPage findPage(FacesContext context, UIComponent component) {
173        javax.faces.component.UIViewRoot view = context.getViewRoot();
174        if (view != null) {
175          TransientStateHolder stateHolder = (TransientStateHolder) view.getAttributes().get(UIPage.COMPONENT_TYPE);
176          if (stateHolder == null || stateHolder.isEmpty()) {
177            UIPage page = findPage(component);
178            stateHolder = new TransientStateHolder(page);
179            context.getViewRoot().getAttributes().put(UIPage.COMPONENT_TYPE, stateHolder);
180          }
181          return (UIPage) stateHolder.get();
182        } else {
183          return findPage(component);
184        }
185      }
186    
187      public static UIPage findPage(UIComponent component) {
188        while (component != null) {
189          if (component instanceof UIPage) {
190            return (UIPage) component;
191          }
192          component = component.getParent();
193        }
194        return null;
195      }
196    
197      public static void addStyles(UIComponent component, String[] styles) {
198        UIPage uiPage = ComponentUtil.findPage(component);
199        uiPage.getStyleFiles().addAll(Arrays.asList(styles));
200      }
201    
202      public static void addScripts(UIComponent component, String[] scripts) {
203        UIPage uiPage = ComponentUtil.findPage(component);
204        uiPage.getScriptFiles().addAll(Arrays.asList(scripts));
205      }
206    
207      public static void addOnloadCommands(UIComponent component, String[] cmds) {
208        UIPage uiPage = ComponentUtil.findPage(component);
209        uiPage.getOnloadScripts().addAll(Arrays.asList(cmds));
210      }
211    
212      public static UIPage findPage(FacesContext facesContext) {
213        return findPageBreadthFirst(facesContext.getViewRoot());
214      }
215    
216      private static UIPage findPageBreadthFirst(UIComponent component) {
217        for (Object o : component.getChildren()) {
218          UIComponent child = (UIComponent) o;
219          if (child instanceof UIPage) {
220            return (UIPage) child;
221          }
222        }
223        for (Object o : component.getChildren()) {
224          UIComponent child = (UIComponent) o;
225          UIPage result = findPageBreadthFirst(child);
226          if (result != null) {
227            return result;
228          }
229        }
230        return null;
231      }
232    
233    
234      public static UIForm findForm(UIComponent component) {
235        while (component != null) {
236          if (component instanceof UIForm) {
237            return (UIForm) component;
238          }
239          component = component.getParent();
240        }
241        return null;
242      }
243    
244      /**
245       * Find all subforms of a component, and collects it.
246       * It does not find subforms of subforms.
247       */
248      public static List<UIForm> findSubForms(UIComponent component) {
249        List<UIForm> collect = new ArrayList<UIForm>();
250        findSubForms(collect, component);
251        return collect;
252      }
253    
254      @SuppressWarnings(value = "unchecked")
255      private static void findSubForms(List<UIForm> collect, UIComponent component) {
256        Iterator<UIComponent> kids = component.getFacetsAndChildren();
257        while (kids.hasNext()) {
258          UIComponent child = kids.next();
259          if (child instanceof UIForm) {
260            collect.add((UIForm) child);
261          } else {
262            findSubForms(collect, child);
263          }
264        }
265      }
266    
267      /**
268       * Searches the component tree beneath the component and return the first component matching the type.
269       */
270      public static <T extends UIComponent> T findDescendant(UIComponent component, Class<T> type) {
271    
272        for (UIComponent child : (List<UIComponent>) component.getChildren()) {
273          if (child.getClass().equals(type)) {
274            return (T) child;
275          }
276          final T descendant = findDescendant(child, type);
277          if (descendant != null) {
278            return descendant;
279          }
280        }
281        return null;
282      }
283    
284      /**
285       * Looks for the attribute "for" in the component. If there is any
286       * search for the component which is referenced by the "for" attribute,
287       * and return their clientId.
288       * If there is no "for" attribute, return the "clientId" of the parent
289       * (if it has a parent). This is useful for labels.
290       */
291      public static String findClientIdFor(UIComponent component,
292          FacesContext facesContext) {
293        UIComponent forComponent = findFor(component);
294        if (forComponent != null) {
295          String clientId = forComponent.getClientId(facesContext);
296          if (LOG.isDebugEnabled()) {
297            LOG.debug("found clientId: '" + clientId + "'");
298          }
299          return clientId;
300        }
301        if (LOG.isDebugEnabled()) {
302          LOG.debug("found no clientId");
303        }
304        return null;
305      }
306    
307      public static UIComponent findFor(UIComponent component) {
308        String forValue = (String) component.getAttributes().get(ATTR_FOR);
309        if (forValue == null) {
310          return component.getParent();
311        }
312        return component.findComponent(forValue);
313      }
314    
315      public static boolean isInActiveForm(UIComponent component) {
316        while (component != null) {
317          //log.debug("compoent= " + component.getClientId(FacesContext.getCurrentInstance())
318          // + " " + component.getRendererType());
319          if (component instanceof UIForm) {
320            UIForm form = (UIForm) component;
321            if (form.isSubmitted()) {
322              //log.debug("in active form = " + form.getClientId(FacesContext.getCurrentInstance()));
323              return true;
324            } /*else {
325              log.debug("form found but not active = " + form.getClientId(FacesContext.getCurrentInstance()));
326            } */
327          }
328          component = component.getParent();
329        }
330        //log.debug("not in an active form");
331        return false;
332      }
333    
334      public static FacesMessage.Severity getMaximumSeverity(UIComponent component) {
335        final boolean invalid = component instanceof javax.faces.component.UIInput
336            && !((javax.faces.component.UIInput) component).isValid();
337        FacesMessage.Severity max = invalid ? FacesMessage.SEVERITY_ERROR : null;
338        FacesContext facesContext = FacesContext.getCurrentInstance();
339        final Iterator messages = facesContext.getMessages(component.getClientId(facesContext));
340        while (messages.hasNext()) {
341          FacesMessage message = (FacesMessage) messages.next();
342          if (max == null || message.getSeverity().getOrdinal() > max.getOrdinal()) {
343            max = message.getSeverity();
344          }
345        }
346        return max;
347      }
348    
349      public static boolean isError(javax.faces.component.UIInput uiInput) {
350        FacesContext facesContext = FacesContext.getCurrentInstance();
351        return !uiInput.isValid()
352            || facesContext.getMessages(uiInput.getClientId(facesContext)).hasNext();
353      }
354    
355      public static boolean isError(UIComponent component) {
356        if (component instanceof UIInput) {
357          return isError((UIInput) component);
358        }
359        return false;
360      }
361    
362      public static boolean isOutputOnly(UIComponent component) {
363        return getBooleanAttribute(component, ATTR_DISABLED)
364            || getBooleanAttribute(component, ATTR_READONLY);
365      }
366    
367      public static boolean mayValidate(UIComponent component) {
368        return !isOutputOnly(component)
369            && ComponentUtil.isInActiveForm(component);
370      }
371    
372      public static boolean mayUpdateModel(UIComponent component) {
373        return mayValidate(component);
374      }
375    
376      public static boolean getBooleanAttribute(UIComponent component, String name) {
377    
378        Object bool = component.getAttributes().get(name);
379        if (bool == null) {
380          return false;
381        }
382        if (bool instanceof ValueBinding) {
383          bool = ((ValueBinding) bool).getValue(FacesContext.getCurrentInstance());
384        }
385        if (bool instanceof Boolean) {
386          return (Boolean) bool;
387        } else if (bool instanceof String) {
388          if (LOG.isInfoEnabled()) {
389            LOG.info("Searching for a boolean, but find a String. Should not happen. "
390                + "attribute: '" + name + "' id: '" + component.getClientId(FacesContext.getCurrentInstance())
391                + "' comp: '" + component + "'");
392          }
393          return Boolean.valueOf((String) bool);
394        } else {
395          LOG.warn("Unknown type '" + bool.getClass().getName()
396              + "' for boolean attribute: " + name + " id: " + component.getClientId(FacesContext.getCurrentInstance())
397              + " comp: " + component);
398          return false;
399        }
400      }
401    
402      public static void setRenderedPartially(org.apache.myfaces.tobago.component.UICommand command,
403          String renderers) {
404        if (renderers != null) {
405          if (UIComponentTag.isValueReference(renderers)) {
406            command.setValueBinding(ATTR_RENDERED_PARTIALLY, createValueBinding(renderers));
407          } else {
408            String[] components = StringUtils.split(renderers, ",");
409            command.setRenderedPartially(components);
410          }
411        }
412      }
413    
414      public static void setStyleClasses(UIComponent component, String styleClasses) {
415        if (styleClasses != null) {
416          if (UIComponentTag.isValueReference(styleClasses)) {
417            component.setValueBinding(ATTR_STYLE_CLASS, createValueBinding(styleClasses));
418          } else {
419            String[] classes = StringUtils.split(styleClasses, ", ");
420            if (classes.length > 0) {
421              StyleClasses styles = StyleClasses.ensureStyleClasses(component);
422              for (String clazz : classes) {
423                styles.addFullQualifiedClass(clazz);
424              }
425            }
426          }
427        }
428      }
429    
430      public static void setMarkup(UIComponent markupComponent, String markup) {
431        if (markup != null) {
432          if (markupComponent instanceof SupportsMarkup) {
433            if (UIComponentTag.isValueReference(markup)) {
434              markupComponent.setValueBinding(ATTR_MARKUP, createValueBinding(markup));
435            } else {
436              String[] markups = StringUtils.split(markup, ",");
437              ((SupportsMarkup) markupComponent).setMarkup(markups);
438            }
439          } else {
440            LOG.error("Component did not support Markup " + markupComponent.getClass().getName());
441          }
442        }
443      }
444    
445      public static Object getAttribute(UIComponent component, String name) {
446        Object value = component.getAttributes().get(name);
447        if (value instanceof ValueBinding) {
448          value = ((ValueBinding) value).getValue(FacesContext.getCurrentInstance());
449        }
450        return value;
451      }
452    
453      public static String getStringAttribute(UIComponent component, String name) {
454        return (String) getAttribute(component, name);
455      }
456    
457      public static int getIntAttribute(UIComponent component, String name) {
458        return getIntAttribute(component, name, 0);
459      }
460    
461      public static int getIntAttribute(UIComponent component, String name,
462          int defaultValue) {
463        Object integer = component.getAttributes().get(name);
464        if (integer instanceof Number) {
465          return ((Number) integer).intValue();
466        } else if (integer instanceof String) {
467          try {
468            return Integer.parseInt((String) integer);
469          } catch (NumberFormatException e) {
470            LOG.warn("Can't parse number from string : \"" + integer + "\"!");
471            return defaultValue;
472          }
473        } else if (integer == null) {
474          return defaultValue;
475        } else {
476          LOG.warn("Unknown type '" + integer.getClass().getName()
477              + "' for integer attribute: " + name + " comp: " + component);
478          return defaultValue;
479        }
480      }
481    
482      /**
483       * @param component
484       * @param name
485       * @deprecated please use the  method {@link #getCharacterAttribute(javax.faces.component.UIComponent, String)}
486       */
487      @Deprecated
488      public static Character getCharakterAttribute(UIComponent component, String name) {
489        return getCharacterAttribute(component, name);
490      }
491    
492      public static Character getCharacterAttribute(UIComponent component, String name) {
493        Object character = component.getAttributes().get(name);
494        if (character == null) {
495          return null;
496        } else if (character instanceof Character) {
497          return ((Character) character);
498        } else if (character instanceof String) {
499          String asString = ((String) character);
500          return asString.length() > 0 ? asString.charAt(0) : null;
501        } else {
502          LOG.warn("Unknown type '" + character.getClass().getName()
503              + "' for integer attribute: " + name + " comp: " + component);
504          return null;
505        }
506      }
507    
508      public static boolean isFacetOf(UIComponent component, UIComponent parent) {
509        for (Object o : parent.getFacets().keySet()) {
510          UIComponent facet = parent.getFacet((String) o);
511          if (component.equals(facet)) {
512            return true;
513          }
514        }
515        return false;
516      }
517    
518      // TODO This should not be neseccary, but UIComponentBase.getRenderer() is protected
519      public static LayoutableRendererBase getRenderer(FacesContext facesContext, UIComponent component) {
520        return getRenderer(facesContext, component.getFamily(), component.getRendererType());
521    
522      }
523    
524      public static LayoutableRendererBase getRenderer(FacesContext facesContext, String family, String rendererType) {
525        if (rendererType == null) {
526          return null;
527        }
528    
529        LayoutableRendererBase renderer;
530    
531        Map requestMap = facesContext.getExternalContext().getRequestMap();
532        renderer = (LayoutableRendererBase) requestMap.get(RENDER_KEY_PREFIX + rendererType);
533    
534        if (renderer == null) {
535          RenderKitFactory rkFactory = (RenderKitFactory)
536              FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
537          RenderKit renderKit = rkFactory.getRenderKit(facesContext, facesContext.getViewRoot().getRenderKitId());
538          Renderer myRenderer = renderKit.getRenderer(family, rendererType);
539          if (myRenderer instanceof LayoutableRendererBase) {
540            requestMap.put(RENDER_KEY_PREFIX + rendererType, myRenderer);
541            renderer = (LayoutableRendererBase) myRenderer;
542          } else {
543            return null;
544          }
545        }
546        return renderer;
547      }
548    
549      public static String currentValue(UIComponent component) {
550        String currentValue = null;
551        if (component instanceof ValueHolder) {
552          Object value;
553          if (component instanceof EditableValueHolder) {
554            value = ((EditableValueHolder) component).getSubmittedValue();
555            if (value != null) {
556              return (String) value;
557            }
558          }
559    
560          value = ((ValueHolder) component).getValue();
561          if (value != null) {
562            Converter converter = ((ValueHolder) component).getConverter();
563            if (converter == null) {
564              FacesContext context = FacesContext.getCurrentInstance();
565              converter = context.getApplication().createConverter(value.getClass());
566            }
567            if (converter != null) {
568              currentValue =
569                  converter.getAsString(FacesContext.getCurrentInstance(),
570                      component, value);
571            } else {
572              currentValue = value.toString();
573            }
574          }
575        }
576        return currentValue;
577      }
578    
579      public static List<SelectItem> getSelectItems(UIComponent component) {
580    
581        ArrayList<SelectItem> list = new ArrayList<SelectItem>();
582    
583        for (Object o1 : component.getChildren()) {
584          UIComponent kid = (UIComponent) o1;
585          if (LOG.isDebugEnabled()) {
586            LOG.debug("kid " + kid);
587            LOG.debug("kid " + kid.getClass().getName());
588          }
589          if (kid instanceof UISelectItem) {
590            Object value = ((UISelectItem) kid).getValue();
591            if (value == null) {
592              UISelectItem item = (UISelectItem) kid;
593              if (kid instanceof org.apache.myfaces.tobago.component.UISelectItem) {
594                list.add(new org.apache.myfaces.tobago.model.SelectItem(
595                    (org.apache.myfaces.tobago.component.UISelectItem) kid));
596              } else {
597                list.add(new SelectItem(item.getItemValue() == null ? "" : item.getItemValue(),
598                    item.getItemLabel() != null ? item.getItemLabel() : item.getItemValue().toString(),
599                    item.getItemDescription()));
600              }
601            } else if (value instanceof SelectItem) {
602              list.add((SelectItem) value);
603            } else {
604              throw new IllegalArgumentException("TYPE ERROR: value NOT instanceof SelectItem. type="
605                  + value.getClass().getName());
606            }
607          } else if (kid instanceof UISelectItems) {
608            Object value = ((UISelectItems) kid).getValue();
609            if (LOG.isDebugEnabled()) {
610              LOG.debug("value " + value);
611              if (value != null) {
612                LOG.debug("value " + value.getClass().getName());
613              }
614            }
615            if (value == null) {
616              if (LOG.isDebugEnabled()) {
617                LOG.debug("value is null");
618              }
619            } else if (value instanceof SelectItem) {
620              list.add((SelectItem) value);
621            } else if (value instanceof SelectItem[]) {
622              SelectItem[] items = (SelectItem[]) value;
623              list.addAll(Arrays.asList(items));
624            } else if (value instanceof Collection) {
625              for (Object o : ((Collection) value)) {
626                list.add((SelectItem) o);
627              }
628            } else if (value instanceof Map) {
629              for (Object key : ((Map) value).keySet()) {
630                if (key != null) {
631                  Object val = ((Map) value).get(key);
632                  if (val != null) {
633                    list.add(new SelectItem(val.toString(), key.toString(), null));
634                  }
635                }
636              }
637            } else {
638              throw new IllegalArgumentException("TYPE ERROR: value NOT instanceof "
639                  + "SelectItem, SelectItem[], Collection, Map. type="
640                  + value.getClass().getName());
641            }
642          }
643        }
644    
645        return list;
646      }
647    
648      public static Object findParameter(UIComponent component, String name) {
649        for (Object o : component.getChildren()) {
650          UIComponent child = (UIComponent) o;
651          if (child instanceof UIParameter) {
652            UIParameter parameter = (UIParameter) child;
653            if (LOG.isDebugEnabled()) {
654              LOG.debug("Select name='" + parameter.getName() + "'");
655              LOG.debug("Select value='" + parameter.getValue() + "'");
656            }
657            if (name.equals(parameter.getName())) {
658              return parameter.getValue();
659            }
660          }
661        }
662        return null;
663      }
664    
665      public static String toString(UIComponent component, int offset) {
666        return toString(component, offset, false);
667      }
668    
669      private static String toString(UIComponent component, int offset, boolean asFacet) {
670        StringBuilder result = new StringBuilder();
671        if (component == null) {
672          result.append("null");
673        } else {
674          result.append('\n');
675          if (!asFacet) {
676            result.append(spaces(offset));
677            result.append(toString(component));
678          }
679          Map facets = component.getFacets();
680          if (facets.size() > 0) {
681            for (Map.Entry<String, UIComponent> entry : (Set<Map.Entry<String, UIComponent>>) facets.entrySet()) {
682              UIComponent facet = entry.getValue();
683              result.append('\n');
684              result.append(spaces(offset + 1));
685              result.append('\"');
686              result.append(entry.getKey());
687              result.append("\" = ");
688              result.append(toString(facet));
689              result.append(toString(facet, offset + 1, true));
690            }
691          }
692          for (Object o : component.getChildren()) {
693            result.append(toString((UIComponent) o, offset + 1, false));
694          }
695        }
696        return result.toString();
697      }
698    
699      private static String toString(UIComponent component) {
700        StringBuilder buf = new StringBuilder(component.getClass().getName());
701        buf.append('@');
702        buf.append(Integer.toHexString(component.hashCode()));
703        buf.append(" ");
704        buf.append(component.getRendererType());
705        buf.append(" ");
706        if (component instanceof javax.faces.component.UIViewRoot) {
707          buf.append(((javax.faces.component.UIViewRoot) component).getViewId());
708        } else {
709          buf.append(component.getId());
710          buf.append(" ");
711          buf.append(component.getClientId(FacesContext.getCurrentInstance()));
712        }
713        return buf.toString();
714      }
715    
716      private static String spaces(int n) {
717        StringBuilder buffer = new StringBuilder(n * 2);
718        for (int i = 0; i < n; i++) {
719          buffer.append("  ");
720        }
721        return buffer.toString();
722      }
723    
724      public static ActionListener createActionListener(String type)
725          throws JspException {
726        try {
727          ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
728          if (classLoader == null) {
729            classLoader = type.getClass().getClassLoader();
730          }
731          Class clazz = classLoader.loadClass(type);
732          return (ActionListener) clazz.newInstance();
733        } catch (Exception e) {
734          if (LOG.isDebugEnabled()) {
735            LOG.debug("type=" + type, e);
736          }
737          throw new JspException(e);
738        }
739      }
740    
741      public static UIGraphic getFirstGraphicChild(UIComponent component) {
742        UIGraphic graphic = null;
743        for (Object o : component.getChildren()) {
744          UIComponent uiComponent = (UIComponent) o;
745          if (uiComponent instanceof UIGraphic) {
746            graphic = (UIGraphic) uiComponent;
747            break;
748          }
749        }
750        return graphic;
751      }
752    
753      public static boolean isHoverEnabled(UIComponent component) {
754        return ComponentUtil.getBooleanAttribute(component, ATTR_HOVER);
755      }
756    
757      public static UIOutput getFirstNonGraphicChild(UIComponent component) {
758        UIOutput output = null;
759        for (Object o : component.getChildren()) {
760          UIComponent uiComponent = (UIComponent) o;
761          if ((uiComponent instanceof UIOutput)
762              && !(uiComponent instanceof UIGraphic)) {
763            output = (UIOutput) uiComponent;
764            break;
765          }
766        }
767        return output;
768      }
769    
770      public static void setIntegerSizeProperty(UIComponent component,
771          String name, String value) {
772        if (value != null) {
773          if (UIComponentTag.isValueReference(value)) {
774            component.setValueBinding(name, createValueBinding(value));
775          } else {
776            value = removePx(value);
777            component.getAttributes().put(name, new Integer(value));
778          }
779        }
780      }
781    
782      public static String removePx(String value) {
783        if (value != null && value.endsWith("px")) {
784          value = value.substring(0, value.length() - 2);
785        }
786        return value;
787      }
788    
789      public static void setIntegerProperty(UIComponent component,
790          String name, String value) {
791        if (value != null) {
792          if (UIComponentTag.isValueReference(value)) {
793            component.setValueBinding(name, createValueBinding(value));
794          } else {
795            component.getAttributes().put(name, new Integer(value));
796          }
797        }
798      }
799    
800      public static void setBooleanProperty(UIComponent component,
801          String name, String value) {
802        if (value != null) {
803          if (UIComponentTag.isValueReference(value)) {
804            component.setValueBinding(name, createValueBinding(value));
805          } else {
806            component.getAttributes().put(name, Boolean.valueOf(value));
807          }
808        }
809      }
810    
811      public static void setStringProperty(UIComponent component, String name,
812          String value) {
813        if (value != null) {
814          if (UIComponentTag.isValueReference(value)) {
815            component.setValueBinding(name, createValueBinding(value));
816          } else {
817            component.getAttributes().put(name, value);
818          }
819        }
820      }
821    
822      public static void setValueForValueBinding(String name, Object value) {
823        FacesContext context = FacesContext.getCurrentInstance();
824        ValueBinding valueBinding = context.getApplication().createValueBinding(name);
825        valueBinding.setValue(context, value);
826      }
827    
828      public static ValueBinding createValueBinding(String value) {
829        return FacesContext.getCurrentInstance().getApplication()
830            .createValueBinding(value);
831      }
832    
833      public static String getValueFromEl(String script) {
834        if (UIComponentTag.isValueReference(script)) {
835          ValueBinding valueBinding = ComponentUtil.createValueBinding(script);
836          script = (String) valueBinding.getValue(FacesContext.getCurrentInstance());
837        }
838        return script;
839      }
840    
841      /**
842       * Please use createComponent(String componentType, String rendererType, String id)
843       *
844       * @deprecated
845       */
846      @Deprecated
847      public static UIComponent createComponent(String componentType, String rendererType) {
848        return createComponent(componentType, rendererType,  null);
849      }
850    
851      public static UIComponent createComponent(String componentType, String rendererType, String id) {
852        final FacesContext facesContext = FacesContext.getCurrentInstance();
853        return createComponent(facesContext, componentType, rendererType, id);
854      }
855    
856      /**
857       * Please use createComponent(FacesContext facesContext, String componentType, String rendererType, String id)
858       *
859       * @deprecated
860       */
861      @Deprecated
862      public static UIComponent createComponent(FacesContext facesContext, String componentType, String rendererType) {
863        return createComponent(facesContext, componentType, rendererType, null);
864      }
865    
866      public static UIComponent createComponent(
867          FacesContext facesContext, String componentType, String rendererType, String id) {
868        UIComponent component
869            = facesContext.getApplication().createComponent(componentType);
870        component.setRendererType(rendererType);
871        component.setId(id);
872        return component;
873      }
874    
875      /**
876      * Please use createTextColumn(String label, String sortable, String align, String value, String id)
877      *
878      * @deprecated
879      */
880     @Deprecated
881      public static UIColumn createTextColumn(String label, String sortable, String align, String value) {
882        return createTextColumn(label, sortable, align, value, null);
883      }
884    
885      public static UIColumn createTextColumn(String label, String sortable, String align, String value, String id) {
886        UIComponent text = createComponent(UIOutput.COMPONENT_TYPE, RENDERER_TYPE_OUT, id + "_t");
887        setStringProperty(text, ATTR_VALUE, value);
888        setBooleanProperty(text, ATTR_CREATE_SPAN, "false");
889        setBooleanProperty(text, ATTR_ESCAPE, "false");
890        return createColumn(label, sortable, align, text, id);
891      }
892    
893      /**
894      * Please use createColumn(String label, String sortable, String align, UIComponent child)
895      *
896      * @deprecated
897      */
898     @Deprecated
899      public static UIColumn createColumn(String label, String sortable, String align, UIComponent child) {
900        return createColumn(label, sortable, align, child, null);
901      }
902    
903      public static UIColumn createColumn(String label, String sortable, String align, UIComponent child, String id) {
904        UIColumn column = createColumn(label, sortable, align, id);
905        column.getChildren().add(child);
906        return column;
907      }
908    
909      private static UIColumn createColumn(String label, String sortable, String align, String id) {
910        UIColumn column = (UIColumn) createComponent(UIColumn.COMPONENT_TYPE, null, id);
911        setStringProperty(column, ATTR_LABEL, label);
912        setBooleanProperty(column, ATTR_SORTABLE, sortable);
913        setStringProperty(column, ATTR_ALIGN, align);
914        return column;
915      }
916    
917      /**
918      * Please use createUIMenuSelectOneFacet(FacesContext facesContext, UICommand command, String id)
919      *
920      * @deprecated
921      */
922     @Deprecated
923      public static UIMenuSelectOne createUIMenuSelectOneFacet(FacesContext facesContext, UICommand command) {
924        return createUIMenuSelectOneFacet(facesContext, command, null);
925      }
926    
927      public static UIMenuSelectOne createUIMenuSelectOneFacet(FacesContext facesContext, UICommand command, String id) {
928        UIMenuSelectOne radio = null;
929        final ValueBinding valueBinding = command.getValueBinding(ATTR_VALUE);
930        if (valueBinding != null) {
931          radio = (UIMenuSelectOne) createComponent(facesContext,
932              UIMenuSelectOne.COMPONENT_TYPE, RENDERER_TYPE_SELECT_ONE_RADIO, id);
933          command.getFacets().put(FACET_ITEMS, radio);
934          radio.setValueBinding(ATTR_VALUE, valueBinding);
935        }
936        return radio;
937      }
938    
939    
940      public static boolean hasSelectedValue(List<SelectItem> items, Object value) {
941        for (SelectItem item : items) {
942          if (ObjectUtils.equals(item.getValue(), value)) {
943            return true;
944          }
945        }
946        return false;
947      }
948    
949      /**
950      * Please use createUISelectBooleanFacet(FacesContext facesContext, UICommand command, String id)
951      *
952      * @deprecated
953      */
954     @Deprecated
955      public static UIComponent createUISelectBooleanFacet(FacesContext facesContext, UICommand command) {
956        return createUISelectBooleanFacet(facesContext, command, null);
957      }
958    
959      public static UIComponent createUISelectBooleanFacet(FacesContext facesContext, UICommand command, String id) {
960        UIComponent checkbox
961            = createComponent(facesContext, UISelectBoolean.COMPONENT_TYPE, RENDERER_TYPE_SELECT_BOOLEAN_CHECKBOX, id);
962        command.getFacets().put(FACET_ITEMS, checkbox);
963        ValueBinding valueBinding = command.getValueBinding(ATTR_VALUE);
964        if (valueBinding != null) {
965          checkbox.setValueBinding(ATTR_VALUE, valueBinding);
966        } else {
967          checkbox.getAttributes().put(ATTR_VALUE, command.getAttributes().get(ATTR_VALUE));
968        }
969        return checkbox;
970      }
971    
972      public static int getIntValue(ValueBinding valueBinding) {
973        return getAsInt(valueBinding.getValue(FacesContext.getCurrentInstance()));
974      }
975    
976      private static int getAsInt(Object value) {
977        int result;
978        if (value instanceof Number) {
979          result = ((Number) value).intValue();
980        } else if (value instanceof String) {
981          result = Integer.parseInt((String) value);
982        } else {
983          throw new IllegalArgumentException("Can't convert " + value + " to int!");
984        }
985        return result;
986      }
987    
988    
989      public static String createPickerId(FacesContext facesContext, UIComponent component, String postfix) {
990        //String id = component.getId();
991        String id = getComponentId(facesContext, component);
992        return id + "_picker" + postfix;
993      }
994    
995      public static String getComponentId(FacesContext facesContext, UIComponent component) {
996        String id = component.getId();
997        //if (id == null) {
998        // XXX What is this?
999        //  id = component.getClientId(facesContext).substring(id.lastIndexOf('_'));
1000        //}
1001        return id;
1002      }
1003    
1004      public static UIComponent provideLabel(FacesContext facesContext, UIComponent component) {
1005        UIComponent label = component.getFacet(FACET_LABEL);
1006    
1007    
1008        if (label == null) {
1009          final Map attributes = component.getAttributes();
1010          Object labelText = component.getValueBinding(ATTR_LABEL);
1011          if (labelText == null) {
1012            labelText = attributes.get(ATTR_LABEL);
1013          }
1014    
1015          if (labelText != null) {
1016            Application application = FacesContext.getCurrentInstance().getApplication();
1017            label = application.createComponent(UIOutput.COMPONENT_TYPE);
1018            label.setRendererType(RENDERER_TYPE_LABEL);
1019            String idprefix = ComponentUtil.getComponentId(facesContext, component);
1020            label.setId(idprefix + "_" + FACET_LABEL);
1021            label.setRendered(true);
1022    
1023            if (labelText instanceof ValueBinding) {
1024              label.setValueBinding(ATTR_VALUE, (ValueBinding) labelText);
1025            } else {
1026              label.getAttributes().put(ATTR_VALUE, labelText);
1027            }
1028    
1029            component.getFacets().put(FACET_LABEL, label);
1030          }
1031        }
1032        return label;
1033      }
1034    
1035      /*public static void debug(UIComponent component) {
1036          LOG.error("###############################");
1037          LOG.error("ID " + component.getId());
1038          LOG.error("ClassName " + component.getClass().getName());
1039          if (component instanceof EditableValueHolder) {
1040            EditableValueHolder editableValueHolder = (EditableValueHolder) component;
1041            LOG.error("Valid " + editableValueHolder.isValid());
1042            LOG.error("SubmittedValue " + editableValueHolder.getSubmittedValue());
1043          }
1044        for (Iterator it = component.getFacetsAndChildren(); it.hasNext(); ) {
1045          debug((UIComponent)it.next());
1046        }
1047      } */
1048    
1049    
1050      public static List<SelectItem> getItemsToRender(javax.faces.component.UISelectOne component) {
1051        return getItems(component);
1052      }
1053    
1054      public static List<SelectItem> getItemsToRender(javax.faces.component.UISelectMany component) {
1055        return getItems(component);
1056      }
1057    
1058      private static List<SelectItem> getItems(javax.faces.component.UIInput component) {
1059    
1060        List<SelectItem> selectItems = ComponentUtil.getSelectItems(component);
1061    
1062        String renderRange = (String)
1063            component.getAttributes().get(ATTR_RENDER_RANGE_EXTERN);
1064        if (renderRange == null) {
1065          renderRange = (String)
1066              component.getAttributes().get(ATTR_RENDER_RANGE);
1067        }
1068        if (renderRange == null) {
1069          return selectItems;
1070        }
1071    
1072        int[] indices = RangeParser.getIndices(renderRange);
1073        List<SelectItem> items = new ArrayList<SelectItem>(indices.length);
1074    
1075        if (selectItems.size() != 0) {
1076          for (int indice : indices) {
1077            items.add(selectItems.get(indice));
1078          }
1079        } else {
1080          LOG.warn("No items found! rendering dummys instead!");
1081          for (int i = 0; i < indices.length; i++) {
1082            items.add(new SelectItem(Integer.toString(i), "Item " + i, ""));
1083          }
1084        }
1085        return items;
1086      }
1087    
1088      public static void setValidator(EditableValueHolder editableValueHolder, String validator) {
1089        if (validator != null && editableValueHolder.getValidator() == null) {
1090          if (UIComponentTag.isValueReference(validator)) {
1091            MethodBinding methodBinding =
1092                FacesContext.getCurrentInstance().getApplication().createMethodBinding(validator, VALIDATOR_ARGS);
1093            editableValueHolder.setValidator(methodBinding);
1094          }
1095        }
1096      }
1097    
1098      /**
1099       * @param component
1100       * @param converterId
1101       * @deprecated please use the typesave method {@link #setConverter(javax.faces.component.ValueHolder, String)}
1102       */
1103      @Deprecated
1104      public static void setConverter(UIComponent component, String converterId) {
1105        if (component instanceof ValueHolder) {
1106          setConverter((ValueHolder) component, converterId);
1107        }
1108      }
1109    
1110      public static void setConverter(ValueHolder valueHolder, String converterId) {
1111        if (converterId != null && valueHolder.getConverter() == null) {
1112          final FacesContext facesContext = FacesContext.getCurrentInstance();
1113          final Application application = facesContext.getApplication();
1114          if (UIComponentTag.isValueReference(converterId)) {
1115            ValueBinding valueBinding = application.createValueBinding(converterId);
1116            if (valueHolder instanceof UIComponent) {
1117              ((UIComponent) valueHolder).setValueBinding(ATTR_CONVERTER, valueBinding);
1118            }
1119          } else {
1120            Converter converter = application.createConverter(converterId);
1121            valueHolder.setConverter(converter);
1122          }
1123        }
1124      }
1125    
1126      /**
1127       * @param component
1128       * @param type
1129       * @param action
1130       * @deprecated please use the typesave method {@link #setAction(javax.faces.component.UICommand, String, String)}
1131       */
1132      @Deprecated
1133      public static void setAction(UIComponent component, String type, String action) {
1134        if (component instanceof UICommand) {
1135          setAction((UICommand) component, type, action);
1136        }
1137      }
1138    
1139      public static void setAction(UICommand component, String type, String action) {
1140        String commandType;
1141        final FacesContext facesContext = FacesContext.getCurrentInstance();
1142        final Application application = facesContext.getApplication();
1143        if (type != null && UIComponentTag.isValueReference(type)) {
1144          commandType = (String) application.createValueBinding(type).getValue(facesContext);
1145        } else {
1146          commandType = type;
1147        }
1148        if (commandType != null
1149            && (commandType.equals(COMMAND_TYPE_NAVIGATE)
1150            || commandType.equals(COMMAND_TYPE_RESET)
1151            || commandType.equals(COMMAND_TYPE_SCRIPT))) {
1152          if (commandType.equals(COMMAND_TYPE_NAVIGATE)) {
1153            setStringProperty(component, ATTR_ACTION_LINK, action);
1154          } else if (commandType.equals(COMMAND_TYPE_SCRIPT)) {
1155            setStringProperty(component, ATTR_ACTION_ONCLICK, action);
1156          } else {
1157            LOG.warn("Type reset is not supported");
1158          }
1159        } else {
1160          if (action != null) {
1161            if (UIComponentTag.isValueReference(action)) {
1162              MethodBinding binding = application.createMethodBinding(action, null);
1163              component.setAction(binding);
1164            } else {
1165              component.setAction(new ConstantMethodBinding(action));
1166            }
1167          }
1168        }
1169    
1170      }
1171    
1172      /**
1173       * @param component
1174       * @param suggestMethod
1175       * @deprecated please use the typesave method {@link #setSuggestMethodBinding(UIInput, String)}
1176       */
1177      @Deprecated
1178      public static void setSuggestMethodBinding(UIComponent component, String suggestMethod) {
1179        if (component instanceof UIInput) {
1180          setSuggestMethodBinding((UIInput) component, suggestMethod);
1181        }
1182      }
1183    
1184      public static void setSuggestMethodBinding(UIInput component, String suggestMethod) {
1185        if (suggestMethod != null) {
1186          if (UIComponentTag.isValueReference(suggestMethod)) {
1187            final MethodBinding methodBinding = FacesContext.getCurrentInstance().getApplication()
1188                .createMethodBinding(suggestMethod, new Class[]{String.class});
1189            component.setSuggestMethod(methodBinding);
1190          } else {
1191            throw new IllegalArgumentException(
1192                "Must be a valueReference (suggestMethod): " + suggestMethod);
1193          }
1194        }
1195      }
1196    
1197      public static void setActionListener(ActionSource command, String actionListener) {
1198        final FacesContext facesContext = FacesContext.getCurrentInstance();
1199        final Application application = facesContext.getApplication();
1200        if (actionListener != null) {
1201          if (UIComponentTag.isValueReference(actionListener)) {
1202            MethodBinding binding
1203                = application.createMethodBinding(actionListener, ACTION_LISTENER_ARGS);
1204            command.setActionListener(binding);
1205          } else {
1206            throw new IllegalArgumentException(
1207                "Must be a valueReference (actionListener): " + actionListener);
1208          }
1209        }
1210      }
1211    
1212      public static void setValueChangeListener(EditableValueHolder valueHolder, String valueChangeListener) {
1213        final FacesContext facesContext = FacesContext.getCurrentInstance();
1214        final Application application = facesContext.getApplication();
1215        if (valueChangeListener != null) {
1216          if (UIComponentTag.isValueReference(valueChangeListener)) {
1217            MethodBinding binding
1218                = application.createMethodBinding(valueChangeListener, VALUE_CHANGE_LISTENER_ARGS);
1219            valueHolder.setValueChangeListener(binding);
1220          } else {
1221            throw new IllegalArgumentException(
1222                "Must be a valueReference (valueChangeListener): " + valueChangeListener);
1223          }
1224        }
1225      }
1226    
1227    
1228      public static void setSortActionListener(UIData data, String actionListener) {
1229        final FacesContext facesContext = FacesContext.getCurrentInstance();
1230        final Application application = facesContext.getApplication();
1231        if (actionListener != null) {
1232          if (UIComponentTag.isValueReference(actionListener)) {
1233            MethodBinding binding = application.createMethodBinding(
1234                actionListener, ACTION_LISTENER_ARGS);
1235            data.setSortActionListener(binding);
1236          } else {
1237            throw new IllegalArgumentException(
1238                "Must be a valueReference (sortActionListener): " + actionListener);
1239          }
1240        }
1241      }
1242    
1243      public static void setValueBinding(UIComponent component, String name, String state) {
1244        // TODO: check, if it is an writeable object
1245        if (state != null && UIComponentTag.isValueReference(state)) {
1246          ValueBinding valueBinding = createValueBinding(state);
1247          component.setValueBinding(name, valueBinding);
1248        }
1249      }
1250    
1251      public static void setStateChangeListener(UIData data, String stateChangeListener) {
1252        final FacesContext facesContext = FacesContext.getCurrentInstance();
1253        final Application application = facesContext.getApplication();
1254    
1255        if (stateChangeListener != null) {
1256          if (UIComponentTag.isValueReference(stateChangeListener)) {
1257            Class[] arguments = {SheetStateChangeEvent.class};
1258            MethodBinding binding
1259                = application.createMethodBinding(stateChangeListener, arguments);
1260            data.setStateChangeListener(binding);
1261          } else {
1262            throw new IllegalArgumentException(
1263                "Must be a valueReference (actionListener): " + stateChangeListener);
1264          }
1265        }
1266      }
1267    
1268    
1269      public static String[] getMarkupBinding(FacesContext facesContext, SupportsMarkup component) {
1270        ValueBinding vb = ((UIComponent) component).getValueBinding(ATTR_MARKUP);
1271        if (vb != null) {
1272          Object markups = vb.getValue(facesContext);
1273          if (markups instanceof String[]) {
1274            return (String[]) markups;
1275          } else if (markups instanceof String) {
1276            String[] strings = StringUtils.split((String) markups, ", ");
1277            List<String> result = new ArrayList<String>(strings.length);
1278            for (String string : strings) {
1279              if (string.trim().length() != 0) {
1280                result.add(string.trim());
1281              }
1282            }
1283            return result.toArray(new String[result.size()]);
1284          } else if (markups == null) {
1285            return new String[0];
1286          } else {
1287            return new String[]{markups.toString()};
1288          }
1289        }
1290    
1291        return new String[0];
1292      }
1293    
1294      /**
1295       * colonCount == 0: fully relative
1296       * colonCount == 1: absolute (still normal findComponent syntax)
1297       * colonCount > 1: for each extra colon after 1, go up a naming container
1298       * (to the view root, if naming containers run out)
1299       */
1300    
1301      public static UIComponent findComponent(UIComponent from, String relativeId) {
1302        int idLength = relativeId.length();
1303        // Figure out how many colons
1304        int colonCount = 0;
1305        while (colonCount < idLength) {
1306          if (relativeId.charAt(colonCount) != NamingContainer.SEPARATOR_CHAR) {
1307            break;
1308          }
1309          colonCount++;
1310        }
1311    
1312        // colonCount == 0: fully relative
1313        // colonCount == 1: absolute (still normal findComponent syntax)
1314        // colonCount > 1: for each extra colon after 1, go up a naming container
1315        // (to the view root, if naming containers run out)
1316        if (colonCount > 1) {
1317          relativeId = relativeId.substring(colonCount);
1318          for (int j = 1; j < colonCount; j++) {
1319            while (from.getParent() != null) {
1320              from = from.getParent();
1321              if (from instanceof NamingContainer) {
1322                break;
1323              }
1324            }
1325          }
1326        }
1327        return from.findComponent(relativeId);
1328      }
1329    
1330      public static void invokeOnComponent(FacesContext facesContext, String clientId, UIComponent component,
1331          Callback callback) {
1332        List<UIComponent> list = new ArrayList<UIComponent>();
1333        while (component != null) {
1334          list.add(component);
1335          component = component.getParent();
1336        }
1337        Collections.reverse(list);
1338        invokeOrPrepare(facesContext, list, clientId, callback);
1339        facesContext.getExternalContext().getRequestMap().remove(TobagoConstants.ATTR_ZINDEX);
1340      }
1341    
1342      private static void invokeOrPrepare(FacesContext facesContext, List<UIComponent> list, String clientId,
1343          Callback callback) {
1344        if (list.size() == 1) {
1345          callback.execute(facesContext, list.get(0));
1346        } else if (list.get(0) instanceof UIData) {
1347          prepareOnUIData(facesContext, list, clientId, callback);
1348        } else if (list.get(0) instanceof UIForm) {
1349          prepareOnUIForm(facesContext, list, clientId, callback);
1350        } else if (list.get(0) instanceof UIPopup) {
1351          prepareOnUIPopup(facesContext, list, clientId, callback);
1352        } else {
1353          prepareOnUIComponent(facesContext, list, clientId, callback);
1354        }
1355      }
1356    
1357      @SuppressWarnings(value = "unchecked")
1358      private static void prepareOnUIForm(FacesContext facesContext, List<UIComponent> list, String clientId,
1359          Callback callback) {
1360        UIComponent currentComponent = list.remove(0);
1361        if (!(currentComponent instanceof UIForm)) {
1362          throw new IllegalStateException(currentComponent.getClass().getName());
1363        }
1364        // TODO is this needed?
1365        if (callback instanceof TobagoCallback) {
1366          if (PhaseId.APPLY_REQUEST_VALUES.equals(((TobagoCallback) callback).getPhaseId())) {
1367            currentComponent.decode(facesContext);
1368          }
1369        }
1370        UIForm uiForm = (UIForm) currentComponent;
1371        facesContext.getExternalContext().getRequestMap().put(UIForm.SUBMITTED_MARKER, uiForm.isSubmitted());
1372        invokeOrPrepare(facesContext, list, clientId, callback);
1373    
1374      }
1375    
1376      private static void prepareOnUIComponent(
1377          FacesContext facesContext, List<UIComponent> list, String clientId, Callback callback) {
1378        list.remove(0);
1379        invokeOrPrepare(facesContext, list, clientId, callback);
1380      }
1381    
1382      private static void prepareOnUIPopup(
1383          FacesContext facesContext, List<UIComponent> list, String clientId, Callback callback) {
1384        if (callback instanceof TobagoCallback
1385            && PhaseId.RENDER_RESPONSE.equals(((TobagoCallback) callback).getPhaseId())) {
1386          Integer zIndex = (Integer) facesContext.getExternalContext().getRequestMap().get(TobagoConstants.ATTR_ZINDEX);
1387          if (zIndex == null) {
1388            zIndex = 0;
1389          } else {
1390            zIndex += 10;
1391          }
1392          facesContext.getExternalContext().getRequestMap().put(TobagoConstants.ATTR_ZINDEX, zIndex);
1393        }
1394        list.remove(0);
1395        invokeOrPrepare(facesContext, list, clientId, callback);
1396      }
1397    
1398      private static void prepareOnUIData(FacesContext facesContext, List<UIComponent> list, String clientId,
1399          Callback callback) {
1400        UIComponent currentComponent = list.remove(0);
1401        if (!(currentComponent instanceof UIData)) {
1402          throw new IllegalStateException(currentComponent.getClass().getName());
1403        }
1404    
1405        // we may need setRowIndex on UIData
1406        javax.faces.component.UIData uiData = (javax.faces.component.UIData) currentComponent;
1407        int oldRowIndex = uiData.getRowIndex();
1408        String sheetId = uiData.getClientId(facesContext);
1409        String idRemainder = clientId.substring(sheetId.length());
1410        if (LOG.isInfoEnabled()) {
1411          LOG.info("idRemainder = \"" + idRemainder + "\"");
1412        }
1413        if (idRemainder.startsWith(String.valueOf(NamingContainer.SEPARATOR_CHAR))) {
1414          idRemainder = idRemainder.substring(1);
1415          int idx = idRemainder.indexOf(NamingContainer.SEPARATOR_CHAR);
1416          if (idx > 0) {
1417            String firstPart = idRemainder.substring(0, idx);
1418            if (NumberUtils.isDigits(firstPart)) {
1419              try {
1420                int rowIndex = Integer.parseInt(firstPart);
1421                if (LOG.isInfoEnabled()) {
1422                  LOG.info("set rowIndex = \"" + rowIndex + "\"");
1423                }
1424                uiData.setRowIndex(rowIndex);
1425              } catch (NumberFormatException e) {
1426                LOG.error("idRemainder = \"" + idRemainder + "\"", e);
1427              }
1428            }
1429          }
1430        } else {
1431          if (LOG.isInfoEnabled()) {
1432            LOG.info("no match for \"^:\\d+:.*\"");
1433          }
1434        }
1435    
1436        invokeOrPrepare(facesContext, list, clientId, callback);
1437    
1438        // we should reset rowIndex on UIData
1439        uiData.setRowIndex(oldRowIndex);
1440      }
1441    
1442      public static Object getConvertedValue(
1443          FacesContext facesContext, javax.faces.component.UIInput component, String stringValue) {
1444        try {
1445          Renderer renderer = getRenderer(facesContext, component);
1446          if (renderer != null) {
1447            if (component instanceof UISelectMany) {
1448              final Object converted = renderer.getConvertedValue(facesContext, component, new String[]{stringValue});
1449              return ((Object[]) converted)[0];
1450            } else {
1451              return renderer.getConvertedValue(facesContext, component, stringValue);
1452            }
1453          } else {
1454            Converter converter = component.getConverter();
1455            if (converter == null) {
1456              //Try to find out by value binding
1457              ValueBinding vb = component.getValueBinding("value");
1458              if (vb != null) {
1459                Class valueType = vb.getType(facesContext);
1460                if (valueType != null) {
1461                  converter = facesContext.getApplication().createConverter(valueType);
1462                }
1463              }
1464            }
1465            if (converter != null) {
1466              converter.getAsObject(facesContext, component, stringValue);
1467            }
1468          }
1469        } catch (Exception e) {
1470          LOG.warn("Can't convert string value '" + stringValue + "'", e);
1471        }
1472        return stringValue;
1473      }
1474    }