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