You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ar...@apache.org on 2013/01/22 18:24:06 UTC

svn commit: r1437062 - /myfaces/trinidad/branches/arobinson74_jira1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/taglib/ForEachTag.java

Author: arobinson74
Date: Tue Jan 22 17:24:05 2013
New Revision: 1437062

URL: http://svn.apache.org/viewvc?rev=1437062&view=rev
Log:
Work so far. Still has issues with nested forEach loops due to the method that the execution ID is being generated (by counter)

Modified:
    myfaces/trinidad/branches/arobinson74_jira1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/taglib/ForEachTag.java

Modified: myfaces/trinidad/branches/arobinson74_jira1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/taglib/ForEachTag.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/arobinson74_jira1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/taglib/ForEachTag.java?rev=1437062&r1=1437061&r2=1437062&view=diff
==============================================================================
--- myfaces/trinidad/branches/arobinson74_jira1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/taglib/ForEachTag.java (original)
+++ myfaces/trinidad/branches/arobinson74_jira1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/taglib/ForEachTag.java Tue Jan 22 17:24:05 2013
@@ -28,7 +28,6 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.el.ELContext;
 import javax.el.PropertyNotWritableException;
@@ -46,6 +45,7 @@ import javax.servlet.jsp.tagext.JspIdCon
 
 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
 import org.apache.myfaces.trinidad.model.CollectionModel;
+import org.apache.myfaces.trinidad.util.ComponentUtils;
 import org.apache.myfaces.trinidad.webapp.TrinidadTagSupport;
 
 
@@ -70,6 +70,23 @@ import org.apache.myfaces.trinidad.webap
 /**
  * Trinidad JSP for each tag that is based on the JSTL c:forEach tag
  * but provides additinal functionality.
+ * <p><b>Internal implementation details</b></p>
+ * <p>
+ * The Trinidad for each tag overcomes limitations of the default forEach tag. This is due to
+ * the standard for each tag using indexed value expressions stored in the variable mappers. This
+ * usage causes issues if the collection that backs the for each is ever changed. In order to
+ * address this, this Trinidad tag uses a level of indirection to store the information. The tag
+ * uses something called and execution ID that represents each invocation of a for each tags'
+ * doStartTag. This execution ID is used between requests to store a map of the keys of the
+ * collection to tie to the iteration status data (exposed via varStatus).
+ * </p>
+ * <p>
+ * This indirection is used my the value expressions. Instead of saving off the current var status
+ * data of an item directly into the variable mapper, the execution ID and the key of the item is
+ * stored into the value expression. This allows the for each tag to keep the iteration status
+ * up to date for each value expression even when changes are made to the collection.
+ * </p>
+ *
  */
 public class ForEachTag
   extends TrinidadTagSupport
@@ -120,18 +137,28 @@ public class ForEachTag
     _jspId = id;
   }
 
