All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jakarta.faces.component.UIData Maven / Gradle / Ivy

There is a newer version: 4.1.0-RC3
Show newest version
/*
 * 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 jakarta.faces.component;

import org.apache.myfaces.core.api.shared.lang.ClassUtils;
import org.apache.myfaces.core.api.shared.ComponentUtils;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import jakarta.el.ValueExpression;
import jakarta.faces.FacesException;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.application.StateManager;
import jakarta.faces.component.visit.VisitCallback;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitHint;
import jakarta.faces.component.visit.VisitResult;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.FacesEvent;
import jakarta.faces.event.FacesListener;
import jakarta.faces.event.PhaseId;
import jakarta.faces.event.PostValidateEvent;
import jakarta.faces.event.PreValidateEvent;
import jakarta.faces.model.ArrayDataModel;
import jakarta.faces.model.CollectionDataModel;
import jakarta.faces.model.DataModel;
import jakarta.faces.model.IterableDataModel;
import jakarta.faces.model.ListDataModel;
import jakarta.faces.model.ResultSetDataModel;
import jakarta.faces.model.ScalarDataModel;
import java.util.regex.Pattern;

import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFacet;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
import org.apache.myfaces.core.api.shared.EditableValueHolderState;
import org.apache.myfaces.core.api.shared.lang.Assert;

/**
 * Represents an abstraction of a component which has multiple "rows" of data.
 * 

* The children of this component are expected to be UIColumn components. *

* Note that the same set of child components are reused to implement each row of the table in turn during such phases * as apply-request-values and render-response. Altering any of the members of these components therefore affects the * attribute for every row, except for the following members: *

    *
  • submittedValue *
  • value (where no EL binding is used) *
  • valid *
*

* This reuse of the child components also means that it is not possible to save a reference to a component during table * processing, then access it later and expect it to still represent the same row of the table. *

* Implementation Notes

*

* Each of the UIColumn children of this component has a few component children of its own to render the contents of the * table cell. However there can be a very large number of rows in a table, so it isn't efficient for the UIColumn and * all its child objects to be duplicated for each row in the table. Instead the "flyweight" pattern is used where a * serialized state is held for each row. When setRowIndex is invoked, the UIColumn objects and their children serialize * their current state then reinitialise themselves from the appropriate saved state. This allows a single set of real * objects to represent multiple objects which have the same types but potentially different internal state. When a row * is selected for the first time, its state is set to a clean "initial" state. Transient components (including any * read-only component) do not save their state; they are just reinitialised as required. The state saved/restored when * changing rows is not the complete component state, just the fields that are expected to vary between rows: * "submittedValue", "value", "isValid". *

*

* Note that a table is a "naming container", so that components within the table have their ids prefixed with the id of * the table. Actually, when setRowIndex has been called on a table with id of "zzz" the table pretends to its children * that its ID is "zzz_n" where n is the row index. This means that renderers for child components which call * component.getClientId automatically get ids of form "zzz_n:childId" thus ensuring that components in different rows * of the table get different ids. *

*

* When decoding a submitted page, this class iterates over all its possible rowIndex values, restoring the appropriate * serialized row state then calling processDecodes on the child components. Because the child components (or their * renderers) use getClientId to get the request key to look for parameter data, and because this object pretends to * have a different id per row ("zzz_n") a single child component can decode data from each table row in turn without * being aware that it is within a table. The table's data model is updated before each call to child.processDecodes, so * the child decode method can assume that the data model's rowData points to the model object associated with the row * currently being decoded. Exactly the same process applies for the later validation and updateModel phases. *

*

* When the data model for the table is bound to a backing bean property, and no validation errors have occured during * processing of a postback, the data model is refetched at the start of the rendering phase (ie after the update model * phase) so that the contents of the data model can be changed as a result of the latest form submission. Because the * saved row state must correspond to the elements within the data model, the row state must be discarded whenever a new * data model is fetched; not doing this would cause all sorts of inconsistency issues. This does imply that changing * the state of any of the members "submittedValue", "value" or "valid" of a component within the table during the * invokeApplication phase has no effect on the rendering of the table. When a validation error has occurred, a new * DataModel is not fetched, and the saved state of the child components is not discarded. *

* see Javadoc of the Faces Specification * for more information. */ @JSFComponent(defaultRendererType = "jakarta.faces.Table") public class UIData extends UIComponentBase implements NamingContainer, UniqueIdVendor { public static final String COMPONENT_FAMILY = "jakarta.faces.Data"; public static final String COMPONENT_TYPE = "jakarta.faces.Data"; // for unit tests private static final String SUB_ID_PATTERN = "oam.UIData.SUB_ID_PATTERN"; private static final String FACES_DATA_MODEL_MANAGER_CLASS_NAME = "org.apache.myfaces.cdi.model.FacesDataModelManager"; private static final Class FACES_DATA_MODEL_MANAGER_CLASS; private static final Method FACES_DATA_MODEL_MANAGER_CREATE_DATAMODEL_METHOD; static { Class dataModelBuilderClass = null; Method createDataModelMethod = null; try { dataModelBuilderClass = ClassUtils.classForName(FACES_DATA_MODEL_MANAGER_CLASS_NAME); if (dataModelBuilderClass != null) { createDataModelMethod = dataModelBuilderClass.getMethod("createDataModel", FacesContext.class, Class.class, Object.class); } } catch(Exception e) { //No Op } FACES_DATA_MODEL_MANAGER_CLASS = dataModelBuilderClass; FACES_DATA_MODEL_MANAGER_CREATE_DATAMODEL_METHOD = createDataModelMethod; } private static final String FOOTER_FACET_NAME = "footer"; private static final String HEADER_FACET_NAME = "header"; private static final Class OBJECT_ARRAY_CLASS = Object[].class; private static final int PROCESS_DECODES = 1; private static final int PROCESS_VALIDATORS = 2; private static final int PROCESS_UPDATES = 3; private static final Object[] LEAF_NO_STATE = new Object[]{null,null}; private int _rowIndex = -1; // Holds for each row the states of the child components of this UIData. // Note that only "partial" component state is saved: the component fields // that are expected to vary between rows. private Map _rowStates = new HashMap<>(); private Map> _rowDeltaStates = new HashMap<>(); private Map> _rowTransientStates = new HashMap<>(); /** * Handle case where this table is nested inside another table. See method getDataModel for more details. *

* Key: parentClientId (aka rowId when nested within a parent table) Value: DataModel */ private Map _dataModelMap = new HashMap<>(3, 1f); // will be set to false if the data should not be refreshed at the beginning of the encode phase private boolean _isValidChilds = true; private Object _initialDescendantComponentState = null; private Object _initialDescendantFullComponentState = null; private static class FacesEventWrapper extends FacesEvent { private static final long serialVersionUID = 6648047974065628773L; private FacesEvent _wrappedFacesEvent; private int _rowIndex; public FacesEventWrapper(FacesEvent facesEvent, int rowIndex, UIData redirectComponent) { super(redirectComponent); _wrappedFacesEvent = facesEvent; _rowIndex = rowIndex; } @Override public PhaseId getPhaseId() { return _wrappedFacesEvent.getPhaseId(); } @Override public void setPhaseId(PhaseId phaseId) { _wrappedFacesEvent.setPhaseId(phaseId); } @Override public void queue() { _wrappedFacesEvent.queue(); } @Override public String toString() { return _wrappedFacesEvent.toString(); } @Override public boolean isAppropriateListener(FacesListener faceslistener) { return _wrappedFacesEvent.isAppropriateListener(faceslistener); } @Override public void processListener(FacesListener faceslistener) { _wrappedFacesEvent.processListener(faceslistener); } public FacesEvent getWrappedFacesEvent() { return _wrappedFacesEvent; } public int getRowIndex() { return _rowIndex; } } private static final DataModel EMPTY_DATA_MODEL = new DataModel() { @Override public boolean isRowAvailable() { return false; } @Override public int getRowCount() { return 0; } @Override public Object getRowData() { throw new IllegalArgumentException(); } @Override public int getRowIndex() { return -1; } @Override public void setRowIndex(int i) { if (i < -1) { throw new IllegalArgumentException(); } } @Override public Object getWrappedData() { return null; } @Override public void setWrappedData(Object obj) { if (obj == null) { return; // Clearing is allowed } throw new UnsupportedOperationException(this.getClass().getName() + " UnsupportedOperationException"); } }; /** * Construct an instance of the UIData. */ public UIData() { setRendererType("jakarta.faces.Table"); } @Override public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException { Assert.notNull(context, "context"); Assert.notNull(clientId, "clientId"); Assert.notNull(callback, "callback"); final String baseClientId = getClientId(context); // searching for this component? boolean returnValue = baseClientId.equals(clientId); boolean isCachedFacesContext = isCachedFacesContext(); if (!isCachedFacesContext) { setCachedFacesContext(context); } pushComponentToEL(context, this); try { if (returnValue) { try { callback.invokeContextCallback(context, this); return true; } catch (Exception e) { throw new FacesException(e); } } // Now Look throught facets on this UIComponent if (this.getFacetCount() > 0) { for (Iterator it = this.getFacets().values().iterator(); !returnValue && it.hasNext();) { returnValue = it.next().invokeOnComponent(context, clientId, callback); } } if (returnValue) { return returnValue; } // is the component an inner component? if (clientId.startsWith(baseClientId)) { // Check if the clientId for the component, which we // are looking for, has a rowIndex attached char separator = context.getNamingContainerSeparatorChar(); Pattern pattern = getSubIdPattern(context, separator); String subId = clientId.substring(baseClientId.length() + 1); //If the char next to baseClientId is the separator one and //the subId matches the regular expression if (clientId.charAt(baseClientId.length()) == separator && pattern.matcher(subId).matches()) { String clientRow = subId.substring(0, subId.indexOf(separator)); //Now we save the current position int oldRow = this.getRowIndex(); // try-finally --> make sure, that the old row index is restored try { //The conversion is safe, because its already checked on the //regular expresion this.setRowIndex(Integer.parseInt(clientRow)); // check, if the row is available if (!isRowAvailable()) { return false; } for (Iterator it1 = getChildren().iterator(); !returnValue && it1.hasNext();) { //recursive call to find the component returnValue = it1.next().invokeOnComponent(context, clientId, callback); } } finally { //Restore the old position. Doing this prevent //side effects. this.setRowIndex(oldRow); } } else { // MYFACES-2370: search the component in the childrens' facets too. // We have to check the childrens' facets here, because in MyFaces // the rowIndex is not attached to the clientId for the children of // facets of the UIColumns. However, in RI the rowIndex is // attached to the clientId of UIColumns' Facets' children. for (Iterator itChildren = this.getChildren().iterator(); !returnValue && itChildren.hasNext();) { UIComponent child = itChildren.next(); if (child instanceof UIColumn && clientId.equals(child.getClientId(context))) { try { callback.invokeContextCallback(context, child); } catch (Exception e) { throw new FacesException(e); } returnValue = true; } // process the child's facets if (child.getFacetCount() > 0) { for (Iterator itChildFacets = child.getFacets().values().iterator(); !returnValue && itChildFacets.hasNext();) { //recursive call to find the component returnValue = itChildFacets.next().invokeOnComponent(context, clientId, callback); } } } } } } finally { //all components must call popComponentFromEl after visiting is finished popComponentFromEL(context); if (!isCachedFacesContext) { setCachedFacesContext(null); } } return returnValue; } private Pattern getSubIdPattern(FacesContext context, char separator) { Pattern pattern = (Pattern) context.getAttributes().get(SUB_ID_PATTERN); if (pattern == null) { pattern = Pattern.compile("[0-9]+" + separator + ".*"); context.getAttributes().put(SUB_ID_PATTERN, pattern); } return pattern; } public void setFooter(UIComponent footer) { getFacets().put(FOOTER_FACET_NAME, footer); } @JSFFacet public UIComponent getFooter() { return getFacets().get(FOOTER_FACET_NAME); } public void setHeader(UIComponent header) { getFacets().put(HEADER_FACET_NAME, header); } @JSFFacet public UIComponent getHeader() { return getFacets().get(HEADER_FACET_NAME); } public boolean isRowAvailable() { return getDataModel().isRowAvailable(); } public int getRowCount() { return getDataModel().getRowCount(); } public Object getRowData() { return getDataModel().getRowData(); } public int getRowIndex() { return _rowIndex; } /** * Set the current row index that methods like getRowData use. *

* Param rowIndex can be -1, meaning "no row". *

* * @param rowIndex */ public void setRowIndex(int rowIndex) { if (isRowStatePreserved()) { setRowIndexPreserveComponentState(rowIndex); } else { setRowIndexWithoutPreserveComponentState(rowIndex); } } private void setRowIndexWithoutPreserveComponentState(int rowIndex) { if (rowIndex < -1) { throw new IllegalArgumentException("rowIndex is less than -1"); } if (_rowIndex == rowIndex) { return; } FacesContext facesContext = getFacesContext(); if (_rowIndex == -1) { if (_initialDescendantComponentState == null) { // Create a template that can be used to initialise any row // that we haven't visited before, ie a "saved state" that can // be pushed to the "restoreState" method of all the child // components to set them up to represent a clean row. _initialDescendantComponentState = saveDescendantComponentStates(this, false, false); } } else { // If no initial component state, there are no EditableValueHolder instances, // and that means there is no state to be saved for the current row, so we can // skip row state saving code safely. if (_initialDescendantComponentState != null) { // We are currently positioned on some row, and are about to // move off it, so save the (partial) state of the components // representing the current row. Later if this row is revisited // then we can restore this state. Collection savedRowState = saveDescendantComponentStates(this, false, false); if (savedRowState != null) { _rowStates.put(getContainerClientId(facesContext), savedRowState); } } } _rowIndex = rowIndex; DataModel dataModel = getDataModel(); dataModel.setRowIndex(rowIndex); String var = (String) getStateHelper().get(PropertyKeys.var); if (rowIndex == -1) { if (var != null) { facesContext.getExternalContext().getRequestMap().remove(var); } } else { if (var != null) { if (isRowAvailable()) { Object rowData = dataModel.getRowData(); facesContext.getExternalContext().getRequestMap().put(var, rowData); } else { facesContext.getExternalContext().getRequestMap().remove(var); } } } if (_rowIndex == -1) { // reset components to initial state // If no initial state, skip row restore state code if (_initialDescendantComponentState != null) { restoreDescendantComponentStates(this, false, _initialDescendantComponentState, false); } else { restoreDescendantComponentWithoutRestoreState(this, false, false); } } else { Object rowState = _rowStates.get(getContainerClientId(facesContext)); if (rowState == null) { // We haven't been positioned on this row before, so just // configure the child components of this component with // the standard "initial" state // If no initial state, skip row restore state code if (_initialDescendantComponentState != null) { restoreDescendantComponentStates(this, false, _initialDescendantComponentState, false); } else { restoreDescendantComponentWithoutRestoreState(this, false, false); } } else { // We have been positioned on this row before, so configure // the child components of this component with the (partial) // state that was previously saved. Fields not in the // partial saved state are left with their original values. restoreDescendantComponentStates(this, false, rowState, false); } } } private void setRowIndexPreserveComponentState(int rowIndex) { if (rowIndex < -1) { throw new IllegalArgumentException("rowIndex is less than -1"); } if (_rowIndex == rowIndex) { return; } FacesContext facesContext = getFacesContext(); if (_initialDescendantFullComponentState != null) { //Just save the row Map sm = saveFullDescendantComponentStates(facesContext, null, getChildren().iterator(), false); if (sm != null && !sm.isEmpty()) { _rowDeltaStates.put(getContainerClientId(facesContext), sm); } if (_rowIndex != -1) { _rowTransientStates.put(getContainerClientId(facesContext), saveTransientDescendantComponentStates(facesContext, null, getChildren().iterator(), false)); } } _rowIndex = rowIndex; DataModel dataModel = getDataModel(); dataModel.setRowIndex(rowIndex); String var = (String) getStateHelper().get(PropertyKeys.var); if (rowIndex == -1) { if (var != null) { facesContext.getExternalContext().getRequestMap().remove(var); } } else { if (var != null) { if (isRowAvailable()) { Object rowData = dataModel.getRowData(); facesContext.getExternalContext().getRequestMap().put(var, rowData); } else { facesContext.getExternalContext().getRequestMap().remove(var); } } } if (_initialDescendantFullComponentState != null) { Map rowState = _rowDeltaStates.get(getContainerClientId(facesContext)); if (rowState == null) { //Restore as original restoreFullDescendantComponentStates(facesContext, getChildren().iterator(), _initialDescendantFullComponentState, false); } else { //Restore first original and then delta restoreFullDescendantComponentDeltaStates(facesContext, getChildren().iterator(), rowState, _initialDescendantFullComponentState, false); } if (_rowIndex == -1) { restoreTransientDescendantComponentStates(facesContext, getChildren().iterator(), null, false); } else { rowState = _rowTransientStates.get(getContainerClientId(facesContext)); if (rowState == null) { restoreTransientDescendantComponentStates(facesContext, getChildren().iterator(), null, false); } else { restoreTransientDescendantComponentStates(facesContext, getChildren().iterator(), rowState, false); } } } } /** * Overwrite the state of the child components of this component with data previously saved by method * saveDescendantComponentStates. *

* The saved state info only covers those fields that are expected to vary between rows of a table. Other fields are * not modified. */ @SuppressWarnings("unchecked") private void restoreDescendantComponentStates(UIComponent parent, boolean iterateFacets, Object state, boolean restoreChildFacets) { int descendantStateIndex = -1; List stateCollection = null; if (iterateFacets && parent.getFacetCount() > 0) { Iterator childIterator = parent.getFacets().values().iterator(); while (childIterator.hasNext()) { UIComponent component = childIterator.next(); // reset the client id (see spec 3.1.6) component.setId(component.getId()); if (!component.isTransient()) { if (descendantStateIndex == -1) { stateCollection = ((List) state); descendantStateIndex = stateCollection.isEmpty() ? -1 : 0; } if (descendantStateIndex != -1 && descendantStateIndex < stateCollection.size()) { Object[] object = stateCollection.get(descendantStateIndex); if (component instanceof EditableValueHolder) { EditableValueHolderState evhState = (EditableValueHolderState) object[0]; if (evhState == null) { evhState = EditableValueHolderState.EMPTY; } evhState.restoreState((EditableValueHolder) component); } // If there is descendant state to restore, call it recursively, otherwise // it is safe to skip iteration. if (object[1] != null) { restoreDescendantComponentStates(component, restoreChildFacets, object[1], true); } else { restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true); } } else { restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true); } descendantStateIndex++; } } } if (parent.getChildCount() > 0) { for (int i = 0; i < parent.getChildCount(); i++) { UIComponent component = parent.getChildren().get(i); // reset the client id (see spec 3.1.6) component.setId(component.getId()); if (!component.isTransient()) { if (descendantStateIndex == -1) { stateCollection = ((List) state); descendantStateIndex = stateCollection.isEmpty() ? -1 : 0; } if (descendantStateIndex != -1 && descendantStateIndex < stateCollection.size()) { Object[] object = stateCollection.get(descendantStateIndex); if (component instanceof EditableValueHolder) { EditableValueHolderState evhState = (EditableValueHolderState) object[0]; if (evhState == null) { evhState = EditableValueHolderState.EMPTY; } evhState.restoreState((EditableValueHolder) component); } // If there is descendant state to restore, call it recursively, otherwise // it is safe to skip iteration. if (object[1] != null) { restoreDescendantComponentStates(component, restoreChildFacets, object[1], true); } else { restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true); } } else { restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true); } descendantStateIndex++; } } } } /** * Just call component.setId(component.getId()) to reset all client ids and * ensure they will be calculated for the current row, but do not waste time * dealing with row state code. * * @param parent * @param iterateFacets * @param restoreChildFacets */ private void restoreDescendantComponentWithoutRestoreState(UIComponent parent, boolean iterateFacets, boolean restoreChildFacets) { if (iterateFacets && parent.getFacetCount() > 0) { Iterator childIterator = parent.getFacets().values().iterator(); while (childIterator.hasNext()) { UIComponent component = childIterator.next(); // reset the client id (see spec 3.1.6) component.setId(component.getId()); if (!component.isTransient()) { restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true); } } } if (parent.getChildCount() > 0) { for (int i = 0; i < parent.getChildCount(); i++) { UIComponent component = parent.getChildren().get(i); // reset the client id (see spec 3.1.6) component.setId(component.getId()); if (!component.isTransient()) { restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true); } } } } /** * Walk the tree of child components of this UIData, saving the parts of their state that can vary between rows. *

* This is very similar to the process that occurs for normal components when the view is serialized. Transient * components are skipped (no state is saved for them). *

* If there are no children then null is returned. If there are one or more children, and all children are transient * then an empty collection is returned; this will happen whenever a table contains only read-only components. *

* Otherwise a collection is returned which contains an object for every non-transient child component; that object * may itself contain a collection of the state of that child's child components. */ private Collection saveDescendantComponentStates(UIComponent parent, boolean iterateFacets, boolean saveChildFacets) { Collection childStates = null; // Index to indicate how many components has been passed without state to save. int childEmptyIndex = 0; int totalChildCount = 0; if (iterateFacets && parent.getFacetCount() > 0) { Iterator childIterator = parent.getFacets().values().iterator(); while (childIterator.hasNext()) { UIComponent child = childIterator.next(); if (!child.isTransient()) { // Add an entry to the collection, being an array of two // elements. The first element is the state of the children // of this component; the second is the state of the current // child itself. if (child instanceof EditableValueHolder) { if (childStates == null) { childStates = new ArrayList<>( parent.getFacetCount() + parent.getChildCount() - totalChildCount + childEmptyIndex); for (int ci = 0; ci < childEmptyIndex; ci++) { childStates.add(LEAF_NO_STATE); } } childStates.add(child.getChildCount() > 0 ? new Object[]{ EditableValueHolderState.create((EditableValueHolder) child), saveDescendantComponentStates(child, saveChildFacets, true) } : new Object[]{ EditableValueHolderState.create((EditableValueHolder) child), null }); } else if (child.getChildCount() > 0 || (saveChildFacets && child.getFacetCount() > 0)) { Object descendantSavedState = saveDescendantComponentStates(child, saveChildFacets, true); if (descendantSavedState == null) { if (childStates == null) { childEmptyIndex++; } else { childStates.add(LEAF_NO_STATE); } } else { if (childStates == null) { childStates = new ArrayList<>( parent.getFacetCount() + parent.getChildCount() - totalChildCount + childEmptyIndex); for (int ci = 0; ci < childEmptyIndex; ci++) { childStates.add(LEAF_NO_STATE); } } childStates.add(new Object[]{null, descendantSavedState}); } } else { if (childStates == null) { childEmptyIndex++; } else { childStates.add(LEAF_NO_STATE); } } } totalChildCount++; } } if (parent.getChildCount() > 0) { for (int i = 0; i < parent.getChildCount(); i++) { UIComponent child = parent.getChildren().get(i); if (!child.isTransient()) { // Add an entry to the collection, being an array of two // elements. The first element is the state of the children // of this component; the second is the state of the current // child itself. if (child instanceof EditableValueHolder) { if (childStates == null) { childStates = new ArrayList<>( parent.getFacetCount() + parent.getChildCount() - totalChildCount + childEmptyIndex); for (int ci = 0; ci < childEmptyIndex; ci++) { childStates.add(LEAF_NO_STATE); } } childStates.add(child.getChildCount() > 0 ? new Object[] { EditableValueHolderState.create((EditableValueHolder) child), saveDescendantComponentStates(child, saveChildFacets, true) } : new Object[] { EditableValueHolderState.create((EditableValueHolder) child), null }); } else if (child.getChildCount() > 0 || (saveChildFacets && child.getFacetCount() > 0)) { Object descendantSavedState = saveDescendantComponentStates(child, saveChildFacets, true); if (descendantSavedState == null) { if (childStates == null) { childEmptyIndex++; } else { childStates.add(LEAF_NO_STATE); } } else { if (childStates == null) { childStates = new ArrayList<>( parent.getFacetCount() + parent.getChildCount() - totalChildCount + childEmptyIndex); for (int ci = 0; ci < childEmptyIndex; ci++) { childStates.add(LEAF_NO_STATE); } } childStates.add(new Object[]{null, descendantSavedState}); } } else { if (childStates == null) { childEmptyIndex++; } else { childStates.add(LEAF_NO_STATE); } } } totalChildCount++; } } return childStates; } @Override public void markInitialState() { if (isRowStatePreserved() && getFacesContext().getAttributes().containsKey(StateManager.IS_BUILDING_INITIAL_STATE)) { _initialDescendantFullComponentState = saveDescendantInitialComponentStates(getFacesContext(), getChildren().iterator(), false); } super.markInitialState(); } private void restoreFullDescendantComponentStates(FacesContext facesContext, Iterator childIterator, Object initialState, boolean restoreChildFacets) { Iterator descendantStateIterator = null; while (childIterator.hasNext()) { if (descendantStateIterator == null && initialState != null) { descendantStateIterator = ((Collection) initialState).iterator(); } UIComponent component = childIterator.next(); // reset the client id (see spec 3.1.6) component.setId(component.getId()); if (!component.isTransient()) { Object childState = null; Object descendantState = null; String childId = null; if (descendantStateIterator != null && descendantStateIterator.hasNext()) { do { Object[] object = descendantStateIterator.next(); childState = object[0]; descendantState = object[1]; childId = (String) object[2]; } while(descendantStateIterator.hasNext() && !component.getId().equals(childId)); if (!component.getId().equals(childId)) { // cannot apply initial state to components correctly. throw new IllegalStateException("Cannot restore row correctly."); } } component.clearInitialState(); component.restoreState(facesContext, childState); component.markInitialState(); Iterator childsIterator = restoreChildFacets ? component.getFacetsAndChildren() : component.getChildren().iterator(); restoreFullDescendantComponentStates(facesContext, childsIterator, descendantState, true); } } } private Collection saveDescendantInitialComponentStates(FacesContext facesContext, Iterator childIterator, boolean saveChildFacets) { Collection childStates = null; while (childIterator.hasNext()) { UIComponent child = childIterator.next(); if (!child.isTransient()) { Iterator childsIterator = saveChildFacets ? child.getFacetsAndChildren() : child.getChildren().iterator(); Object descendantState = saveDescendantInitialComponentStates(facesContext, childsIterator, true); Object state = null; if (child.initialStateMarked()) { child.clearInitialState(); state = child.saveState(facesContext); child.markInitialState(); } else { state = child.saveState(facesContext); } // Add an entry to the collection, being an array of two elements. // The first element is the state of the children of this component; // the second is the state of the current child itself. if (childStates == null) { childStates = new ArrayList<>(); } childStates.add(new Object[] { state, descendantState, child.getId()}); } } return childStates; } private Map saveFullDescendantComponentStates(FacesContext facesContext, Map stateMap, Iterator childIterator, boolean saveChildFacets) { while (childIterator.hasNext()) { UIComponent child = childIterator.next(); if (!child.isTransient()) { // Add an entry to the collection, being an array of two // elements. The first element is the state of the children // of this component; the second is the state of the current // child itself. Iterator childsIterator = saveChildFacets ? child.getFacetsAndChildren() : child.getChildren().iterator(); stateMap = saveFullDescendantComponentStates(facesContext, stateMap, childsIterator, true); Object state = child.saveState(facesContext); if (state != null) { if (stateMap == null) { stateMap = new HashMap<>(); } stateMap.put(child.getClientId(facesContext), state); } } } return stateMap; } private void restoreFullDescendantComponentDeltaStates(FacesContext facesContext, Iterator childIterator, Map state, Object initialState, boolean restoreChildFacets) { Iterator descendantFullStateIterator = null; while (childIterator.hasNext()) { if (descendantFullStateIterator == null && initialState != null) { descendantFullStateIterator = ((Collection) initialState).iterator(); } UIComponent component = childIterator.next(); // reset the client id (see spec 3.1.6) component.setId(component.getId()); if (!component.isTransient()) { Object childInitialState = null; Object descendantInitialState = null; Object childState = null; String childId = null; childState = (state == null) ? null : state.get(component.getClientId(facesContext)); if (descendantFullStateIterator != null && descendantFullStateIterator.hasNext()) { do { Object[] object = descendantFullStateIterator.next(); childInitialState = object[0]; descendantInitialState = object[1]; childId = (String) object[2]; } while(descendantFullStateIterator.hasNext() && !component.getId().equals(childId)); if (!component.getId().equals(childId)) { // cannot apply initial state to components correctly. State is corrupt throw new IllegalStateException("Cannot restore row correctly."); } } component.clearInitialState(); if (childInitialState != null) { component.restoreState(facesContext, childInitialState); component.markInitialState(); component.restoreState(facesContext, childState); } else { component.restoreState(facesContext, childState); component.markInitialState(); } Iterator childsIterator = restoreChildFacets ? component.getFacetsAndChildren() : component.getChildren().iterator(); restoreFullDescendantComponentDeltaStates(facesContext, childsIterator, state, descendantInitialState , true); } } } private void restoreTransientDescendantComponentStates(FacesContext facesContext, Iterator childIterator, Map state, boolean restoreChildFacets) { while (childIterator.hasNext()) { UIComponent component = childIterator.next(); // reset the client id (see spec 3.1.6) component.setId(component.getId()); if (!component.isTransient()) { component.restoreTransientState(facesContext, state == null ? null : state.get(component.getClientId(facesContext))); Iterator childsIterator = restoreChildFacets ? component.getFacetsAndChildren() : component.getChildren().iterator(); restoreTransientDescendantComponentStates(facesContext, childsIterator, state, true); } } } private Map saveTransientDescendantComponentStates(FacesContext facesContext, Map childStates, Iterator childIterator, boolean saveChildFacets) { while (childIterator.hasNext()) { UIComponent child = childIterator.next(); if (!child.isTransient()) { Iterator childsIterator = saveChildFacets ? child.getFacetsAndChildren() : child.getChildren().iterator(); childStates = saveTransientDescendantComponentStates(facesContext, childStates, childsIterator, true); Object state = child.saveTransientState(facesContext); if (state != null) { if (childStates == null) { childStates = new HashMap<>(); } childStates.put(child.getClientId(facesContext), state); } } } return childStates; } @Override public void restoreState(FacesContext context, Object state) { if (state == null) { return; } Object[] values = (Object[]) state; super.restoreState(context, values[0]); Object restoredRowStates = UIComponentBase.restoreAttachedState(context, values[1]); if (restoredRowStates == null) { if (!_rowDeltaStates.isEmpty()) { _rowDeltaStates.clear(); } } else { _rowDeltaStates = (Map >) restoredRowStates; } if (values.length > 2) { Object rs = UIComponentBase.restoreAttachedState(context, values[2]); if (rs == null) { if (!_rowStates.isEmpty()) { _rowStates.clear(); } } else { _rowStates = (Map) rs; } } if (values.length > 3) { Object rs = UIComponentBase.restoreAttachedState(context, values[3]); if (rs == null) { if (!_rowTransientStates.isEmpty()) { _rowTransientStates.clear(); } } else { _rowTransientStates = (Map >) rs; } } } @Override public Object saveState(FacesContext context) { if (context.getViewRoot() != null) { if (context.getViewRoot().getResetSaveStateMode() == RESET_MODE_SOFT) { _dataModelMap.clear(); _isValidChilds = true; _rowTransientStates.clear(); } if (context.getViewRoot().getResetSaveStateMode() == RESET_MODE_HARD) { _dataModelMap.clear(); _isValidChilds = true; _rowTransientStates.clear(); _rowStates.clear(); _rowDeltaStates.clear(); } } if (initialStateMarked()) { Object parentSaved = super.saveState(context); if (context.getCurrentPhaseId() != null && !PhaseId.RENDER_RESPONSE.equals(context.getCurrentPhaseId())) { if (parentSaved == null &&_rowDeltaStates.isEmpty() && _rowStates.isEmpty()) { return null; } else { Object[] values = new Object[4]; values[0] = super.saveState(context); values[1] = UIComponentBase.saveAttachedState(context, _rowDeltaStates); values[2] = UIComponentBase.saveAttachedState(context, _rowStates); values[3] = UIComponentBase.saveAttachedState(context, _rowTransientStates); return values; } } else { if (parentSaved == null &&_rowDeltaStates.isEmpty()) { return null; } else { Object[] values = new Object[2]; values[0] = super.saveState(context); values[1] = UIComponentBase.saveAttachedState(context, _rowDeltaStates); return values; } } } else { if (context.getCurrentPhaseId() != null && !PhaseId.RENDER_RESPONSE.equals(context.getCurrentPhaseId())) { Object[] values = new Object[4]; values[0] = super.saveState(context); values[1] = UIComponentBase.saveAttachedState(context, _rowDeltaStates); values[2] = UIComponentBase.saveAttachedState(context, _rowStates); values[3] = UIComponentBase.saveAttachedState(context, _rowTransientStates); return values; } else { Object[] values = new Object[2]; values[0] = super.saveState(context); values[1] = UIComponentBase.saveAttachedState(context, _rowDeltaStates); return values; } } } @Override public void setValueExpression(String name, ValueExpression binding) { Assert.notNull(name, "name"); if (name.equals("value")) { _dataModelMap.clear(); } else if (name.equals("rowIndex")) { throw new IllegalArgumentException("name " + name); } super.setValueExpression(name, binding); } @Override public String getContainerClientId(FacesContext context) { //MYFACES-2744 UIData.getClientId() should not append rowIndex, instead use UIData.getContainerClientId() String clientId = super.getContainerClientId(context); int rowIndex = getRowIndex(); if (rowIndex == -1) { return clientId; } StringBuilder bld = _getSharedStringBuilder(context); return bld.append(clientId).append(context.getNamingContainerSeparatorChar()).append(rowIndex).toString(); } /** * Modify events queued for any child components so that the UIData state will be correctly configured before the * event's listeners are executed. *

* Child components or their renderers may register events against those child components. When the listener for * that event is eventually invoked, it may expect the uidata's rowData and rowIndex to be referring to the same * object that caused the event to fire. *

* The original queueEvent call against the child component has been forwarded up the chain of ancestors in the * standard way, making it possible here to wrap the event in a new event whose source is this component, not * the original one. When the event finally is executed, this component's broadcast method is invoked, which ensures * that the UIData is set to be at the correct row before executing the original event. */ @Override public void queueEvent(FacesEvent event) { Assert.notNull(event, "event"); super.queueEvent(new FacesEventWrapper(event, getRowIndex(), this)); } /** * Ensure that before the event's listeners are invoked this UIData component's "current row" is set to the row * associated with the event. *

* See queueEvent for more details. */ @Override public void broadcast(FacesEvent event) throws AbortProcessingException { if (event instanceof FacesEventWrapper) { FacesEvent originalEvent = ((FacesEventWrapper) event).getWrappedFacesEvent(); int eventRowIndex = ((FacesEventWrapper) event).getRowIndex(); final int currentRowIndex = getRowIndex(); UIComponent source = originalEvent.getComponent(); UIComponent compositeParent = UIComponent.getCompositeComponentParent(source); setRowIndex(eventRowIndex); if (compositeParent != null) { pushComponentToEL(getFacesContext(), compositeParent); } pushComponentToEL(getFacesContext(), source); try { source.broadcast(originalEvent); } finally { source.popComponentFromEL(getFacesContext()); if (compositeParent != null) { compositeParent.popComponentFromEL(getFacesContext()); } setRowIndex(currentRowIndex); } } else { super.broadcast(event); } } /** * * {@inheritDoc} * * @since 2.0 */ @Override public String createUniqueId(FacesContext context, String seed) { StringBuilder bld = _getSharedStringBuilder(context); // Generate an identifier for a component. The identifier will be prefixed with UNIQUE_ID_PREFIX, // and will be unique within this UIViewRoot. if(seed == null) { Long uniqueIdCounter = (Long) getStateHelper().get(PropertyKeys.uniqueIdCounter); uniqueIdCounter = (uniqueIdCounter == null) ? 0 : uniqueIdCounter; getStateHelper().put(PropertyKeys.uniqueIdCounter, (uniqueIdCounter+1L)); return bld.append(UIViewRoot.UNIQUE_ID_PREFIX).append(uniqueIdCounter).toString(); } // Optionally, a unique seed value can be supplied by component creators // which should be included in the generated unique id. else { return bld.append(UIViewRoot.UNIQUE_ID_PREFIX).append(seed).toString(); } } /** * Perform necessary actions when rendering of this component starts, before delegating to the inherited * implementation which calls the associated renderer's encodeBegin method. */ @Override public void encodeBegin(FacesContext context) throws IOException { _initialDescendantComponentState = null; if (_isValidChilds && !hasErrorMessages(context)) { // Clear the data model so that when rendering code calls // getDataModel a fresh model is fetched from the backing // bean via the value-binding. _dataModelMap.clear(); // When the data model is cleared it is also necessary to // clear the saved row state, as there is an implicit 1:1 // relation between objects in the _rowStates and the // corresponding DataModel element. if (!isRowStatePreserved()) { _rowStates.clear(); } } super.encodeBegin(context); } private boolean hasErrorMessages(FacesContext context) { // perf: getMessageList() return a RandomAccess instance. // See org.apache.myfaces.context.servlet.FacesContextImpl.addMessage List messageList = context.getMessageList(); for (int i = 0, size = messageList.size(); i < size; i++) { FacesMessage message = messageList.get(i); if (FacesMessage.SEVERITY_ERROR.compareTo(message.getSeverity()) <= 0) { return true; } } return false; } /** * @see jakarta.faces.component.UIComponentBase#encodeEnd(jakarta.faces.context.FacesContext) */ @Override public void encodeEnd(FacesContext context) throws IOException { try { setCachedFacesContext(context); setRowIndex(-1); } finally { setCachedFacesContext(null); } super.encodeEnd(context); } @Override public void processDecodes(FacesContext context) { Assert.notNull(context, "context"); try { setCachedFacesContext(context); pushComponentToEL(context, this); if (!isRendered()) { return; } setRowIndex(-1); processFacets(context, PROCESS_DECODES); processColumnFacets(context, PROCESS_DECODES); processColumnChildren(context, PROCESS_DECODES); setRowIndex(-1); try { decode(context); } catch (RuntimeException e) { context.renderResponse(); throw e; } } finally { popComponentFromEL(context); setCachedFacesContext(null); } } @Override public void processValidators(FacesContext context) { Assert.notNull(context, "context"); try { setCachedFacesContext(context); pushComponentToEL(context, this); if (!isRendered()) { return; } //Pre validation event dispatch for component context.getApplication().publishEvent(context, PreValidateEvent.class, getClass(), this); try { setRowIndex(-1); processFacets(context, PROCESS_VALIDATORS); processColumnFacets(context, PROCESS_VALIDATORS); processColumnChildren(context, PROCESS_VALIDATORS); setRowIndex(-1); } finally { context.getApplication().publishEvent(context, PostValidateEvent.class, getClass(), this); } // check if an validation error forces the render response for our data if (context.getRenderResponse()) { _isValidChilds = false; } } finally { popComponentFromEL(context); setCachedFacesContext(null); } } @Override public void processUpdates(FacesContext context) { Assert.notNull(context, "context"); try { setCachedFacesContext(context); pushComponentToEL(context, this); if (!isRendered()) { return; } setRowIndex(-1); processFacets(context, PROCESS_UPDATES); processColumnFacets(context, PROCESS_UPDATES); processColumnChildren(context, PROCESS_UPDATES); setRowIndex(-1); if (context.getRenderResponse()) { _isValidChilds = false; } } finally { popComponentFromEL(context); setCachedFacesContext(null); } } private void processFacets(FacesContext context, int processAction) { if (this.getFacetCount() > 0) { for (UIComponent facet : getFacets().values()) { process(context, facet, processAction); } } } /** * Invoke the specified phase on all facets of all UIColumn children of this component. Note that no methods are * called on the UIColumn child objects themselves. * * @param context * is the current faces context. * @param processAction * specifies a Faces phase: decode, validate or update. */ private void processColumnFacets(FacesContext context, int processAction) { for (int i = 0, childCount = getChildCount(); i < childCount; i++) { UIComponent child = getChildren().get(i); if (child instanceof UIColumn) { if (!ComponentUtils.isRendered(context, child)) { // Column is not visible continue; } if (child.getFacetCount() > 0) { for (UIComponent facet : child.getFacets().values()) { process(context, facet, processAction); } } } } } /** * Invoke the specified phase on all non-facet children of all UIColumn children of this component. Note that no * methods are called on the UIColumn child objects themselves. * * @param context * is the current faces context. * @param processAction * specifies a Faces phase: decode, validate or update. */ private void processColumnChildren(FacesContext context, int processAction) { int first = getFirst(); int rows = getRows(); int last; if (rows == 0) { last = getRowCount(); } else { last = first + rows; } for (int rowIndex = first; last == -1 || rowIndex < last; rowIndex++) { setRowIndex(rowIndex); // scrolled past the last row if (!isRowAvailable()) { break; } for (int i = 0, childCount = getChildCount(); i < childCount; i++) { UIComponent child = getChildren().get(i); if (child instanceof UIColumn) { if (!ComponentUtils.isRendered(context, child)) { // Column is not visible continue; } for (int j = 0, columnChildCount = child.getChildCount(); j < columnChildCount; j++) { UIComponent columnChild = child.getChildren().get(j); process(context, columnChild, processAction); } } } } } private void process(FacesContext context, UIComponent component, int processAction) { switch (processAction) { case PROCESS_DECODES: component.processDecodes(context); break; case PROCESS_VALIDATORS: component.processValidators(context); break; case PROCESS_UPDATES: component.processUpdates(context); break; default: // do nothing } } /** * Return the datamodel for this table, potentially fetching the data from a backing bean via a value-binding if * this is the first time this method has been called. *

* This is complicated by the fact that this table may be nested within another table. In this case a different * datamodel should be fetched for each row. When nested within a parent table, the parent reference won't change * but parent.getContainerClientId() will, as the suffix changes * depending upon the current row index. A map object on this * component is therefore used to cache the datamodel for each row of the table. In the normal case where this table * is not nested inside a component that changes its id (like a table does) then this map only ever has one entry. */ protected DataModel getDataModel() { DataModel dataModel; String clientID = ""; UIComponent parent = getParent(); if (parent != null) { clientID = parent.getContainerClientId(getFacesContext()); } dataModel = _dataModelMap.get(clientID); if (dataModel == null) { dataModel = createDataModel(); _dataModelMap.put(clientID, dataModel); } return dataModel; } protected void setDataModel(DataModel dataModel) { String clientID = ""; UIComponent parent = getParent(); if (parent != null) { clientID = parent.getContainerClientId(getFacesContext()); } if (dataModel == null) { _dataModelMap.remove(clientID); } else { _dataModelMap.put(clientID, dataModel); } } /** * Evaluate this object's value property and convert the result into a DataModel. Normally this object's value * property will be a value-binding which will cause the value to be fetched from some backing bean. *

* The result of fetching the value may be a DataModel object, in which case that object is returned directly. If * the value is of type List, Array, ResultSet, Result, other object or null then an appropriate wrapper is created * and returned. *

* Null is never returned by this method. */ private DataModel createDataModel() { Object value = getValue(); if (value == null) { return EMPTY_DATA_MODEL; } else if (value instanceof DataModel) { return (DataModel) value; } else { DataModel dataModel = null; if (FACES_DATA_MODEL_MANAGER_CLASS != null && value != null) { try { dataModel = (DataModel) FACES_DATA_MODEL_MANAGER_CREATE_DATAMODEL_METHOD.invoke(null, getFacesContext(), value.getClass(), value); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { //No op } } if (dataModel == null) { if (value instanceof List) { return new ListDataModel((List) value); } else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass())) { return new ArrayDataModel((Object[]) value); } else if (value instanceof ResultSet) { return new ResultSetDataModel((ResultSet) value); } else if (value instanceof Iterable) { return new IterableDataModel<>((Iterable) value); } else if (value instanceof Map) { return new IterableDataModel<>(((Map) value).entrySet()); } else if (value instanceof Collection) { return new CollectionDataModel((Collection) value); } else { return new ScalarDataModel(value); } } else { return dataModel; } } } /** * An EL expression that specifies the data model that backs this table. *

* The value referenced by the EL expression can be of any type. *

*
    *
  • A value of type DataModel is used directly.
  • *
  • Array-like parameters of type array-of-Object, java.util.List or java.sql.ResultSet * are wrapped in a corresponding DataModel that knows how to iterate over the * elements.
  • *
  • Other values are wrapped in a DataModel as a single row.
  • *
*

* Note in particular that unordered collections, eg Set are not supported. Therefore if the value expression * references such an object then the table will be considered to contain just one element - the collection itself. *

*/ @JSFProperty public Object getValue() { return getStateHelper().eval(PropertyKeys.value); } public void setValue(Object value) { getStateHelper().put(PropertyKeys.value, value); _dataModelMap.clear(); _rowStates.clear(); _isValidChilds = true; } /** * Defines the index of the first row to be displayed, starting from 0. */ @JSFProperty public int getFirst() { return (Integer) getStateHelper().eval(PropertyKeys.first,0); } public void setFirst(int first) { if (first < 0) { throw new IllegalArgumentException("Illegal value for first row: " + first); } getStateHelper().put(PropertyKeys.first, first ); } /** * Defines the maximum number of rows of data to be displayed. *

* Specify zero to display all rows from the "first" row to the end of available data. *

*/ @JSFProperty public int getRows() { return (Integer) getStateHelper().eval(PropertyKeys.rows,0); } /** * Set the maximum number of rows displayed in the table. */ public void setRows(int rows) { if (rows < 0) { throw new IllegalArgumentException("rows: " + rows); } getStateHelper().put(PropertyKeys.rows, rows ); } /** * Defines the name of the request-scope variable that will hold the current row during iteration. *

* During rendering of child components of this UIData, the variable with this name can be read to learn what the * "rowData" object for the row currently being rendered is. *

*

* This value must be a static value, ie an EL expression is not permitted. *

*/ @JSFProperty(literalOnly = true) public String getVar() { return (String) getStateHelper().get(PropertyKeys.var); } /** * Overrides the behavior in * UIComponent.visitTree(jakarta.faces.component.visit.VisitContext, jakarta.faces.component.visit.VisitCallback) * to handle iteration correctly. * * @param context the visit context which handles the processing details * @param callback the callback to be performed * @return false if the processing is not done true if we can shortcut * the visiting because we are done with everything * * @since 2.0 */ @Override public boolean visitTree(VisitContext context, VisitCallback callback) { boolean skipIterationHint = context.getHints().contains(VisitHint.SKIP_ITERATION); if (skipIterationHint) { return super.visitTree(context, callback); } // push the Component to EL pushComponentToEL(context.getFacesContext(), this); try { if (!isVisitable(context)) { return false; } boolean isCachedFacesContext = isCachedFacesContext(); if (!isCachedFacesContext) { setCachedFacesContext(context.getFacesContext()); } // save the current row index int oldRowIndex = getRowIndex(); // set row index to -1 to process the facets and to get the rowless clientId setRowIndex(-1); try { VisitResult visitResult = context.invokeVisitCallback(this, callback); switch (visitResult) { case COMPLETE: //we are done nothing has to be processed anymore return true; case REJECT: return false; default: // accept; determine if we need to visit our children Collection subtreeIdsToVisit = context.getSubtreeIdsToVisit(this); boolean doVisitChildren = subtreeIdsToVisit != null && !subtreeIdsToVisit.isEmpty(); if (doVisitChildren) { // visit the facets of the component if (getFacetCount() > 0) { for (UIComponent facet : getFacets().values()) { if (facet.visitTree(context, callback)) { return true; } } } // visit every column directly without visiting its children // (the children of every UIColumn will be visited later for // every row) and also visit the column's facets for (int i = 0, childCount = getChildCount(); i < childCount; i++) { UIComponent child = getChildren().get(i); if (child instanceof UIColumn) { VisitResult columnResult = context.invokeVisitCallback(child, callback); if (columnResult == VisitResult.COMPLETE) { return true; } if (child.getFacetCount() > 0) { for (UIComponent facet : child.getFacets().values()) { if (facet.visitTree(context, callback)) { return true; } } } } } // iterate over the rows int rowsToProcess = getRows(); // if getRows() returns 0, all rows have to be processed if (rowsToProcess == 0) { rowsToProcess = getRowCount(); } int rowIndex = getFirst(); for (int rowsProcessed = 0; rowsProcessed < rowsToProcess; rowsProcessed++, rowIndex++) { setRowIndex(rowIndex); if (!isRowAvailable()) { return false; } // visit the children of every child of the UIData that is an instance of UIColumn for (int i = 0, childCount = getChildCount(); i < childCount; i++) { UIComponent child = getChildren().get(i); if (child instanceof UIColumn) { for (int j = 0, grandChildCount = child.getChildCount(); j < grandChildCount; j++) { UIComponent grandchild = child.getChildren().get(j); if (grandchild.visitTree(context, callback)) { return true; } } } } } } } } finally { // restore the old row index setRowIndex(oldRowIndex); if (!isCachedFacesContext) { setCachedFacesContext(null); } } } finally { // pop the component from EL popComponentFromEL(context.getFacesContext()); } // Return false to allow the visiting to continue return false; } public void setVar(String var) { getStateHelper().put(PropertyKeys.var, var ); } /** * Indicates whether the state for a component in each row should not be * discarded before the datatable is rendered again. * * This will only work reliable if the datamodel of the * datatable did not change either by sorting, removing or * adding rows. Default: false * * @return */ @JSFProperty(literalOnly=true, faceletsOnly=true) public boolean isRowStatePreserved() { Boolean b = (Boolean) getStateHelper().get(PropertyKeys.rowStatePreserved); return b == null ? false : b; } public void setRowStatePreserved(boolean preserveComponentState) { getStateHelper().put(PropertyKeys.rowStatePreserved, preserveComponentState); } enum PropertyKeys { value, first, rows, var, uniqueIdCounter, rowStatePreserved } @Override public String getFamily() { return COMPONENT_FAMILY; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy