/FileExtraction/Java_unseen/dlitz_resin/modules/jsf/src/javax/faces/component/UIData.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package javax.faces.component;

import java.util.*;
import java.sql.ResultSet;

import javax.el.*;

import javax.faces.context.*;
import javax.faces.event.*;
import javax.faces.model.*;
import javax.faces.FacesException;

import javax.servlet.jsp.jstl.sql.Result;

public class UIData extends UIComponentBase
  implements NamingContainer
{
  public static final String COMPONENT_FAMILY = "javax.faces.Data";
  public static final String COMPONENT_TYPE = "javax.faces.Data";

  private static final HashMap<String,PropEnum> _propMap
    = new HashMap<String,PropEnum>();

  private static final Object[] NULL_ARRAY = new Object[0];

  private DataModel _dataModel;
  private Object _dataModelValue;

  private Integer _first;
  private ValueExpression _firstExpr;

  private Integer _rows;
  private ValueExpression _rowsExpr;

  private Object _value;
  private ValueExpression _valueExpr;

  private String _var;
  private int _rowIndex = -1;

  private ArrayList<ArrayList<State>> _state;

  public UIData()
  {
    setRendererType("javax.faces.Table");
  }

  /**
   * Returns the component family, used to select the renderer.
   */
  public String getFamily()
  {
    return COMPONENT_FAMILY;
  }

  //
  // Properties
  //

  public int getFirst()
  {
    if (_first != null)
      return _first;
    else if (_firstExpr != null)
      return Util.evalInt(_firstExpr, getFacesContext());
    else
      return 0;
  }

  public void setFirst(int first)
  {
    if (first < 0)
      throw new IllegalArgumentException("UIData.setFirst must have a positive value at '" + first + "'");
    _first = first;
  }

  public int getRows()
  {
    int rows;
    
    if (_rows != null)
      rows = _rows;
    else if (_rowsExpr != null)
      rows = Util.evalInt(_rowsExpr, getFacesContext());
    else
      rows = 0;

     return rows;
  }

  public void setRows(int rows)
  {
    if (rows < 0)
      throw new IllegalArgumentException("UIData.setFirst must have a positive value at '" + rows + "'");
    _rows = rows;
  }

  public String getVar()
  {
    return _var;
  }

  public void setVar(String var)
  {
    _var = var;
  }

  public Object getValue()
  {
    Object value;
    
    if (_value != null)
      value = _value;
    else if (_valueExpr != null)
      value = Util.eval(_valueExpr, getFacesContext());
    else
      value = null;
    
    return value;
  }

  public void setValue(Object value)
  {
    _value = value;
    setDataModel(null);
    
  }

  protected DataModel getDataModel()
  {
    if (_dataModel != null)
      return _dataModel;

    _dataModel = createDataModel(getValue());
    
    return _dataModel;
  }

  protected void setDataModel(DataModel dataModel)
  {
    _dataModel = dataModel;
    _state = null;
  }

  private void resetDataModel()
  {
    Object value = getValue();

    if (value != _dataModelValue)
      setDataModel(null);
  }

  private DataModel createDataModel(Object value)
  {
    _dataModelValue = value;
    
    if (value == null)
      return new ArrayDataModel(NULL_ARRAY);
    else if (value instanceof DataModel)
      return (DataModel) value;
    else if (value instanceof List)
      return new ListDataModel((List) value);
    else if (value instanceof ResultSet)
      return new ResultSetDataModel((ResultSet) value);
    else if (value instanceof Result)
      return new ResultDataModel((Result) value);
    else if (value.getClass().isArray())
      return new ArrayDataModel((Object []) value);
    else
      return new ScalarDataModel(value);
  }

  public int getRowIndex()
  {
    return _rowIndex;
  }

  public Object getRowData()
  {
    return getDataModel().getRowData();
  }

  public void setRowIndex(int value)
  {
    if (value < -1)
      throw new IllegalArgumentException("UIData.setRowIndex must not be less than -1 at '" + value + "'");
    
    DataModel dataModel = getDataModel();

    int oldRow = _rowIndex;
    
    _rowIndex = value;

    dataModel.setRowIndex(value);

    if (dataModel.isRowAvailable())
      setRowIndexState(dataModel, oldRow, _rowIndex);
    else
      setRowIndexState(dataModel, oldRow, -1);

    if (_var == null) {
    }
    else if (value >= 0 && dataModel.isRowAvailable()) {
      Object rowData = dataModel.getRowData();
      
      FacesContext context = FacesContext.getCurrentInstance();
      
      context.getExternalContext().getRequestMap().put(_var, rowData);
    }
    else {
      FacesContext context = FacesContext.getCurrentInstance();

      context.getExternalContext().getRequestMap().remove(_var);
    }
  }

  private void setRowIndexState(DataModel model, int oldRow, int newRow)
  {
    if (_state == null)
      _state = new ArrayList<ArrayList<State>>();
    
    setRowIndexState(this, oldRow, newRow, false, 0);
  }

  private int setRowIndexState(UIComponent comp,
                               int oldRow,
                               int newRow,
                               boolean isTransient,
                               int valueIndex)
  {
    //skip self
    if (comp != this)
      comp.setId(comp.getId());

    if (comp.isTransient())
      isTransient = true;
    else if (comp instanceof EditableValueHolder) {
      EditableValueHolder holder = (EditableValueHolder) comp;
      
      if (oldRow >= 0) {
        ArrayList<State> oldList;

        while (_state.size() <= oldRow)
          _state.add(null);

        oldList = _state.get(oldRow);

        if (oldList == null) {
          oldList = new ArrayList<State>();

          _state.set(oldRow, oldList);
        }

        while (oldList.size() < (valueIndex + 1))
          oldList.add(null);

        State state = oldList.get(valueIndex);

        if (state != null)
          state = state.update(holder);
        else
          state = new State(holder);

        oldList.set(valueIndex, state);
      }

      ArrayList<State> newList = null;

      if (newRow >= 0 && newRow < _state.size())
        newList = _state.get(newRow);

      State state;
      
      if (newList != null && valueIndex < newList.size())
        state = newList.get(valueIndex);
      else
        state = null;

      if (state != null)
        state.restore(holder);
      else {
        holder.setSubmittedValue(null);
        holder.setValue(null);
        holder.setLocalValueSet(false);
        holder.setValid(true);
      }

      valueIndex += 1;
    }
    
    if (comp instanceof UIComponentBase) {
      UIComponentBase base = (UIComponentBase) comp;
      
      for (UIComponent child : base.getFacetsAndChildrenArray()) {
        valueIndex = setRowIndexState(child, oldRow, newRow,
                                      isTransient, valueIndex);
      }
    }
    else {
      Iterator<UIComponent> iter = comp.getFacetsAndChildren();

      while (iter.hasNext()) {
        UIComponent child = iter.next();

        valueIndex = setRowIndexState(child, oldRow, newRow,
                                      isTransient, valueIndex);
      }
    }

    return valueIndex;
  }

  public int getRowCount()
  {
    DataModel model = getDataModel();

    if (model != null)
      return model.getRowCount();
    else
      return -1;
  }

  public boolean isRowAvailable()
  {
    DataModel model = getDataModel();

    if (model != null)
      return model.isRowAvailable();
    else
      return false;
  }

  /**
   * Returns the value expression with the given name.
   */
  @Override
  public ValueExpression getValueExpression(String name)
  {
    PropEnum prop = _propMap.get(name);

    if (prop != null) {
      switch (_propMap.get(name)) {
      case VALUE:
        return _valueExpr;
      
      case FIRST:
        return _firstExpr;
      
      case ROWS:
        return _rowsExpr;
      }
    }

    return super.getValueExpression(name);
  }

  /**
   * Sets the value expression with the given name.
   */
  @Override
  public void setValueExpression(String name, ValueExpression expr)
  {
    PropEnum prop = _propMap.get(name);

    if (prop != null) {
      switch (_propMap.get(name)) {
      case VALUE:
        _dataModel = null;
        if (expr != null && expr.isLiteralText()) {
          _value = expr.getValue(null);
          return;
        }
        else
          _valueExpr = expr;
        break;
      
      case FIRST:
        if (expr != null && expr.isLiteralText()) {
          _first = (Integer) expr.getValue(null);
          return;
        }
        else
          _firstExpr = expr;
        break;
      
      case ROWS:
        if (expr != null && expr.isLiteralText()) {
          _rows = (Integer) expr.getValue(null);
          return;
        }
        else
          _rowsExpr = expr;
        break;
      }
    }

    super.setValueExpression(name, expr);
  }

  //
  // Facets
  //

  public UIComponent getHeader()
  {
    return getFacet("header");
  }

  public void setHeader(UIComponent header)
  {
    getFacets().put("header", header);
  }

  public UIComponent getFooter()
  {
    return getFacet("footer");
  }

  public void setFooter(UIComponent footer)
  {
    getFacets().put("footer", footer);
  }

  @Override
  public boolean invokeOnComponent(FacesContext context,
                                   String clientId,
                                   ContextCallback callback)
    throws FacesException
  {
    if (context == null || clientId == null || callback == null)
      throw new NullPointerException();


    if (clientId.equals(this.getClientId(context))) {
      try {
        callback.invokeContextCallback(context, this);

        return true;
      }
      catch (Exception e) {
        throw new FacesException(e);
      }
    }

    String head = getClientId(context) + SEPARATOR_CHAR;

    int oldIdx = getRowIndex();

    String tail = Character.toString(SEPARATOR_CHAR) + oldIdx + SEPARATOR_CHAR; 

    if (head.endsWith(tail))
      head = head.substring(0, head.length() - tail.length() + 1);

    if (! clientId.startsWith(head))
      return false;

    int separatorIdx = clientId.indexOf(SEPARATOR_CHAR, head.length());

    int newIdx;

    try {
      newIdx = Integer.parseInt(clientId.substring(head.length(), separatorIdx));
    }
    catch (Exception e) {
      throw new FacesException("clientId '" +
                               clientId +
                               "' is expected to contain a positive integer at position '" +
                               head.length() +
                               "'");
    } 

    try {
      setRowIndex(newIdx);

      if (! isRowAvailable())
        return false;

      for (Iterator<UIComponent> it = getFacetsAndChildren(); it.hasNext();) {
        if (it.next().invokeOnComponent(context, clientId, callback))
          return true;
      }
    } catch (Exception e) {
      throw new FacesException(e);
    } finally {
      this.setRowIndex(oldIdx);
    }

    return false;
  }

  //
  // overrides

  /**
   * Returns the client-specific id for the component.
   */
  @Override
  public String getClientId(FacesContext context)
  {
    String clientId = super.getClientId(context);

    int rowIndex = getRowIndex();

    if (rowIndex < 0)
      return clientId;
    else
      return clientId + SEPARATOR_CHAR + rowIndex;
  }

  /**
   * Queues the event, wrapping the rowIndex.
   */
  @Override
  public void queueEvent(FacesEvent event)
  {
    int rowIndex = getRowIndex();

    super.queueEvent(new UIDataEventWrapper(event, this, rowIndex));
  }

  /**
   * Broadcasts the event, unwrapping the rowIndex.
   */
  @Override
  public void broadcast(FacesEvent event)
    throws AbortProcessingException
  {
    if (event instanceof UIDataEventWrapper) {
      UIDataEventWrapper wrapper = (UIDataEventWrapper) event;

      event = wrapper.getEvent();
      
      int oldIndex = getRowIndex();
      setRowIndex(wrapper.getRowIndex());

      event.getComponent().broadcast(event);
      
      setRowIndex(oldIndex);
    }
    else
      super.broadcast(event);
  }

  /**
   * Recursively calls the decodes for any children, then calls
   * decode().
   */
  @Override
  public void processDecodes(FacesContext context)
  {
    if (context == null)
      throw new NullPointerException();

    if (! isRendered())
      return;

    setRowIndex(-1);

    if (getFacetCount() > 0) {
      for (UIComponent facet : getFacets().values()) {
        facet.processDecodes(context);
      }
    }

    int childCount = getChildCount();

    if (childCount > 0) {
      List<UIComponent> children = getChildren();

      for (int i = 0; i < children.size(); i++) {
        UIComponent child = children.get(i);

        if (! child.isRendered() && child.getFacetCount() == 0)
          continue;

        for (UIComponent facet : child.getFacets().values()) {
          facet.processDecodes(context);
        }
      }
      
      int first = getFirst();
      int rows = getRows();

      if (rows <= 0)
        rows = Integer.MAX_VALUE;
      
      for (int i = 0; i < rows; i++) {
        setRowIndex(first + i);

        if (! isRowAvailable())
          break;

        for (int j = 0; j < childCount; j++) {
          UIComponent child = children.get(j);

          if (! child.isRendered())
            continue;

          int grandchildCount = child.getChildCount();

          if (grandchildCount > 0) {
            List<UIComponent> grandchildren = child.getChildren();

            for (int k = 0; k < grandchildCount; k++) {
              grandchildren.get(k).processDecodes(context);
            }
          }

          child.decode(context);
        }
      }
    }

    setRowIndex(-1);
    
    decode(context);
  }

  /**
   * Recursively calls the validators for any children, then calls
   * decode().
   */
  @Override
  public void processValidators(FacesContext context)
  {
    if (context == null)
      throw new NullPointerException();

    if (! isRendered())
      return;

    setRowIndex(-1);

    if (getFacetCount() > 0) {
      for (UIComponent facet : getFacets().values()) {
        facet.processValidators(context);
      }
    }

    int childCount = getChildCount();

    if (childCount > 0) {
      List<UIComponent> children = getChildren();

      for (int i = 0; i < children.size(); i++) {
        UIComponent child = children.get(i);

        if (! child.isRendered() && child.getFacetCount() == 0)
          continue;

        for (UIComponent facet : child.getFacets().values()) {
          facet.processValidators(context);
        }
      }
      
      int first = getFirst();
      int rows = getRows();

      if (rows <= 0)
        rows = Integer.MAX_VALUE;

      for (int i = 0; i < rows; i++) {
        setRowIndex(first + i);

        if (! isRowAvailable())
          break;

        for (int j = 0; j < childCount; j++) {
          UIComponent child = children.get(j);

          if (! child.isRendered())
            continue;

          int grandchildCount = child.getChildCount();
          List<UIComponent> grandchildren = child.getChildren();

          for (int k = 0; k < grandchildCount; k++) {
            grandchildren.get(k).processValidators(context);
          }
        }
      }
    }

    setRowIndex(-1);
  }

  /**
   * Recursively calls the updates for any children, then calls
   * decode().
   */
  @Override
  public void processUpdates(FacesContext context)
  {
    if (context == null)
      throw new NullPointerException();

    if (! isRendered())
      return;

    setRowIndex(-1);

    if (getFacetCount() > 0) {
      for (UIComponent facet : getFacets().values()) {
        facet.processUpdates(context);
      }
    }

    int childCount = getChildCount();

    if (childCount > 0) {
      List<UIComponent> children = getChildren();

      for (int i = 0; i < children.size(); i++) {
        UIComponent child = children.get(i);

        if (! child.isRendered() && child.getFacetCount() == 0)
          continue;

        for (UIComponent facet : child.getFacets().values()) {
          facet.processUpdates(context);
        }
      }
      
      int first = getFirst();
      int rows = getRows();

      if (rows <= 0)
        rows = Integer.MAX_VALUE;

      for (int i = 0; i < rows; i++) {
        setRowIndex(first + i);

        if (! isRowAvailable())
          break;

        for (int j = 0; j < childCount; j++) {
          UIComponent child = children.get(j);

          if (! child.isRendered())
            continue;

          int grandchildCount = child.getChildCount();
          List<UIComponent> grandchildren = child.getChildren();

          for (int k = 0; k < grandchildCount; k++) {
            grandchildren.get(k).processUpdates(context);
          }
        }
      }
    }

    setRowIndex(-1);
  }

  /**
   * Recursively calls the encodes for any children, then calls
   * decode().
   */
  @Override
  public void encodeBegin(FacesContext context)
    throws java.io.IOException
  {
    if (context == null)
      throw new NullPointerException();

    if (! isRendered())
      return;

    resetDataModel();

    if (! context.getRenderResponse() || ! context.getResponseComplete())
      _state = null;

    super.encodeBegin(context);
  }

  //
  // state
  //

  public Object saveState(FacesContext context)
  {
    return new Object[] {
      super.saveState(context),

      _value,
      _first,
      _rows,
      _var,
    };
  }

  public void restoreState(FacesContext context, Object value)
  {
    Object []state = (Object []) value;
    
    super.restoreState(context, state[0]);

    _value = state[1];
    _first = (Integer) state[2];
    _rows = (Integer) state[3];
    _var = (String) state[4];
  }

  //
  // inner classes
  //

  static class UIDataEventWrapper extends FacesEvent
  {
    private FacesEvent _event;
    private int _rowIndex;

    UIDataEventWrapper(FacesEvent event, UIData component, int rowIndex)
    {
      super(component);

      _event = event;
      _rowIndex = rowIndex;
    }

    FacesEvent getEvent()
    {
      return _event;
    }

    int getRowIndex()
    {
      return _rowIndex;
    }

    public void setPhaseId(PhaseId phaseId)
    {
      _event.setPhaseId(phaseId);
    }

    public PhaseId getPhaseId()
    {
      return _event.getPhaseId();
    }

    public boolean isAppropriateListener(FacesListener listener)
    {
      return _event.isAppropriateListener(listener);
    }

    public void processListener(FacesListener listener)
      throws AbortProcessingException
    {
      ((UIData) getComponent()).setRowIndex(_rowIndex);

      _event.processListener(listener);
    }
  }

  static class State implements java.io.Serializable
  {
    private final Object _submittedValue;
    private final Object _value;
    private final boolean _isLocal;
    private final boolean _isValid;

    State()
    {
      _submittedValue = null;
      _value = null;
      _isLocal = false;
      _isValid = false;
    }
      
    State(EditableValueHolder holder)
    {
      _submittedValue = holder.getSubmittedValue();
      _value = holder.getValue();
      _isLocal = holder.isLocalValueSet();
      _isValid = holder.isValid();
    }

    State update(EditableValueHolder holder)
    {
      if (_submittedValue == holder.getSubmittedValue()
          && _value == holder.getValue()
          && _isLocal == holder.isLocalValueSet()
          && _isValid == holder.isValid())
        return this;
      else
        return new State(holder);
    }

    void restore(EditableValueHolder holder)
    {
      holder.setSubmittedValue(_submittedValue);
      holder.setValue(_value);
      holder.setLocalValueSet(_isLocal);
      holder.setValid(_isValid);
    }
  }

  //
  // private helpers
  //

  private static enum PropEnum {
    VALUE,
    FIRST,
    ROWS,
  }

  static {
    _propMap.put("value", PropEnum.VALUE);
    _propMap.put("first", PropEnum.FIRST);
    _propMap.put("rows", PropEnum.ROWS);
  }
}
