/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.myfaces.tobago.component;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.tobago.TobagoConstants;
import org.apache.myfaces.tobago.context.TransientStateHolder;
import org.apache.myfaces.tobago.el.ConstantMethodBinding;
import org.apache.myfaces.tobago.event.PopupActionListener;
import org.apache.myfaces.tobago.event.SheetStateChangeEvent;
import org.apache.myfaces.tobago.renderkit.LayoutableRendererBase;
import org.apache.myfaces.tobago.renderkit.html.StyleClasses;
import org.apache.myfaces.tobago.util.Callback;
import org.apache.myfaces.tobago.util.RangeParser;
import org.apache.myfaces.tobago.util.TobagoCallback;

import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.component.ActionSource;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.component.UIGraphic;
import javax.faces.component.UIOutput;
import javax.faces.component.UIParameter;
import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.component.ValueHolder;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.el.MethodBinding;
import javax.faces.el.ValueBinding;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import javax.faces.event.PhaseId;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.Renderer;
import javax.faces.webapp.UIComponentTag;
import javax.servlet.jsp.JspException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ACTION_LINK;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ACTION_ONCLICK;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ALIGN;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_CONVERTER;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_CREATE_SPAN;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_DISABLED;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ESCAPE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_FOR;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_HOVER;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LABEL;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_MARKUP;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_READONLY;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_RENDERED_PARTIALLY;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_RENDER_RANGE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_RENDER_RANGE_EXTERN;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SORTABLE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE_CLASS;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_VALUE;
import static org.apache.myfaces.tobago.TobagoConstants.COMMAND_TYPE_NAVIGATE;
import static org.apache.myfaces.tobago.TobagoConstants.COMMAND_TYPE_RESET;
import static org.apache.myfaces.tobago.TobagoConstants.COMMAND_TYPE_SCRIPT;
import static org.apache.myfaces.tobago.TobagoConstants.FACET_ITEMS;
import static org.apache.myfaces.tobago.TobagoConstants.FACET_LABEL;
import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_LABEL;
import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_OUT;
import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_SELECT_BOOLEAN_CHECKBOX;
import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_SELECT_ONE_RADIO;

public class ComponentUtil {

  private static final Log LOG = LogFactory.getLog(ComponentUtil.class);

  private static final String RENDER_KEY_PREFIX
      = "org.apache.myfaces.tobago.component.ComponentUtil.RendererKeyPrefix_";

  public static final Class[] ACTION_ARGS = {};
  public static final Class[] ACTION_LISTENER_ARGS = {ActionEvent.class};
  public static final Class[] VALUE_CHANGE_LISTENER_ARGS = {ValueChangeEvent.class};
  public static final Class[] VALIDATOR_ARGS = {FacesContext.class, UIComponent.class, Object.class};

  private ComponentUtil() {
  }


  public static boolean hasErrorMessages(FacesContext context) {
    for (Iterator iter = context.getMessages(); iter.hasNext();) {
      FacesMessage message = (FacesMessage) iter.next();
      if (FacesMessage.SEVERITY_ERROR.compareTo(message.getSeverity()) <= 0) {
        return true;
      }
    }
    return false;
  }

  public static boolean containsPopupActionListener(UICommand command) {
    ActionListener[] actionListeners = command.getActionListeners();
    for (ActionListener actionListener : actionListeners) {
      if (actionListener instanceof PopupActionListener) {
        return true;
      }
    }
    return false;
  }

  public static String getFacesMessageAsString(FacesContext facesContext, UIComponent component) {
    Iterator messages = facesContext.getMessages(
        component.getClientId(facesContext));
    StringBuilder stringBuffer = new StringBuilder();
    while (messages.hasNext()) {
      FacesMessage message = (FacesMessage) messages.next();
      stringBuffer.append(message.getDetail());
    }
    if (stringBuffer.length() > 0) {
      return stringBuffer.toString();
    } else {
      return null;
    }
  }

  public static boolean isInPopup(UIComponent component) {
    while (component != null) {
      if (component instanceof UIPopup) {
        return true;
      }
      component = component.getParent();
    }
    return false;
  }

  public static void resetPage(FacesContext context) {
    javax.faces.component.UIViewRoot view = context.getViewRoot();
    if (view != null) {
      view.getAttributes().remove(UIPage.COMPONENT_TYPE);
    }
  }

  @SuppressWarnings(value = "unchecked")
  public static UIPage findPage(FacesContext context, UIComponent component) {
    javax.faces.component.UIViewRoot view = context.getViewRoot();
    if (view != null) {
      TransientStateHolder stateHolder = (TransientStateHolder) view.getAttributes().get(UIPage.COMPONENT_TYPE);
      if (stateHolder == null || stateHolder.isEmpty()) {
        UIPage page = findPage(component);
        stateHolder = new TransientStateHolder(page);
        context.getViewRoot().getAttributes().put(UIPage.COMPONENT_TYPE, stateHolder);
      }
      return (UIPage) stateHolder.get();
    } else {
      return findPage(component);
    }
  }

  public static UIPage findPage(UIComponent component) {
    while (component != null) {
      if (component instanceof UIPage) {
        return (UIPage) component;
      }
      component = component.getParent();
    }
    return null;
  }

  public static void addStyles(UIComponent component, String[] styles) {
    UIPage uiPage = ComponentUtil.findPage(component);
    uiPage.getStyleFiles().addAll(Arrays.asList(styles));
  }

  public static void addScripts(UIComponent component, String[] scripts) {
    UIPage uiPage = ComponentUtil.findPage(component);
    uiPage.getScriptFiles().addAll(Arrays.asList(scripts));
  }

  public static void addOnloadCommands(UIComponent component, String[] cmds) {
    UIPage uiPage = ComponentUtil.findPage(component);
    uiPage.getOnloadScripts().addAll(Arrays.asList(cmds));
  }

  public static UIPage findPage(FacesContext facesContext) {
    return findPageBreadthFirst(facesContext.getViewRoot());
  }