+  /**
+   * Process the start tag. This is called at the beginning of the first iteration. It may be called
+   * multiple times on this tag instance if the tag is nested in another iterating JSP tag. Each
+   * time this method is called, the code treats as one execution. Each step of the for each loop
+   * is referred to as an iteration.
+   * @return the JSP ID to control body processing
+   * @throws JspException if a JSF exception occurs
+   */
   @Override
   public int doStartTag()
     throws JspException
   {
     _LOG.finest("doStartTag called");
-    _validateAttributes();
 
     FacesContext facesContext = FacesContext.getCurrentInstance();
-    int          length;
 
-    _currentBegin = (_begin == null) ? 0 : _begin.intValue();
-    _isFirst = true;
+    // Get the values for end, start and begin.
+    _parseTagAttributeExpressions(facesContext);
+    _validateAttributes();
+
+    int begin = (_begin == null) ? 0 : _begin.intValue();
+    int end;
 
     if (null != _items)
     {
@@ -141,10 +168,10 @@ public class ForEachTag
       // to the JSF ELContext seems to resolve that.  We certainly
       // have to use the JSPs ELResolver for calling through
       // to the VariableMapper
-      Object items = _items.getValue(facesContext.getELContext());//pageContext.getELContext());
+      Object items = _items.getValue(facesContext.getELContext());
 
-      //pu: If items is specified and resolves to null, it is treated as an
-      //  empty collection, i.e., no iteration is performed.
+      // If items is specified and resolves to null then we do not loop, regardless of if the
+      // begin or end have been specified
       if (items == null)
       {
         _LOG.fine("Items expression {0} resolved to null.", _items);
@@ -154,7 +181,7 @@ public class ForEachTag
       // Build a wrapper around the items so that a common API can be used to interact with
       // the items regardless of the type.
       _itemsWrapper = _buildItemsWrapper(items);
-      length = _itemsWrapper.getSize();
+      int length = _itemsWrapper.getSize();
 
       if (length == 0)
       {
@@ -162,107 +189,109 @@ public class ForEachTag
         return SKIP_BODY;
       }
 
-      //pu: If valid 'items' was specified, and so was 'begin', get out if size
-      //  of collection were to be less than the begin. A mimic of c:forEach.
-      if (length < _currentBegin)
+      // If the begin attribute has been set, there must be at least "begin" number of items.
+      if (length < begin)
       {
         _LOG.fine("Size of 'items' is less than 'begin'");
         return SKIP_BODY;
       }
 
-      _currentEnd = (_end == null) ? length - 1 : _end.intValue();
-      //pu: If 'end' were specified, but is beyond the size of collection, limit
-      //  the iteration to where the collection ends. A mimic of c:forEach and
-      //  fix for bug 4029853.
-      if (length <= _currentEnd)
+      end = (_end == null) ? length - 1 : _end.intValue();
+
+      // Ensure that the end is no more than the number of items available
+      if (length <= end)
       {
-        _currentEnd = length - 1;
+        end = length - 1;
       }
     }
     else
     {
-      _currentEnd = (_end == null) ? 0 : _end.intValue();
+      end = (_end == null) ? 0 : _end.intValue();
     }
 
-    _currentIndex = _currentBegin;
-    _currentCount = 1;
-    _currentStep = (_step == null) ? 1 : _step.intValue();
-
-    //pu: Now check the valid relation between 'begin','end' and validity of 'step'
-    _validateRangeAndStep();
-
-    // If we can bail, do it now
-    if (_currentEnd < _currentIndex)
+    if (end < begin)
     {
       return SKIP_BODY;
     }
 
-    _isLast = _currentIndex == _currentEnd;
-
-    // Save off the previous deferred variables
-    VariableMapper vm = pageContext.getELContext().getVariableMapper();
-
-    if (_var != null)
+    int step = (_step == null) ? 1 : _step;
+    if (begin < 0)
     {
-      // Store off the current variable so that it may be restored after tag processing
-      _previousDeferredVar = vm.resolveVariable(_var);
+      throw new JspTagException("'begin' < 0");
     }
 
-    if (_LOG.isFiner())
+    if (step < 1)
     {
-      _LOG.finer("Iterating from {0} to {1} by {2}",
-        new Object[] { _currentIndex, _currentEnd, _currentStep });
+      throw new JspTagException("'step' < 1");
     }
 
-    _parentComponent = _getParentComponent();
+    if (step != 1)
+    {
+      // If the step is not one the last item may not fall on the "end". Therefore adjust the
+      // end to match the index of the last item to be processed
+      int count = (int)Math.floor(((end - begin) / (double)step) + 1d);
+      end = ((count - 1) * step) + begin;
+    }
+
+    // Setup the current status
+    _currentIterationStatus = new IterationStatus(
+      _getIterationKey(_itemsWrapper, begin),
+      true,
+      begin == end,
+      begin,
+      1,
+      begin,
+      end,
+      step);
+
+    // Save off the previous deferred variables
+    VariableMapper vm = pageContext.getELContext().getVariableMapper();
+
+    _backupContextVariables(vm);
 
-    // Do not process the meta-data functionality unless the user needs the varStatus variable
     if (_varStatus != null)
     {
-      _configureMetaDataMap();
+      // Setup the map to store the iteration status values for this execution of this for each tag
+      _setupExecutionMap(facesContext);
     }
 
-    // Create a set to track all keys viewed during this iteration to be able to detect
-    // which keys from a previous request are no longer used.
-    _processedKeys = new HashSet<Serializable>();
+    // Create a set to track all keys viewed during the execution of this forEach tag to be able to
+    // detect which keys from a previous request are no longer used.
+    _processedKeysDuringIteration = new HashSet<Serializable>();
     _updateVars(vm);
 
+    if (_LOG.isFiner())
+    {
+      _LOG.finer("Initial iteration status: {0}", new Object[] { _currentIterationStatus });
+    }
+
     return EVAL_BODY_INCLUDE;
   }
 
   @Override
   public int doAfterBody()
   {
-    _LOG.finest("doAfterBody processing with iteration meta data map key of {0}", _iterationMapKey);
+    _LOG.finest("doAfterBody processing for execution ID {0}", _executionId);
 
-    _currentIndex += _currentStep;
-    ++_currentCount;
-    _isFirst = false;
-    _isLast = _currentIndex == _currentEnd;
-
-    // Clear any cached variables
-    _key = null;
-    _metaData = null;
+    _currentIterationStatus = _currentIterationStatus.next(_itemsWrapper);
 
     VariableMapper vm = pageContext.getELContext().getVariableMapper();
 
-    // If we're at the end, bail
-    if (_currentEnd < _currentIndex)
+    if (_currentIterationStatus == null)
     {
-      // Restore EL state
-      if (_var != null)
-      {
-        vm.setVariable(_var, _previousDeferredVar);
-      }
+      // We've finished iterating, we need to clean up by restore EL state and the variable mapper
+      _restoreContextVariables(vm);
+
       if (_varStatus != null)
       {
-        vm.setVariable(_varStatus, _previousDeferredVarStatus);
         // Due to the fact that we are retaining the map keys in the view attributes, check to see
-        // if any keys were not used during this execution and remove them
-        for (Iterator<Serializable> iter = _metaDataMap.keySet().iterator(); iter.hasNext(); )
+        // if any keys were not used during this execution and remove them so that we are not
+        // retaining more objects than necessary in the view state
+        for (Iterator<Serializable> iter = _iterationKeyToIterationStatusMap.keySet().iterator();
+          iter.hasNext();)
         {
           Serializable key = iter.next();
-          if (!_processedKeys.contains(key))
+          if (!_processedKeysDuringIteration.contains(key))
           {
             _LOG.finest("Removing unused key: {0}", key);
             iter.remove();
@@ -270,15 +299,17 @@ public class ForEachTag
         }
       }
 
-      _processedKeys = null;
+      _processedKeysDuringIteration = null;
 
       return SKIP_BODY;
     }
+    else
+    {
+      // Otherwise, update the variables and iterate again
+      _updateVars(vm);
 
-    // Otherwise, update the variables and go again
-    _updateVars(vm);
-
-    return EVAL_BODY_AGAIN;
+      return EVAL_BODY_AGAIN;
+    }
   }
 
   /**
@@ -294,102 +325,119 @@ public class ForEachTag
     _items = null;
     _var = null;
     _varStatus = null;
-    _previousDeferredVar = null;
-    _previousDeferredVarStatus = null;
-
-    _metaDataMap = null;
-    _metaData = null;
-    _viewAttributes = null;
-    _iterationMapKey = null;
+    _executionId = null;
+    _previousVarExpression = null;
+    _previousVarStatusExpression = null;
+    _currentIterationStatus = null;
+    _iterationKeyToIterationStatusMap = null;
     _itemsWrapper = null;
-    _key = null;
-    _parentComponent = null;
+    _processedKeysDuringIteration = null;
 
     _LOG.finest("release called");
   }
 
-  private UIComponent _getParentComponent()
+  /**
+   * Restore the variables backed up in the {@link #_backupContextVariables(VariableMapper)}
+   * function.
+   * @param vm the current variable mapper
+   */
+  private void _restoreContextVariables(VariableMapper vm)
   {
-    UIComponentClassicTagBase tag = UIComponentClassicTagBase.getParentUIComponentClassicTagBase(
-                                      pageContext);
-    return tag == null ? null : tag.getComponentInstance();
+    if (_var != null)
+    {
+      vm.setVariable(_var, _previousVarExpression);
+      pageContext.setAttribute(_var, _previousPageContextVarValue);
+    }
+
+    if (_varStatus != null)
+    {
+      vm.setVariable(_varStatus, _previousVarStatusExpression);
+      pageContext.setAttribute(_varStatus, _previousPageContextVarStatusValue);
+    }
   }
 
   /**
-   * Get the key for the current item in the items. For non-key based collections, this is the
-   * index. If there is no items attribute, this simply returns the current index as well.
-   *
-   * @return the key or index
+   * Saves the var and varStatus information from both the variable mapper as well as the
+   * page context so that the values may be restored to their values after the for each tag
+   * has finished iterating.
+   * @param vm the current variable mapper
    */
-  private Serializable _getKey()
+  private void _backupContextVariables(VariableMapper vm)
   {
-    if (_key != null)
+    if (_var != null)
     {
-      return _key;
+      // Store off the current values used by the var name so that it may be restored after
+      // tag processing
+      _previousVarExpression = vm.resolveVariable(_var);
+      _previousPageContextVarValue = pageContext.getAttribute(_var);
     }
 
-    _key = (_itemsWrapper == null) ?
-      _currentIndex :
-      _asSerializable(_itemsWrapper.getKey(_currentIndex));
-
-    return _key;
+    if (_varStatus != null)
+    {
+      // Store off the current values for the varStatus name to be able to restore it after
+      // processing this tag
+      _previousVarStatusExpression = vm.resolveVariable(_varStatus);
+      _previousPageContextVarStatusValue = pageContext.getAttribute(_varStatus);
+    }
   }
 
-  // Push new values into the VariableMapper and the pageContext
+  /**
+   * Sets up the variable mapper, exposing the var and the iteration status data to EL.
+   * @param vm the variable mapper to modify
+   */
   private void _updateVars(
     VariableMapper vm)
   {
-    Serializable key = null;
+    Serializable key = _currentIterationStatus.getKey();
 
     if (_var != null)
     {
-      // Catch programmer error where _var has been set but _items has not
       if (_items != null)
       {
-        key = _getKey();
-        vm.setVariable(_var, new KeyedValueExpression(_items, key));
+        // Expose the var to the user. The key is used instead of the index so that changes to the
+        // order of items in the collection will not cause the value expression to get out of sync
+        // with the collection resulting in corruputed component state. This expression will be
+        // used inside the variable mapper that is stored in each value expression that is created
+        // by JSP when components in the body of the for each tag are created.
+        KeyedValueExpression keyExpr = new KeyedValueExpression(_items, key);
+        vm.setVariable(_var, keyExpr);
       }
 
-      // Ditto (though, technically, one check for
-      // _items is sufficient, because if _items evaluated
-      // to null, we'd skip the whole loop)
       if (_itemsWrapper != null)
       {
-        Object item = _itemsWrapper.getValue(_currentIndex);
+        // Put the item onto the page context
+        Object item = _itemsWrapper.getValue(_currentIterationStatus.getIndex());
         pageContext.setAttribute(_var, item);
       }
     }
 
     if (_varStatus != null)
     {
-      _previousDeferredVarStatus = vm.resolveVariable(_varStatus);
-
-      if (key == null)
-      {
-        key = _getKey();
-      }
-
-      if (_LOG.isFinest())
-      {
-        _LOG.finest("Storing iteration map key for varStatus." +
-          "\n  Key              : {0}" +
-          "\n  Meta data map key: {1}",
-          new Object[] { key, _iterationMapKey });
-      }
       // Store a new var status value expression into the variable mapper
-      vm.setVariable(_varStatus, new VarStatusValueExpression(key, _iterationMapKey));
+      vm.setVariable(_varStatus, new VarStatusValueExpression(key, _executionId));
 
-      // Only set up the meta data if the varStatus attribute is used
-      MetaData metaData = new MetaData(key,
-        _isFirst, _isLast, _currentBegin, _currentCount, _currentIndex, _currentEnd);
+      // We only need to store the current iteration status on a map if the varStatus variable is
+      // being used. If the varStatus is not used, no value expressions would be stored that need
+      // to be able to get the current information and it is okay to have the iteration status
+      // be transient for this request.
+      _iterationKeyToIterationStatusMap.put(key, _currentIterationStatus);
 
-      _metaDataMap.put(key, metaData);
+      // Record that this key was used during this execution
+      _processedKeysDuringIteration.add(key);
 
-      // Record that this key was used during this request
-      _processedKeys.add(key);
+      // Put the value onto the page context
+      pageContext.setAttribute(_varStatus, _currentIterationStatus);
     }
   }
 
+  /**
+   * Get the integer value from a value expression. Leaves null values as null and converts Number
+   * to Integer. Invalid values will result in a value of null.
+   *
+   * @param context
+   * @param ve
+   * @return
+   */
   private Integer _evaluateInteger(
     FacesContext context,
     ValueExpression ve)
@@ -401,21 +449,27 @@ public class ForEachTag
     if (val instanceof Integer)
       return (Integer) val;
     else if (val instanceof Number)
-      return Integer.valueOf(((Number) val).intValue());
+      return Integer.valueOf(((Number)val).intValue());
 
     return null;
   }
 
-  private void _validateAttributes() throws JspTagException
+  /**
+   * Get the integer values of the begin, end and step value expressions, if set
+   * @param facesContext
+   */
+  private void _parseTagAttributeExpressions(
+    FacesContext facesContext)
   {
-    // Evaluate these three ValueExpressions into integers
-    // For why we use FacesContext instead of PageContext, see
-    // above (the evaluation of _items)
-    FacesContext context = FacesContext.getCurrentInstance();
-    _end = _evaluateInteger(context, _endVE);
-    _begin = _evaluateInteger(context, _beginVE);
-    _step = _evaluateInteger(context, _stepVE);
+    _end = _evaluateInteger(facesContext, _endVE);
+    _begin = _evaluateInteger(facesContext, _beginVE);
+    _step = _evaluateInteger(facesContext, _stepVE);
+  }
 
+  private void _validateAttributes(
+    ) throws JspTagException
+  {
+    // Ensure that the begin and and have been specified if the items attribute was not
     if (null == _items)
     {
       if (null == _begin || null == _end)
@@ -424,25 +478,32 @@ public class ForEachTag
           "'begin' and 'end' should be specified if 'items' is not specified");
       }
     }
-    //pu: This is our own check - c:forEach behavior un-defined & unpredictable.
-    if ((_var != null) &&
-        _var.equals(_varStatus))
+
+    // Ensure the user has not set the var and varStatus attributes to the save value
+    if ((_var != null) && _var.equals(_varStatus))
     {
       throw new JspTagException(
         "'var' and 'varStatus' should not have the same value");
     }
   }
 
-  private void _validateRangeAndStep() throws JspTagException
+  /**
+   * Given the index, get the key to use as the identifier for the current iteration.
+   * Returns the key for key-based collections otherwise returns the index as an object.
+   *
+   * @return the key or index
+   */
+  private static Serializable _getIterationKey(
+    ItemsWrapper itemsWrapper,
+    int         index)
   {
-    if (_currentBegin < 0)
-      throw new JspTagException("'begin' < 0");
-    if (_currentStep < 1)
-      throw new JspTagException("'step' < 1");
-  }
+    if (itemsWrapper == null)
+    {
+      return index;
+    }
+
+    Object key = itemsWrapper.getKey(index);
 
-  private Serializable _asSerializable(Object key)
-  {
     if (key instanceof Serializable)
     {
       return (Serializable)key;
@@ -455,6 +516,12 @@ public class ForEachTag
     }
   }
 
+  /**
+   * Create a wrapper around the items collection to provide a single API to be able to interact
+   * with the data.
+   * @param items The value from evaluating the EL on the items attribute
+   * @return a wrapper class
+   */
   @SuppressWarnings("unchecked")
   private static ItemsWrapper _buildItemsWrapper(
     Object items)
