You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2011/07/22 23:01:50 UTC

svn commit: r1149735 - in /myfaces/core/trunk/api/src: main/java/javax/faces/component/UIData.java main/java/javax/faces/model/DataModel.java test/java/javax/faces/component/UIDataRowStateTest.java

Author: lu4242
Date: Fri Jul 22 21:01:49 2011
New Revision: 1149735

URL: http://svn.apache.org/viewvc?rev=1149735&view=rev
Log:
MYFACES-3236 UIData performance improvements

Added:
    myfaces/core/trunk/api/src/test/java/javax/faces/component/UIDataRowStateTest.java
Modified:
    myfaces/core/trunk/api/src/main/java/javax/faces/component/UIData.java
    myfaces/core/trunk/api/src/main/java/javax/faces/model/DataModel.java

Modified: myfaces/core/trunk/api/src/main/java/javax/faces/component/UIData.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/api/src/main/java/javax/faces/component/UIData.java?rev=1149735&r1=1149734&r2=1149735&view=diff
==============================================================================
--- myfaces/core/trunk/api/src/main/java/javax/faces/component/UIData.java (original)
+++ myfaces/core/trunk/api/src/main/java/javax/faces/component/UIData.java Fri Jul 22 21:01:49 2011
@@ -130,6 +130,8 @@ public class UIData extends UIComponentB
     private static final int PROCESS_UPDATES = 3;
     //private static final String SKIP_ITERATION_HINT = "javax.faces.visit.SKIP_ITERATION";
 
+    private static final Object[] LEAF_NO_STATE = new Object[]{null,null};
+
     private int _rowIndex = -1;
 
     // Holds for each row the states of the child components of this UIData.
@@ -523,16 +525,26 @@ public class UIData extends UIComponentB
                 // that we haven't visited before, ie a "saved state" that can
                 // be pushed to the "restoreState" method of all the child
                 // components to set them up to represent a clean row.
-                _initialDescendantComponentState = saveDescendantComponentStates(getChildren().iterator(), false);
+                _initialDescendantComponentState = saveDescendantComponentStates(this, false, false);
             }
         }
         else
         {
-            // We are currently positioned on some row, and are about to
-            // move off it, so save the (partial) state of the components
-            // representing the current row. Later if this row is revisited
-            // then we can restore this state.
-            _rowStates.put(getContainerClientId(facesContext), saveDescendantComponentStates(getChildren().iterator(), false));
+            // If no initial component state, there are no EditableValueHolder instances,
+            // and that means there is no state to be saved for the current row, so we can
+            // skip row state saving code safely.
+            if (_initialDescendantComponentState != null)
+            {
+                // We are currently positioned on some row, and are about to
+                // move off it, so save the (partial) state of the components
+                // representing the current row. Later if this row is revisited
+                // then we can restore this state.
+                Collection<Object[]> savedRowState = saveDescendantComponentStates(this, false, false);
+                if (savedRowState != null)
+                {
+                    _rowStates.put(getContainerClientId(facesContext), savedRowState);
+                }
+            }
         }
 
         _rowIndex = rowIndex;
@@ -567,7 +579,15 @@ public class UIData extends UIComponentB
         if (_rowIndex == -1)
         {
             // reset components to initial state
-            restoreDescendantComponentStates(getChildren().iterator(), _initialDescendantComponentState, false);
+            // If no initial state, skip row restore state code
+            if (_initialDescendantComponentState != null)
+            {
+                restoreDescendantComponentStates(this, false, _initialDescendantComponentState, false);
+            }
+            else
+            {
+                restoreDescendantComponentWithoutRestoreState(this, false, false);
+            }
         }
         else
         {
@@ -577,7 +597,15 @@ public class UIData extends UIComponentB
                 // We haven't been positioned on this row before, so just
                 // configure the child components of this component with
                 // the standard "initial" state
-                restoreDescendantComponentStates(getChildren().iterator(), _initialDescendantComponentState, false);
+                // If no initial state, skip row restore state code
+                if (_initialDescendantComponentState != null)
+                {
+                    restoreDescendantComponentStates(this, false, _initialDescendantComponentState, false);
+                }
+                else
+                {
+                    restoreDescendantComponentWithoutRestoreState(this, false, false);
+                }
             }
             else
             {
@@ -585,7 +613,7 @@ public class UIData extends UIComponentB
                 // the child components of this component with the (partial)
                 // state that was previously saved. Fields not in the
                 // partial saved state are left with their original values.
-                restoreDescendantComponentStates(getChildren().iterator(), rowState, false);
+                restoreDescendantComponentStates(this, false, rowState, false);
             }
         }
     }
