/* * 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); } }