@@ -481,78 +548,108 @@ public class ForEachTag
     }
   }
 
-  private void _configureMetaDataMap()
+  /**
+   * Generate an ID (key) that can be used to identify the for each tag for each time the tag is
+   * executed (each time doStartTag is called, not each time the tag iterates).
+   * @param facesContext The faces context
+   * @return an ID to be used as a key
+   */
+  private String _generateExecutionId(
+    FacesContext facesContext)
   {
-    FacesContext facesContext = FacesContext.getCurrentInstance();
+    // Due to the fact that the number of includes that use the forEach loop may change over time,
+    // (consider regions that navigate that may or may not have for each tags), we need a way to
+    // store iteration status information between requests and be able to match the tags between
+    // requests.
+    // As such, this code uses the parent scoped ID plus the JSP ID assigned to the tag by the
+    // container to identify the forEach tag. Since a forEach tag may be executed more than once
+    // (use case of a nested forEach tag), we must also use a counter to ensure that the
+    // execution is unique for each time the for each tag's doStartTag is called.
+    // Since we also keep data based on this keep in the view scope, we also need to track which
+    // execution IDs have been generated during each request so that we can delete any unused
+    // data. This must be done using a phaseListener
+
+    // Create an ID that represents the execution of this forEach tag (tied to each invocation
+    // of the doStartTag). This ID is used as a key to save a map to store the iteration status
+    // data between requests to be able to keep the varStatus value expressions up to date between
+    // requests taking into account that the collection may be changed between requests.
+
+    // TODO: be able to clean up unused execution IDs in the current request
+    UIComponentClassicTagBase tag = UIComponentClassicTagBase.getParentUIComponentClassicTagBase(
+                                      pageContext);
+    UIComponent parentComponent = tag == null ? null : tag.getComponentInstance();
+
+    String scopedId = parentComponent == null ? "" : ComponentUtils.getLogicalScopedIdForComponent(
+                                                       parentComponent, facesContext.getViewRoot());
 
-    // Use an atomic integer to use for tracking how many times a for each loop has been
-    // created for any JSP page during the current request. Unfortunately there is no hook to
-    // tie the include to the page that is being included to make this page based.
-    AtomicInteger includeCounter = (AtomicInteger)facesContext.getAttributes()
-      .get(_INCLUDE_COUNTER_KEY);
-
-    if (includeCounter == null)
-    {
-      // If the include counter is null, that means that this is the first for each tag processed
-      // during this request.
-      includeCounter = new AtomicInteger(0);
-      facesContext.getAttributes().put(_INCLUDE_COUNTER_KEY, includeCounter);
-    }
-
-    Integer pageContextCounter = (Integer)pageContext.getAttribute(_INCLUDE_COUNTER_KEY);
-    if (pageContextCounter == null)
-    {
-      // In this case, the page context has not been seen before. This means that this is the first
-      // for each tag in this page (the actual jspx file, not necessarily the requested one).
-      pageContextCounter = includeCounter.incrementAndGet();
-      pageContext.setAttribute(_INCLUDE_COUNTER_KEY, pageContextCounter);
-      _LOG.finest("Page context not seen before. Using counter value {0}", pageContextCounter);
+    String id = new StringBuilder(_VIEW_ATTR_KEY_LENGTH + _jspId.length() +
+                                         scopedId.length() + 1)
+      .append(_VIEW_ATTR_KEY)
+      .append(scopedId)
+      .append('.')
+      .append(_jspId)
+      .toString();
+
+    Map<Object, Object> fcAttrs = facesContext.getAttributes();
+    @SuppressWarnings("unchecked")
+    Set<String> usedExecutionIds = (Set<String>)fcAttrs.get(_EXECUTION_ID_MAP_KEY);
+
+    if (usedExecutionIds == null)
+    {
+      usedExecutionIds = new HashSet<String>();
+      fcAttrs.put(_EXECUTION_ID_MAP_KEY, usedExecutionIds);
     }
     else
     {
-      _LOG.finest("Page context has already been seen. Using counter value {0}",
-        includeCounter);
+      String origId = id;
+      for (int i = 1; usedExecutionIds.contains(id); ++i)
+      {
+        id = origId + Integer.toString(i);
+      }
     }
 
-    // If the view attributes are null, then this is the first time this method has been called
-    // for this request.
-    if (_viewAttributes == null)
-    {
-      String pcId = includeCounter.toString();
-
-      // The iteration map key is a key that will allow us to get the map for this tag instance,
-      // separated from other ForEachTags, that will map an iteration ID to the IterationMetaData
-      // instances. EL will use this map to get to the IterationMetaData and the indirection will
-      // allow the IterationMetaData to be updated without having to update the EL expressions.
-      _iterationMapKey = new StringBuilder(_VIEW_ATTR_KEY_LENGTH + _jspId.length() +
-                                           pcId.length() + 1)
-        .append(_VIEW_ATTR_KEY)
-        .append(pcId)
-        .append('.')
-        .append(_jspId)
-        .toString();
+    usedExecutionIds.add(id);
 
-      // store the map into the view attributes to put it in a location that the EL expressions
-      // can access for not only the remainder of this request, but also the next request.
-      UIViewRoot viewRoot = facesContext.getViewRoot();
+    _LOG.finest("Execution ID: {0}", id);
 
-      // We can cache the view attributes in the tag as a JSP tag marked with JspIdConsumer
-      // is never reused.
-      _viewAttributes = viewRoot.getAttributes();
-
-      @SuppressWarnings("unchecked")
-      Map<Serializable, MetaData> metaDataMap = (Map<Serializable, MetaData>)
-        _viewAttributes.get(_iterationMapKey);
-      if (metaDataMap == null)
-      {
-        _metaDataMap = new HashMap<Serializable, MetaData>();
-        _LOG.finest("Created a new meta data map for key {0}", _iterationMapKey);
-        _viewAttributes.put(_iterationMapKey, _metaDataMap);
-      }
-      else
-      {
-        _metaDataMap = metaDataMap;
-      }
+    // Save the execution ID just for logging and debugging reasons
+    _executionId = id;
+
+    return id;
+  }
+
+  /**
+   * Sets up the execution map. The execution map is the map used to store the map keys to their
+   * iteration status values for each execution of the tag (one map for each invocation of
+   * doStartTag). This method is called by doStartTag.
+   * @param facesContext
+   */
+  private void _setupExecutionMap(
+    FacesContext facesContext)
+  {
+    String executionId = _generateExecutionId(facesContext);
+
+    // We need to store the information into something that will persist between requests. This
+    // is so that we can keep a handle on each key that is used during each request and be able
+    // to update the iteration status data so that the varStatus reports correct information
+    // in each request even if the collection has been changed.
+    Map<String, Object> viewAttributes = facesContext.getViewRoot().getAttributes();
+
+    // Use a local variable instead of assigning directly to _iterationKeyToIterationStatusMap
+    // To avoid compiler errors on the generic cast.
+    @SuppressWarnings("unchecked")
+    Map<Serializable, IterationStatus> iterationKeyToIterationStatusMap =
+      (Map<Serializable, IterationStatus>)viewAttributes.get(executionId);
+
+    if (iterationKeyToIterationStatusMap == null)
+    {
+      _iterationKeyToIterationStatusMap = new HashMap<Serializable, IterationStatus>();
+      _LOG.finest("Created a new iteration status map for execution ID {0}", executionId);
+      viewAttributes.put(executionId, _iterationKeyToIterationStatusMap);
+    }
+    else
+    {
+      _iterationKeyToIterationStatusMap = iterationKeyToIterationStatusMap;
     }
   }
 
@@ -666,18 +763,18 @@ public class ForEachTag
   }
 
   /**
-   * Value Expression instance used to get an object containing the var status properties.
+   * Value Expression instance used to get an object containing the var status information.
    */
   static private class VarStatusValueExpression
     extends ValueExpression
     implements Serializable
   {
     private VarStatusValueExpression(
-      Serializable itemsKey,
-      String       metaDataMapKey)
+      Serializable iterationKey,
+      String       executionId)
     {
-      _key = itemsKey;
-      _metaDataMapKey = metaDataMapKey;
+      _iterationKey = iterationKey;
+      _exectionId = executionId;
     }
 
     @Override
@@ -691,12 +788,9 @@ public class ForEachTag
       assert viewRoot != null :
         "Illegal attempt to evaluate for each EL outside of an active view root";
 
-      // We can cache the view attributes in the tag as a JSP tag marked with JspIdConsumer
-      // is never reused.
       Map<String, Object> viewAttributes = viewRoot.getAttributes();
-
-      Map<Serializable, MetaData> metaDataMap = (Map<Serializable, MetaData>)
-        viewAttributes.get(_metaDataMapKey);
+      Map<Serializable, IterationStatus> metaDataMap = (Map<Serializable, IterationStatus>)
+        viewAttributes.get(_exectionId);
 
       if (metaDataMap == null)
       {
@@ -704,7 +798,7 @@ public class ForEachTag
         return null;
       }
 
-      MetaData metaData = metaDataMap.get(_key);
+      IterationStatus metaData = metaDataMap.get(_iterationKey);
       if (metaData == null)
       {
         _LOG.warning("FOR_EACH_META_DATA_KEY_UNAVAILABLE");
@@ -717,7 +811,7 @@ public class ForEachTag
     @Override
     public Class getExpectedType()
     {
-      return MetaData.class;
+      return IterationStatus.class;
     }
 
     @Override
@@ -735,7 +829,7 @@ public class ForEachTag
     @Override
     public Class<?> getType(ELContext context)
     {
-      return MetaData.class;
+      return IterationStatus.class;
     }
 
     @Override
@@ -750,7 +844,7 @@ public class ForEachTag
       if (obj instanceof VarStatusValueExpression)
       {
         VarStatusValueExpression vsve = (VarStatusValueExpression)obj;
-        return _key.equals(vsve._key) && _metaDataMapKey.equals(vsve._metaDataMapKey);
+        return _iterationKey.equals(vsve._iterationKey) && _exectionId.equals(vsve._exectionId);
       }
 
       return false;
@@ -759,9 +853,9 @@ public class ForEachTag
     @Override
     public int hashCode()
     {
-      int hc = _key.hashCode();
+      int hc = _iterationKey.hashCode();
       // Use 31 as a prime number, a technique used in the JRE classes:
-      hc = 31 * hc + _metaDataMapKey.hashCode();
+      hc = 31 * hc + _exectionId.hashCode();
       return hc;
     }
 
@@ -771,20 +865,42 @@ public class ForEachTag
       return false;
     }
 
-    @SuppressWarnings("compatibility:7866012729338284490")
-    private static final long serialVersionUID = 1L;
+    @SuppressWarnings("compatibility:6103993756296276343")
+    private static final long serialVersionUID = 2L;
 
-    private final String _metaDataMapKey;
-    private final Serializable    _key;
+    private final String _exectionId;
+    private final Serializable _iterationKey;
   }
 