@@ -689,44 +717,141 @@ public class UIData extends UIComponentB
      * not modified.
      */
     @SuppressWarnings("unchecked")
-    private void restoreDescendantComponentStates(Iterator<UIComponent> childIterator, Object state,
+    private void restoreDescendantComponentStates(UIComponent parent, boolean iterateFacets, Object state,
                                                   boolean restoreChildFacets)
     {
-        Iterator<? extends Object[]> descendantStateIterator = null;
-        while (childIterator.hasNext())
+        int descendantStateIndex = -1;
+        List<? extends Object[]> stateCollection = null;
+        
+        if (iterateFacets && parent.getFacetCount() > 0)
         {
-            if (descendantStateIterator == null && state != null)
+            Iterator<UIComponent> childIterator = parent.getFacets().values().iterator();
+            
+            while (childIterator.hasNext())
             {
-                descendantStateIterator = ((Collection<? extends Object[]>) state).iterator();
-            }
-            UIComponent component = childIterator.next();
+                UIComponent component = childIterator.next();
 
-            // reset the client id (see spec 3.1.6)
-            component.setId(component.getId());
-            if (!component.isTransient())
-            {
-                Object childState = null;
-                Object descendantState = null;
-                if (descendantStateIterator != null && descendantStateIterator.hasNext())
+                // reset the client id (see spec 3.1.6)
+                component.setId(component.getId());
+                if (!component.isTransient())
                 {
-                    Object[] object = descendantStateIterator.next();
-                    childState = object[0];
-                    descendantState = object[1];
+                    if (descendantStateIndex == -1)
+                    {
+                        stateCollection = ((List<? extends Object[]>) state);
+                        descendantStateIndex = stateCollection.isEmpty() ? -1 : 0;
+                    }
+                    
+                    if (descendantStateIndex != -1 && descendantStateIndex < stateCollection.size())
+                    {
+                        Object[] object = stateCollection.get(descendantStateIndex);
+                        if (object[0] != null && component instanceof EditableValueHolder)
+                        {
+                            ((EditableValueHolderState) object[0]).restoreState((EditableValueHolder) component);
+                        }
+                        // If there is descendant state to restore, call it recursively, otherwise
+                        // it is safe to skip iteration.
+                        if (object[1] != null)
+                        {
+                            restoreDescendantComponentStates(component, restoreChildFacets, object[1], true);
+                        }
+                        else
+                        {
+                            restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true);
+                        }
+                    }
+                    else
+                    {
+                        restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true);
+                    }
+                    descendantStateIndex++;
                 }
-                if (childState != null && component instanceof EditableValueHolder)
+            }
+        }
+        
+        if (parent.getChildCount() > 0)
+        {
+            for (int i = 0; i < parent.getChildCount(); i++)
+            {
+                UIComponent component = parent.getChildren().get(i);
+
+                // reset the client id (see spec 3.1.6)
+                component.setId(component.getId());
+                if (!component.isTransient())
                 {
-                    ((EditableValueHolderState) childState).restoreState((EditableValueHolder) component);
+                    if (descendantStateIndex == -1)
+                    {
+                        stateCollection = ((List<? extends Object[]>) state);
+                        descendantStateIndex = stateCollection.isEmpty() ? -1 : 0;
+                    }
+                    
+                    if (descendantStateIndex != -1 && descendantStateIndex < stateCollection.size())
+                    {
+                        Object[] object = stateCollection.get(descendantStateIndex);
+                        if (object[0] != null && component instanceof EditableValueHolder)
+                        {
+                            ((EditableValueHolderState) object[0]).restoreState((EditableValueHolder) component);
+                        }
+                        // If there is descendant state to restore, call it recursively, otherwise
+                        // it is safe to skip iteration.
+                        if (object[1] != null)
+                        {
+                            restoreDescendantComponentStates(component, restoreChildFacets, object[1], true);
+                        }
+                        else
+                        {
+                            restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true);
+                        }
+                    }
+                    else
+                    {
+                        restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true);
+                    }
+                    descendantStateIndex++;
                 }
