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