+  /**
+   * Class to provide a common API to all the collection types that are supported by the items
+   * attribute to avoid having to branch code by the data type.
+   */
   private abstract static class ItemsWrapper
   {
+    /**
+     * Get the key for the item at the given index
+     * @param index the index
+     * @return the key
+     */
     public abstract Object getKey(int index);
+
+    /**
+     * Get the value for the item at the given index
+     * @param index the index
+     * @return the value from the collection
+     */
     public abstract Object getValue(int index);
+
+    /**
+     * Get the number of items in the collection
+     */
     public abstract int getSize();
   }
 
+  /**
+   * Wrapper for CollectionModel objects
+   */
   private static class CollectionModelWrapper
     extends ItemsWrapper
   {
@@ -824,6 +940,9 @@ public class ForEachTag
     private CollectionModel _collectionModel;
   }
 
+  /**
+   * Wrapper for Map objects
+   */
   private static class MapWrapper
     extends ItemsWrapper
   {
@@ -881,6 +1000,9 @@ public class ForEachTag
     private int                                 _currentIndex = -1;
   }
 
+  /**
+   * Wrapper for List objects
+   */
   private static class ListWrapper
     extends ItemsWrapper
   {
@@ -911,6 +1033,9 @@ public class ForEachTag
     private final List<?> _list;
   }
 