-                Iterator<UIComponent> childsIterator;
-                if (restoreChildFacets)
+            }
+        }
+    }
+
+    /**
+     * Just call component.setId(component.getId()) to reset all client ids and 
+     * ensure they will be calculated for the current row, but do not waste time
+     * dealing with row state code.
+     * 
+     * @param parent
+     * @param iterateFacets
+     * @param restoreChildFacets 
+     */
+    private void restoreDescendantComponentWithoutRestoreState(UIComponent parent, boolean iterateFacets, boolean restoreChildFacets)
+    {
+        if (iterateFacets && parent.getFacetCount() > 0)
+        {
+            Iterator<UIComponent> childIterator = parent.getFacets().values().iterator();
+            
+            while (childIterator.hasNext())
+            {
+                UIComponent component = childIterator.next();
+
+                // reset the client id (see spec 3.1.6)
+                component.setId(component.getId());
+                if (!component.isTransient())
                 {
-                    childsIterator = component.getFacetsAndChildren();
+                    restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true);
                 }
-                else
+            }
+        }
+        
+        if (parent.getChildCount() > 0)
+        {
+            for (int i = 0; i < parent.getChildCount(); i++)
+            {
+                UIComponent component = parent.getChildren().get(i);
+
+                // reset the client id (see spec 3.1.6)
+                component.setId(component.getId());
+                if (!component.isTransient())
                 {
-                    childsIterator = component.getChildren().iterator();
+                    restoreDescendantComponentWithoutRestoreState(component, restoreChildFacets, true);
                 }
-                restoreDescendantComponentStates(childsIterator, descendantState, true);
             }
         }
     }
@@ -743,52 +868,176 @@ public class UIData extends UIComponentB
      * Otherwise a collection is returned which contains an object for every non-transient child component; that object
      * may itself contain a collection of the state of that child's child components.
      */
