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 2011/11/04 18:07:24 UTC

svn commit: r1197669 - in /myfaces/trinidad/branches/ar_1940: ./ trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/ trinidad-examples/trinidad-demo/src/main/java/org/apache/myfaces/trinidaddemo/tagDemos/ trinidad-examples/trinidad-demo/src/...

Author: arobinson74
Date: Fri Nov  4 17:07:23 2011
New Revision: 1197669

URL: http://svn.apache.org/viewvc?rev=1197669&view=rev
Log:
Merge of r1042072

Added:
    myfaces/trinidad/branches/ar_1940/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/ComponentIdSuffixStack.java
      - copied unchanged from r1042072, myfaces/trinidad/branches/ar_TRINIDAD-1940/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/ComponentIdSuffixStack.java
    myfaces/trinidad/branches/ar_1940/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/TagComponentBridge.java
      - copied unchanged from r1042072, myfaces/trinidad/branches/ar_TRINIDAD-1940/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/TagComponentBridge.java
    myfaces/trinidad/branches/ar_1940/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/TrinidadIterationTag.java
      - copied unchanged from r1042072, myfaces/trinidad/branches/ar_TRINIDAD-1940/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/TrinidadIterationTag.java
    myfaces/trinidad/branches/ar_1940/trinidad-examples/trinidad-demo/src/main/java/org/apache/myfaces/trinidaddemo/tagDemos/ForEachBean.java
      - copied unchanged from r1042072, myfaces/trinidad/branches/ar_TRINIDAD-1940/trinidad-examples/trinidad-demo/src/main/java/org/apache/myfaces/trinidaddemo/tagDemos/ForEachBean.java
    myfaces/trinidad/branches/ar_1940/trinidad-examples/trinidad-demo/src/main/webapp/demos/tags/forEach.jspx
      - copied unchanged from r1042072, myfaces/trinidad/branches/ar_TRINIDAD-1940/trinidad-examples/trinidad-demo/src/main/webapp/demos/tags/forEach.jspx
Modified:
    myfaces/trinidad/branches/ar_1940/   (props changed)
    myfaces/trinidad/branches/ar_1940/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/UIXComponentELTag.java
    myfaces/trinidad/branches/ar_1940/trinidad-examples/trinidad-demo/src/main/webapp/WEB-INF/faces-config.xml
    myfaces/trinidad/branches/ar_1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/el/TrinidadELResolver.java
    myfaces/trinidad/branches/ar_1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/taglib/ForEachTag.java

Propchange: myfaces/trinidad/branches/ar_1940/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Fri Nov  4 17:07:23 2011
@@ -3,6 +3,7 @@
 /myfaces/trinidad/branches/1.2.9.1-branch:697924,699406,699496
 /myfaces/trinidad/branches/TRINIDAD-1402:745675
 /myfaces/trinidad/branches/ar-1715:908782
+/myfaces/trinidad/branches/ar_TRINIDAD-1940:1042072
 /myfaces/trinidad/branches/ar_clientBehaviors:881469-891464
 /myfaces/trinidad/branches/jsf2_ajax.3:926625-936022
 /myfaces/trinidad/branches/jwaldman_StyleMap:754977-770778

Modified: myfaces/trinidad/branches/ar_1940/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/UIXComponentELTag.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/ar_1940/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/UIXComponentELTag.java?rev=1197669&r1=1197668&r2=1197669&view=diff
==============================================================================
--- myfaces/trinidad/branches/ar_1940/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/UIXComponentELTag.java (original)
+++ myfaces/trinidad/branches/ar_1940/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/UIXComponentELTag.java Fri Nov  4 17:07:23 2011
@@ -6,9 +6,9 @@
  *  to you under the Apache License, Version 2.0 (the
  *  "License"); you may not use this file except in compliance
  *  with the License.  You may obtain a copy of the License at
- * 
+ *
  *  http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  *  Unless required by applicable law or agreed to in writing,
  *  software distributed under the License is distributed on an
  *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -27,12 +27,14 @@ import java.util.Calendar;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
 
 import javax.el.MethodExpression;
 import javax.el.ValueExpression;
 
+import javax.faces.component.NamingContainer;
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIViewRoot;
 import javax.faces.webapp.UIComponentELTag;
@@ -64,6 +66,11 @@ abstract public class UIXComponentELTag 
   @Override
   public int doStartTag() throws JspException
   {
+    ComponentIdSuffixStack suffixStack =
+      ComponentIdSuffixStack.getInstance(pageContext);
+
+    _suffixId(suffixStack);
+
     int retVal = super.doStartTag();
 
     //pu: There could have been some validation error during property setting
@@ -71,10 +78,70 @@ abstract public class UIXComponentELTag 
     if (_validationError != null)
       throw new JspException(_validationError);
 
+    if (getComponentInstance() instanceof NamingContainer)
+    {
+      // If a naming container, do not carry component suffixes over from
+      // outside of the naming container.
+      suffixStack.suspend();
+    }
+
     return retVal;
   }
 
   @Override
+  public int doEndTag() throws JspException
+  {
+    UIComponent component = getComponentInstance();
+
+    // Apply changes once we have a stable UIComponent subtree is completely
+    //  created. End of document tag is a best bet.
+    if (component instanceof UIXDocument)
+    {
+      if (getCreated())
+      {
+        ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
+        // Used by SessionChangeManager to confirm that the state was not restored.
+        ec.getRequestMap().put(DOCUMENT_CREATED_KEY, Boolean.TRUE);
+      }
+      ChangeManager cm = RequestContext.getCurrentInstance().getChangeManager();
+      cm.applyComponentChangesForCurrentView(FacesContext.getCurrentInstance());
+    }
+
+    if (getComponentInstance() instanceof NamingContainer)
+    {
+      ComponentIdSuffixStack suffixStack =
+        ComponentIdSuffixStack.getInstance(pageContext);
+      suffixStack.resume();
+    }
+
+    // In the case where this component has had a suffix appended to it,
+    // clear the suffix and revert back to the original ID
+    setId(_origId);
+    _origId = null;
+
+    TagComponentBridge bridge = TagComponentBridge
+      .getInstance(pageContext);
+    bridge.notifyAfterComponentProcessed(getComponentInstance());
+
+    return super.doEndTag();
+  }
+
+  @Override
+  protected UIComponent findComponent(FacesContext context)
+    throws JspException
+  {
+    UIComponent component = super.findComponent(context);
+
+    // Notify any listening tags that this component was found or
+    // created (this method actually does create the compnoent if it
+    // was not found, so it is a bit mis-named in JSF)
+    TagComponentBridge bridge = TagComponentBridge
+      .getInstance(pageContext);
+    bridge.notifyComponentProcessed(component);
+
+    return component;
+  }
+
   protected final void setProperties(UIComponent component)
   {
     if (component instanceof UIViewRoot)
@@ -156,7 +223,7 @@ abstract public class UIXComponentELTag 
 
     if (expression.isLiteralText())
     {
-      bean.setProperty(key, 
+      bean.setProperty(key,
                        _parseNameTokensAsList(expression.getValue(null)));
     }
     else
@@ -182,7 +249,7 @@ abstract public class UIXComponentELTag 
 
     if (expression.isLiteralText())
     {
-      bean.setProperty(key, 
+      bean.setProperty(key,
                        _parseNameTokensAsSet(expression.getValue(null)));
     }
     else
@@ -209,7 +276,7 @@ abstract public class UIXComponentELTag 
     {
       Object value = expression.getValue(null);
       if (value != null)
-      { 
+      {
         if (value instanceof Number)
         {
           bean.setProperty(key, value);
@@ -453,10 +520,55 @@ abstract public class UIXComponentELTag 
     if (list == null)
       return null;
     else
-      return new HashSet(list);
+      return new HashSet<String>(list);
+  }
+
+  private void _suffixId(
+    ComponentIdSuffixStack suffixStack)
+  {
+    // Check to see if this component needs to have its ID suffixed.
+    // This will happen when the component is inside of a suffix
+    // supporting tag like the for each tag. This will allow iterating
+    // components to define how unique IDs will be generated
+    // for components without relying on the UIComponentClassicTagBase
+    // code, which in the Mojarra implementation of JSF appends "j_id_#"
+    // to each component beyond the first, but is not able to be used from
+    // code in a supported fashion.
+    String currentSuffix = suffixStack.getSuffix();
+    if (currentSuffix != null)
+    {
+      _origId = getId();
+      if (_origId == null)
+      {
+        // If the original ID is null, then we cannot suffix the ID correctly. We also cannot
+        // rely on the component ID generation of the UIComponentClassicTagBase is it prevents
+        // anyone from assigning an ID with the autogenerated prefix. As a result, we have to
+        // generate our own unique ID ourselves.
+        // This will ensure that component state will stick with Trinidad components with
+        // generated IDs if components are reordered.
+        Map<String, Object> viewAttrs = FacesContext.getCurrentInstance().getViewRoot()
+          .getAttributes();
+        Integer lastUniqueId = (Integer)viewAttrs.get(_UNIQUE_ID_KEY);
+        if (lastUniqueId == null)
+        {
+          lastUniqueId = 0;
+        }
+        else
+        {
+          ++lastUniqueId;
+        }
+
+        _origId = "tr_" + lastUniqueId;
+        viewAttrs.put(_UNIQUE_ID_KEY, lastUniqueId);
+
+        _origIdGenerated = true;
+      }
+
+      setId(_origId + currentSuffix);
+    }
   }
 
-  private static final TrinidadLogger _LOG = 
+  private static final TrinidadLogger _LOG =
     TrinidadLogger.createTrinidadLogger(UIXComponentELTag.class);
 
   // We rely strictly on ISO 8601 formats
@@ -469,11 +581,11 @@ abstract public class UIXComponentELTag 
     return sdf;
   }
 
-  //  No more used anywhere in Trinidad code, so deprecate since 2.0.x.
-  @Deprecated
-  public static final String DOCUMENT_CREATED_KEY = "org.apache.myfaces.trinidad.DOCUMENTCREATED";
+  private static final String _DOCUMENT_CREATED_KEY = "org.apache.myfaces.trinidad.DOCUMENTCREATED";
+  private final static String _UNIQUE_ID_KEY = UIXComponentELTag.class.getName() + ".ID";
 
   private MethodExpression  _attributeChangeListener;
   private String            _validationError;
-  
+  private String            _origId;
+  private boolean           _origIdGenerated = false;
 }

Modified: myfaces/trinidad/branches/ar_1940/trinidad-examples/trinidad-demo/src/main/webapp/WEB-INF/faces-config.xml
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/ar_1940/trinidad-examples/trinidad-demo/src/main/webapp/WEB-INF/faces-config.xml?rev=1197669&r1=1197668&r2=1197669&view=diff
==============================================================================
--- myfaces/trinidad/branches/ar_1940/trinidad-examples/trinidad-demo/src/main/webapp/WEB-INF/faces-config.xml (original)
+++ myfaces/trinidad/branches/ar_1940/trinidad-examples/trinidad-demo/src/main/webapp/WEB-INF/faces-config.xml Fri Nov  4 17:07:23 2011
@@ -3139,4 +3139,10 @@
     <managed-bean-class>org.apache.myfaces.trinidaddemo.SkinDirtyPhaseListener</managed-bean-class>
     <managed-bean-scope>request</managed-bean-scope>
   </managed-bean>
+
+  <managed-bean>
+    <managed-bean-name>forEachBean</managed-bean-name>
+    <managed-bean-class>org.apache.myfaces.trinidaddemo.tagDemos.ForEachBean</managed-bean-class>
+    <managed-bean-scope>request</managed-bean-scope>
+  </managed-bean>
 </faces-config>

Modified: myfaces/trinidad/branches/ar_1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/el/TrinidadELResolver.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/ar_1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/el/TrinidadELResolver.java?rev=1197669&r1=1197668&r2=1197669&view=diff
==============================================================================
--- myfaces/trinidad/branches/ar_1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/el/TrinidadELResolver.java (original)
+++ myfaces/trinidad/branches/ar_1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/el/TrinidadELResolver.java Fri Nov  4 17:07:23 2011
@@ -6,9 +6,9 @@
  *  to you under the Apache License, Version 2.0 (the
  *  "License"); you may not use this file except in compliance
  *  with the License.  You may obtain a copy of the License at
- * 
+ *
  *  http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  *  Unless required by applicable law or agreed to in writing,
  *  software distributed under the License is distributed on an
  *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -23,7 +23,6 @@ import java.beans.FeatureDescriptor;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
-
 import java.util.List;
 import java.util.Map;
 
@@ -31,6 +30,8 @@ import javax.el.ELContext;
 import javax.el.ELResolver;
 
 import org.apache.myfaces.trinidad.context.RequestContext;
+import org.apache.myfaces.trinidad.model.CollectionModel;
+
 
 /**
  * ELResolver implementation for Trinidad.  Serves up:
@@ -52,8 +53,10 @@ public class TrinidadELResolver
   {
   }
 
-  public Object getValue(ELContext elContext, Object base, 
-                         Object property)
+  public Object getValue(
+    ELContext elContext,
+    Object    base,
+    Object    property)
   {
     if (base == null)
     {
@@ -71,12 +74,22 @@ public class TrinidadELResolver
         return RequestContext.getCurrentInstance().getPageFlowScope();
       }
     }
-    
+    else if (base != null && base instanceof CollectionModel)
+    {
+      elContext.setPropertyResolved(true);
+      CollectionModel model = (CollectionModel)base;
+      return (property instanceof Integer) ?
+        model.getRowData(((Integer)property).intValue()) :
+        model.getRowData(property);
+    }
+
     return null;
   }
 
-  public Class<?> getType(ELContext elContext, Object base, 
-                          Object property)
+  public Class<?> getType(
+    ELContext elContext,
+    Object    base,
+    Object    property)
   {
     if (base == null)
     {
@@ -94,11 +107,16 @@ public class TrinidadELResolver
         return Map.class;
       }
     }
-    
+    else if (base != null && base instanceof CollectionModel)
+    {
+      elContext.setPropertyResolved(true);
+      return Object.class;
+    }
+
     return null;
   }
 
-  public void setValue(ELContext elContext, Object base, Object property, 
+  public void setValue(ELContext elContext, Object base, Object property,
                        Object value)
   {
     if (PAGE_FLOW_SCOPE_VARIABLE_NAME.equals(base) ||
@@ -110,13 +128,20 @@ public class TrinidadELResolver
     }
   }
 
-  public boolean isReadOnly(ELContext elContext, Object base, 
-                            Object property)
+  public boolean isReadOnly(
+    ELContext elContext,
+    Object    base,
+    Object    property)
   {
+    if (base != null && base instanceof CollectionModel)
+    {
+      return true;
+    }
+
     return false;
   }
 
-  public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext elContext, 
+  public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext elContext,
                                                            Object base)
   {
     if (base == null)
@@ -125,15 +150,61 @@ public class TrinidadELResolver
         _featureDescriptorList = _createFeatureDescriptorList();
       return _featureDescriptorList.iterator();
     }
-    
+    else if (base != null && base instanceof CollectionModel)
+    {
+      CollectionModel model = (CollectionModel)base;
+      int rowCount = model.getRowCount();
+
+      List<FeatureDescriptor> list = rowCount == -1 ?
+        new ArrayList<FeatureDescriptor>() :
+        new ArrayList<FeatureDescriptor>(rowCount);
+
+      Object origRowKey = model.getRowKey();
+      try
+      {
+        for (int rowIndex = 0; model.isRowAvailable(rowIndex); ++rowIndex)
+        {
+          model.setRowIndex(rowIndex);
+
+          String name = Integer.toString(rowIndex);
+          FeatureDescriptor descriptor = new FeatureDescriptor();
+
+          descriptor.setName(name);
+          descriptor.setDisplayName(name);
+          descriptor.setShortDescription("");
+          descriptor.setExpert(false);
+          descriptor.setHidden(false);
+          descriptor.setPreferred(true);
+          descriptor.setValue("type", Integer.class);
+          descriptor.setValue("resolvableAtDesignTime", Boolean.TRUE);
+
+          list.add(descriptor);
+        }
+      }
+      finally
+      {
+        model.setRowKey(origRowKey);
+      }
+
+      return list.iterator();
+    }
+
     return null;
   }
 
-  public Class<?> getCommonPropertyType(ELContext elContext, Object base)
+  public Class<?> getCommonPropertyType(
+    ELContext elContext,
+    Object    base)
   {
     if (base == null)
+    {
       return String.class;
-    
+    }
+    else if (base != null && base instanceof CollectionModel)
+    {
+      return Object.class;
+    }
+
     return null;
   }
 
@@ -151,7 +222,7 @@ public class TrinidadELResolver
     requestContext.setValue(ELResolver.RESOLVABLE_AT_DESIGN_TIME, false);
     requestContext.setValue(ELResolver.TYPE, RequestContext.class);
     list.add(requestContext);
-    
+
     // FeatureDescriptor for "pageFlowScope"
     FeatureDescriptor pageFlowScope = new FeatureDescriptor();
     pageFlowScope.setName(PAGE_FLOW_SCOPE_VARIABLE_NAME);
@@ -160,7 +231,7 @@ public class TrinidadELResolver
     pageFlowScope.setValue(ELResolver.RESOLVABLE_AT_DESIGN_TIME, false);
     pageFlowScope.setValue(ELResolver.TYPE, Map.class);
     list.add(pageFlowScope);
-    
+
     // FeatureDescriptor for "processScope"
     FeatureDescriptor processScope = new FeatureDescriptor();
     processScope.setName(PAGE_FLOW_SCOPE_VARIABLE_NAME);

Modified: myfaces/trinidad/branches/ar_1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/taglib/ForEachTag.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/ar_1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/taglib/ForEachTag.java?rev=1197669&r1=1197668&r2=1197669&view=diff
==============================================================================
--- myfaces/trinidad/branches/ar_1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/taglib/ForEachTag.java (original)
+++ myfaces/trinidad/branches/ar_1940/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/taglib/ForEachTag.java Fri Nov  4 17:07:23 2011
@@ -6,9 +6,9 @@
  *  to you under the Apache License, Version 2.0 (the
  *  "License"); you may not use this file except in compliance
  *  with the License.  You may obtain a copy of the License at
- * 
+ *
  *  http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  *  Unless required by applicable law or agreed to in writing,
  *  software distributed under the License is distributed on an
  *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -19,8 +19,11 @@
 package org.apache.myfaces.trinidadinternal.taglib;
 
 import java.io.Serializable;
+
 import java.lang.reflect.Array;
+
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -28,13 +31,20 @@ import javax.el.ELContext;
 import javax.el.PropertyNotWritableException;
 import javax.el.ValueExpression;
 import javax.el.VariableMapper;
+
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIViewRoot;
 import javax.faces.context.FacesContext;
+import javax.faces.webapp.UIComponentClassicTagBase;
+
 import javax.servlet.jsp.JspException;
 import javax.servlet.jsp.JspTagException;
-import javax.servlet.jsp.jstl.core.IndexedValueExpression;
+import javax.servlet.jsp.tagext.JspIdConsumer;
 
 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
-import org.apache.myfaces.trinidad.webapp.TrinidadTagSupport;
+import org.apache.myfaces.trinidad.model.CollectionModel;
+import org.apache.myfaces.trinidad.webapp.TrinidadIterationTag;
+
 
 //JSTL Core Library - <c:forEach> Tag
 //===================================
@@ -55,10 +65,18 @@ import org.apache.myfaces.trinidad.webap
 //</c:forEach>
 
 /**
- *
+ * Trinidad JSP for each tag that is based on the JSTL c:forEach tag
+ * but provides additinal functionality.
  */
-public class ForEachTag extends TrinidadTagSupport
+public class ForEachTag
+  extends TrinidadIterationTag
+  implements JspIdConsumer
 {
+  public ForEachTag()
+  {
+    System.out.println("ForEachTag created");
+  }
+
   public void setItems(ValueExpression items)
   {
     if (items.isLiteralText())
@@ -93,14 +111,51 @@ public class ForEachTag extends Trinidad
   }
 
   @Override
-  public int doStartTag() throws JspException
+  public void setJspId(String id)
   {
+    System.out.println("setJspId: " + id);
+    // If the view attributes are null, then this is the first time this method has been called
+    // for this request.
+    if (_viewAttributes == null)
+    {
+      // 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 + id.length())
+        .append(_VIEW_ATTR_KEY)
+        .append(id)
+        .toString();
+
+      FacesContext facesContext = FacesContext.getCurrentInstance();
+
+      // 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();
+
+      // We can cache the view attributes in the tag as a JSP tag marked with JspIdConsumer
+      // is never reused.
+      _viewAttributes = viewRoot.getAttributes();
+
+      // Create a new iteration map per-request. This will ensure that values from the previous
+      // request are not kept.
+      _iterationMap = new HashMap<Integer, IterationMetaData>();
+      _viewAttributes.put(_iterationMapKey, _iterationMap);
+    }
+  }
+
+  @Override
+  protected int doStartTagImpl()
+    throws JspException
+  {
+    System.out.println("doStartTagImpl");
     _validateAttributes();
 
-    FacesContext context = FacesContext.getCurrentInstance();
+    FacesContext facesContext = FacesContext.getCurrentInstance();
+    int          length;
+
     _currentBegin = (_begin == null) ? 0 : _begin.intValue();
     _isFirst = true;
-    int length;
 
     if (null != _items)
     {
@@ -110,39 +165,32 @@ public class ForEachTag extends Trinidad
       // 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(context.getELContext());//pageContext.getELContext());
+      Object items = _items.getValue(facesContext.getELContext());//pageContext.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 == null)
       {
-        if (_LOG.isFine())
-          _LOG.fine("Items expression " + _items + " resolved to null.");
+        _LOG.fine("Items expression {0} resolved to null.", _items);
         return SKIP_BODY;
       }
 
-      _itemsValue = items;
-      // =-=AEW <c:forEach> supports arbitrary collections;  but
-      // JSF only supports List in its EL.
-      if (items instanceof List)
-        length = ((List) items).size();
-      else if (items.getClass().isArray())
-        length = Array.getLength(items);
-      else
-        throw new JspException(_LOG.getMessage(
-          "MUST_POINT_TO_LIST_OR_ARRAY"));
+      // 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();
+
       if (length == 0)
       {
-        if (_LOG.isFine())
-          _LOG.fine("Items found at " + _items + " is empty.");
+        _LOG.fine("Items found at {0} is empty.", _items);
         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 (_LOG.isFine())
-          _LOG.fine("Size of 'items' is less than 'begin'");
+        _LOG.fine("Size of 'items' is less than 'begin'");
         return SKIP_BODY;
       }
 
@@ -151,57 +199,49 @@ public class ForEachTag extends Trinidad
       //  the iteration to where the collection ends. A mimic of c:forEach and
       //  fix for bug 4029853.
       if (length < _currentEnd)
+      {
         _currentEnd = length - 1;
+      }
     }
     else
     {
       _currentEnd = (_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)
+    {
       return SKIP_BODY;
+    }
 
     _isLast = _currentIndex == _currentEnd;
 
     // Save off the previous deferred variables
-    VariableMapper vm = 
+    VariableMapper vm =
       pageContext.getELContext().getVariableMapper();
-    if (_var != null)
-      _previousDeferredVar = vm.resolveVariable(_var);
 
-    if (null != _varStatus)
+    if (_var != null)
     {
-      _previousDeferredVarStatus = vm.resolveVariable(_varStatus);
-      _propertyReplacementMap = new HashMap<String, Object>(9, 1);
-      _propertyReplacementMap.put("begin", Integer.valueOf(_currentBegin));
-      _propertyReplacementMap.put("end", Integer.valueOf(_currentEnd));
-      _propertyReplacementMap.put("step", Integer.valueOf(_currentStep));
-      _propertyReplacementMap.put("count", Integer.valueOf(_currentCount));
-      _propertyReplacementMap.put("index", Integer.valueOf(_currentIndex));
-      // FIXME: Can we support "current" efficiently?
-      //      _propertyReplacementMap.put("current", _varReplacement);
-      _propertyReplacementMap.put(
-        "first",
-        (_isFirst)? Boolean.TRUE:Boolean.FALSE);
-      _propertyReplacementMap.put(
-        "last",
-        (_isLast)? Boolean.TRUE:Boolean.FALSE);
+      // Store off the current variable so that it may be restored after tag processing
+      _previousDeferredVar = vm.resolveVariable(_var);
     }
 
     if (_LOG.isFiner())
     {
-      _LOG.finer("Iterating from " + _currentIndex + " to " + _currentEnd +
-                 " by " + _currentStep);
+      _LOG.finer("Iterating from {0} to {1} by {2}",
+        new Object[] { _currentIndex, _currentEnd, _currentStep });
     }
 
-    // Update the variables
-    _updateVars();
+    _parentComponent = _getParentComponent();
+
+    _updateVars(true);
 
     return EVAL_BODY_INCLUDE;
   }
@@ -209,46 +249,34 @@ public class ForEachTag extends Trinidad
   @Override
   public int doAfterBody()
   {
+    System.out.println("doAfterBody");
     _currentIndex += _currentStep;
-    _currentCount += 1;
-
-    //pu: if there is no varStatus set, no point in keeping loop status
-    //  variables updated.
-    if (null != _varStatus)
-    {
-      if (_isFirst)
-      {
-        _propertyReplacementMap.put("first", Boolean.FALSE);
-        _isFirst = false;
-      }
-
-      _isLast = (_currentIndex == _currentEnd);
-      if (_isLast)
-      {
-        _propertyReplacementMap.put("last", _isLast);
-      }
-      _propertyReplacementMap.put("count", Integer.valueOf(_currentCount));
-      _propertyReplacementMap.put("index", Integer.valueOf(_currentIndex));
-      // FIXME: Can we support "current" efficiently?
-      //      _propertyReplacementMap.put("current", _varReplacement);
-    }
+    ++_currentCount;
+    _isFirst = false;
+    _isLast = _currentIndex == _currentEnd;
 
     // If we're at the end, bail
     if (_currentEnd < _currentIndex)
     {
       // Restore EL state
-      VariableMapper vm = 
+      VariableMapper vm =
         pageContext.getELContext().getVariableMapper();
       if (_var != null)
         vm.setVariable(_var, _previousDeferredVar);
       if (_varStatus != null)
         vm.setVariable(_varStatus, _previousDeferredVarStatus);
 
+      if (_suffixPushed)
+      {
+        popComponentSuffix();
+        _suffixPushed = false;
+      }
+
       return SKIP_BODY;
     }
-    
+
     // Otherwise, update the variables and go again
-    _updateVars();
+    _updateVars(true);
 
     return EVAL_BODY_AGAIN;
   }
@@ -264,52 +292,166 @@ public class ForEachTag extends Trinidad
     _end = null;
     _step = null;
     _items = null;
-    _itemsValue = null;
     _var = null;
     _varStatus = null;
-    _propertyReplacementMap = null;
     _previousDeferredVar = null;
     _previousDeferredVarStatus = null;
+
+    System.out.println("release called");
+    _iterationId = null;
+    _iterationData = null;
+    _viewAttributes = null;
+    _iterationMapKey = null;
+    _iterationMap = null;
+    _itemsWrapper = null;
+
+    _parentComponent = null;
+
+    _suffixPushed = false;
+}
+
+  @Override
+  public final void childComponentProcessed(
+    UIComponent component)
+  {
+    // This code is called when a component is created or found, see which it is.
+    // We are only interested in components that are directly under our parent.
+    if (component.getParent() == _parentComponent)
+    {
+      Map<String, Object> compAttrs = component.getAttributes();
+      Integer iterationId = (Integer)compAttrs.get(_ITERATION_ID_KEY);
+      System.out.println("childComponentProcessed: " + component +
+                         " Previous component iteration ID: " + iterationId);
+
+      if (iterationId == null)
+      {
+        // This is a new component, use the current iteration ID
+        compAttrs.put(_ITERATION_ID_KEY, _iterationId);
+
+        // Remember that the iteration ID was used
+        _iterationIdRequiresIncrement = true;
+      }
+      else
+      {
+        // This component has been seen before, register the old iteration ID with the iteration
+        // map so that the EL may look up the iteration data.
+        _iterationMap.put(iterationId, _iterationData);
+      }
+    }
+  }
+
+  @Override
+  public void afterChildComponentProcessed(
+    UIComponent component)
+  {
+    // This code is called when a component is created or found, see which it is.
+    // We are only interested in components that are directly under our parent.
+    if (component.getParent() == _parentComponent)
+    {
+      System.out.println("afterChildComponentProcessed: " + component);
+      // Store a unique iteration ID in each component. That way, if a component is ever moved
+      // from one iteration to another between requests, but not all the components, no problems
+      // will ensue. The use case is that ${} is used in the ID of one or more child components
+      // of a for each loop to pin the component to the item in the collection rather than the
+      // for each index.
+      if (_iterationIdRequiresIncrement)
+      {
+        _updateVars(false);
+        _iterationIdRequiresIncrement = false;
+      }
+    }
+  }
+
+  private UIComponent _getParentComponent()
+  {
+    UIComponentClassicTagBase tag = UIComponentClassicTagBase.getParentUIComponentClassicTagBase(
+                                      pageContext);
+    return tag == null ? null : tag.getComponentInstance();
   }
 
   // Push new values into the VariableMapper and the pageContext
-  private void _updateVars()
+  private void _updateVars(
+    boolean createNewIterationData)
   {
-    VariableMapper vm = 
+    VariableMapper vm =
       pageContext.getELContext().getVariableMapper();
+
+    // Generate a new iteration ID
+    _updateIterationId();
+
     if (_var != null)
     {
       // Catch programmer error where _var has been set but
       // _items has not
       if (_items != null)
       {
-        ValueExpression iterated = new IndexedValueExpression(_items,
-                                                              _currentIndex);
-        vm.setVariable(_var, iterated);
+        // Determine if we need to use a key or an index based value expression
+        // for the current items.
+        ValueExpression expr;
+        if (_itemsWrapper.isKeyBased())
+        {
+          // Use a key to get the value
+          Serializable key = _asSerializable(_itemsWrapper.getKey(_currentIndex));
+          expr = new KeyedValueExpression(_items, key);
+        }
+        else
+        {
+          // Use indirection to get the index from the iteration data using the iteration ID
+          // so that the expression is not hard-coded to one index
+          expr = new IndexedValueExpression(_iterationId, _iterationMapKey, _items);
+        }
+
+        vm.setVariable(_var, expr);
       }
-      
+
       // Ditto (though, technically, one check for
       // _items is sufficient, because if _items evaluated
       // to null, we'd skip the whole loop)
-      Object items = _itemsValue;
-      if (items != null)
+      if (_itemsWrapper != null)
       {
-        Object item;
-        if (items instanceof List)
-          item = ((List) items).get(_currentIndex);
-        else
-          item = Array.get(items, _currentIndex);
-
+        Object item = _itemsWrapper.getValue(_currentIndex);
         pageContext.setAttribute(_var, item);
       }
     }
-    
+
+    Object key = _itemsWrapper == null ?
+      _currentIndex : _itemsWrapper.getKey(_currentIndex);
+
+    if (!(key instanceof Serializable))
+    {
+      throw new IllegalStateException("For each loop keys must be serializable");
+    }
+
+    if (createNewIterationData || _iterationData == null)
+    {
+      _iterationData = new IterationMetaData((Serializable)key, _isFirst, _isLast,
+                         _currentBegin, _currentCount, _currentIndex, _currentEnd);
+    }
+
+    // Store the iteration data into the view attributes to allow the EL expressions
+    // gain access to it
+    _iterationMap.put(_iterationId, _iterationData);
+
     if (_varStatus != null)
     {
-      pageContext.setAttribute(_varStatus, _propertyReplacementMap);
-      ValueExpression constant = new Constants(
-                                      new HashMap(_propertyReplacementMap));
-      vm.setVariable(_varStatus, constant);
+      _previousDeferredVarStatus = vm.resolveVariable(_varStatus);
+
+
+      // Store a new var status value expression into the variable mapper
+      vm.setVariable(_varStatus, new VarStatusValueExpression(_iterationId, _iterationMapKey));
+    }
+
+    if (_suffixPushed)
+    {
+      popComponentSuffix();
+      _suffixPushed = false;
+    }
+
+    // To do, support non-key pass-through
+    if (_itemsWrapper != null && _itemsWrapper.isIdSuffixSupported())
+    {
+      pushComponentSuffix("_" + key.toString());
+      _suffixPushed = true;
     }
   }
 
@@ -364,21 +506,84 @@ public class ForEachTag extends Trinidad
       throw new JspTagException("'step' < 1");
   }
 
-  // Basic ValueExpression that always returns a constant object
-  static private class Constants extends ValueExpression
-                                 implements Serializable
+  private void _updateIterationId()
   {
-    public Constants(Object o)
+    Integer intObj = (Integer)_viewAttributes.get(_ITERATION_ID_KEY);
+
+    if (intObj == null)
     {
-      _o = o;
+      // By using MIN_VALUE, we can achive 4.2E9 requests for the current view (should
+      // be way more than we need)
+      _iterationId = new Integer(Integer.MIN_VALUE);
+    }
+    else
+    {
+      _iterationId = intObj + 1;
     }
 
-    public Object getValue(ELContext context)
+    System.out.println("Iteration ID is now " + _iterationId);
+    _viewAttributes.put(_ITERATION_ID_KEY, _iterationId);
+
+    if (_iterationData != null)
     {
-      return _o;
+      _iterationMap.put(_iterationId, _iterationData);
     }
+  }
 
-    public void setValue(ELContext context, Object value)
+  private Serializable _asSerializable(Object key)
+  {
+    if (key instanceof Serializable)
+    {
+      return (Serializable)key;
+    }
+    else
+    {
+      throw new IllegalStateException("The forEach tag only supports serializable keys for " +
+        "maps and collection models");
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private static ItemsWrapper _buildItemsWrapper(
+    Object items)
+  {
+    if (items instanceof Array)
+    {
+      return new ArrayWrapper(items);
+    }
+    else if (items instanceof List)
+    {
+      return new ListWrapper((List<?>)items);
+    }
+    else if (items instanceof CollectionModel)
+    {
+      return new CollectionModelWrapper((CollectionModel)items);
+    }
+    else if (items instanceof Map)
+    {
+      return new MapWrapper((Map<?, ?>)items);
+    }
+    else
+    {
+      throw new IllegalArgumentException("Illegal items type: " + items.getClass());
+    }
+  }
+
+  private static abstract class ForEachBaseValueExpression
+    extends ValueExpression
+    implements Serializable
+  {
+    protected ForEachBaseValueExpression(
+      Integer iterationId,
+      String  mapKey)
+    {
+      _iterationId = iterationId;
+      _mapKey      = mapKey;
+    }
+
+    public void setValue(
+      ELContext context,
+      Object    value)
     {
       throw new PropertyNotWritableException();
     }
@@ -390,12 +595,7 @@ public class ForEachTag extends Trinidad
 
     public Class getType(ELContext context)
     {
-      return _o.getClass();
-    }
-
-    public Class getExpectedType()
-    {
-      return _o.getClass();
+      return getExpectedType();
     }
 
     public String getExpressionString()
@@ -410,7 +610,7 @@ public class ForEachTag extends Trinidad
 
     public int hashCode()
     {
-      return _o.hashCode();
+      return _iterationId.hashCode() | _mapKey.hashCode();
     }
 
     public boolean isLiteralText()
@@ -418,10 +618,488 @@ public class ForEachTag extends Trinidad
       return true;
     }
 
-    private Object _o;    
+    protected IterationMetaData getIterationMetaData()
+    {
+      // A value expression should ever only be used for one view root,
+      // so keep a transient reference to the view to increase performance
+      if (_viewAttributes == null)
+      {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        UIViewRoot view = facesContext.getViewRoot();
+        _viewAttributes = view.getAttributes();
+      }
+
+      // Get the map from the view attributes created by the tag:
+      @SuppressWarnings("unchecked")
+      Map<Integer, IterationMetaData> map = (Map<Integer, IterationMetaData>)
+        _viewAttributes.get(_mapKey);
+
+      // The map will be null if, somehow, the component for a given for each loop execution
+      // is still around, but the for each loop did not match the component during this request
+      // (probably a temporary state until the unmatched component is removed).
+      return map == null ? null : map.get(_iterationId);
+    }
+
+    private final Integer _iterationId;
+    private final String  _mapKey;
+    private transient Map<String, Object> _viewAttributes;
+
+    @SuppressWarnings("compatibility:29745293788177755")
+    private static final long serialVersionUID = 1L;
+  }
+
+  private static class KeyedValueExpression
+    extends ValueExpression
+  {
+    private KeyedValueExpression(
+      ValueExpression itemsExpression,
+      Serializable    key)
+    {
+      _itemsExpression = itemsExpression;
+      _key = key;
+    }
+
+    public Object getValue(ELContext context)
+    {
+      Object items = _itemsExpression.getValue(context);
+
+      if (items == null)
+      {
+        return null;
+      }
+
+      context.setPropertyResolved(false);
+      return context.getELResolver().getValue(context, items, _key);
+    }
+
+    public void setValue(ELContext context, Object value)
+    {
+      Object items = _itemsExpression.getValue(context);
+
+      if (items != null)
+      {
+        context.setPropertyResolved(false);
+        context.getELResolver().setValue(context, items, _key, value);
+      }
+    }
+
+    public boolean isReadOnly(ELContext context)
+    {
+      Object items = _itemsExpression.getValue(context);
+      if (items == null)
+      {
+        return true;
+      }
+
+      return context.getELResolver().isReadOnly(context, items, _key);
+    }
+
+    public Class<?> getType(ELContext context)
+    {
+      return null;
+    }
+
+    public Class<?> getExpectedType()
+    {
+      return Object.class;
+    }
+
+    public String getExpressionString()
+    {
+      return _itemsExpression.getExpressionString();
+    }
+
+    public boolean isLiteralText()
+    {
+      return false;
+    }
+
+    @Override
+    public int hashCode()
+    {
+      return _itemsExpression.hashCode();
+    }
+
+    public boolean equals(Object obj)
+    {
+      return _itemsExpression.equals(obj);
+    }
+
+    private final ValueExpression _itemsExpression;
+    private final Serializable    _key;
+
+    @SuppressWarnings("compatibility:-8374272730669095059")
+    private static final long serialVersionUID = 1L;
+  }
+
+  /**
+   * Value expression that looks up the var value using an index.
+   * This class is written in such a way that the index is dynamic, so that if a component is
+   * used in different iterations of the for each loop across requests, the correct variable
+   * is returned.
+   */
+  private static class IndexedValueExpression
+    extends ForEachBaseValueExpression
+    implements Serializable
+  {
+    private IndexedValueExpression(
+      Integer         iterationId,
+      String          mapKey,
+      ValueExpression itemsExpression)
+    {
+      super(iterationId, mapKey);
+      _itemsExpression = itemsExpression;
+    }
+
+    public Object getValue(ELContext context)
+    {
+      // By using a layer of indirection, we can ensure that the correct value is returned for
+      // users who pin their component ID to the varStatus so that the component is processed
+      // during a different index across requests. We can use the index from the varStatus
+      // to determine the correct index to use in the items collection.
+      IterationMetaData data = getIterationMetaData();
+      if (data == null)
+      {
+        return null;
+      }
+
+      Object items = _itemsExpression.getValue(context);
+      if (items == null)
+      {
+        return null;
+      }
+
+      context.setPropertyResolved(false);
+      return context.getELResolver().getValue(context, items, data.getIndex());
+    }
+
+    public Class<?> getExpectedType()
+    {
+      return Object.class;
+    }
+
+    @Override
+    public int hashCode()
+    {
+      return _itemsExpression.hashCode();
+    }
+
+    public boolean equals(Object obj)
+    {
+      return _itemsExpression.equals(obj);
+    }
+
+    private final ValueExpression _itemsExpression;
+
+    @SuppressWarnings("compatibility:1734834404228501647")
     private static final long serialVersionUID = 1L;
   }
 
+  /**
+   * Value Expression instance used to get an object containing the var status properties.
+   */
+  static private class VarStatusValueExpression
+    extends ForEachBaseValueExpression
+    implements Serializable
+  {
+    private VarStatusValueExpression(
+      Integer iterationId,
+      String  mapKey)
+    {
+      super(iterationId, mapKey);
+    }
+
+    public Object getValue(ELContext context)
+    {
+      return super.getIterationMetaData();
+    }
+
+    public Class getExpectedType()
+    {
+      return IterationMetaData.class;
+    }
+
+    @SuppressWarnings("compatibility:-3014844132563306923")
+    private static final long serialVersionUID = 1L;
+  }
+
+  private abstract static class ItemsWrapper
+  {
+    public abstract Object getKey(int index);
+    public abstract Object getValue(int index);
+    public abstract int getSize();
+    public abstract boolean isIdSuffixSupported();
+    public abstract boolean isKeyBased();
+  }
+
+  private static class CollectionModelWrapper
+    extends ItemsWrapper
+  {
+    private CollectionModelWrapper(
+      CollectionModel collectionModel)
+    {
+      _collectionModel = collectionModel;
+    }
+
+    @Override
+    public Object getKey(int index)
+    {
+      Object oldRowKey = _collectionModel.getRowKey();
+      try
+      {
+        _collectionModel.setRowIndex(index);
+        return _collectionModel.getRowKey();
+      }
+      finally
+      {
+        _collectionModel.setRowKey(oldRowKey);
+      }
+    }
+
+    @Override
+    public Object getValue(int index)
+    {
+      return _collectionModel.getRowData(index);
+    }
+
+    @Override
+    public int getSize()
+    {
+      return _collectionModel.getRowCount();
+    }
+
+    public boolean isIdSuffixSupported()
+    {
+      return true;
+    }
+
+    public boolean isKeyBased()
+    {
+      return true;
+    }
+
+    private CollectionModel _collectionModel;
+  }
+
+  private static class MapWrapper
+    extends ItemsWrapper
+  {
+    private MapWrapper(
+      Map<?, ?> map)
+    {
+      _map = map;
+    }
+
+    @Override
+    public Object getKey(int index)
+    {
+      _moveToIndex(index);
+      return _currentEntry.getKey();
+    }
+
+    @Override
+    public Object getValue(int index)
+    {
+      _moveToIndex(index);
+      return _currentEntry.getValue();
+    }
+
+    @Override
+    public int getSize()
+    {
+      return _map.size();
+    }
+
+    private void _moveToIndex(int index)
+    {
+      if (index == _currentIndex)
+      {
+        return;
+      }
+
+      if (index < _currentIndex)
+      {
+        // Need to re-create the iterator
+        _iter = _map.entrySet().iterator();
+        _currentIndex = -1;
+      }
+
+      for (int i = _currentIndex; i < index; ++i)
+      {
+        _currentEntry = _iter.next();
+      }
+
+      _currentIndex = index;
+    }
+
+    public boolean isIdSuffixSupported()
+    {
+      return true;
+    }
+
+    public boolean isKeyBased()
+    {
+      return true;
+    }
+
+    private final Map<?, ?>                     _map;
+    private Iterator<? extends Map.Entry<?, ?>> _iter;
+    private Map.Entry<?, ?>                     _currentEntry;
+    private int                                 _currentIndex = -1;
+  }
+
+  private static class ListWrapper
+    extends ItemsWrapper
+  {
+    private ListWrapper(
+      List<?>  list)
+    {
+      _list = list;
+    }
+
+    @Override
+    public Object getKey(int index)
+    {
+      return index;
+    }
+
+    @Override
+    public Object getValue(int index)
+    {
+      return _list.get(index);
+    }
+
+    @Override
+    public int getSize()
+    {
+      return _list.size();
+    }
+
+    public boolean isIdSuffixSupported()
+    {
+      return false;
+    }
+
+    public boolean isKeyBased()
+    {
+      return false;
+    }
+
+    private final List<?> _list;
+  }
+
+  private static class ArrayWrapper
+    extends ItemsWrapper
+  {
+    private ArrayWrapper(
+      Object      array)
+    {
+      _array = array;
+    }
+
+    @Override
+    public Object getKey(int index)
+    {
+      return index;
+    }
+
+    @Override
+    public Object getValue(int index)
+    {
+      return Array.get(_array, index);
+    }
+
+    @Override
+    public int getSize()
+    {
+      return Array.getLength(_array);
+    }
+
+    public boolean isIdSuffixSupported()
+    {
+      return false;
+    }
+
+    public boolean isKeyBased()
+    {
+      return false;
+    }
+
+    private final Object _array;
+  }
+
+  /**
+   * Data that is used for the children content of the tag. This contains
+   * the var status information.
+   */
+  public static class IterationMetaData
+    implements Serializable
+  {
+    private IterationMetaData(
+      Serializable key,
+      boolean      first,
+      boolean      last,
+      int          begin,
+      int          count,
+      int          index,
+      int          end)
+    {
+      super();
+      this._key = key;
+      this._first = first;
+      this._last = last;
+      this._begin = begin;
+      this._count = count;
+      this._index = index;
+      this._end = end;
+    }
+
+    public final boolean isLast()
+    {
+      return _last;
+    }
+
+    public final boolean isFirst()
+    {
+      return _first;
+    }
+
+    public final int getIndex()
+    {
+      return _index;
+    }
+
+    public final int getCount()
+    {
+      return _count;
+    }
+
+    public final int getBegin()
+    {
+      return _begin;
+    }
+
+    public final int getEnd()
+    {
+      return _end;
+    }
+
+    public final Serializable getKey()
+    {
+      return _key;
+    }
+
+    private boolean _last;
+    private boolean _first;
+    private int _begin;
+    private int _count;
+    private int _index;
+    private int _end;
+    private Serializable _key;
+
+    private static final long serialVersionUID = 0L;
+  }
+
   private int _currentBegin;
   private int _currentIndex;
   private int _currentEnd;
@@ -429,11 +1107,9 @@ public class ForEachTag extends Trinidad
   private int _currentCount;
   private boolean _isFirst;
   private boolean _isLast;
-
+  private boolean _iterationIdRequiresIncrement;
 
   private ValueExpression _items;
-  private Object          _itemsValue;
-
   private ValueExpression _beginVE;
   private ValueExpression _endVE;
   private ValueExpression _stepVE;
@@ -442,16 +1118,29 @@ public class ForEachTag extends Trinidad
   private Integer _end;
   private Integer _step;
 
+  private boolean _suffixPushed = false;
+
+  private UIComponent _parentComponent;
+
+  private Integer _iterationId;
+  private IterationMetaData _iterationData;
+  private Map<String, Object> _viewAttributes;
+
+  private String _iterationMapKey;
+  private Map<Integer, IterationMetaData> _iterationMap;
+  private ItemsWrapper _itemsWrapper;
+
   private String _var;
   private String _varStatus;
-  
+
   // Saved values on the VariableMapper
   private ValueExpression _previousDeferredVar;
   private ValueExpression _previousDeferredVarStatus;
 
-  // Map for properties referred off from 'varStatus' and their replacements
-  private Map<String, Object> _propertyReplacementMap;
-
   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(ForEachTag.class);
+  private static final String _VIEW_ATTR_KEY = ForEachTag.class.getName() + ".";
+  private static final int _VIEW_ATTR_KEY_LENGTH = _VIEW_ATTR_KEY.length();
+  private static final String _ITERATION_ID_KEY =
+    ForEachTag.class.getName() + ".ITER";
   private static final long serialVersionUID = 1L;
 }