+  /**
+   * Wrapper for plain Java arrays
+   */
   private static class ArrayWrapper
     extends ItemsWrapper
   {
@@ -942,20 +1067,21 @@ public class ForEachTag
   }
 
   /**
-   * Data that is used for the children content of the tag. This contains
-   * the var status information.
+   * Data that is used for the children content of the tag. This provides the information that is
+   * exposed to the user via the varStatus attribute.
    */
-  public static class MetaData
+  public static class IterationStatus
     implements Serializable
   {
-    private MetaData(
+    private IterationStatus(
       Serializable key,
       boolean      first,
       boolean      last,
       int          begin,
       int          count,
       int          index,
-      int          end)
+      int          end,
+      int          step)
     {
       _key = key;
       _first = first;
@@ -964,38 +1090,70 @@ public class ForEachTag
       _count = count;
       _index = index;
       _end = end;
+      _step = step;
     }
 
+    /**
+     * Retrieve if this represents the last iteration of the forEach tag
+     * @return true if the last iteration
+     */
     public final boolean isLast()
     {
       return _last;
     }
 
+    /**
+     * Retrieve if this is the first iteration of the tag
+     * @return true if the first iteration
+     */
     public final boolean isFirst()
     {
       return _first;
     }
 
+    /**
+     * Get the iteration index (0-based). For index based collections this represents the index
+     * of the item in the collection.
+     * @return the index of this iteration (0 based)
+     */
     public final int getIndex()
     {
       return _index;
     }
 
+    /**
+     * Get the number associated with the iteration. The first iteration is one. This is different
+     * from the index property if the begin attribute has been set to a non-zero value.
+     * @return the count of this iteration
+     */
     public final int getCount()
     {
       return _count;
     }
 
+    /**
+     * Get the index of the first item with which the tag begins iteration
+     * @return the first index processed
+     */
     public final int getBegin()
     {
       return _begin;
     }
 
+    /**
+     * Get the last index to be processed
+     * @return the last index
+     */
     public final int getEnd()
     {
       return _end;
     }
 
+    /**
+     * Get the key of the iteration. For index based collections, this is the index and for
+     * key based collections, it is the key.
+     * @return the key
+     */
     public final Serializable getKey()
     {
       return _key;
@@ -1004,32 +1162,52 @@ public class ForEachTag
     @Override
     public String toString()
     {
-      return String.format("MetaData[Key: %s, index: %d, first: %s, last: %s]",
-               _key, _index, _first, _last);
-    }
+      return String.format("IterationStatus[key: %s, index: %d, count: %d, " +
+        "first: %s, last: %s, begin: %d, end: %d, step: %d]",
+               _key, _index, _count, _first, _last, _begin, _end, _step);
+    }
+
+    /**
+     * Generate the status information for the next iteration.
+     * @param itemsWrapper The items wrapper, used to get the key for the current iteration
+     * @return a status object for the next iteration or null if the for each tag is done iterating.
+     */
+    IterationStatus next(
+      ItemsWrapper itemsWrapper)
+    {
+      int nextIndex = _index + _step;
 
-    @SuppressWarnings("compatibility:-1418334454154750553")
-    private static final long serialVersionUID = 1L;
+      if (nextIndex > _end)
+      {
+        return null;
+      }
 
-    private boolean _last;
-    private boolean _first;
-    private int _begin;
-    private int _count;
-    private int _index;
-    private int _end;
-    private Serializable _key;
+      return new IterationStatus(
+        _getIterationKey(itemsWrapper, nextIndex),
+        false,
+        nextIndex == _end,
+        _begin,
+        _count + 1,
+        nextIndex,
+        _end,
+        _step);
+    }
+
+    @SuppressWarnings("compatibility:-8691554623604161106")
+    private static final long serialVersionUID = 2L;
+
+    private final boolean _last;
+    private final boolean _first;
+    private final int _begin;
+    private final int _count;
+    private final int _index;
+    private final int _end;
+    private final int _step;
+    private final Serializable _key;
   }
 
   private String _jspId;
 