-    private Collection<Object[]> saveDescendantComponentStates(Iterator<UIComponent> childIterator,
+    private Collection<Object[]> saveDescendantComponentStates(UIComponent parent, boolean iterateFacets,
                                                                boolean saveChildFacets)
     {
         Collection<Object[]> childStates = null;
-        boolean hasChildren = childIterator.hasNext();
-        
-        while (childIterator.hasNext())
+        // Index to indicate how many components has been passed without state to save.
+        int childEmptyIndex = 0;
+        int totalChildCount = 0;
+                
+        if (iterateFacets && parent.getFacetCount() > 0)
         {
-            
-            UIComponent child = childIterator.next();
-            if (!child.isTransient())
+            Iterator<UIComponent> childIterator = parent.getFacets().values().iterator();
+
+            while (childIterator.hasNext())
             {
-                // Add an entry to the collection, being an array of two
-                // elements. The first element is the state of the children
-                // of this component; the second is the state of the current
-                // child itself.
-                Object descendantState = null;
-                if (child.getChildCount() > 0)
+                UIComponent child = childIterator.next();
+                if (!child.isTransient())
                 {
-                    Iterator<UIComponent> childsIterator;
-                    if (saveChildFacets)
+                    // Add an entry to the collection, being an array of two
+                    // elements. The first element is the state of the children
+                    // of this component; the second is the state of the current
+                    // child itself.
+
+                    if (child instanceof EditableValueHolder)
+                    {
+                        if (childStates == null)
+                        {
+                            childStates = new ArrayList<Object[]>(
+                                    parent.getFacetCount()
+                                    + parent.getChildCount()
+                                    - totalChildCount
+                                    + childEmptyIndex);
+                            for (int ci = 0; ci < childEmptyIndex; ci++)
+                            {
+                                childStates.add(LEAF_NO_STATE);
+                            }
+                        }
+                    
+                        childStates.add(child.getChildCount() > 0 ? 
+                                new Object[]{new EditableValueHolderState((EditableValueHolder) child),
+                                    saveDescendantComponentStates(child, saveChildFacets, true)} :
+                                new Object[]{new EditableValueHolderState((EditableValueHolder) child),
+                                    null});
+                    }
+                    else if (child.getChildCount() > 0)
                     {
-                        childsIterator = child.getFacetsAndChildren();
+                        Object descendantSavedState = saveDescendantComponentStates(child, saveChildFacets, true);
+                        
+                        if (descendantSavedState == null)
+                        {
+                            if (childStates == null)
+                            {
+                                childEmptyIndex++;
+                            }
+                            else
+                            {
+                                childStates.add(LEAF_NO_STATE);
+                            }
+                        }
+                        else
+                        {
+                            if (childStates == null)
+                            {
+                                childStates = new ArrayList<Object[]>(
+                                        parent.getFacetCount()
+                                        + parent.getChildCount()
+                                        - totalChildCount
+                                        + childEmptyIndex);
+                                for (int ci = 0; ci < childEmptyIndex; ci++)
+                                {
+                                    childStates.add(LEAF_NO_STATE);
+                                }
+                            }
+                            childStates.add(new Object[]{null, descendantSavedState});
+                        }
                     }
                     else
                     {
-                        childsIterator = child.getChildren().iterator();
+                        if (childStates == null)
+                        {
+                            childEmptyIndex++;
+                        }
+                        else
+                        {
+                            childStates.add(LEAF_NO_STATE);
+                        }
                     }
-
-                    descendantState = saveDescendantComponentStates(childsIterator, true);
-                }
-                Object state = null;
-                if (child instanceof EditableValueHolder)
-                {
-                    state = new EditableValueHolderState((EditableValueHolder) child);
-                }
-                if (childStates == null)
-                {
-                    childStates = new ArrayList<Object[]>();
                 }
-                childStates.add(new Object[] { state, descendantState });
+                totalChildCount++;
             }
         }
         
-        if (hasChildren == true && childStates == null) {
-            childStates = Collections.emptyList();
+        if (parent.getChildCount() > 0)
+        {
+            for (int i = 0; i < parent.getChildCount(); i++)
+            {
+                UIComponent child = parent.getChildren().get(i);
+                if (!child.isTransient())
+                {
+                    // Add an entry to the collection, being an array of two
+                    // elements. The first element is the state of the children
+                    // of this component; the second is the state of the current
+                    // child itself.
+
+                    if (child instanceof EditableValueHolder)
+                    {
+                        if (childStates == null)
+                        {
+                            childStates = new ArrayList<Object[]>(
+                                    parent.getFacetCount()
+                                    + parent.getChildCount()
+                                    - totalChildCount
+                                    + childEmptyIndex);
+                            for (int ci = 0; ci < childEmptyIndex; ci++)
+                            {
+                                childStates.add(LEAF_NO_STATE);
+                            }
+                        }
+                    
+                        childStates.add(child.getChildCount() > 0 ? 
+                                new Object[]{new EditableValueHolderState((EditableValueHolder) child),
+                                    saveDescendantComponentStates(child, saveChildFacets, true)} :
+                                new Object[]{new EditableValueHolderState((EditableValueHolder) child),
+                                    null});
+                    }
+                    else if (child.getChildCount() > 0)
+                    {
+                        Object descendantSavedState = saveDescendantComponentStates(child, saveChildFacets, true);
+                        
+                        if (descendantSavedState == null)
+                        {
+                            if (childStates == null)
+                            {
+                                childEmptyIndex++;
+                            }
+                            else
+                            {
+                                childStates.add(LEAF_NO_STATE);
+                            }
+                        }
+                        else
+                        {
+                            if (childStates == null)
+                            {
+                                childStates = new ArrayList<Object[]>(
+                                        parent.getFacetCount()
+                                        + parent.getChildCount()
+                                        - totalChildCount
+                                        + childEmptyIndex);
+                                for (int ci = 0; ci < childEmptyIndex; ci++)
+                                {
+                                    childStates.add(LEAF_NO_STATE);
+                                }
+                            }
+                            childStates.add(new Object[]{null, descendantSavedState});
+                        }
+                    }
+                    else
+                    {
+                        if (childStates == null)
+                        {
+                            childEmptyIndex++;
+                        }
+                        else
+                        {
+                            childStates.add(LEAF_NO_STATE);
+                        }
+                    }
+                }
+                totalChildCount++;
+            }
         }
         
         return childStates;

Modified: myfaces/core/trunk/api/src/main/java/javax/faces/model/DataModel.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/api/src/main/java/javax/faces/model/DataModel.java?rev=1149735&r1=1149734&r2=1149735&view=diff
==============================================================================
--- myfaces/core/trunk/api/src/main/java/javax/faces/model/DataModel.java (original)
+++ myfaces/core/trunk/api/src/main/java/javax/faces/model/DataModel.java Fri Jul 22 21:01:49 2011
@@ -51,8 +51,11 @@ import java.util.NoSuchElementException;
 */
 public abstract class DataModel<E> implements Iterable<E>
 {
+    private final static DataModelListener[] EMPTY_DATA_MODEL_LISTENER = new DataModelListener[]{};
     // FIELDS
     private List<DataModelListener> _listeners;
+    
+    private DataModelListener[] _cachedListenersArray = null;
 
     // METHODS
     public void addDataModelListener(DataModelListener listener)
@@ -63,15 +66,20 @@ public abstract class DataModel<E> imple
             _listeners = new ArrayList<DataModelListener>();
         }
         _listeners.add(listener);
+        _cachedListenersArray = null;
     }
 
     public DataModelListener[] getDataModelListeners()
     {
         if (_listeners == null)
         {
-            return new DataModelListener[0];
+            return EMPTY_DATA_MODEL_LISTENER;
+        }
+        if (_cachedListenersArray == null)
+        {
+            _cachedListenersArray = _listeners.toArray(new DataModelListener[_listeners.size()]);
         }
-        return _listeners.toArray(new DataModelListener[_listeners.size()]);
+        return _cachedListenersArray;
     }
 
     /**
@@ -141,6 +149,7 @@ public abstract class DataModel<E> imple
         {
             _listeners.remove(listener);
         }
+        _cachedListenersArray = null;
     }
 
     /**

Added: myfaces/core/trunk/api/src/test/java/javax/faces/component/UIDataRowStateTest.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/api/src/test/java/javax/faces/component/UIDataRowStateTest.java?rev=1149735&view=auto
==============================================================================
--- myfaces/core/trunk/api/src/test/java/javax/faces/component/UIDataRowStateTest.java (added)
+++ myfaces/core/trunk/api/src/test/java/javax/faces/component/UIDataRowStateTest.java Fri Jul 22 21:01:49 2011
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2011 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package javax.faces.component;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.faces.component.html.HtmlColumn;
+import javax.faces.component.html.HtmlDataTable;
+import javax.faces.component.html.HtmlInputText;
+import javax.faces.component.html.HtmlOutputText;
+import javax.faces.model.ListDataModel;
+import javax.faces.render.Renderer;
+import junit.framework.Assert;
+import org.apache.myfaces.test.base.junit4.AbstractJsfTestCase;
+import org.junit.Test;
+
+/**
+ *
+ * @author Leonardo Uribe
+ */
+public class UIDataRowStateTest extends AbstractJsfTestCase
+{
+    
+    public static class Item
+    {
+        
+        private Integer id;
+        private String name;
+        private String lastName;
+
+        public Item(Integer id, String name, String lastName)
+        {
+            this.id = id;
+            this.name = name;
+            this.lastName = lastName;
+        }
+        /**
+         * @return the id
+         */
+        public Integer getId() {
+            return id;
+        }
+
+        /**
+         * @param id the id to set
+         */
+        public void setId(Integer id) {
+            this.id = id;
+        }
+
+        /**
+         * @return the name
+         */
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * @param name the name to set
+         */
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        /**
+         * @return the lastName
+         */
+        public String getLastName() {
+            return lastName;
+        }
+
+        /**
+         * @param lastName the lastName to set
+         */
+        public void setLastName(String lastName) {
+            this.lastName = lastName;
+        }
+    }
+
+    @Override
+    protected void setUpRenderKit() throws Exception {
+        super.setUpRenderKit();
+        renderKit.addRenderer(HtmlDataTable.COMPONENT_FAMILY, new HtmlDataTable().getRendererType(), new Renderer(){});
+        renderKit.addRenderer(HtmlOutputText.COMPONENT_FAMILY, new HtmlOutputText().getRendererType(), new Renderer(){});
+        renderKit.addRenderer(HtmlInputText.COMPONENT_FAMILY, new HtmlInputText().getRendererType(), new Renderer(){});
+    }
+    
+    /**
+     * 
+     */
+    @Test
+    public void testChangeIdsAfterSetRowIndex()
+    {
+        List<Item> list = new ArrayList<Item>();
+        int rowCount = 10;
+        for (int i = 0; i < rowCount; i++)
+        {
+            list.add(new Item(i, "name"+i, "lastName"+i));
+        }
+        
+        facesContext.getExternalContext().getRequestMap().put("items", list);
+        
+        UIViewRoot root = facesContext.getViewRoot();
+        UIData data = new HtmlDataTable();
+        data.setId("table");
+        root.getChildren().add(data);
+        data.setValue(new ListDataModel(list));
+        data.setVar("item");
+        data.setRows(rowCount);
+        
+        UIColumn col = new HtmlColumn();
+        data.getChildren().add(col);
+
+        UIOutput text = new HtmlOutputText();
+        text.setId("text");
+        text.setValue(facesContext.getApplication().
+                getExpressionFactory().createValueExpression(
+                facesContext.getELContext(), "#{item.name}", String.class));
+        col.getChildren().add(text);
+        
+        for (int i = 0; i < rowCount ; i++)
+        {
+            data.setRowIndex(i);
+            Assert.assertEquals(data.getId()+":"+i+":"+text.getId(), text.getClientId());
+        }
+        data.setRowIndex(-1);
+        Assert.assertEquals(data.getId()+":"+text.getId(), text.getClientId());
+    }
+    
+    @Test
+    public void testChangeIdsAfterSetRowIndex2()
+    {
+        List<Item> list = new ArrayList<Item>();
+        int rowCount = 10;
+        for (int i = 0; i < rowCount; i++)
+        {
+            list.add(new Item(i, "name"+i, "lastName"+i));
+        }
+        
+        facesContext.getExternalContext().getRequestMap().put("items", list);
+        
+        UIViewRoot root = facesContext.getViewRoot();
+        UIData data = new HtmlDataTable();
+        data.setId("table");
+        root.getChildren().add(data);
+        data.setValue(new ListDataModel(list));
+        data.setVar("item");
+        data.setRows(rowCount);
+        
+        UIColumn col = new HtmlColumn();
+        data.getChildren().add(col);
+
+        UIOutput text = new HtmlOutputText();
+        text.setId("text");
+        text.setValue(facesContext.getApplication().
+                getExpressionFactory().createValueExpression(
+                facesContext.getELContext(), "#{item.name}", String.class));
+        col.getChildren().add(text);
+        
+        UIInput inputText = new HtmlInputText();
+        inputText.setId("text");
+        inputText.setValue(facesContext.getApplication().
+                getExpressionFactory().createValueExpression(
+                facesContext.getELContext(), "#{item.lastName}", String.class));
+        col.getChildren().add(inputText);
+
+        for (int i = 0; i < rowCount ; i++)
+        {
+            data.setRowIndex(i);
+            Assert.assertEquals(data.getId()+":"+i+":"+text.getId(), text.getClientId());
+            Assert.assertEquals(data.getId()+":"+i+":"+inputText.getId(), inputText.getClientId());
+        }
+        data.setRowIndex(-1);
+        Assert.assertEquals(data.getId()+":"+text.getId(), text.getClientId());
+        Assert.assertEquals(data.getId()+":"+inputText.getId(), inputText.getClientId());
+    }
+    
+    @Test
+    public void testAddRowAfterSetRowIndex()
+    {
+        List<Item> list = new ArrayList<Item>();
+        int rowCount = 10;
+
+        facesContext.getExternalContext().getRequestMap().put("items", list);
+        
+        UIViewRoot root = facesContext.getViewRoot();
+        UIData data = new HtmlDataTable();
+        data.setId("table");
+        root.getChildren().add(data);
+        data.setValue(new ListDataModel(list));
+        data.setVar("item");
+        data.setRows(rowCount);
+        
+        UIColumn col = new HtmlColumn();
+        data.getChildren().add(col);
+
+        UIOutput text = new HtmlOutputText();
+        text.setId("text");
+        text.setValue(facesContext.getApplication().
+                getExpressionFactory().createValueExpression(
+                facesContext.getELContext(), "#{item.name}", String.class));
+        col.getChildren().add(text);
+        
+        data.setRowIndex(-1);
+        
+        data.processDecodes(facesContext);
+        
+        for (int i = 0; i < rowCount; i++)
+        {
+            list.add(new Item(i, "name"+i, "lastName"+i));
+        }
+        
+        data.processDecodes(facesContext);
+    }
+    
+    /**
+     * Check if EditableValueHolder is being saved and restored.
+     */
+    @Test
+    public void testEditableValueHolderState()
+    {
+        List<Item> list = new ArrayList<Item>();
+        int rowCount = 10;
+        for (int i = 0; i < rowCount; i++)
+        {
+            list.add(new Item(i, "name"+i, "lastName"+i));
+        }
+        
+        facesContext.getExternalContext().getRequestMap().put("items", list);
+        
+        UIViewRoot root = facesContext.getViewRoot();
+        UIData data = new HtmlDataTable();
+        data.setId("table");
+        root.getChildren().add(data);
+        data.setValue(new ListDataModel(list));
+        data.setVar("item");
+        data.setRows(rowCount);
+        
+        UIColumn col = new HtmlColumn();
+        data.getChildren().add(col);
+
+        UIOutput text = new HtmlOutputText();
+        text.setId("text");
+        text.setValue(facesContext.getApplication().
+                getExpressionFactory().createValueExpression(
+                facesContext.getELContext(), "#{item.name}", String.class));
+        col.getChildren().add(text);
+        
+        UIInput inputText = new HtmlInputText();
+        inputText.setId("text");
+        inputText.setValue(facesContext.getApplication().
+                getExpressionFactory().createValueExpression(
+                facesContext.getELContext(), "#{item.lastName}", String.class));
+        col.getChildren().add(inputText);
+
+        for (int i = 0; i < rowCount ; i++)
+        {
+            data.setRowIndex(i);
+            inputText.setSubmittedValue("someString"+i);
+        }
+        data.setRowIndex(-1);
+        
+        for (int i = 0; i < rowCount ; i++)
+        {
+            data.setRowIndex(i);
+            Assert.assertEquals("someString"+i, inputText.getSubmittedValue());
+        }
+    }
+}