Finishing implementation of ListViewAdapter.  An extensive test class will be submitted in a later change because the rest relies on API changes that haven't been submitted yet.

Review at http://gwt-code-reviews.appspot.com/636802

Review by: jgw@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8305 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/view/client/ListViewAdapter.java b/user/src/com/google/gwt/view/client/ListViewAdapter.java
index d88f895..0694d16 100644
--- a/user/src/com/google/gwt/view/client/ListViewAdapter.java
+++ b/user/src/com/google/gwt/view/client/ListViewAdapter.java
@@ -47,7 +47,23 @@
      */
     private final class WrappedListIterator implements ListIterator<T> {
 
-      int i = 0, last = -1;
+      /**
+       * The error message when {@link #add(Object)} or {@link #remove()} is
+       * called more than once per call to {@link #next()} or
+       * {@link #previous()}.
+       */
+      private static final String IMPERMEABLE_EXCEPTION = "Cannot call add/remove more than once per call to next/previous.";
+
+      /**
+       * The index of the object that will be returned by {@link #next()}.
+       */
+      private int i = 0;
+
+      /**
+       * The index of the last object accessed through {@link #next()} or
+       * {@link #previous()}.
+       */
+      private int last = -1;
 
       private WrappedListIterator() {
       }
@@ -62,6 +78,9 @@
       }
 
       public void add(T o) {
+        if (last < 0) {
+          throw new IllegalStateException(IMPERMEABLE_EXCEPTION);
+        }
         ListWrapper.this.add(i++, o);
         last = -1;
       }