-  private int _currentBegin;
-  private int _currentIndex;
-  private int _currentEnd;
-  private int _currentStep;
-  private int _currentCount;
-  private boolean _isFirst;
-  private boolean _isLast;
-
   private ValueExpression _items;
   private ValueExpression _beginVE;
   private ValueExpression _endVE;
@@ -1039,23 +1217,20 @@ public class ForEachTag
   private Integer _end;
   private Integer _step;
 
-  private UIComponent _parentComponent;
-
-  private Serializable _key;
-  private MetaData _metaData;
-  private Map<String, Object> _viewAttributes;
-
-  private String _iterationMapKey;
-  private Map<Serializable, MetaData> _metaDataMap;
-  private Set<Serializable> _processedKeys;
+  private String _executionId;
+  private IterationStatus _currentIterationStatus;
+  private Map<Serializable, IterationStatus> _iterationKeyToIterationStatusMap;
+  private Set<Serializable> _processedKeysDuringIteration;
   private ItemsWrapper _itemsWrapper;
 
   private String _var;
   private String _varStatus;
 
-  // Saved values on the VariableMapper
-  private ValueExpression _previousDeferredVar;
-  private ValueExpression _previousDeferredVarStatus;
+  // Saved values
+  private Object _previousPageContextVarValue;
+  private Object _previousPageContextVarStatusValue;
+  private ValueExpression _previousVarExpression;
+  private ValueExpression _previousVarStatusExpression;
 
   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(ForEachTag.class);
 
@@ -1065,6 +1240,6 @@ public class ForEachTag
   private static final String _VIEW_ATTR_KEY =
     ForEachTag.class.getName() + ".VIEW.";
   private static final int _VIEW_ATTR_KEY_LENGTH = _VIEW_ATTR_KEY.length();
-  private static final String _META_DATA_MAP_KEY =
-    ForEachTag.class.getName() + ".ITER";
+  private static final String _EXECUTION_ID_MAP_KEY =
+    ForEachTag.class.getName() + ".EXEC_ID";
 }