diff --git a/conf/checkstyle-tests.xml b/conf/checkstyle-tests.xml index 46c4d0fe41..4aab27347d 100644 --- a/conf/checkstyle-tests.xml +++ b/conf/checkstyle-tests.xml @@ -4,6 +4,11 @@ "https://checkstyle.org/dtds/configuration_1_2.dtd"> + + + + + diff --git a/conf/checkstyle.xml b/conf/checkstyle.xml index 270b19eda2..99d27b1b9b 100644 --- a/conf/checkstyle.xml +++ b/conf/checkstyle.xml @@ -4,6 +4,11 @@ "https://checkstyle.org/dtds/configuration_1_2.dtd"> + + + + + diff --git a/primefaces/src/main/java/org/primefaces/component/api/UIData.java b/primefaces/src/main/java/org/primefaces/component/api/UIData.java index 5d7a9390d5..f5c1233f4a 100644 --- a/primefaces/src/main/java/org/primefaces/component/api/UIData.java +++ b/primefaces/src/main/java/org/primefaces/component/api/UIData.java @@ -23,29 +23,26 @@ */ package org.primefaces.component.api; -import java.io.IOException; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.logging.Logger; import javax.faces.FacesException; -import javax.faces.application.Application; -import javax.faces.application.FacesMessage; -import javax.faces.application.StateManager; import javax.faces.component.*; import javax.faces.component.visit.VisitCallback; import javax.faces.component.visit.VisitContext; import javax.faces.component.visit.VisitResult; import javax.faces.context.FacesContext; import javax.faces.event.PhaseId; -import javax.faces.event.PostValidateEvent; -import javax.faces.event.PreValidateEvent; -import javax.faces.model.*; import javax.faces.render.Renderer; import org.primefaces.component.column.Column; import org.primefaces.component.columngroup.ColumnGroup; import org.primefaces.component.columns.Columns; import org.primefaces.model.LazyDataModel; +import org.primefaces.shaded.faces.UIDataMojarraImpl; import org.primefaces.util.ComponentTraversalUtils; import org.primefaces.util.ComponentUtils; import org.primefaces.util.ELUtils; @@ -53,28 +50,15 @@ /** * Enhanced version of the JSF UIData. - * It also contains some methods of the Mojarra impl (e.g. setRowIndexRowStatePreserved), maybe can remove it in the future. */ -@SuppressWarnings("unchecked") -public class UIData extends javax.faces.component.UIData { +public class UIData extends UIDataMojarraImpl { private static final Logger LOGGER = Logger.getLogger(UIData.class.getName()); private static final String SB_ID = UIData.class.getName() + "#id"; - private final Map _rowTransientStates = new HashMap<>(); - private Map _rowDeltaStates = new HashMap<>(); - private Object _initialDescendantFullComponentState; - - private String clientId; - private Boolean isNested; - private Object oldVar; - public enum PropertyKeys { - rowIndex, rowIndexVar, - saved, lazy, - rowStatePreserved } public boolean isLazy() { @@ -82,9 +66,7 @@ public boolean isLazy() { boolean lazy = false; // if not set by xhtml, we need to check the type of the value binding - Class type = ELUtils.getType(getFacesContext(), - getValueExpression("value"), - () -> getValue()); + Class type = ELUtils.getType(getFacesContext(), getValueExpression("value"), this::getValue); if (type == null) { LOGGER.warning("Unable to automatically determine the `lazy` attribute, fallback to false. " + "Either define the `lazy` attribute on the component or make sure the `value` attribute doesn't resolve to `null`. " @@ -114,56 +96,7 @@ public void setRowIndexVar(String rowIndexVar) { } @Override - public boolean isRowStatePreserved() { - return (Boolean) getStateHelper().eval(PropertyKeys.rowStatePreserved, false); - } - - @Override - public void setRowStatePreserved(boolean rowStatePreserved) { - getStateHelper().put(PropertyKeys.rowStatePreserved, rowStatePreserved); - } - - @Override - public void processDecodes(FacesContext context) { - if (!isRendered()) { - return; - } - - pushComponentToEL(context, this); - preDecode(context); - processPhase(context, PhaseId.APPLY_REQUEST_VALUES); - decode(context); - popComponentFromEL(context); - } - - @Override - public void processValidators(FacesContext context) { - if (!isRendered()) { - return; - } - - pushComponentToEL(context, this); - Application app = context.getApplication(); - app.publishEvent(context, PreValidateEvent.class, this); - preValidate(context); - processPhase(context, PhaseId.PROCESS_VALIDATIONS); - app.publishEvent(context, PostValidateEvent.class, this); - popComponentFromEL(context); - } - - @Override - public void processUpdates(FacesContext context) { - if (!isRendered()) { - return; - } - - pushComponentToEL(context, this); - preUpdate(context); - processPhase(context, PhaseId.UPDATE_MODEL_VALUES); - popComponentFromEL(context); - } - - protected void processPhase(FacesContext context, PhaseId phaseId) { + protected void iterate(FacesContext context, PhaseId phaseId) { processFacets(context, phaseId); if (requiresColumns()) { processColumnFacets(context, phaseId); @@ -197,7 +130,7 @@ protected void processColumnFacets(FacesContext context, PhaseId phaseId) { } } - protected boolean shouldProcessChild(FacesContext context, int rowIndex, PhaseId phaseId ) { + protected boolean shouldProcessChild(FacesContext context, int rowIndex, PhaseId phaseId) { return true; } @@ -254,8 +187,8 @@ else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) { @Override public String getClientId(FacesContext context) { - if (clientId != null) { - return clientId; + if (baseClientId != null) { + return baseClientId; } String id = getId(); @@ -285,22 +218,22 @@ public String getClientId(FacesContext context) { if (containerClientId != null) { StringBuilder sb = SharedStringBuilder.get(context, SB_ID, containerClientId.length() + 10); - clientId = sb.append(containerClientId).append(UINamingContainer.getSeparatorChar(context)).append(id).toString(); + baseClientId = sb.append(containerClientId).append(UINamingContainer.getSeparatorChar(context)).append(id).toString(); } else { - clientId = id; + baseClientId = id; } } else { - clientId = id; + baseClientId = id; } Renderer renderer = getRenderer(context); if (renderer != null) { - clientId = renderer.convertClientId(context, clientId); + baseClientId = renderer.convertClientId(context, baseClientId); } - return clientId; + return baseClientId; } @Override @@ -314,9 +247,7 @@ public String getContainerClientId(FacesContext context) { } StringBuilder sb = SharedStringBuilder.get(context, SB_ID, componentClientId.length() + 4); - String containerClientId = sb.append(componentClientId).append(UINamingContainer.getSeparatorChar(context)).append(rowIndex).toString(); - - return containerClientId; + return sb.append(componentClientId).append(UINamingContainer.getSeparatorChar(context)).append(rowIndex).toString(); } @Override @@ -324,91 +255,11 @@ public void setId(String id) { super.setId(id); //clear - clientId = null; - } - - //Row State preserved implementation is taken from Mojarra - private void setRowIndexRowStatePreserved(int rowIndex) { - if (rowIndex < -1) { - throw new IllegalArgumentException("rowIndex is less than -1"); - } - - if (getRowIndex() == 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 (getRowIndex() != -1) { - _rowTransientStates.put(getContainerClientId(facesContext), - saveTransientDescendantComponentStates(facesContext, null, getChildren().iterator(), false)); - } - } - - // Update to the new row index - //this.rowIndex = rowIndex; - getStateHelper().put(PropertyKeys.rowIndex, rowIndex); - DataModel localModel = getDataModel(); - localModel.setRowIndex(rowIndex); - - // if rowIndex is -1, clear the cache - if (rowIndex == -1) { - setDataModel(null); - } - - // Clear or expose the current row data as a request scope attribute - String var = getVar(); - if (var != null) { - Map requestMap - = getFacesContext().getExternalContext().getRequestMap(); - if (rowIndex == -1) { - oldVar = requestMap.remove(var); - } - else if (isRowAvailable()) { - requestMap.put(var, getRowData()); - } - else { - requestMap.remove(var); - if (null != oldVar) { - requestMap.put(var, oldVar); - oldVar = null; - } - } - } - - if (_initialDescendantFullComponentState != null) { - Object 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 (getRowIndex() == -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(), (Map) rowState, false); - } - } - } + baseClientId = null; } - private void setRowIndexWithoutRowStatePreserved(int rowIndex) { + @Override + protected void setRowIndexWithoutRowStatePreserved(int rowIndex) { saveDescendantState(); setRowModel(rowIndex); restoreDescendantState(); @@ -416,7 +267,7 @@ private void setRowIndexWithoutRowStatePreserved(int rowIndex) { public void setRowModel(int rowIndex) { //update rowIndex - getStateHelper().put(PropertyKeys.rowIndex, rowIndex); + getStateHelper().put(UIDataMojarraImpl.PropertyKeys.rowIndex, rowIndex); getDataModel().setRowIndex(rowIndex); //clear datamodel @@ -460,29 +311,10 @@ else if (isRowAvailable()) { } @Override - public int getRowIndex() { - return (Integer) getStateHelper().eval(PropertyKeys.rowIndex, -1); - } - - @Override - public void setRowIndex(int rowIndex) { - if (isRowStatePreserved()) { - setRowIndexRowStatePreserved(rowIndex); - } - else { - setRowIndexWithoutRowStatePreserved(rowIndex); - } - } - protected void saveDescendantState() { FacesContext context = getFacesContext(); - if (getChildCount() > 0) { - for (int i = 0; i < getChildCount(); i++) { - UIComponent kid = getChildren().get(i); - saveDescendantState(kid, context); - } - } + super.saveDescendantState(); if (getFacetCount() > 0) { for (UIComponent facet : getFacets().values()) { @@ -491,80 +323,11 @@ protected void saveDescendantState() { } } - protected void saveDescendantState(UIComponent component, FacesContext context) { - // Save state for this component (if it is a EditableValueHolder) - Map saved = (Map) getStateHelper().get(PropertyKeys.saved); - if (component instanceof EditableValueHolder) { - EditableValueHolder input = (EditableValueHolder) component; - SavedState state = null; - String clientId = component.getClientId(context); - if (saved == null) { - state = new SavedState(); - } - if (state == null) { - state = saved.get(clientId); - if (state == null) { - state = new SavedState(); - } - } - state.setValue(input.getLocalValue()); - state.setValid(input.isValid()); - state.setSubmittedValue(input.getSubmittedValue()); - state.setLocalValueSet(input.isLocalValueSet()); - if (state.hasDeltaState()) { - getStateHelper().put(PropertyKeys.saved, clientId, state); - } - else if (saved != null) { - getStateHelper().remove(PropertyKeys.saved, clientId); - } - } - else if (component instanceof UIForm) { - UIForm form = (UIForm) component; - String clientId = component.getClientId(context); - SavedState state = null; - if (saved == null) { - state = new SavedState(); - } - if (state == null) { - state = saved.get(clientId); - if (state == null) { - state = new SavedState(); - } - } - state.setSubmitted(form.isSubmitted()); - if (state.hasDeltaState()) { - getStateHelper().put(PropertyKeys.saved, clientId, state); - } - else if (saved != null) { - getStateHelper().remove(PropertyKeys.saved, clientId); - } - } - - //save state for children - if (component.getChildCount() > 0) { - for (int i = 0; i < component.getChildCount(); i++) { - UIComponent kid = component.getChildren().get(i); - saveDescendantState(kid, context); - } - } - - //save state for facets - if (component.getFacetCount() > 0) { - for (UIComponent facet : component.getFacets().values()) { - saveDescendantState(facet, context); - } - } - } - + @Override protected void restoreDescendantState() { FacesContext context = getFacesContext(); - if (getChildCount() > 0) { - for (int i = 0; i < getChildCount(); i++) { - UIComponent kid = getChildren().get(i); - restoreDescendantState(kid, context); - } - } + super.restoreDescendantState(); if (getFacetCount() > 0) { for (UIComponent facet : getFacets().values()) { @@ -573,71 +336,10 @@ protected void restoreDescendantState() { } } - protected void restoreDescendantState(UIComponent component, FacesContext context) { - // Reset the client identifier for this component - String id = component.getId(); - component.setId(id); // Forces client id to be reset - Map saved = (Map) getStateHelper().get(PropertyKeys.saved); - // Restore state for this component (if it is a EditableValueHolder) - if (component instanceof EditableValueHolder) { - EditableValueHolder input = (EditableValueHolder) component; - String clientId = component.getClientId(context); - - SavedState state = saved == null ? null : saved.get(clientId); - if (state == null) { - input.resetValue(); - } - else { - input.setValue(state.getValue()); - input.setValid(state.isValid()); - input.setSubmittedValue(state.getSubmittedValue()); - // This *must* be set after the call to setValue(), since - // calling setValue() always resets "localValueSet" to true. - input.setLocalValueSet(state.isLocalValueSet()); - } - } - else if (component instanceof UIForm) { - UIForm form = (UIForm) component; - String clientId = component.getClientId(context); - SavedState state = saved == null ? null : saved.get(clientId); - if (state == null) { - // submitted is transient state - form.setSubmitted(false); - } - else { - form.setSubmitted(state.getSubmitted()); - } - } - - // Restore state for children of this component - if (component.getChildCount() > 0) { - for (UIComponent kid : component.getChildren()) { - restoreDescendantState(kid, context); - } - } - - // Restore state for facets of this component - if (component.getFacetCount() > 0) { - for (UIComponent facet : component.getFacets().values()) { - restoreDescendantState(facet, context); - } - } - } - protected boolean shouldSkipChildren(FacesContext context) { return false; } - protected boolean shouldVisitChildren(VisitContext context, boolean visitRows) { - if (visitRows) { - setRowIndex(-1); - } - - Collection idsToVisit = context.getSubtreeIdsToVisit(this); - - return (!idsToVisit.isEmpty()); - } - @Override public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException { @@ -657,7 +359,7 @@ public boolean visitTree(VisitContext context, VisitCallback callback) { } FacesContext facesContext = context.getFacesContext(); - boolean visitRows = !ComponentUtils.isSkipIteration(context, facesContext); + boolean visitRows = requiresRowIteration(context); int rowIndex = -1; if (visitRows) { @@ -674,7 +376,7 @@ public boolean visitTree(VisitContext context, VisitCallback callback) { return true; } - if ((result == VisitResult.ACCEPT) && shouldVisitChildren(context, visitRows)) { + if ((result == VisitResult.ACCEPT) && doVisitChildren(context, visitRows)) { if (visitFacets(context, callback, visitRows)) { return true; } @@ -701,22 +403,6 @@ public boolean visitTree(VisitContext context, VisitCallback callback) { return false; } - protected boolean visitFacets(VisitContext context, VisitCallback callback, boolean visitRows) { - if (visitRows) { - setRowIndex(-1); - } - - if (getFacetCount() > 0) { - for (UIComponent facet : getFacets().values()) { - if (facet.visitTree(context, callback)) { - return true; - } - } - } - - return false; - } - protected boolean visitColumnsAndColumnFacets(VisitContext context, VisitCallback callback, boolean visitRows, Set rejectedChildren) { if (visitRows) { setRowIndex(-1); @@ -901,245 +587,6 @@ protected List getIterableChildren() { return getChildren(); } - @Override - public void markInitialState() { - if (isRowStatePreserved()) { - if (getFacesContext().getAttributes().containsKey(StateManager.IS_BUILDING_INITIAL_STATE)) { - _initialDescendantFullComponentState = saveDescendantInitialComponentStates(getFacesContext(), getChildren().iterator(), false); - } - } - super.markInitialState(); - } - - private void restoreFullDescendantComponentStates(FacesContext facesContext, - Iterator childIterator, Object state, - boolean restoreChildFacets) { - Iterator descendantStateIterator = null; - while (childIterator.hasNext()) { - if (descendantStateIterator == null && state != null) { - descendantStateIterator = ((Collection) state) - .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; - if (descendantStateIterator != null - && descendantStateIterator.hasNext()) { - Object[] object = descendantStateIterator.next(); - childState = object[0]; - descendantState = object[1]; - } - - component.clearInitialState(); - component.restoreState(facesContext, childState); - component.markInitialState(); - - Iterator childsIterator; - if (restoreChildFacets) { - childsIterator = component.getFacetsAndChildren(); - } - else { - childsIterator = component.getChildren().iterator(); - } - restoreFullDescendantComponentStates(facesContext, childsIterator, - descendantState, true); - } - } - } - - private Collection saveDescendantInitialComponentStates(FacesContext facesContext, - Iterator childIterator, boolean saveChildFacets) { - Collection childStates = null; - while (childIterator.hasNext()) { - if (childStates == null) { - childStates = new ArrayList<>(); - } - - 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; - if (saveChildFacets) { - childsIterator = child.getFacetsAndChildren(); - } - else { - childsIterator = 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); - } - childStates.add(new Object[]{state, descendantState}); - } - } - return childStates; - } - - private Map saveFullDescendantComponentStates(FacesContext facesContext, Map stateMap, - Iterator childIterator, boolean saveChildFacets) { - while (childIterator.hasNext()) { - UIComponent child = childIterator.next(); - if (!child.isTransient()) { - Iterator childsIterator; - if (saveChildFacets) { - childsIterator = child.getFacetsAndChildren(); - } - else { - childsIterator = 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, Object state, Object initialState, - boolean restoreChildFacets) { - Map descendantStateIterator = null; - Iterator descendantFullStateIterator = null; - while (childIterator.hasNext()) { - if (descendantStateIterator == null && state != null) { - descendantStateIterator = (Map) state; - } - 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; - if (descendantStateIterator != null - && descendantStateIterator.containsKey(component.getClientId(facesContext))) { - //Object[] object = (Object[]) descendantStateIterator.get(component.getClientId(facesContext)); - //childState = object[0]; - childState = descendantStateIterator.get(component.getClientId(facesContext)); - } - if (descendantFullStateIterator != null - && descendantFullStateIterator.hasNext()) { - Object[] object = descendantFullStateIterator.next(); - childInitialState = object[0]; - descendantInitialState = object[1]; - } - - component.clearInitialState(); - if (childInitialState != null) { - component.restoreState(facesContext, childInitialState); - component.markInitialState(); - component.restoreState(facesContext, childState); - } - else { - component.restoreState(facesContext, childState); - component.markInitialState(); - } - - Iterator childsIterator; - if (restoreChildFacets) { - childsIterator = component.getFacetsAndChildren(); - } - else { - childsIterator = 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; - if (restoreChildFacets) { - childsIterator = component.getFacetsAndChildren(); - } - else { - childsIterator = 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; - if (saveChildFacets) { - childsIterator = child.getFacetsAndChildren(); - } - else { - childsIterator = 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; - } - } - @Override public Object saveState(FacesContext context) { // See MyFaces UIData @@ -1148,7 +595,7 @@ public Object saveState(FacesContext context) { _rowTransientStates.clear(); _initialDescendantFullComponentState = null; - clientId = null; + baseClientId = null; isNested = null; oldVar = null; } @@ -1157,84 +604,20 @@ else if (viewPoolingResetMode == ComponentUtils.ViewPoolingResetMode.HARD) { _rowDeltaStates.clear(); _initialDescendantFullComponentState = null; - clientId = null; + baseClientId = null; isNested = null; oldVar = null; } - if (initialStateMarked()) { - Object superState = super.saveState(context); - - if (superState == null && _rowDeltaStates.isEmpty()) { - return null; - } - else { - Object[] values = null; - Object attachedState = UIComponentBase.saveAttachedState(context, _rowDeltaStates); - if (superState != null || attachedState != null) { - values = new Object[]{superState, attachedState}; - } - return values; - } - } - else { - Object[] values = new Object[2]; - values[0] = super.saveState(context); - values[1] = UIComponentBase.saveAttachedState(context, _rowDeltaStates); - return values; - } + return super.saveState(context); } - protected boolean isNestedWithinIterator() { + @Override + protected Boolean isNestedWithinIterator(FacesContext context) { if (isNested == null) { isNested = ComponentUtils.isNestedWithinIterator(this); } return isNested; } - - protected void preDecode(FacesContext context) { - setDataModel(null); - Map saved = (Map) getStateHelper().get(PropertyKeys.saved); - if (null == saved || !keepSaved(context)) { - getStateHelper().remove(PropertyKeys.saved); - } - } - - protected void preValidate(FacesContext context) { - if (isNestedWithinIterator()) { - setDataModel(null); - } - } - - protected void preUpdate(FacesContext context) { - if (isNestedWithinIterator()) { - setDataModel(null); - } - } - - protected void preEncode(FacesContext context) { - setDataModel(null); - if (!keepSaved(context)) { - - getStateHelper().remove(PropertyKeys.saved); - } - } - - private boolean keepSaved(FacesContext context) { - return (contextHasErrorMessages(context) || isNestedWithinIterator()); - } - - private boolean contextHasErrorMessages(FacesContext context) { - FacesMessage.Severity sev = context.getMaximumSeverity(); - return (sev != null && (FacesMessage.SEVERITY_ERROR.compareTo(sev) >= 0)); - } - - @Override - public void encodeBegin(FacesContext context) throws IOException { - - preEncode(context); - - super.encodeBegin(context); - } } diff --git a/primefaces/src/main/java/org/primefaces/component/datatable/DataTable.java b/primefaces/src/main/java/org/primefaces/component/datatable/DataTable.java index ef716c42fd..901f534589 100644 --- a/primefaces/src/main/java/org/primefaces/component/datatable/DataTable.java +++ b/primefaces/src/main/java/org/primefaces/component/datatable/DataTable.java @@ -28,6 +28,7 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.IntStream; + import javax.el.ELContext; import javax.el.ValueExpression; import javax.faces.FacesException; @@ -1108,6 +1109,6 @@ protected boolean isCacheableColumns(List columns) { // do not cache if nested in iterator component and contains dynamic columns since number of columns may vary per iteration // see https://github.com/primefaces/primefaces/issues/2154 return getFacesContext().getCurrentPhaseId() == PhaseId.RENDER_RESPONSE - && (!isNestedWithinIterator() || columns.stream().noneMatch(DynamicColumn.class::isInstance)); + && (!isNestedWithinIterator(getFacesContext()) || columns.stream().noneMatch(DynamicColumn.class::isInstance)); } } diff --git a/primefaces/src/main/java/org/primefaces/shaded/faces/UIDataMojarraImpl.java b/primefaces/src/main/java/org/primefaces/shaded/faces/UIDataMojarraImpl.java new file mode 100644 index 0000000000..6bd40ef13c --- /dev/null +++ b/primefaces/src/main/java/org/primefaces/shaded/faces/UIDataMojarraImpl.java @@ -0,0 +1,2253 @@ +/* + * The MIT License + * + * Copyright (c) 2009-2024 PrimeTek Informatics + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.primefaces.shaded.faces; + +import java.io.IOException; +import java.io.Serializable; +import java.util.*; + +import javax.el.ValueExpression; +import javax.faces.FacesException; +import javax.faces.application.Application; +import javax.faces.application.FacesMessage; +import javax.faces.application.StateManager; +import javax.faces.component.*; +import javax.faces.component.visit.VisitCallback; +import javax.faces.component.visit.VisitContext; +import javax.faces.component.visit.VisitHint; +import javax.faces.component.visit.VisitResult; +import javax.faces.context.FacesContext; +import javax.faces.el.ValueBinding; +import javax.faces.event.AbortProcessingException; +import javax.faces.event.FacesEvent; +import javax.faces.event.FacesListener; +import javax.faces.event.PhaseId; +import javax.faces.event.PostValidateEvent; +import javax.faces.event.PreValidateEvent; +import javax.faces.model.DataModel; +import javax.faces.model.ScalarDataModel; + +// ------------------------------------------------------------- Private Classes +// Private class to represent saved state information + +/** + * {@code UIDataMojarraImpl} is largely a copy of Mojarra 2.3.1's {@code UIData}. + * The idea is to make a clear distinction between what belongs to the JSF implementation + * and PrimeFaces. The code replicates exactly the code of the original class with a few exceptions: + *
    + *
  • All members become protected, so that it's possible for PrimeFaces to override methods if necessary.
  • + *
  • The methods {@code invokeOnComponent}, {@code getDataModel}, {@code setDataModel}, + * {@code createDataModel}, and {@code setValueExpression} are not copied since these implementations + * are tightly coupled with Mojarra.
  • + *
  • The method {@code isNestedWithinIterator} is abstract.
  • + *
+ * + *

UIData is a {@link UIComponent} that + * supports data binding to a collection of data objects represented by + * a {@link DataModel} instance, which is the current value of this + * component itself (typically established via a {@link + * ValueExpression}). During iterative processing over the rows of data + * in the data model, the object for the current row is exposed as a + * request attribute under the key specified by the var + * property.

Only children of type {@link UIColumn} should + * be processed by renderers associated with this component.

+ *

By default, the rendererType property is set to + * javax.faces.Table. This value can be changed by calling + * the setRendererType() method.

+ */ + +public abstract class UIDataMojarraImpl extends UIData { + + // ------------------------------------------------------ Manifest Constants + + /** + *

The standard component type for this component.

+ */ + public static final String COMPONENT_TYPE = "javax.faces.Data"; + + + /** + *

The standard component family for this component.

+ */ + public static final String COMPONENT_FAMILY = "javax.faces.Data"; + + // ------------------------------------------------------------ Constructors + + /** + *

Create a new {@link UIDataMojarraImpl} instance with default property + * values.

+ */ + public UIDataMojarraImpl() { + + super(); + setRendererType("javax.faces.Table"); + + } + + // ------------------------------------------------------ Instance Variables + + + /** + * Properties that are tracked by state saving. + */ + protected enum PropertyKeys { + /** + *

The first row number (zero-relative) to be displayed.

+ */ + first, + + /** + *

The zero-relative index of the current row number, or -1 for no + * current row association.

+ */ + rowIndex, + + /** + *

The number of rows to display, or zero for all remaining rows in the + * table.

+ */ + rows, + + /** + *

This map contains SavedState instances for each + * descendant component, keyed by the client identifier of the descendant. + * Because descendant client identifiers will contain the + * rowIndex value of the parent, per-row state information is + * actually preserved.

+ */ + saved, + + /** + *

The local value of this {@link UIComponent}.

+ */ + value, + + /** + *

The request scope attribute under which the data object for the + * current row will be exposed when iterating.

+ */ + var, + + /** + *

Last id vended by {@link UIData#createUniqueId(javax.faces.context.FacesContext, String)}.

+ */ + lastId, + + /** + * + */ + rowStatePreserved + } + + /** + *

During iteration through the rows of this table, This ivar is used to + * store the previous "var" value for this instance. When the row iteration + * is complete, this value is restored to the request map. + */ + protected Object oldVar; + + + /** + *

Holds the base client ID that will be used to generate per-row + * client IDs (this will be null if this UIData is nested within another).

+ * + *

This is not part of the component state.

+ */ + protected String baseClientId = null; + + + /** + *

Length of the cached baseClientId plus one for + * the {@link UINamingContainer#getSeparatorChar}.

+ * + *

This is not part of the component state.

+ */ + protected int baseClientIdLength; + + + /** + *

StringBuilder used to build per-row client IDs.

+ * + *

This is not part of the component state.

+ */ + protected StringBuilder clientIdBuilder = null; + + + /** + *

Flag indicating whether or not this UIData instance is nested + * within another UIData instance

+ * + *

This is not part of the component state.

+ */ + protected Boolean isNested = null; + + protected Map _rowDeltaStates = new HashMap<>(); + protected Map _rowTransientStates = new HashMap<>(); + + protected Object _initialDescendantFullComponentState = null; + + // -------------------------------------------------------------- Properties + + + @Override + public String getFamily() { + + return (COMPONENT_FAMILY); + + } + + + /** + *

Return the zero-relative row number of the first row to be + * displayed.

+ * + * @return the row number. + */ + public int getFirst() { + + return (Integer) getStateHelper().eval(PropertyKeys.first, 0); + + } + + + /** + *

Set the zero-relative row number of the first row to be + * displayed.

+ * + * @param first New first row number + * + * @throws IllegalArgumentException if first is negative + */ + public void setFirst(int first) { + + if (first < 0) { + throw new IllegalArgumentException(String.valueOf(first)); + } + getStateHelper().put(PropertyKeys.first, first); + + } + + + /** + *

Return the footer facet of this component (if any). A convenience + * method for getFacet("footer").

+ * + * @return the footer facet. + */ + public UIComponent getFooter() { + + return getFacet("footer"); + + } + + + /** + *

Set the footer facet of this component. A convenience method for + * getFacets().put("footer", footer).

+ * + * @param footer the new footer facet + * + * @throws NullPointerException if footer is null + */ + public void setFooter(UIComponent footer) { + + getFacets().put("footer", footer); + + } + + + /** + *

Return the header facet of this component (if any). A convenience + * method for getFacet("header").

+ * + * @return the header facet. + */ + public UIComponent getHeader() { + + return getFacet("header"); + + } + + + /** + *

Set the header facet of this component. A convenience method for + * getFacets().put("header", header).

+ * + * @param header the new header facet + * + * @throws NullPointerException if header is null + */ + public void setHeader(UIComponent header) { + + getFacets().put("header", header); + + } + + + /** + *

Return a flag indicating whether there is rowData + * available at the current rowIndex. If no + * wrappedData is available, return false.

+ * + * @return whether the row is available. + * + * @throws FacesException if an error occurs getting the row availability + */ + public boolean isRowAvailable() { + + return (getDataModel().isRowAvailable()); + + } + + + /** + *

Return the number of rows in the underlying data model. If the number + * of available rows is unknown, return -1.

+ * + * @return the row count. + * @throws FacesException if an error occurs getting the row count + */ + public int getRowCount() { + + return (getDataModel().getRowCount()); + + } + + + /** + *

Return the data object representing the data for the currently + * selected row index, if any.

+ * + * @return the row data. + * + * @throws FacesException if an error occurs getting the row data + * @throws IllegalArgumentException if now row data is available at the + * currently specified row index + */ + public Object getRowData() { + + return (getDataModel().getRowData()); + + } + + + /** + *

Return the zero-relative index of the currently selected row. If we + * are not currently positioned on a row, return -1. This property is + * not enabled for value binding expressions.

+ * + * @return the row index. + * + * @throws FacesException if an error occurs getting the row index + */ + public int getRowIndex() { + + return (Integer) getStateHelper().eval(PropertyKeys.rowIndex, -1); + + } + + + /** + *

Set the zero + * relative index of the current row, or -1 to indicate that no row + * is currently selected, by implementing the following algorithm. + * It is possible to set the row index at a value for which the + * underlying data collection does not contain any row data. + * Therefore, callers may use the isRowAvailable() + * method to detect whether row data will be available for use by + * the getRowData() method.

+ + *

To support transient state among + * descendents, please consult the specification for {@link + * #setRowStatePreserved}, which details the requirements + * for setRowIndex() when the + * rowStatePreserved JavaBeans property is set + * to true.

+ + *
    + *
  • Save current state information for all descendant components (as + * described below). + *
  • Store the new row index, and pass it on to the {@link DataModel} + * associated with this {@link UIData} instance.
  • + *
  • If the new rowIndex value is -1: + *
      + *
    • If the var property is not null, + * remove the corresponding request scope attribute (if any).
    • + *
    • Reset the state information for all descendant components + * (as described below).
    • + *
  • + *
  • If the new rowIndex value is not -1: + *
      + *
    • If the var property is not null, call + * getRowData() and expose the resulting data object + * as a request scope attribute whose key is the var + * property value.
    • + *
    • Reset the state information for all descendant components + * (as described below). + *
  • + *
+ * + *

To save current state information for all descendant components, + * {@link UIData} must maintain per-row information for each descendant + * as follows:

+ *
    + *
  • If the descendant is an instance of EditableValueHolder, save + * the state of its localValue property.
  • + *
  • If the descendant is an instance of EditableValueHolder, + * save the state of the localValueSet property.
  • + *
  • If the descendant is an instance of EditableValueHolder, save + * the state of the valid property.
  • + *
  • If the descendant is an instance of EditableValueHolder, + * save the state of the submittedValue property.
  • + *
+ * + *

To restore current state information for all descendant components, + * {@link UIData} must reference its previously stored information for the + * current rowIndex and call setters for each descendant + * as follows:

+ *
    + *
  • If the descendant is an instance of EditableValueHolder, + * restore the value property.
  • + *
  • If the descendant is an instance of EditableValueHolder, + * restore the state of the localValueSet property.
  • + *
  • If the descendant is an instance of EditableValueHolder, + * restore the state of the valid property.
  • + *
  • If the descendant is an instance of EditableValueHolder, + * restore the state of the submittedValue property.
  • + *
+ * + * @param rowIndex The new row index value, or -1 for no associated row + * + * @throws FacesException if an error occurs setting the row index + * @throws IllegalArgumentException if rowIndex + * is less than -1 + */ + public void setRowIndex(int rowIndex) + { + if (isRowStatePreserved()) + { + setRowIndexRowStatePreserved(rowIndex); + } + else + { + setRowIndexWithoutRowStatePreserved(rowIndex); + } + } + + protected void setRowIndexWithoutRowStatePreserved(int rowIndex) { + // Save current state for the previous row index + saveDescendantState(); + + // Update to the new row index + //this.rowIndex = rowIndex; + getStateHelper().put(PropertyKeys.rowIndex, rowIndex); + DataModel localModel = getDataModel(); + localModel.setRowIndex(rowIndex); + + // if rowIndex is -1, clear the cache + if (rowIndex == -1) { + setDataModel(null); + } + + // Clear or expose the current row data as a request scope attribute + String var = (String) getStateHelper().get(PropertyKeys.var); + if (var != null) { + Map requestMap = + getFacesContext().getExternalContext().getRequestMap(); + if (rowIndex == -1) { + oldVar = requestMap.remove(var); + } else if (isRowAvailable()) { + requestMap.put(var, getRowData()); + } else { + requestMap.remove(var); + if (null != oldVar) { + requestMap.put(var, oldVar); + oldVar = null; + } + } + } + + // Reset current state information for the new row index + restoreDescendantState(); + + } + + protected void setRowIndexRowStatePreserved(int rowIndex) + { + if (rowIndex < -1) + { + throw new IllegalArgumentException("rowIndex is less than -1"); + } + + if (getRowIndex() == 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 (getRowIndex() != -1) + { + _rowTransientStates.put(getContainerClientId(facesContext), saveTransientDescendantComponentStates(facesContext, null, getChildren().iterator(), false)); + } + } + + // Update to the new row index + //this.rowIndex = rowIndex; + getStateHelper().put(PropertyKeys.rowIndex, rowIndex); + DataModel localModel = getDataModel(); + localModel.setRowIndex(rowIndex); + + // if rowIndex is -1, clear the cache + if (rowIndex == -1) { + setDataModel(null); + } + + // Clear or expose the current row data as a request scope attribute + String var = (String) getStateHelper().get(PropertyKeys.var); + if (var != null) { + Map requestMap = + getFacesContext().getExternalContext().getRequestMap(); + if (rowIndex == -1) { + oldVar = requestMap.remove(var); + } else if (isRowAvailable()) { + requestMap.put(var, getRowData()); + } else { + requestMap.remove(var); + if (null != oldVar) { + requestMap.put(var, oldVar); + oldVar = null; + } + } + } + + if (_initialDescendantFullComponentState != null) + { + Object 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 (getRowIndex() == -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(), (Map) rowState, false); + } + } + } + } + + /** + *

Return the number of rows to be displayed, or zero for all remaining + * rows in the table. The default value of this property is zero.

+ * + * @return the number of rows. + */ + public int getRows() { + + + return (Integer) getStateHelper().eval(PropertyKeys.rows, 0); + + } + + + /** + *

Set the number of rows to be displayed, or zero for all remaining rows + * in the table.

+ * + * @param rows New number of rows + * + * @throws IllegalArgumentException if rows is negative + */ + public void setRows(int rows) { + + if (rows < 0) { + throw new IllegalArgumentException(String.valueOf(rows)); + } + getStateHelper().put(PropertyKeys.rows, rows); + + } + + + /** + *

Return the request-scope attribute under which the data object for the + * current row will be exposed when iterating. This property is + * not enabled for value binding expressions.

+ * + * @return he request-scope attribute. + */ + public String getVar() { + + return (String) getStateHelper().get(PropertyKeys.var); + + } + + + /** + *

Set the request-scope attribute under which the data object for the + * current row wil be exposed when iterating.

+ * + * @param var The new request-scope attribute name + */ + public void setVar(String var) { + + getStateHelper().put(PropertyKeys.var, var); + + } + + /** + *

Return the value of the + * rowStatePreserved JavaBeans property. See + * {@link #setRowStatePreserved}.

+ * + * @return the value of the rowStatePreserved. + * + * @since 2.1 + */ + + public boolean isRowStatePreserved() + { + Boolean b = (Boolean) getStateHelper().get(PropertyKeys.rowStatePreserved); + return b != null && b; + } + + /** + *

If this property is set to + * true, the UIData must take steps to + * ensure that modifications to its iterated children will be + * preserved on a per-row basis. This allows applications to modify + * component properties, such as the style-class, for a specific + * row, rather than having such modifications apply to all rows.

+ + *
+ + *

To accomplish this, UIData must call {@link + * StateHolder#saveState} and {@link + * TransientStateHolder#saveTransientState} on its children to + * capture their state on exiting each row. When re-entering the + * row, {@link StateHolder#restoreState} and {@link + * TransientStateHolder#restoreTransientState} must be called in + * order to reinitialize the children to the correct state for the + * new row. All of this action must take place during the + * processing of {@link #setRowIndex}.

+ + *

Users should consider enabling this feature for cases where + * it is necessary to modify properties of UIData's + * children in a row-specific way. Note, however, that row-level + * state saving/restoring does add overhead. As such, this feature + * should be used judiciously.

+ + *
+ * + * @param preserveComponentState the flag if the state should be preserved. + * + * @since 2.1 + */ + + public void setRowStatePreserved(boolean preserveComponentState) + { + getStateHelper().put(PropertyKeys.rowStatePreserved, preserveComponentState); + } + + + // ----------------------------------------------------- StateHolder Methods + + + + + /** + *

Return the value of the UIData. This value must either be + * be of type {@link DataModel}, or a type that can be adapted + * into a {@link DataModel}. UIData will automatically + * adapt the following types:

+ *
    + *
  • Arrays
  • + *
  • java.util.List
  • + *
  • java.sql.ResultSet
  • + *
  • javax.servlet.jsp.jstl.sql.Result + *
  • java.util.Collection
  • + *
+ *

All other types will be adapted using the {@link ScalarDataModel} + * class, which will treat the object as a single row of data.

+ * + * @return the object for the value. + */ + public Object getValue() { + + return getStateHelper().eval(PropertyKeys.value); + + } + + + /** + *

Set the value of the UIData. This value must either be + * be of type {@link DataModel}, or a type that can be adapted into a {@link + * DataModel}.

+ * + * @param value the new value + */ + public void setValue(Object value) { + setDataModel(null); + getStateHelper().put(PropertyKeys.value, value); + + } + + // ----------------------------------------------------- UIComponent Methods + + + /** + *

If "name" is something other than "value", "var", or "rowIndex", rely + * on the superclass conversion from ValueBinding to + * ValueExpression.

+ * + * @param name Name of the attribute or property for which to set a + * {@link ValueBinding} + * @param binding The {@link ValueBinding} to set, or null to + * remove any currently set {@link ValueBinding} + * + * @throws IllegalArgumentException if name is one of + * id, parent, + * var, or rowIndex + * @throws NullPointerException if name is null + * @deprecated This has been replaced by {@link #setValueExpression(java.lang.String, + *javax.el.ValueExpression)}. + */ + @Override + public void setValueBinding(String name, ValueBinding binding) { + + if (null != name) { + switch (name) { + case "value": + setDataModel(null); + break; + case "var": + case "rowIndex": + throw new IllegalArgumentException(); + } + } + super.setValueBinding(name, binding); + + } + + /** + *

Return a client identifier for this component that includes the + * current value of the rowIndex property, if it is not set to + * -1. This implies that multiple calls to getClientId() may + * return different results, but ensures that child components can + * themselves generate row-specific client identifiers (since {@link UIData} + * is a {@link NamingContainer}).

+ * + * @throws NullPointerException if context is null + */ + @Override + public String getClientId(FacesContext context) { + + if (context == null) { + throw new NullPointerException(); + } + + // If baseClientId and clientIdBuilder are both null, this is the + // first time that getClientId() has been called. + // If we're not nested within another UIData, then: + // - create a new StringBuilder assigned to clientIdBuilder containing + // our client ID. + // - toString() the builder - this result will be our baseClientId + // for the duration of the component + // - append UINamingContainer.getSeparatorChar() to the builder + // If we are nested within another UIData, then: + // - create an empty StringBuilder that will be used to build + // this instance's ID + if (baseClientId == null && clientIdBuilder == null) { + if (!isNestedWithinIterator(context)) { + clientIdBuilder = new StringBuilder(super.getClientId(context)); + baseClientId = clientIdBuilder.toString(); + baseClientIdLength = (baseClientId.length() + 1); + clientIdBuilder.append(UINamingContainer.getSeparatorChar(context)); + clientIdBuilder.setLength(baseClientIdLength); + } else { + clientIdBuilder = new StringBuilder(); + } + } + int rowIndex = getRowIndex(); + if (rowIndex >= 0) { + String cid; + if (!isNestedWithinIterator(context)) { + // we're not nested, so the clientIdBuilder is already + // primed with clientID + + // UINamingContainer.getSeparatorChar(). Append the + // current rowIndex, and toString() the builder. reset + // the builder to it's primed state. + cid = clientIdBuilder.append(rowIndex).toString(); + clientIdBuilder.setLength(baseClientIdLength); + } else { + // we're nested, so we have to build the ID from scratch + // each time. Reuse the same clientIdBuilder instance + // for each call by resetting the length to 0 after + // the ID has been computed. + cid = clientIdBuilder.append(super.getClientId(context)) + .append(UINamingContainer.getSeparatorChar(context)).append(rowIndex) + .toString(); + clientIdBuilder.setLength(0); + } + return (cid); + } else { + if (!isNestedWithinIterator(context)) { + // Not nested and no row available, so just return our baseClientId + return (baseClientId); + } else { + // nested and no row available, return the result of getClientId(). + // this is necessary as the client ID will reflect the row that + // this table represents + return super.getClientId(context); + } + } + + } + + /** + *

Override the default {@link UIComponentBase#queueEvent} processing to + * wrap any queued events in a wrapper so that we can reset the current row + * index in broadcast().

+ * + * @param event {@link FacesEvent} to be queued + * + * @throws IllegalStateException if this component is not a descendant of a + * {@link UIViewRoot} + * @throws NullPointerException if event is null + */ + @Override + public void queueEvent(FacesEvent event) { + + super.queueEvent(new WrapperEvent(this, event, getRowIndex())); + + } + + + /** + *

Override the default {@link UIComponentBase#broadcast} processing to + * unwrap any wrapped {@link FacesEvent} and reset the current row index, + * before the event is actually broadcast. For events that we did not wrap + * (in queueEvent()), default processing will occur.

+ * + * @param event The {@link FacesEvent} to be broadcast + * + * @throws AbortProcessingException Signal the Jakarta Server Faces + * implementation that no further + * processing on the current event should + * be performed + * @throws IllegalArgumentException if the implementation class of this + * {@link FacesEvent} is not supported by + * this component + * @throws NullPointerException if event is null + */ + @Override + public void broadcast(FacesEvent event) + throws AbortProcessingException { + + if (!(event instanceof WrapperEvent)) { + super.broadcast(event); + return; + } + FacesContext context = event.getFacesContext(); + // Set up the correct context and fire our wrapped event + WrapperEvent revent = (WrapperEvent) event; + if (isNestedWithinIterator(context)) { + setDataModel(null); + } + int currentRowIndex = getRowIndex(); + int broadcastedRowIndex = revent.getRowIndex(); + boolean needsToSetIndex = currentRowIndex != -1 || broadcastedRowIndex != -1; // #5213 + + if (needsToSetIndex) { + setRowIndex(broadcastedRowIndex); + } + + FacesEvent rowEvent = revent.getFacesEvent(); + UIComponent source = rowEvent.getComponent(); + UIComponent compositeParent = null; + try { + if (!UIComponent.isCompositeComponent(source)) { + compositeParent = UIComponent.getCompositeComponentParent(source); + } + if (compositeParent != null) { + compositeParent.pushComponentToEL(context, null); + } + source.pushComponentToEL(context, null); + source.broadcast(rowEvent); + } finally { + source.popComponentFromEL(context); + if (compositeParent != null) { + compositeParent.popComponentFromEL(context); + } + } + + if (needsToSetIndex) { + setRowIndex(currentRowIndex); + } + } + + /** + *

In addition to the default behavior, ensure that any saved per-row + * state for our child input components is discarded unless it is needed to + * rerender the current page with errors. + * + * @param context FacesContext for the current request + * + * @throws IOException if an input/output error occurs while + * rendering + * @throws NullPointerException if context is null + */ + @Override + public void encodeBegin(FacesContext context) throws IOException { + + preEncode(context); + super.encodeBegin(context); + + } + + + /** + *

Override the default {@link UIComponentBase#processDecodes} processing + * to perform the following steps.

  • If the rendered + * property of this {@link UIComponent} is false, skip further + * processing.
  • Set the current rowIndex to -1.
  • + *
  • Call the processDecodes() method of all facets of this + * {@link UIData}, in the order determined by a call to + * getFacets().keySet().iterator().
  • Call the + * processDecodes() method of all facets of the {@link + * UIColumn} children of this {@link UIData}.
  • Iterate over the set + * of rows that were included when this component was rendered (i.e. those + * defined by the first and rows properties), + * performing the following processing for each row:
    • Set the + * current rowIndex to the appropriate value for this row.
    • + *
    • If isRowAvailable() returns true, iterate + * over the children components of each {@link UIColumn} child of this + * {@link UIData} component, calling the processDecodes() + * method for each such child.
  • Set the current + * rowIndex to -1.
  • Call the decode() + * method of this component.
  • If a RuntimeException is + * thrown during decode processing, call {@link FacesContext#renderResponse} + * and re-throw the exception.
+ * + * @param context {@link FacesContext} for the current request + * + * @throws NullPointerException if context is null + */ + @Override + public void processDecodes(FacesContext context) { + + if (context == null) { + throw new NullPointerException(); + } + if (!isRendered()) { + return; + } + + pushComponentToEL(context, this); + preDecode(context); + iterate(context, PhaseId.APPLY_REQUEST_VALUES); + decode(context); + popComponentFromEL(context); + + } + + + /** + *

Override the default {@link UIComponentBase#processValidators} + * processing to perform the following steps.

  • If the + * rendered property of this {@link UIComponent} is + * false, skip further processing.
  • Set the current + * rowIndex to -1.
  • Call the processValidators() + * method of all facets of this {@link UIData}, in the order determined by a + * call to getFacets().keySet().iterator().
  • Call the + * processValidators() method of all facets of the {@link + * UIColumn} children of this {@link UIData}.
  • Iterate over the set + * of rows that were included when this component was rendered (i.e. those + * defined by the first and rows properties), + * performing the following processing for each row:
    • Set the + * current rowIndex to the appropriate value for this row.
    • + *
    • If isRowAvailable() returns true, iterate + * over the children components of each {@link UIColumn} child of this + * {@link UIData} component, calling the processValidators() + * method for each such child.
  • Set the current + * rowIndex to -1.
+ * + * @param context {@link FacesContext} for the current request + * @throws NullPointerException if context is null + * @see javax.faces.event.PreValidateEvent + * @see javax.faces.event.PostValidateEvent + */ + @Override + public void processValidators(FacesContext context) { + + if (context == null) { + throw new NullPointerException(); + } + if (!isRendered()) { + return; + } + pushComponentToEL(context, this); + Application app = context.getApplication(); + app.publishEvent(context, PreValidateEvent.class, this); + preValidate(context); + iterate(context, PhaseId.PROCESS_VALIDATIONS); + app.publishEvent(context, PostValidateEvent.class, this); + popComponentFromEL(context); + + } + + + /** + *

Override the default {@link UIComponentBase#processUpdates} + * processing to perform the following steps.

+ *
    + *
  • If the rendered property of this {@link UIComponent} + * is false, skip further processing.
  • + *
  • Set the current rowIndex to -1.
  • + *
  • Call the processUpdates() method of all facets + * of this {@link UIData}, in the order determined + * by a call to getFacets().keySet().iterator().
  • + *
  • Call the processUpdates() method of all facets + * of the {@link UIColumn} children of this {@link UIData}.
  • + *
  • Iterate over the set of rows that were included when this + * component was rendered (i.e. those defined by the first + * and rows properties), performing the following + * processing for each row: + *
      + *
    • Set the current rowIndex to the appropriate + * value for this row.
    • + *
    • If isRowAvailable() returns true, + * iterate over the children components of each {@link UIColumn} + * child of this {@link UIData} component, calling the + * processUpdates() method for each such child.
    • + *
  • + *
  • Set the current rowIndex to -1.
  • + *
+ * + * @param context {@link FacesContext} for the current request + * + * @throws NullPointerException if context is null + */ + @Override + public void processUpdates(FacesContext context) { + + if (context == null) { + throw new NullPointerException(); + } + if (!isRendered()) { + return; + } + + pushComponentToEL(context, this); + preUpdate(context); + iterate(context, PhaseId.UPDATE_MODEL_VALUES); + popComponentFromEL(context); + // This is not a EditableValueHolder, so no further processing is required + + } + + @Override + public String createUniqueId(FacesContext context, String seed) { + Integer i = (Integer) getStateHelper().get(PropertyKeys.lastId); + int lastId = ((i != null) ? i : 0); + getStateHelper().put(PropertyKeys.lastId, ++lastId); + return UIViewRoot.UNIQUE_ID_PREFIX + (seed == null ? lastId : seed); + } + + /** + *

Override the behavior + * in {@link UIComponent#visitTree} to handle iteration + * correctly.

+ * + *
+ + *

If the {@link UIComponent#isVisitable} method of this instance + * returns false, take no action and return.

+ + *

Call {@link UIComponent#pushComponentToEL} and + * invoke the visit callback on this UIData instance as + * described in {@link UIComponent#visitTree}. Let the result of + * the invoctaion be visitResult. If visitResult + * is {@link VisitResult#COMPLETE}, take no further action and + * return true. Otherwise, determine if we need to + * visit our children. The default implementation calls {@link + * VisitContext#getSubtreeIdsToVisit} passing this as + * the argument. If the result of that call is non-empty, let + * doVisitChildren be true. If + * doVisitChildren is true and + * visitResult is {@link VisitResult#ACCEPT}, take the + * following action.

+ + *
    + + *
  • If this component has facets, call {@link + * UIComponent#getFacets} on this instance and invoke the + * values() method. For each + * UIComponent in the returned Map, + * call {@link UIComponent#visitTree}.

  • + + *
  • + + *
    + + *

    If this component has children, for each + * UIColumn child:

    + * + *

    Call {@link VisitContext#invokeVisitCallback} on that + UIColumn instance. + * If such a call returns true, terminate visiting and + return true from this method.

    + * + *

    If the child UIColumn has facets, call + * {@link UIComponent#visitTree} on each one.

    + * + *

    Take no action on non-UIColumn children.

    + * + *
    + *
  • + * + *
  • + + *
    + * + *

    Save aside the result of a call to {@link + * #getRowIndex}.

    + + *

    For each child component of this UIData that is + * also an instance of {@link UIColumn}, + *

    + + *

    Iterate over the rows.

    + + *
    + + *
      + + *
    • Let rowsToProcess be the return from {@link + * #getRows}.

    • + + *
    • Let rowIndex be the return from {@link + * #getFirst} - 1.

    • + + *
    • While the number of rows processed is less than + * rowsToProcess, take the following actions.

      + + *

      Call {@link #setRowIndex}, passing the current row index.

      + + *

      If {@link #isRowAvailable} returns false, take no + * further action and return false.

      + * + *

      Call {@link + * UIComponent#visitTree} on each of the children of this + * UIColumn instance.

      + + *
    • + + *
    + + *
  • + + *
+ + *

Call {@link #popComponentFromEL} and restore the saved row + * index with a call to {@link #setRowIndex}.

+ + *

Return false to allow the visiting to + * continue.

+ + *
+ * + * @param context the VisitContext that provides + * context for performing the visit. + * + * @param callback the callback to be invoked for each node + * encountered in the visit. + + * @throws NullPointerException if any of the parameters are + * null. + + * + */ + @Override + public boolean visitTree(VisitContext context, + VisitCallback callback) { + + // First check to see whether we are visitable. If not + // short-circuit out of this subtree, though allow the + // visit to proceed through to other subtrees. + if (!isVisitable(context)) + return false; + + FacesContext facesContext = context.getFacesContext(); + // NOTE: that the visitRows local will be obsolete once the + // appropriate visit hints have been added to the API + boolean visitRows = requiresRowIteration(context); + + // Clear out the row index is one is set so that + // we start from a clean slate. + int oldRowIndex = -1; + if (visitRows) { + oldRowIndex = getRowIndex(); + setRowIndex(-1); + } + + // Push ourselves to EL + pushComponentToEL(facesContext, null); + + try { + + // Visit ourselves. Note that we delegate to the + // VisitContext to actually perform the visit. + VisitResult result = context.invokeVisitCallback(this, callback); + + // If the visit is complete, short-circuit out and end the visit + if (result == VisitResult.COMPLETE) + return true; + + // Visit children, short-circuiting as necessary + // NOTE: that the visitRows parameter will be obsolete once the + // appropriate visit hints have been added to the API + if ((result == VisitResult.ACCEPT) && doVisitChildren(context, visitRows)) { + + // First visit facets + // NOTE: that the visitRows parameter will be obsolete once the + // appropriate visit hints have been added to the API + if (visitFacets(context, callback, visitRows)) + return true; + + // Next column facets + // NOTE: that the visitRows parameter will be obsolete once the + // appropriate visit hints have been added to the API + if (visitColumnsAndColumnFacets(context, callback, visitRows)) + return true; + + // And finally, visit rows + // NOTE: that the visitRows parameter will be obsolete once the + // appropriate visit hints have been added to the API + if (visitRows(context, callback, visitRows)) + return true; + } + } + finally { + // Clean up - pop Jakarta Expression Language and restore old row index + popComponentFromEL(facesContext); + if (visitRows) { + setRowIndex(oldRowIndex); + } + } + + // Return false to allow the visit to continue + return false; + } + + /** + *

Override the base class method to + * take special action if the method is being invoked when {@link + * StateManager#IS_BUILDING_INITIAL_STATE} is true + * and the rowStatePreserved + * JavaBeans property for this instance is true.

+ * + *

The additional action taken is to + * traverse the descendents and save their state without regard to + * any particular row value.

+ * + * @since 2.1 + */ + + + @Override + public void markInitialState() + { + if (isRowStatePreserved()) + { + if (getFacesContext().getAttributes().containsKey(StateManager.IS_BUILDING_INITIAL_STATE)) + { + _initialDescendantFullComponentState = saveDescendantInitialComponentStates(getFacesContext(), getChildren().iterator(), false); + } + } + super.markInitialState(); + } + + protected void restoreFullDescendantComponentStates(FacesContext facesContext, + Iterator childIterator, Object state, + boolean restoreChildFacets) + { + Iterator descendantStateIterator = null; + while (childIterator.hasNext()) + { + if (descendantStateIterator == null && state != null) + { + descendantStateIterator = ((Collection) state) + .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; + if (descendantStateIterator != null + && descendantStateIterator.hasNext()) + { + Object[] object = descendantStateIterator.next(); + childState = object[0]; + descendantState = object[1]; + } + + component.clearInitialState(); + component.restoreState(facesContext, childState); + component.markInitialState(); + + Iterator childsIterator; + if (restoreChildFacets) + { + childsIterator = component.getFacetsAndChildren(); + } + else + { + childsIterator = component.getChildren().iterator(); + } + restoreFullDescendantComponentStates(facesContext, childsIterator, + descendantState, true); + } + } + } + + protected Collection saveDescendantInitialComponentStates(FacesContext facesContext, + Iterator childIterator, boolean saveChildFacets) + { + Collection childStates = null; + while (childIterator.hasNext()) + { + if (childStates == null) + { + childStates = new ArrayList<>(); + } + + 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; + if (saveChildFacets) + { + childsIterator = child.getFacetsAndChildren(); + } + else + { + childsIterator = child.getChildren().iterator(); + } + Object descendantState = saveDescendantInitialComponentStates( + facesContext, childsIterator, true); + Object state = child.saveState(facesContext); + childStates.add(new Object[] { state, descendantState }); + } + } + return childStates; + } + + protected Map saveFullDescendantComponentStates(FacesContext facesContext, Map stateMap, + Iterator childIterator, boolean saveChildFacets) + { + while (childIterator.hasNext()) + { + UIComponent child = childIterator.next(); + if (!child.isTransient()) + { + Iterator childsIterator; + if (saveChildFacets) + { + childsIterator = child.getFacetsAndChildren(); + } + else + { + childsIterator = 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; + } + + protected void restoreFullDescendantComponentDeltaStates(FacesContext facesContext, + Iterator childIterator, Object state, Object initialState, + boolean restoreChildFacets) + { + Map descendantStateIterator = null; + Iterator descendantFullStateIterator = null; + while (childIterator.hasNext()) + { + if (descendantStateIterator == null && state != null) + { + descendantStateIterator = (Map) state; + } + 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; + if (descendantStateIterator != null + && descendantStateIterator.containsKey(component.getClientId(facesContext))) + { + //Object[] object = (Object[]) descendantStateIterator.get(component.getClientId(facesContext)); + //childState = object[0]; + childState = descendantStateIterator.get(component.getClientId(facesContext)); + } + if (descendantFullStateIterator != null + && descendantFullStateIterator.hasNext()) + { + Object[] object = (Object[]) descendantFullStateIterator.next(); + childInitialState = object[0]; + descendantInitialState = object[1]; + } + + component.clearInitialState(); + if (childInitialState != null) + { + component.restoreState(facesContext, childInitialState); + component.markInitialState(); + component.restoreState(facesContext, childState); + } + else + { + component.restoreState(facesContext, childState); + component.markInitialState(); + } + + Iterator childsIterator; + if (restoreChildFacets) + { + childsIterator = component.getFacetsAndChildren(); + } + else + { + childsIterator = component.getChildren().iterator(); + } + restoreFullDescendantComponentDeltaStates(facesContext, childsIterator, + state, descendantInitialState , true); + } + } + } + + protected 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; + if (restoreChildFacets) + { + childsIterator = component.getFacetsAndChildren(); + } + else + { + childsIterator = component.getChildren().iterator(); + } + restoreTransientDescendantComponentStates(facesContext, childsIterator, state, true); + } + } + + } + + protected Map saveTransientDescendantComponentStates(FacesContext facesContext, Map childStates, Iterator childIterator, + boolean saveChildFacets) + { + while (childIterator.hasNext()) + { + UIComponent child = childIterator.next(); + if (!child.isTransient()) + { + Iterator childsIterator; + if (saveChildFacets) + { + childsIterator = child.getFacetsAndChildren(); + } + else + { + childsIterator = 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; + } + } + + private void resetClientIds(UIComponent component) { + Iterator iterator = component.getFacetsAndChildren(); + while(iterator.hasNext()) { + UIComponent child = iterator.next(); + resetClientIds(child); + child.setId(child.getId()); + } + } + + @Override + public Object saveState(FacesContext context) + { + resetClientIds(this); + + if (initialStateMarked()) { + Object superState = super.saveState(context); + + if (superState == null && _rowDeltaStates.isEmpty()) { + return null; + } + else { + Object values[] = null; + Object attachedState = UIComponentBase.saveAttachedState(context, _rowDeltaStates); + if (superState != null || attachedState != null) { + values = new Object[] { superState, attachedState }; + } + return values; + } + } else { + Object values[] = new Object[2]; + values[0] = super.saveState(context); + values[1] = UIComponentBase.saveAttachedState(context, _rowDeltaStates); + return values; + } + } + + // --------------------------------------------------------- Protected Methods + + + /** + * Called by {@link javax.faces.component.UIData#visitTree} to determine whether or not the + * visitTree implementation should visit the rows of UIData + * or by manipulating the row index before visiting the components themselves. + * + * Once we have the appropriate Visit hints for state saving, this method + * will become obsolete. + * + * @param ctx the FacesContext for the current request + * + * @return true if row index manipulation is required by the visit to this + * UIData instance + */ + protected boolean requiresRowIteration(VisitContext ctx) { + + return !ctx.getHints().contains(VisitHint.SKIP_ITERATION); + + } + + // Perform pre-decode initialization work. Note that this + // initialization may be performed either during a normal decode + // (ie. processDecodes()) or during a tree visit (ie. visitTree()). + protected void preDecode(FacesContext context) { + setDataModel(null); // Re-evaluate even with server-side state saving + Map saved = + (Map) getStateHelper().get(PropertyKeys.saved); + if (null == saved || !keepSaved(context)) { + //noinspection CollectionWithoutInitialCapacity + getStateHelper().remove(PropertyKeys.saved); + } + } + + // Perform pre-validation initialization work. Note that this + // initialization may be performed either during a normal validation + // (ie. processValidators()) or during a tree visit (ie. visitTree()). + protected void preValidate(FacesContext context) { + if (isNestedWithinIterator(context)) { + setDataModel(null); + } + } + + // Perform pre-update initialization work. Note that this + // initialization may be performed either during normal update + // (ie. processUpdates()) or during a tree visit (ie. visitTree()). + protected void preUpdate(FacesContext context) { + if (isNestedWithinIterator(context)) { + setDataModel(null); + } + } + + // Perform pre-encode initialization work. Note that this + // initialization may be performed either during a normal encode + // (ie. encodeBegin()) or during a tree visit (ie. visitTree()). + protected void preEncode(FacesContext context) { + setDataModel(null); // re-evaluate even with server-side state saving + if (!keepSaved(context)) { + ////noinspection CollectionWithoutInitialCapacity + //saved = new HashMap(); + getStateHelper().remove(PropertyKeys.saved); + } + } + + /** + *

Perform the appropriate phase-specific processing and per-row + * iteration for the specified phase, as follows: + *

    + *
  • Set the rowIndex property to -1, and process the facets + * of this {@link UIData} component exactly once.
  • + *
  • Set the rowIndex property to -1, and process the facets + * of the {@link UIColumn} children of this {@link UIData} component + * exactly once.
  • + *
  • Iterate over the relevant rows, based on the first + * and row properties, and process the children + * of the {@link UIColumn} children of this {@link UIData} component + * once per row.
  • + *
+ * + * @param context {@link FacesContext} for the current request + * @param phaseId {@link PhaseId} of the phase we are currently running + */ + protected void iterate(FacesContext context, PhaseId phaseId) { + + // Process each facet of this component exactly once + setRowIndex(-1); + if (getFacetCount() > 0) { + for (UIComponent facet : getFacets().values()) { + if (phaseId == PhaseId.APPLY_REQUEST_VALUES) { + facet.processDecodes(context); + } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) { + facet.processValidators(context); + } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) { + facet.processUpdates(context); + } else { + throw new IllegalArgumentException(); + } + } + } + + // collect rendered columns once + List renderedColumns = new ArrayList<>(getChildCount()); + if (getChildCount() > 0) { + for (UIComponent child : getChildren()) { + if (child instanceof UIColumn && child.isRendered()) { + renderedColumns.add((UIColumn)child); + } + } + } + + // Process each facet of our child UIColumn components exactly once + setRowIndex(-1); + for (UIColumn column : renderedColumns) { + if (column.getFacetCount() > 0) { + for (UIComponent columnFacet : column.getFacets().values()) { + if (phaseId == PhaseId.APPLY_REQUEST_VALUES) { + columnFacet.processDecodes(context); + } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) { + columnFacet.processValidators(context); + } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) { + columnFacet.processUpdates(context); + } else { + throw new IllegalArgumentException(); + } + } + } + } + + // Iterate over our UIColumn children, once per row + int processed = 0; + int rowIndex = getFirst() - 1; + int rows = getRows(); + + while (true) { + + // Have we processed the requested number of rows? + if ((rows > 0) && (++processed > rows)) { + break; + } + + // Expose the current row in the specified request attribute + setRowIndex(++rowIndex); + if (!isRowAvailable()) { + break; // Scrolled past the last row + } + + // Perform phase-specific processing as required + // on the *children* of the UIColumn (facets have + // been done a single time with rowIndex=-1 already) + for (UIColumn kid : renderedColumns) { + if (kid.getChildCount() > 0) { + for (UIComponent grandkid : kid.getChildren()) { + if (!grandkid.isRendered()) { + continue; + } + if (phaseId == PhaseId.APPLY_REQUEST_VALUES) { + grandkid.processDecodes(context); + } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) { + grandkid.processValidators(context); + } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) { + grandkid.processUpdates(context); + } else { + throw new IllegalArgumentException(); + } + } + } + } + + } + + // Clean up after ourselves + setRowIndex(-1); + + } + + // Tests whether we need to visit our children as part of + // a tree visit + protected boolean doVisitChildren(VisitContext context, boolean visitRows) { + + // Just need to check whether there are any ids under this + // subtree. Make sure row index is cleared out since + // getSubtreeIdsToVisit() needs our row-less client id. + if (visitRows) { + setRowIndex(-1); + } + Collection idsToVisit = context.getSubtreeIdsToVisit(this); + assert(idsToVisit != null); + + // All ids or non-empty collection means we need to visit our children. + return (!idsToVisit.isEmpty()); + } + +// // Performs pre-phase initialization before visiting children +// // (if necessary). +// private void preVisitChildren(VisitContext visitContext) { +// +// // If EXECUTE_LIFECYCLE hint is set, we need to do +// // lifecycle-related initialization before visiting children +// if (visitContext.getHints().contains(VisitHint.EXECUTE_LIFECYCLE)) { +// FacesContext facesContext = visitContext.getFacesContext(); +// PhaseId phaseId = facesContext.getCurrentPhaseId(); +// +// if (phaseId == PhaseId.APPLY_REQUEST_VALUES) +// preDecode(facesContext); +// else if (phaseId == PhaseId.PROCESS_VALIDATIONS) +// preValidate(facesContext); +// else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) +// preUpdate(facesContext); +// else if (phaseId == PhaseId.RENDER_RESPONSE) +// preEncode(facesContext); +// } +// } + + // Visit each facet of this component exactly once. + protected boolean visitFacets(VisitContext context, + VisitCallback callback, + boolean visitRows) { + + if (visitRows) { + setRowIndex(-1); + } + if (getFacetCount() > 0) { + for (UIComponent facet : getFacets().values()) { + if (facet.visitTree(context, callback)) + return true; + } + } + + return false; + } + + // Visit each UIColumn and any facets it may have defined exactly once + protected boolean visitColumnsAndColumnFacets(VisitContext context, + VisitCallback callback, + boolean visitRows) { + if (visitRows) { + setRowIndex(-1); + } + if (getChildCount() > 0) { + for (UIComponent column : getChildren()) { + if (column instanceof UIColumn) { + VisitResult result = context.invokeVisitCallback(column, callback); // visit the column directly + if (result == VisitResult.COMPLETE) { + return true; + } + if (column.getFacetCount() > 0) { + for (UIComponent columnFacet : column.getFacets().values()) { + if (columnFacet.visitTree(context, callback)) { + return true; + } + } + } + } + } + } + + return false; + } + + // Visit each column and row + protected boolean visitRows(VisitContext context, + VisitCallback callback, + boolean visitRows) { + + // Iterate over our UIColumn children, once per row + int processed = 0; + int rowIndex = 0; + int rows = 0; + if (visitRows) { + rowIndex = getFirst() - 1; + rows = getRows(); + } + + while (true) { + + // Have we processed the requested number of rows? + if (visitRows) { + if ((rows > 0) && (++processed > rows)) { + break; + } + // Expose the current row in the specified request attribute + setRowIndex(++rowIndex); + if (!isRowAvailable()) { + break; // Scrolled past the last row + } + } + + // Visit as required on the *children* of the UIColumn + // (facets have been done a single time with rowIndex=-1 already) + if (getChildCount() > 0) { + for (UIComponent kid : getChildren()) { + if (!(kid instanceof UIColumn)) { + continue; + } + if (kid.getChildCount() > 0) { + for (UIComponent grandkid : kid.getChildren()) { + if (grandkid.visitTree(context, callback)) { + return true; + } + } + } + } + } + + if (!visitRows) { + break; + } + + } + + return false; + } + + + /** + *

Return true if we need to keep the saved + * per-child state information. This will be the case if any of the + * following are true:

+ * + *
    + * + *
  • there are messages queued with severity ERROR or FATAL.
  • + * + *
  • this UIData instance is nested inside of another + * UIData instance
  • + * + *
+ * + * @param context {@link FacesContext} for the current request + */ + protected boolean keepSaved(FacesContext context) { + + return (contextHasErrorMessages(context) || isNestedWithinIterator(context)); + + } + + + protected abstract Boolean isNestedWithinIterator(FacesContext context); + + + protected boolean contextHasErrorMessages(FacesContext context) { + + FacesMessage.Severity sev = context.getMaximumSeverity(); + return (sev != null && (FacesMessage.SEVERITY_ERROR.compareTo(sev) >= 0)); + + } + + + /** + *

Restore state information for all descendant components, as described + * for setRowIndex().

+ */ + protected void restoreDescendantState() { + + FacesContext context = getFacesContext(); + if (getChildCount() > 0) { + for (UIComponent kid : getChildren()) { + if (kid instanceof UIColumn) { + restoreDescendantState(kid, context); + } + } + } + + } + + + /** + *

Restore state information for the specified component and its + * descendants.

+ * + * @param component Component for which to restore state information + * @param context {@link FacesContext} for the current request + */ + protected void restoreDescendantState(UIComponent component, + FacesContext context) { + + // Reset the client identifier for this component + String id = component.getId(); + component.setId(id); // Forces client id to be reset + Map saved = (Map) + getStateHelper().get(PropertyKeys.saved); + // Restore state for this component (if it is a EditableValueHolder) + if (component instanceof EditableValueHolder) { + EditableValueHolder input = (EditableValueHolder) component; + String clientId = component.getClientId(context); + + SavedState state = (saved == null ? null : saved.get(clientId)); + if (state == null) { + input.resetValue(); + } else { + input.setValue(state.getValue()); + input.setValid(state.isValid()); + input.setSubmittedValue(state.getSubmittedValue()); + // This *must* be set after the call to setValue(), since + // calling setValue() always resets "localValueSet" to true. + input.setLocalValueSet(state.isLocalValueSet()); + } + } else if (component instanceof UIForm) { + UIForm form = (UIForm) component; + String clientId = component.getClientId(context); + SavedState state = (saved == null ? null : saved.get(clientId)); + if (state == null) { + // submitted is transient state + form.setSubmitted(false); + } else { + form.setSubmitted(state.getSubmitted()); + } + } + + // Restore state for children of this component + if (component.getChildCount() > 0) { + for (UIComponent kid : component.getChildren()) { + restoreDescendantState(kid, context); + } + } + + // Restore state for facets of this component + if (component.getFacetCount() > 0) { + for (UIComponent facet : component.getFacets().values()) { + restoreDescendantState(facet, context); + } + } + + } + + + /** + *

Save state information for all descendant components, as described for + * setRowIndex().

+ */ + protected void saveDescendantState() { + + FacesContext context = getFacesContext(); + if (getChildCount() > 0) { + for (UIComponent kid : getChildren()) { + if (kid instanceof UIColumn) { + saveDescendantState(kid, context); + } + } + } + + } + + + /** + *

Save state information for the specified component and its + * descendants.

+ * + * @param component Component for which to save state information + * @param context {@link FacesContext} for the current request + */ + protected void saveDescendantState(UIComponent component, + FacesContext context) { + + // Save state for this component (if it is a EditableValueHolder) + Map saved = (Map) + getStateHelper().get(PropertyKeys.saved); + if (component instanceof EditableValueHolder) { + EditableValueHolder input = (EditableValueHolder) component; + SavedState state = null; + String clientId = component.getClientId(context); + if (saved == null) { + state = new SavedState(); + } + if (state == null) { + state = saved.get(clientId); + if (state == null) { + state = new SavedState(); + } + } + state.setValue(input.getLocalValue()); + state.setValid(input.isValid()); + state.setSubmittedValue(input.getSubmittedValue()); + state.setLocalValueSet(input.isLocalValueSet()); + if (state.hasDeltaState()) { + getStateHelper().put(PropertyKeys.saved, clientId, state); + } else if (saved != null) { + getStateHelper().remove(PropertyKeys.saved, clientId); + } + } else if (component instanceof UIForm) { + UIForm form = (UIForm) component; + String clientId = component.getClientId(context); + SavedState state = null; + if (saved == null) { + state = new SavedState(); + } + if (state == null) { + state = saved.get(clientId); + if (state == null) { + state = new SavedState(); + } + } + state.setSubmitted(form.isSubmitted()); + if (state.hasDeltaState()) { + getStateHelper().put(PropertyKeys.saved, clientId, state); + } else if (saved != null) { + getStateHelper().remove(PropertyKeys.saved, clientId); + } + } + + // Save state for children of this component + if (component.getChildCount() > 0) { + for (UIComponent uiComponent : component.getChildren()) { + saveDescendantState(uiComponent, context); + } + } + + // Save state for facets of this component + if (component.getFacetCount() > 0) { + for (UIComponent facet : component.getFacets().values()) { + saveDescendantState(facet, context); + } + } + + } + +} +@SuppressWarnings({"SerializableHasSerializationMethods", + "NonSerializableFieldInSerializableClass"}) +class SavedState implements Serializable { + + private static final long serialVersionUID = 2920252657338389849L; + private Object submittedValue; + private boolean submitted; + + Object getSubmittedValue() { + return (this.submittedValue); + } + + void setSubmittedValue(Object submittedValue) { + this.submittedValue = submittedValue; + } + + private boolean valid = true; + + boolean isValid() { + return (this.valid); + } + + void setValid(boolean valid) { + this.valid = valid; + } + + private Object value; + + Object getValue() { + return (this.value); + } + + public void setValue(Object value) { + this.value = value; + } + + private boolean localValueSet; + + boolean isLocalValueSet() { + return (this.localValueSet); + } + + public void setLocalValueSet(boolean localValueSet) { + this.localValueSet = localValueSet; + } + + public boolean getSubmitted() { + return this.submitted; + } + + public void setSubmitted(boolean submitted) { + this.submitted = submitted; + } + + public boolean hasDeltaState() { + return submittedValue != null || value != null || localValueSet + || !valid || submitted; + } + + @Override + public String toString() { + return ("submittedValue: " + submittedValue + + " value: " + value + + " localValueSet: " + localValueSet); + } + +} + +// Private class to wrap an event with a row index +class WrapperEvent extends FacesEvent { + + + private static final long serialVersionUID = -1064272913195655452L; + + public WrapperEvent(UIComponent component, FacesEvent event, int rowIndex) { + super(component); + this.event = event; + this.rowIndex = rowIndex; + } + + private FacesEvent event = null; + private int rowIndex = -1; + + public FacesEvent getFacesEvent() { + return (this.event); + } + + public int getRowIndex() { + return (this.rowIndex); + } + + @Override + public PhaseId getPhaseId() { + return (this.event.getPhaseId()); + } + + @Override + public void setPhaseId(PhaseId phaseId) { + this.event.setPhaseId(phaseId); + } + + @Override + public boolean isAppropriateListener(FacesListener listener) { + return (false); + } + + @Override + public void processListener(FacesListener listener) { + throw new IllegalStateException(); + } + + +}