  private static UIPage findPageBreadthFirst(UIComponent component) {
    for (Object o : component.getChildren()) {
      UIComponent child = (UIComponent) o;
      if (child instanceof UIPage) {
        return (UIPage) child;
      }
    }
    for (Object o : component.getChildren()) {
      UIComponent child = (UIComponent) o;
      UIPage result = findPageBreadthFirst(child);
      if (result != null) {
        return result;
      }
    }
    return null;
  }


  public static UIForm findForm(UIComponent component) {
    while (component != null) {
      if (component instanceof UIForm) {
        return (UIForm) component;
      }
      component = component.getParent();
    }
    return null;
  }

  /**
   * Find all subforms of a component, and collects it.
   * It does not find subforms of subforms.
   */
  public static List<UIForm> findSubForms(UIComponent component) {
    List<UIForm> collect = new ArrayList<UIForm>();
    findSubForms(collect, component);
    return collect;
  }

  @SuppressWarnings(value = "unchecked")
  private static void findSubForms(List<UIForm> collect, UIComponent component) {
    Iterator<UIComponent> kids = component.getFacetsAndChildren();
    while (kids.hasNext()) {
      UIComponent child = kids.next();
      if (child instanceof UIForm) {
        collect.add((UIForm) child);
      } else {
        findSubForms(collect, child);
      }
    }
  }

  /**
   * Searches the component tree beneath the component and return the first component matching the type.
   */
  public static <T extends UIComponent> T findDescendant(UIComponent component, Class<T> type) {

    for (UIComponent child : (List<UIComponent>) component.getChildren()) {
      if (child.getClass().equals(type)) {
        return (T) child;
      }
      final T descendant = findDescendant(child, type);
      if (descendant != null) {
        return descendant;
      }
    }
    return null;
  }

  /**
   * Looks for the attribute "for" in the component. If there is any
   * search for the component which is referenced by the "for" attribute,
   * and return their clientId.
   * If there is no "for" attribute, return the "clientId" of the parent
   * (if it has a parent). This is useful for labels.
   */
  public static String findClientIdFor(UIComponent component,
      FacesContext facesContext) {
    UIComponent forComponent = findFor(component);
    if (forComponent != null) {
      String clientId = forComponent.getClientId(facesContext);
      if (LOG.isDebugEnabled()) {
        LOG.debug("found clientId: '" + clientId + "'");
      }
      return clientId;
    }
    if (LOG.isDebugEnabled()) {
      LOG.debug("found no clientId");
    }
    return null;
  }

  public static UIComponent findFor(UIComponent component) {
    String forValue = (String) component.getAttributes().get(ATTR_FOR);
    if (forValue == null) {
      return component.getParent();
    }
    return component.findComponent(forValue);
  }

  public static boolean isInActiveForm(UIComponent component) {
    while (component != null) {
      //log.debug("compoent= " + component.getClientId(FacesContext.getCurrentInstance())
      // + " " + component.getRendererType());
      if (component instanceof UIForm) {
        UIForm form = (UIForm) component;
        if (form.isSubmitted()) {
          //log.debug("in active form = " + form.getClientId(FacesContext.getCurrentInstance()));
          return true;
        } /*else {
          log.debug("form found but not active = " + form.getClientId(FacesContext.getCurrentInstance()));
        } */
      }
      component = component.getParent();
    }
    //log.debug("not in an active form");
    return false;
  }

  public static FacesMessage.Severity getMaximumSeverity(UIComponent component) {
    final boolean invalid = component instanceof javax.faces.component.UIInput
        && !((javax.faces.component.UIInput) component).isValid();
    FacesMessage.Severity max = invalid ? FacesMessage.SEVERITY_ERROR : null;
    FacesContext facesContext = FacesContext.getCurrentInstance();
    final Iterator messages = facesContext.getMessages(component.getClientId(facesContext));
    while (messages.hasNext()) {
      FacesMessage message = (FacesMessage) messages.next();
      if (max == null || message.getSeverity().getOrdinal() > max.getOrdinal()) {
        max = message.getSeverity();
      }
    }
    return max;
  }

  public static boolean isError(javax.faces.component.UIInput uiInput) {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    return !uiInput.isValid()
        || facesContext.getMessages(uiInput.getClientId(facesContext)).hasNext();
  }

  public static boolean isError(UIComponent component) {
    if (component instanceof UIInput) {
      return isError((UIInput) component);
    }
    return false;
  }

  public static boolean isOutputOnly(UIComponent component) {
    return getBooleanAttribute(component, ATTR_DISABLED)
        || getBooleanAttribute(component, ATTR_READONLY);
  }

  public static boolean mayValidate(UIComponent component) {
    return !isOutputOnly(component)
        && ComponentUtil.isInActiveForm(component);
  }

  public static boolean mayUpdateModel(UIComponent component) {
    return mayValidate(component);
  }

  public static boolean getBooleanAttribute(UIComponent component, String name) {

    Object bool = component.getAttributes().get(name);
    if (bool == null) {
      return false;
    }
    if (bool instanceof ValueBinding) {
      bool = ((ValueBinding) bool).getValue(FacesContext.getCurrentInstance());
    }
    if (bool instanceof Boolean) {
      return (Boolean) bool;
    } else if (bool instanceof String) {
      if (LOG.isInfoEnabled()) {
        LOG.info("Searching for a boolean, but find a String. Should not happen. "
            + "attribute: '" + name + "' id: '" + component.getClientId(FacesContext.getCurrentInstance())
            + "' comp: '" + component + "'");
      }
      return Boolean.valueOf((String) bool);
    } else {
      LOG.warn("Unknown type '" + bool.getClass().getName()
          + "' for boolean attribute: " + name + " id: " + component.getClientId(FacesContext.getCurrentInstance())
          + " comp: " + component);
      return false;
    }
  }

  public static void setRenderedPartially(org.apache.myfaces.tobago.component.UICommand command,
      String renderers) {
    if (renderers != null) {
      if (UIComponentTag.isValueReference(renderers)) {
        command.setValueBinding(ATTR_RENDERED_PARTIALLY, createValueBinding(renderers));
      } else {
        String[] components = StringUtils.split(renderers, ",");
        command.setRenderedPartially(components);
      }
    }
  }