@@ -98,7 +117,7 @@
 
       public void remove() {
         if (last < 0) {
-          throw new IllegalStateException();
+          throw new IllegalStateException(IMPERMEABLE_EXCEPTION);
         }
         ListWrapper.this.remove(last);
         i = last;
@@ -119,6 +138,16 @@
     private int curSize = 0;
 
     /**
+     * The delegate wrapper.
+     */
+    private final ListWrapper delegate;
+
+    /**
+     * Set to true if the pending flush has been cancelled.
+     */
+    private boolean flushCancelled;
+
+    /**
      * We wait until the end of the current event loop before flushing changes
      * so that we don't spam the views. This also allows users to clear and
      * replace all of the data without forcing the view back to page 0.
@@ -126,21 +155,11 @@
     private Command flushCommand = new Command() {
       public void execute() {
         flushPending = false;
-
-        int newSize = list.size();
-        if (curSize != newSize) {
-          curSize = newSize;
-          updateDataSize(curSize, true);
+        if (flushCancelled) {
+          flushCancelled = false;
+          return;
         }
-
-        if (modified) {
-          int length = maxModified - minModified;
-          updateViewData(minModified, length, list.subList(minModified,
-              maxModified));
-          modified = false;
-        }
-        minModified = Integer.MAX_VALUE;
-        maxModified = Integer.MIN_VALUE;
+        flushNow();
       }
     };
 
@@ -155,14 +174,19 @@
     private List<T> list;
 
     /**
+     * If this is a sublist, the offset it the index relative to the main list.
+     */
+    private final int offset;
+
+    /**
      * If modified is true, the smallest modified index.
      */
-    private int maxModified;
+    private int maxModified = Integer.MIN_VALUE;
 
     /**
      * If modified is true, one past the largest modified index.
      */
-    private int minModified;
+    private int minModified = Integer.MAX_VALUE;
 
     /**
      * True if the list data has been modified.
@@ -170,10 +194,21 @@
     private boolean modified;
 
     public ListWrapper(List<T> list) {
+      this(list, null, 0);
+    }
+
+    /**
+     * Construct a new {@link ListWrapper} that delegates flush calls to the
+     * specified delegate.
+     * 
+     * @param list the list to wrap
+     * @param delegate the delegate
+     * @param offset the offset of this list
+     */
+    private ListWrapper(List<T> list, ListWrapper delegate, int offset) {
       this.list = list;
-      minModified = 0;
-      maxModified = list.size();
-      modified = true;
+      this.delegate = delegate;
+      this.offset = offset;
     }
 
     public void add(int index, T element) {
@@ -193,7 +228,8 @@
       minModified = Math.min(minModified, size() - 1);
       maxModified = size();
       modified = true;
-      return flush(toRet);
+      flush();
+      return toRet;
     }
 
     public boolean addAll(Collection<? extends T> c) {
@@ -201,7 +237,8 @@
       boolean toRet = list.addAll(c);
       maxModified = size();
       modified = true;
-      return flush(toRet);
+      flush();
+      return toRet;
     }
 
     public boolean addAll(int index, Collection<? extends T> c) {
@@ -210,7 +247,8 @@
         minModified = Math.min(minModified, index);
         maxModified = size();
         modified = true;
-        return flush(toRet);
+        flush();
+        return toRet;
       } catch (IndexOutOfBoundsException e) {
         throw new IndexOutOfBoundsException(e.getMessage());
       }
@@ -254,8 +292,7 @@
     }
 
     public Iterator<T> iterator() {
-      // TODO(jlabanca): Wrap the iterator
-      return list.iterator();
+      return listIterator();
     }
 
     public int lastIndexOf(Object o) {
@@ -297,7 +334,8 @@
       minModified = 0;
       maxModified = size();
       modified = true;
-      return flush(toRet);
+      flush();
+      return toRet;
     }
 
     public boolean retainAll(Collection<?> c) {
@@ -305,7 +343,8 @@
       minModified = 0;
       maxModified = size();
       modified = true;
-      return flush(toRet);
+      flush();
+      return toRet;
     }
 
     public T set(int index, T element) {
@@ -322,8 +361,7 @@
     }
 
     public List<T> subList(int fromIndex, int toIndex) {
-      // TODO(jlabanca): wrap the sublist
-      return list.subList(fromIndex, toIndex);
+      return new ListWrapper(list.subList(fromIndex, toIndex), this, fromIndex);
     }
 
     public Object[] toArray() {
@@ -338,6 +376,18 @@
      * Flush the data to the model.
      */
     private void flush() {
+      // Defer to the delegate.
+      if (delegate != null) {
+        delegate.minModified = Math.min(minModified + offset,
+            delegate.minModified);
+        delegate.maxModified = Math.max(maxModified + offset,
+            delegate.maxModified);
+        delegate.modified = modified || delegate.modified;
+        delegate.flush();
+        return;
+      }
+
+      flushCancelled = false;
       if (!flushPending) {
         flushPending = true;
         DeferredCommand.addCommand(flushCommand);
@@ -345,13 +395,28 @@
     }
 
     /**
-     * Flush the data to the model and return the boolean.
-     * 
-     * @param toRet the boolean to return
+     * Flush pending list changes to the views. By default,
      */
-    private boolean flush(boolean toRet) {
-      flush();
-      return toRet;
+    private void flushNow() {
+      // Cancel any pending flush command.
+      if (flushPending) {
+        flushCancelled = true;
+      }
+
+      int newSize = list.size();
+      if (curSize != newSize) {
+        curSize = newSize;
+        updateDataSize(curSize, true);
+      }
+
+      if (modified) {
+        int length = maxModified - minModified;
+        updateViewData(minModified, length, list.subList(minModified,
+            maxModified));
+        modified = false;
+      }
+      minModified = Integer.MAX_VALUE;
+      maxModified = Integer.MIN_VALUE;
     }
   }
 
@@ -377,6 +442,18 @@
   }
 
   /**
+   * Flush pending list changes to the views. By default, views are informed of
+   * modifications to the underlying list at the end of the current event loop,
+   * which makes it possible to perform multiple operations synchronously
+   * without repeatedly refreshing the views. This method can be called to flush
+   * the changes immediately instead of waiting until the end of the current
+   * event loop.
+   */
+  public void flush() {
+    listWrapper.flushNow();
+  }
+
+  /**
    * Get the list that backs this model. Changes to the list will be reflected
    * in the model.
    * 
@@ -400,7 +477,10 @@
    */
   public void setList(List<T> wrappee) {
     listWrapper = new ListWrapper(wrappee);
-    listWrapper.flush();
+    listWrapper.minModified = 0;
+    listWrapper.maxModified = listWrapper.size();
+    listWrapper.modified = true;
+    flush();
   }
 
   @Override