  public static void setStyleClasses(UIComponent component, String styleClasses) {
    if (styleClasses != null) {
      if (UIComponentTag.isValueReference(styleClasses)) {
        component.setValueBinding(ATTR_STYLE_CLASS, createValueBinding(styleClasses));
      } else {
        String[] classes = StringUtils.split(styleClasses, ", ");
        if (classes.length > 0) {
          StyleClasses styles = StyleClasses.ensureStyleClasses(component);
          for (String clazz : classes) {
            styles.addFullQualifiedClass(clazz);
          }
        }
      }
    }
  }

  public static void setMarkup(UIComponent markupComponent, String markup) {
    if (markup != null) {
      if (markupComponent instanceof SupportsMarkup) {
        if (UIComponentTag.isValueReference(markup)) {
          markupComponent.setValueBinding(ATTR_MARKUP, createValueBinding(markup));
        } else {
          String[] markups = StringUtils.split(markup, ",");
          ((SupportsMarkup) markupComponent).setMarkup(markups);
        }
      } else {
        LOG.error("Component did not support Markup " + markupComponent.getClass().getName());
      }
    }
  }

  public static Object getAttribute(UIComponent component, String name) {
    Object value = component.getAttributes().get(name);
    if (value instanceof ValueBinding) {
      value = ((ValueBinding) value).getValue(FacesContext.getCurrentInstance());
    }
    return value;
  }

  public static String getStringAttribute(UIComponent component, String name) {
    return (String) getAttribute(component, name);
  }

  public static int getIntAttribute(UIComponent component, String name) {
    return getIntAttribute(component, name, 0);
  }

  public static int getIntAttribute(UIComponent component, String name,
      int defaultValue) {
    Object integer = component.getAttributes().get(name);
    if (integer instanceof Number) {
      return ((Number) integer).intValue();
    } else if (integer instanceof String) {
      try {
        return Integer.parseInt((String) integer);
      } catch (NumberFormatException e) {
        LOG.warn("Can't parse number from string : \"" + integer + "\"!");
        return defaultValue;
      }
    } else if (integer == null) {
      return defaultValue;
    } else {
      LOG.warn("Unknown type '" + integer.getClass().getName()
          + "' for integer attribute: " + name + " comp: " + component);
      return defaultValue;
    }
  }

  /**
   * @param component
   * @param name
   * @deprecated please use the  method {@link #getCharacterAttribute(javax.faces.component.UIComponent, String)}
   */
  @Deprecated
  public static Character getCharakterAttribute(UIComponent component, String name) {
    return getCharacterAttribute(component, name);
  }

  public static Character getCharacterAttribute(UIComponent component, String name) {
    Object character = component.getAttributes().get(name);
    if (character == null) {
      return null;
    } else if (character instanceof Character) {
      return ((Character) character);
    } else if (character instanceof String) {
      String asString = ((String) character);
      return asString.length() > 0 ? asString.charAt(0) : null;
    } else {
      LOG.warn("Unknown type '" + character.getClass().getName()
          + "' for integer attribute: " + name + " comp: " + component);
      return null;
    }
  }

  public static boolean isFacetOf(UIComponent component, UIComponent parent) {
    for (Object o : parent.getFacets().keySet()) {
      UIComponent facet = parent.getFacet((String) o);
      if (component.equals(facet)) {
        return true;
      }
    }
    return false;
  }

  // TODO This should not be neseccary, but UIComponentBase.getRenderer() is protected
  public static LayoutableRendererBase getRenderer(FacesContext facesContext, UIComponent component) {
    return getRenderer(facesContext, component.getFamily(), component.getRendererType());

  }

  public static LayoutableRendererBase getRenderer(FacesContext facesContext, String family, String rendererType) {
    if (rendererType == null) {
      return null;
    }

    LayoutableRendererBase renderer;

    Map requestMap = facesContext.getExternalContext().getRequestMap();
    renderer = (LayoutableRendererBase) requestMap.get(RENDER_KEY_PREFIX + rendererType);

    if (renderer == null) {
      RenderKitFactory rkFactory = (RenderKitFactory)
          FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
      RenderKit renderKit = rkFactory.getRenderKit(facesContext, facesContext.getViewRoot().getRenderKitId());
      Renderer myRenderer = renderKit.getRenderer(family, rendererType);
      if (myRenderer instanceof LayoutableRendererBase) {
        requestMap.put(RENDER_KEY_PREFIX + rendererType, myRenderer);
        renderer = (LayoutableRendererBase) myRenderer;
      } else {
        return null;
      }
    }
    return renderer;
  }

  public static String currentValue(UIComponent component) {
    String currentValue = null;
    if (component instanceof ValueHolder) {
      Object value;
      if (component instanceof EditableValueHolder) {
        value = ((EditableValueHolder) component).getSubmittedValue();
        if (value != null) {
          return (String) value;
        }
      }

      value = ((ValueHolder) component).getValue();
      if (value != null) {
        Converter converter = ((ValueHolder) component).getConverter();
        if (converter == null) {
          FacesContext context = FacesContext.getCurrentInstance();
          converter = context.getApplication().createConverter(value.getClass());
        }
        if (converter != null) {
          currentValue =
              converter.getAsString(FacesContext.getCurrentInstance(),
                  component, value);
        } else {
          currentValue = value.toString();
        }
      }
    }
    return currentValue;
  }

  public static List<SelectItem> getSelectItems(UIComponent component) {

    ArrayList<SelectItem> list = new ArrayList<SelectItem>();

    for (Object o1 : component.getChildren()) {
      UIComponent kid = (UIComponent) o1;
      if (LOG.isDebugEnabled()) {
        LOG.debug("kid " + kid);
        LOG.debug("kid " + kid.getClass().getName());
      }
      if (kid instanceof UISelectItem) {
        Object value = ((UISelectItem) kid).getValue();
        if (value == null) {
          UISelectItem item = (UISelectItem) kid;
          if (kid instanceof org.apache.myfaces.tobago.component.UISelectItem) {
            list.add(new org.apache.myfaces.tobago.model.SelectItem(
                (org.apache.myfaces.tobago.component.UISelectItem) kid));
          } else {
            list.add(new SelectItem(item.getItemValue() == null ? "" : item.getItemValue(),
                item.getItemLabel() != null ? item.getItemLabel() : item.getItemValue().toString(),
                item.getItemDescription()));
          }
        } else if (value instanceof SelectItem) {
          list.add((SelectItem) value);
        } else {
          throw new IllegalArgumentException("TYPE ERROR: value NOT instanceof SelectItem. type="
              + value.getClass().getName());
        }
      } else if (kid instanceof UISelectItems) {
        Object value = ((UISelectItems) kid).getValue();
        if (LOG.isDebugEnabled()) {
          LOG.debug("value " + value);
          if (value != null) {
            LOG.debug("value " + value.getClass().getName());
          }
        }
        if (value == null) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("value is null");
          }
        } else if (value instanceof SelectItem) {
          list.add((SelectItem) value);
        } else if (value instanceof SelectItem[]) {
          SelectItem[] items = (SelectItem[]) value;
          list.addAll(Arrays.asList(items));
        } else if (value instanceof Collection) {
          for (Object o : ((Collection) value)) {
            list.add((SelectItem) o);
          }
        } else if (value instanceof Map) {
          for (Object key : ((Map) value).keySet()) {
            if (key != null) {
              Object val = ((Map) value).get(key);
              if (val != null) {
                list.add(new SelectItem(val.toString(), key.toString(), null));
              }
            }
          }
        } else {
          throw new IllegalArgumentException("TYPE ERROR: value NOT instanceof "
              + "SelectItem, SelectItem[], Collection, Map. type="
              + value.getClass().getName());
        }
      }
    }

    return list;
  }

  public static Object findParameter(UIComponent component, String name) {
    for (Object o : component.getChildren()) {
      UIComponent child = (UIComponent) o;
      if (child instanceof UIParameter) {
        UIParameter parameter = (UIParameter) child;
        if (LOG.isDebugEnabled()) {
          LOG.debug("Select name='" + parameter.getName() + "'");
          LOG.debug("Select value='" + parameter.getValue() + "'");
        }
        if (name.equals(parameter.getName())) {
          return parameter.getValue();
        }
      }
    }
    return null;
  }

  public static String toString(UIComponent component, int offset) {
    return toString(component, offset, false);
  }

  private static String toString(UIComponent component, int offset, boolean asFacet) {
    StringBuilder result = new StringBuilder();
    if (component == null) {
      result.append("null");
    } else {
      result.append('\n');
      if (!asFacet) {
        result.append(spaces(offset));
        result.append(toString(component));
      }
      Map facets = component.getFacets();
      if (facets.size() > 0) {
        for (Map.Entry<String, UIComponent> entry : (Set<Map.Entry<String, UIComponent>>) facets.entrySet()) {
          UIComponent facet = entry.getValue();
          result.append('\n');
          result.append(spaces(offset + 1));
          result.append('\"');
          result.append(entry.getKey());
          result.append("\" = ");
          result.append(toString(facet));
          result.append(toString(facet, offset + 1, true));
        }
      }
      for (Object o : component.getChildren()) {
        result.append(toString((UIComponent) o, offset + 1, false));
      }
    }
    return result.toString();
  }

  private static String toString(UIComponent component) {
    StringBuilder buf = new StringBuilder(component.getClass().getName());
    buf.append('@');
    buf.append(Integer.toHexString(component.hashCode()));
    buf.append(" ");
    buf.append(component.getRendererType());
    buf.append(" ");
    if (component instanceof javax.faces.component.UIViewRoot) {
      buf.append(((javax.faces.component.UIViewRoot) component).getViewId());
    } else {
      buf.append(component.getId());
      buf.append(" ");
      buf.append(component.getClientId(FacesContext.getCurrentInstance()));
    }
    return buf.toString();
  }

  private static String spaces(int n) {
    StringBuilder buffer = new StringBuilder(n * 2);
    for (int i = 0; i < n; i++) {
      buffer.append("  ");
    }
    return buffer.toString();
  }

  public static ActionListener createActionListener(String type)
      throws JspException {
    try {
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
      if (classLoader == null) {
        classLoader = type.getClass().getClassLoader();
      }
      Class clazz = classLoader.loadClass(type);
      return (ActionListener) clazz.newInstance();
    } catch (Exception e) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("type=" + type, e);
      }
      throw new JspException(e);
    }
  }

  public static UIGraphic getFirstGraphicChild(UIComponent component) {
    UIGraphic graphic = null;
    for (Object o : component.getChildren()) {
      UIComponent uiComponent = (UIComponent) o;
      if (uiComponent instanceof UIGraphic) {
        graphic = (UIGraphic) uiComponent;
        break;
      }
    }
    return graphic;
  }

  public static boolean isHoverEnabled(UIComponent component) {
    return ComponentUtil.getBooleanAttribute(component, ATTR_HOVER);
  }

  public static UIOutput getFirstNonGraphicChild(UIComponent component) {
    UIOutput output = null;
    for (Object o : component.getChildren()) {
      UIComponent uiComponent = (UIComponent) o;
      if ((uiComponent instanceof UIOutput)
          && !(uiComponent instanceof UIGraphic)) {
        output = (UIOutput) uiComponent;
        break;
      }
    }
    return output;
  }

  public static void setIntegerSizeProperty(UIComponent component,
      String name, String value) {
    if (value != null) {
      if (UIComponentTag.isValueReference(value)) {
        component.setValueBinding(name, createValueBinding(value));
      } else {
        value = removePx(value);
        component.getAttributes().put(name, new Integer(value));
      }
    }
  }

  public static String removePx(String value) {
    if (value != null && value.endsWith("px")) {
      value = value.substring(0, value.length() - 2);
    }
    return value;
  }

  public static void setIntegerProperty(UIComponent component,
      String name, String value) {
    if (value != null) {
      if (UIComponentTag.isValueReference(value)) {
        component.setValueBinding(name, createValueBinding(value));
      } else {
        component.getAttributes().put(name, new Integer(value));
      }
    }
  }

  public static void setBooleanProperty(UIComponent component,
      String name, String value) {
    if (value != null) {
      if (UIComponentTag.isValueReference(value)) {
        component.setValueBinding(name, createValueBinding(value));
      } else {
        component.getAttributes().put(name, Boolean.valueOf(value));
      }
    }
  }

  public static void setStringProperty(UIComponent component, String name,
      String value) {
    if (value != null) {
      if (UIComponentTag.isValueReference(value)) {
        component.setValueBinding(name, createValueBinding(value));
      } else {
        component.getAttributes().put(name, value);
      }
    }
  }

  public static void setValueForValueBinding(String name, Object value) {
    FacesContext context = FacesContext.getCurrentInstance();
    ValueBinding valueBinding = context.getApplication().createValueBinding(name);
    valueBinding.setValue(context, value);
  }

  public static ValueBinding createValueBinding(String value) {
    return FacesContext.getCurrentInstance().getApplication()
        .createValueBinding(value);
  }

  public static String getValueFromEl(String script) {
    if (UIComponentTag.isValueReference(script)) {
      ValueBinding valueBinding = ComponentUtil.createValueBinding(script);
      script = (String) valueBinding.getValue(FacesContext.getCurrentInstance());
    }
    return script;
  }

  /**
   * Please use createComponent(String componentType, String rendererType, String id)
   *
   * @deprecated
   */
  @Deprecated
  public static UIComponent createComponent(String componentType, String rendererType) {
    return createComponent(componentType, rendererType,  null);
  }

  public static UIComponent createComponent(String componentType, String rendererType, String id) {
    final FacesContext facesContext = FacesContext.getCurrentInstance();
    return createComponent(facesContext, componentType, rendererType, id);
  }

  /**
   * Please use createComponent(FacesContext facesContext, String componentType, String rendererType, String id)
   *
   * @deprecated
   */
  @Deprecated
  public static UIComponent createComponent(FacesContext facesContext, String componentType, String rendererType) {
    return createComponent(facesContext, componentType, rendererType, null);
  }

  public static UIComponent createComponent(
      FacesContext facesContext, String componentType, String rendererType, String id) {
    UIComponent component
        = facesContext.getApplication().createComponent(componentType);
    component.setRendererType(rendererType);
    component.setId(id);
    return component;
  }

  /**
  * Please use createTextColumn(String label, String sortable, String align, String value, String id)
  *
  * @deprecated
  */
 @Deprecated
  public static UIColumn createTextColumn(String label, String sortable, String align, String value) {
    return createTextColumn(label, sortable, align, value, null);
  }

  public static UIColumn createTextColumn(String label, String sortable, String align, String value, String id) {
    UIComponent text = createComponent(UIOutput.COMPONENT_TYPE, RENDERER_TYPE_OUT, id + "_t");
    setStringProperty(text, ATTR_VALUE, value);
    setBooleanProperty(text, ATTR_CREATE_SPAN, "false");
    setBooleanProperty(text, ATTR_ESCAPE, "false");
    return createColumn(label, sortable, align, text, id);
  }

  /**
  * Please use createColumn(String label, String sortable, String align, UIComponent child)
  *
  * @deprecated
  */
 @Deprecated
  public static UIColumn createColumn(String label, String sortable, String align, UIComponent child) {
    return createColumn(label, sortable, align, child, null);
  }

  public static UIColumn createColumn(String label, String sortable, String align, UIComponent child, String id) {
    UIColumn column = createColumn(label, sortable, align, id);
    column.getChildren().add(child);
    return column;
  }

  private static UIColumn createColumn(String label, String sortable, String align, String id) {
    UIColumn column = (UIColumn) createComponent(UIColumn.COMPONENT_TYPE, null, id);
    setStringProperty(column, ATTR_LABEL, label);
    setBooleanProperty(column, ATTR_SORTABLE, sortable);
    setStringProperty(column, ATTR_ALIGN, align);
    return column;
  }

  /**
  * Please use createUIMenuSelectOneFacet(FacesContext facesContext, UICommand command, String id)
  *
  * @deprecated
  */
 @Deprecated
  public static UIMenuSelectOne createUIMenuSelectOneFacet(FacesContext facesContext, UICommand command) {
    return createUIMenuSelectOneFacet(facesContext, command, null);
  }

  public static UIMenuSelectOne createUIMenuSelectOneFacet(FacesContext facesContext, UICommand command, String id) {
    UIMenuSelectOne radio = null;
    final ValueBinding valueBinding = command.getValueBinding(ATTR_VALUE);
    if (valueBinding != null) {
      radio = (UIMenuSelectOne) createComponent(facesContext,
          UIMenuSelectOne.COMPONENT_TYPE, RENDERER_TYPE_SELECT_ONE_RADIO, id);
      command.getFacets().put(FACET_ITEMS, radio);
      radio.setValueBinding(ATTR_VALUE, valueBinding);
    }
    return radio;
  }


  public static boolean hasSelectedValue(List<SelectItem> items, Object value) {
    for (SelectItem item : items) {
      if (ObjectUtils.equals(item.getValue(), value)) {
        return true;
      }
    }
    return false;
  }

  /**
  * Please use createUISelectBooleanFacet(FacesContext facesContext, UICommand command, String id)
  *
  * @deprecated
  */
 @Deprecated
  public static UIComponent createUISelectBooleanFacet(FacesContext facesContext, UICommand command) {
    return createUISelectBooleanFacet(facesContext, command, null);
  }

  public static UIComponent createUISelectBooleanFacet(FacesContext facesContext, UICommand command, String id) {
    UIComponent checkbox
        = createComponent(facesContext, UISelectBoolean.COMPONENT_TYPE, RENDERER_TYPE_SELECT_BOOLEAN_CHECKBOX, id);
    command.getFacets().put(FACET_ITEMS, checkbox);
    ValueBinding valueBinding = command.getValueBinding(ATTR_VALUE);
    if (valueBinding != null) {
      checkbox.setValueBinding(ATTR_VALUE, valueBinding);
    } else {
      checkbox.getAttributes().put(ATTR_VALUE, command.getAttributes().get(ATTR_VALUE));
    }
    return checkbox;
  }

  public static int getIntValue(ValueBinding valueBinding) {
    return getAsInt(valueBinding.getValue(FacesContext.getCurrentInstance()));
  }

  private static int getAsInt(Object value) {
    int result;
    if (value instanceof Number) {
      result = ((Number) value).intValue();
    } else if (value instanceof String) {
      result = Integer.parseInt((String) value);
    } else {
      throw new IllegalArgumentException("Can't convert " + value + " to int!");
    }
    return result;
  }


  public static String createPickerId(FacesContext facesContext, UIComponent component, String postfix) {
    //String id = component.getId();
    String id = getComponentId(facesContext, component);
    return id + "_picker" + postfix;
  }

  public static String getComponentId(FacesContext facesContext, UIComponent component) {
    String id = component.getId();
    //if (id == null) {
    // XXX What is this?
    //  id = component.getClientId(facesContext).substring(id.lastIndexOf('_'));
    //}
    return id;
  }

  public static UIComponent provideLabel(FacesContext facesContext, UIComponent component) {
    UIComponent label = component.getFacet(FACET_LABEL);


    if (label == null) {
      final Map attributes = component.getAttributes();
      Object labelText = component.getValueBinding(ATTR_LABEL);
      if (labelText == null) {
        labelText = attributes.get(ATTR_LABEL);
      }

      if (labelText != null) {
        Application application = FacesContext.getCurrentInstance().getApplication();
        label = application.createComponent(UIOutput.COMPONENT_TYPE);
        label.setRendererType(RENDERER_TYPE_LABEL);
        String idprefix = ComponentUtil.getComponentId(facesContext, component);
        label.setId(idprefix + "_" + FACET_LABEL);
        label.setRendered(true);

        if (labelText instanceof ValueBinding) {
          label.setValueBinding(ATTR_VALUE, (ValueBinding) labelText);
        } else {
          label.getAttributes().put(ATTR_VALUE, labelText);
        }

        component.getFacets().put(FACET_LABEL, label);
      }
    }
    return label;
  }

  /*public static void debug(UIComponent component) {
      LOG.error("###############################");
      LOG.error("ID " + component.getId());
      LOG.error("ClassName " + component.getClass().getName());
      if (component instanceof EditableValueHolder) {
        EditableValueHolder editableValueHolder = (EditableValueHolder) component;
        LOG.error("Valid " + editableValueHolder.isValid());
        LOG.error("SubmittedValue " + editableValueHolder.getSubmittedValue());
      }
    for (Iterator it = component.getFacetsAndChildren(); it.hasNext(); ) {
      debug((UIComponent)it.next());
    }
  } */


  public static List<SelectItem> getItemsToRender(javax.faces.component.UISelectOne component) {
    return getItems(component);
  }

  public static List<SelectItem> getItemsToRender(javax.faces.component.UISelectMany component) {
    return getItems(component);
  }

  private static List<SelectItem> getItems(javax.faces.component.UIInput component) {

    List<SelectItem> selectItems = ComponentUtil.getSelectItems(component);

    String renderRange = (String)
        component.getAttributes().get(ATTR_RENDER_RANGE_EXTERN);
    if (renderRange == null) {
      renderRange = (String)
          component.getAttributes().get(ATTR_RENDER_RANGE);
    }
    if (renderRange == null) {
      return selectItems;
    }

    int[] indices = RangeParser.getIndices(renderRange);
    List<SelectItem> items = new ArrayList<SelectItem>(indices.length);

    if (selectItems.size() != 0) {
      for (int indice : indices) {
        items.add(selectItems.get(indice));
      }
    } else {
      LOG.warn("No items found! rendering dummys instead!");
      for (int i = 0; i < indices.length; i++) {
        items.add(new SelectItem(Integer.toString(i), "Item " + i, ""));
      }
    }
    return items;
  }

  public static void setValidator(EditableValueHolder editableValueHolder, String validator) {
    if (validator != null && editableValueHolder.getValidator() == null) {
      if (UIComponentTag.isValueReference(validator)) {
        MethodBinding methodBinding =
            FacesContext.getCurrentInstance().getApplication().createMethodBinding(validator, VALIDATOR_ARGS);
        editableValueHolder.setValidator(methodBinding);
      }
    }
  }

  /**
   * @param component
   * @param converterId
   * @deprecated please use the typesave method {@link #setConverter(javax.faces.component.ValueHolder, String)}
   */
  @Deprecated
  public static void setConverter(UIComponent component, String converterId) {
    if (component instanceof ValueHolder) {
      setConverter((ValueHolder) component, converterId);
    }
  }

  public static void setConverter(ValueHolder valueHolder, String converterId) {
    if (converterId != null && valueHolder.getConverter() == null) {
      final FacesContext facesContext = FacesContext.getCurrentInstance();
      final Application application = facesContext.getApplication();
      if (UIComponentTag.isValueReference(converterId)) {
        ValueBinding valueBinding = application.createValueBinding(converterId);
        if (valueHolder instanceof UIComponent) {
          ((UIComponent) valueHolder).setValueBinding(ATTR_CONVERTER, valueBinding);
        }
      } else {
        Converter converter = application.createConverter(converterId);
        valueHolder.setConverter(converter);
      }
    }
  }

  /**
   * @param component
   * @param type
   * @param action
   * @deprecated please use the typesave method {@link #setAction(javax.faces.component.UICommand, String, String)}
   */
  @Deprecated
  public static void setAction(UIComponent component, String type, String action) {
    if (component instanceof UICommand) {
      setAction((UICommand) component, type, action);
    }
  }

  public static void setAction(UICommand component, String type, String action) {
    String commandType;
    final FacesContext facesContext = FacesContext.getCurrentInstance();
    final Application application = facesContext.getApplication();
    if (type != null && UIComponentTag.isValueReference(type)) {
      commandType = (String) application.createValueBinding(type).getValue(facesContext);
    } else {
      commandType = type;
    }
    if (commandType != null
        && (commandType.equals(COMMAND_TYPE_NAVIGATE)
        || commandType.equals(COMMAND_TYPE_RESET)
        || commandType.equals(COMMAND_TYPE_SCRIPT))) {
      if (commandType.equals(COMMAND_TYPE_NAVIGATE)) {
        setStringProperty(component, ATTR_ACTION_LINK, action);
      } else if (commandType.equals(COMMAND_TYPE_SCRIPT)) {
        setStringProperty(component, ATTR_ACTION_ONCLICK, action);
      } else {
        LOG.warn("Type reset is not supported");
      }
    } else {
      if (action != null) {
        if (UIComponentTag.isValueReference(action)) {
          MethodBinding binding = application.createMethodBinding(action, null);
          component.setAction(binding);
        } else {
          component.setAction(new ConstantMethodBinding(action));
        }
      }
    }

  }

  /**
   * @param component
   * @param suggestMethod
   * @deprecated please use the typesave method {@link #setSuggestMethodBinding(UIInput, String)}
   */
  @Deprecated
  public static void setSuggestMethodBinding(UIComponent component, String suggestMethod) {
    if (component instanceof UIInput) {
      setSuggestMethodBinding((UIInput) component, suggestMethod);
    }
  }

  public static void setSuggestMethodBinding(UIInput component, String suggestMethod) {
    if (suggestMethod != null) {
      if (UIComponentTag.isValueReference(suggestMethod)) {
        final MethodBinding methodBinding = FacesContext.getCurrentInstance().getApplication()
            .createMethodBinding(suggestMethod, new Class[]{String.class});
        component.setSuggestMethod(methodBinding);
      } else {
        throw new IllegalArgumentException(
            "Must be a valueReference (suggestMethod): " + suggestMethod);
      }
    }
  }

  public static void setActionListener(ActionSource command, String actionListener) {
    final FacesContext facesContext = FacesContext.getCurrentInstance();
    final Application application = facesContext.getApplication();
    if (actionListener != null) {
      if (UIComponentTag.isValueReference(actionListener)) {
        MethodBinding binding
            = application.createMethodBinding(actionListener, ACTION_LISTENER_ARGS);
        command.setActionListener(binding);
      } else {
        throw new IllegalArgumentException(
            "Must be a valueReference (actionListener): " + actionListener);
      }
    }
  }

  public static void setValueChangeListener(EditableValueHolder valueHolder, String valueChangeListener) {
    final FacesContext facesContext = FacesContext.getCurrentInstance();
    final Application application = facesContext.getApplication();
    if (valueChangeListener != null) {
      if (UIComponentTag.isValueReference(valueChangeListener)) {
        MethodBinding binding
            = application.createMethodBinding(valueChangeListener, VALUE_CHANGE_LISTENER_ARGS);
        valueHolder.setValueChangeListener(binding);
      } else {
        throw new IllegalArgumentException(
            "Must be a valueReference (valueChangeListener): " + valueChangeListener);
      }
    }
  }


  public static void setSortActionListener(UIData data, String actionListener) {
    final FacesContext facesContext = FacesContext.getCurrentInstance();
    final Application application = facesContext.getApplication();
    if (actionListener != null) {
      if (UIComponentTag.isValueReference(actionListener)) {
        MethodBinding binding = application.createMethodBinding(
            actionListener, ACTION_LISTENER_ARGS);
        data.setSortActionListener(binding);
      } else {
        throw new IllegalArgumentException(
            "Must be a valueReference (sortActionListener): " + actionListener);
      }
    }
  }

  public static void setValueBinding(UIComponent component, String name, String state) {
    // TODO: check, if it is an writeable object
    if (state != null && UIComponentTag.isValueReference(state)) {
      ValueBinding valueBinding = createValueBinding(state);
      component.setValueBinding(name, valueBinding);
    }
  }

  public static void setStateChangeListener(UIData data, String stateChangeListener) {
    final FacesContext facesContext = FacesContext.getCurrentInstance();
    final Application application = facesContext.getApplication();

    if (stateChangeListener != null) {
      if (UIComponentTag.isValueReference(stateChangeListener)) {
        Class[] arguments = {SheetStateChangeEvent.class};
        MethodBinding binding
            = application.createMethodBinding(stateChangeListener, arguments);
        data.setStateChangeListener(binding);
      } else {
        throw new IllegalArgumentException(
            "Must be a valueReference (actionListener): " + stateChangeListener);
      }
    }
  }


  public static String[] getMarkupBinding(FacesContext facesContext, SupportsMarkup component) {
    ValueBinding vb = ((UIComponent) component).getValueBinding(ATTR_MARKUP);
    if (vb != null) {
      Object markups = vb.getValue(facesContext);
      if (markups instanceof String[]) {
        return (String[]) markups;
      } else if (markups instanceof String) {
        String[] strings = StringUtils.split((String) markups, ", ");
        List<String> result = new ArrayList<String>(strings.length);
        for (String string : strings) {
          if (string.trim().length() != 0) {
            result.add(string.trim());
          }
        }
        return result.toArray(new String[result.size()]);
      } else if (markups == null) {
        return new String[0];
      } else {
        return new String[]{markups.toString()};
      }
    }

    return new String[0];
  }

  /**
   * colonCount == 0: fully relative
   * colonCount == 1: absolute (still normal findComponent syntax)
   * colonCount > 1: for each extra colon after 1, go up a naming container
   * (to the view root, if naming containers run out)
   */

  public static UIComponent findComponent(UIComponent from, String relativeId) {
    int idLength = relativeId.length();
    // Figure out how many colons
    int colonCount = 0;
    while (colonCount < idLength) {
      if (relativeId.charAt(colonCount) != NamingContainer.SEPARATOR_CHAR) {
        break;
      }
      colonCount++;
    }

    // colonCount == 0: fully relative
    // colonCount == 1: absolute (still normal findComponent syntax)
    // colonCount > 1: for each extra colon after 1, go up a naming container
    // (to the view root, if naming containers run out)
    if (colonCount > 1) {
      relativeId = relativeId.substring(colonCount);
      for (int j = 1; j < colonCount; j++) {
        while (from.getParent() != null) {
          from = from.getParent();
          if (from instanceof NamingContainer) {
            break;
          }
        }
      }
    }
    return from.findComponent(relativeId);
  }

  public static void invokeOnComponent(FacesContext facesContext, String clientId, UIComponent component,
      Callback callback) {
    List<UIComponent> list = new ArrayList<UIComponent>();
    while (component != null) {
      list.add(component);
      component = component.getParent();
    }
    Collections.reverse(list);
    invokeOrPrepare(facesContext, list, clientId, callback);
    facesContext.getExternalContext().getRequestMap().remove(TobagoConstants.ATTR_ZINDEX);
  }

  private static void invokeOrPrepare(FacesContext facesContext, List<UIComponent> list, String clientId,
      Callback callback) {
    if (list.size() == 1) {
      callback.execute(facesContext, list.get(0));
    } else if (list.get(0) instanceof UIData) {
      prepareOnUIData(facesContext, list, clientId, callback);
    } else if (list.get(0) instanceof UIForm) {
      prepareOnUIForm(facesContext, list, clientId, callback);
    } else if (list.get(0) instanceof UIPopup) {
      prepareOnUIPopup(facesContext, list, clientId, callback);
    } else {
      prepareOnUIComponent(facesContext, list, clientId, callback);
    }
  }

  @SuppressWarnings(value = "unchecked")
  private static void prepareOnUIForm(FacesContext facesContext, List<UIComponent> list, String clientId,
      Callback callback) {
    UIComponent currentComponent = list.remove(0);
    if (!(currentComponent instanceof UIForm)) {
      throw new IllegalStateException(currentComponent.getClass().getName());
    }
    // TODO is this needed?
    if (callback instanceof TobagoCallback) {
      if (PhaseId.APPLY_REQUEST_VALUES.equals(((TobagoCallback) callback).getPhaseId())) {
        currentComponent.decode(facesContext);
      }
    }
    UIForm uiForm = (UIForm) currentComponent;
    facesContext.getExternalContext().getRequestMap().put(UIForm.SUBMITTED_MARKER, uiForm.isSubmitted());
    invokeOrPrepare(facesContext, list, clientId, callback);

  }

  private static void prepareOnUIComponent(
      FacesContext facesContext, List<UIComponent> list, String clientId, Callback callback) {
    list.remove(0);
    invokeOrPrepare(facesContext, list, clientId, callback);
  }

  private static void prepareOnUIPopup(
      FacesContext facesContext, List<UIComponent> list, String clientId, Callback callback) {
    if (callback instanceof TobagoCallback
        && PhaseId.RENDER_RESPONSE.equals(((TobagoCallback) callback).getPhaseId())) {
      Integer zIndex = (Integer) facesContext.getExternalContext().getRequestMap().get(TobagoConstants.ATTR_ZINDEX);
      if (zIndex == null) {
        zIndex = 0;
      } else {
        zIndex += 10;
      }
      facesContext.getExternalContext().getRequestMap().put(TobagoConstants.ATTR_ZINDEX, zIndex);
    }
    list.remove(0);
    invokeOrPrepare(facesContext, list, clientId, callback);
  }

  private static void prepareOnUIData(FacesContext facesContext, List<UIComponent> list, String clientId,
      Callback callback) {
    UIComponent currentComponent = list.remove(0);
    if (!(currentComponent instanceof UIData)) {
      throw new IllegalStateException(currentComponent.getClass().getName());
    }

    // we may need setRowIndex on UIData
    javax.faces.component.UIData uiData = (javax.faces.component.UIData) currentComponent;
    int oldRowIndex = uiData.getRowIndex();
    String sheetId = uiData.getClientId(facesContext);
    String idRemainder = clientId.substring(sheetId.length());
    if (LOG.isInfoEnabled()) {
      LOG.info("idRemainder = \"" + idRemainder + "\"");
    }
    if (idRemainder.startsWith(String.valueOf(NamingContainer.SEPARATOR_CHAR))) {
      idRemainder = idRemainder.substring(1);
      int idx = idRemainder.indexOf(NamingContainer.SEPARATOR_CHAR);
      if (idx > 0) {
        String firstPart = idRemainder.substring(0, idx);
        if (NumberUtils.isDigits(firstPart)) {
          try {
            int rowIndex = Integer.parseInt(firstPart);
            if (LOG.isInfoEnabled()) {
              LOG.info("set rowIndex = \"" + rowIndex + "\"");
            }
            uiData.setRowIndex(rowIndex);
          } catch (NumberFormatException e) {
            LOG.error("idRemainder = \"" + idRemainder + "\"", e);
          }
        }
      }
    } else {
      if (LOG.isInfoEnabled()) {
        LOG.info("no match for \"^:\\d+:.*\"");
      }
    }

    invokeOrPrepare(facesContext, list, clientId, callback);

    // we should reset rowIndex on UIData
    uiData.setRowIndex(oldRowIndex);
  }

  public static Object getConvertedValue(
      FacesContext facesContext, javax.faces.component.UIInput component, String stringValue) {
    try {
      Renderer renderer = getRenderer(facesContext, component);
      if (renderer != null) {
        if (component instanceof UISelectMany) {
          final Object converted = renderer.getConvertedValue(facesContext, component, new String[]{stringValue});
          return ((Object[]) converted)[0];
        } else {
          return renderer.getConvertedValue(facesContext, component, stringValue);
        }
      } else {
        Converter converter = component.getConverter();
        if (converter == null) {
          //Try to find out by value binding
          ValueBinding vb = component.getValueBinding("value");
          if (vb != null) {
            Class valueType = vb.getType(facesContext);
            if (valueType != null) {
              converter = facesContext.getApplication().createConverter(valueType);
            }
          }
        }
        if (converter != null) {
          converter.getAsObject(facesContext, component, stringValue);
        }
      }
    } catch (Exception e) {
      LOG.warn("Can't convert string value '" + stringValue + "'", e);
    }
    return stringValue;
  }
}
