Replace HeaderPanel's custom iterator with a new package protected FiniteWidgetIterator that iterates over children in a WidgetProvider. FiniteWidgetIterator is a generic iterator over a finite set of child widgets (versus WidgetIterator, which iterators over panel of arbitrary size). I intended to use FiniteWidgetIterator in another change then ended up not needing it, but I'm sure it will come in handy for other widgets.

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

Review by: rjrjr@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10046 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/FiniteWidgetIterator.java b/user/src/com/google/gwt/user/client/ui/FiniteWidgetIterator.java
new file mode 100644
index 0000000..5253fd5
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/FiniteWidgetIterator.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * 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 com.google.gwt.user.client.ui;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator over a finite number of widgets, which are stored in a delegate
+ * class (usually a widget panel).
+ * 
+ * <p>
+ * In order to use this class, assign each widget in the panel an arbitrary
+ * index. For example, {@link HeaderPanel} defines the header as index 0, the
+ * content as index 1, and the footer as index 2. Construct a new
+ * {@link FiniteWidgetIterator} with a {@link WidgetProvider} that provides the
+ * child widgets.
+ */
+class FiniteWidgetIterator implements Iterator<Widget> {
+
+  /**
+   * Provides widgets to the iterator.
+   */
+  public static interface WidgetProvider {
+    IsWidget get(int index);
+  }
+
+  private int index = -1;
+  private final WidgetProvider provider;
+  private final int widgetCount;
+
+  /**
+   * Construct a new {@link FiniteWidgetIterator}.
+   * 
+   * <p>
+   * The widget count is the number of child widgets that the panel supports,
+   * regardless of whether or not they are set.
+   * 
+   * @param provider the widget provider
+   * @param widgetCount the finite number of widgets that can be provided
+   */
+  public FiniteWidgetIterator(WidgetProvider provider, int widgetCount) {
+    this.provider = provider;
+    this.widgetCount = widgetCount;
+  }
+
+  public boolean hasNext() {
+    // Iterate over the remaining widgets until we find one.
+    for (int i = index + 1; i < widgetCount; i++) {
+      IsWidget w = provider.get(i);
+      if (w != null) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public Widget next() {
+    // Iterate over the remaining widgets until we find one.
+    for (int i = index + 1; i < widgetCount; i++) {
+      index = i;
+      IsWidget w = provider.get(i);
+      if (w != null) {
+        return w.asWidget();
+      }
+    }
+    throw new NoSuchElementException();
+  }
+
+  public void remove() {
+    if (index < 0 || index >= widgetCount) {
+      throw new IllegalStateException();
+    }
+    IsWidget w = provider.get(index);
+    if (w == null) {
+      throw new IllegalStateException("Widget was already removed.");
+    }
+    w.asWidget().removeFromParent();
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/HeaderPanel.java b/user/src/com/google/gwt/user/client/ui/HeaderPanel.java
index 4a58724..3ea4b14 100644
--- a/user/src/com/google/gwt/user/client/ui/HeaderPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/HeaderPanel.java
@@ -24,9 +24,9 @@
 import com.google.gwt.dom.client.Style.Position;
 import com.google.gwt.dom.client.Style.Unit;
 import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.FiniteWidgetIterator.WidgetProvider;
 
 import java.util.Iterator;
-import java.util.NoSuchElementException;
 
 /**
  * A panel that includes a header (top), footer (bottom), and content (middle)
@@ -35,16 +35,40 @@
  */
 public class HeaderPanel extends Panel implements RequiresResize {
 
+  /**
+   * The widget provider for this panel.
+   * 
+   * <p>
+   * Widgets are returned in the following order:
+   * <ol>
+   * <li>Header widget</li>
+   * <li>Content widget</li>
+   * <li>Footer widget</li>
+   * </ol>
+   */
+  private class WidgetProviderImpl implements WidgetProvider {
+
+    public Widget get(int index) {
+      switch (index) {
+        case 0:
+          return header;
+        case 1:
+          return content;
+        case 2:
+          return footer;
+      }
+      throw new ArrayIndexOutOfBoundsException(index);
+    }
+  }
+
   private Widget content;
   private final Element contentContainer;
   private Widget footer;
   private final Element footerContainer;
-  private final ResizeLayoutPanel.Impl footerImpl =
-    GWT.create(ResizeLayoutPanel.Impl.class);
+  private final ResizeLayoutPanel.Impl footerImpl = GWT.create(ResizeLayoutPanel.Impl.class);
   private Widget header;
   private final Element headerContainer;
-  private final ResizeLayoutPanel.Impl headerImpl =
-    GWT.create(ResizeLayoutPanel.Impl.class);
+  private final ResizeLayoutPanel.Impl headerImpl = GWT.create(ResizeLayoutPanel.Impl.class);
   private final ScheduledCommand layoutCmd = new ScheduledCommand() {
     public void execute() {
       layoutScheduled = false;
@@ -135,73 +159,7 @@
   }
 
   public Iterator<Widget> iterator() {
-    // Return a simple iterator that iterates over the header, content, and
-    // footer in order.
-    return new Iterator<Widget>() {
-      private int index = -1;
-
-      public boolean hasNext() {
-        switch (index) {
-          case -1:
-            if (header != null) {
-              return true;
-            }
-          case 0: // Intentional fallthrough.
-            if (content != null) {
-              return true;
-            }
-          case 1: // Intentional fallthrough.
-            if (footer != null) {
-              return true;
-            }
-        }
-        return false;
-      }
-
-      public Widget next() {
-        switch (index) {
-          case -1:
-            index++;
-            if (header != null) {
-              return header;
-            }
-          case 0: // Intentional fallthrough.
-            index++;
-            if (content != null) {
-              return content;
-            }
-          case 1: // Intentional fallthrough.
-            index++;
-            if (footer != null) {
-              return footer;
-            }
-        }
-        throw new NoSuchElementException();
-      }
-
-      public void remove() {
-        switch (index) {
-          case 0:
-            doRemove(header, "Header");
-            break;
-          case 1:
-            doRemove(content, "Content");
-            break;
-          case 2:
-            doRemove(footer, "Footer");
-            break;
-          default:
-            throw new IllegalStateException();
-        }
-      }
-
-      private void doRemove(Widget widget, String position) {
-        if (widget == null) {
-          throw new IllegalStateException(position + " was already removed.");
-        }
-        HeaderPanel.this.remove(widget);
-      }
-    };
+    return new FiniteWidgetIterator(new WidgetProviderImpl(), 3);
   }
 
   @Override
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java
index 5ab2004..055968d 100644
--- a/user/test/com/google/gwt/user/UISuite.java
+++ b/user/test/com/google/gwt/user/UISuite.java
@@ -57,6 +57,7 @@
 import com.google.gwt.user.client.ui.ElementWrappingTest;
 import com.google.gwt.user.client.ui.FastStringMapTest;
 import com.google.gwt.user.client.ui.FileUploadTest;
+import com.google.gwt.user.client.ui.FiniteWidgetIteratorTest;
 import com.google.gwt.user.client.ui.FlexTableTest;
 import com.google.gwt.user.client.ui.FlowPanelTest;
 import com.google.gwt.user.client.ui.FocusPanelTest;
@@ -169,6 +170,7 @@
     suite.addTestSuite(EventTest.class);
     suite.addTestSuite(FastStringMapTest.class);
     suite.addTestSuite(FileUploadTest.class);
+    suite.addTestSuite(FiniteWidgetIteratorTest.class);
     suite.addTestSuite(FlexTableTest.class);
     suite.addTestSuite(FlowPanelTest.class);
     suite.addTestSuite(FocusPanelTest.class);
diff --git a/user/test/com/google/gwt/user/client/ui/FiniteWidgetIteratorTest.java b/user/test/com/google/gwt/user/client/ui/FiniteWidgetIteratorTest.java
new file mode 100644
index 0000000..6003146
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/FiniteWidgetIteratorTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * 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 com.google.gwt.user.client.ui;
+
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.ui.FiniteWidgetIterator.WidgetProvider;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * Test cases for {@link FiniteWidgetIterator}.
+ */
+public class FiniteWidgetIteratorTest extends GWTTestCase {
+
+  /**
+   * Implementation of {@link WidgetProvider}.
+   */
+  private static class WidgetProviderImpl implements WidgetProvider {
+
+    private final Map<Integer, Widget> widgets = new HashMap<Integer, Widget>();
+
+    /**
+     * Construct a new {@link WidgetProviderImpl} with the specified widgets.
+     */
+    public WidgetProviderImpl(Widget... widgets) {
+      if (widgets != null) {
+        for (int i = 0; i < widgets.length; i++) {
+          setWidget(i, widgets[i]);
+        }
+      }
+    }
+
+    /**
+     * Set the widget at the specified index.
+     * 
+     * @param index the index
+     * @param w the widget
+     */
+    public void setWidget(int index, Widget w) {
+      widgets.put(index, w);
+    }
+
+    public Widget get(int index) {
+      if (!widgets.containsKey(index)) {
+        fail("A widget was not specified for index: " + index);
+      }
+      return widgets.get(index);
+    }
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.user.User";
+  }
+
+  public void testHasNextEmpty() {
+    FiniteWidgetIterator iterator = new FiniteWidgetIterator(new WidgetProviderImpl(), 0);
+    assertFalse(iterator.hasNext());
+    try {
+      iterator.next();
+      fail("Expected NoSuchElementException");
+    } catch (NoSuchElementException e) {
+      // Expected.
+    }
+  }
+
+  public void testNext() {
+    Widget w0 = new Widget();
+    Widget w1 = new Widget();
+    Widget w2 = new Widget();
+    FiniteWidgetIterator iterator = new FiniteWidgetIterator(new WidgetProviderImpl(w0, w1, w2), 3);
+
+    assertTrue(iterator.hasNext());
+    assertEquals(w0, iterator.next());
+
+    assertTrue(iterator.hasNext());
+    assertEquals(w1, iterator.next());
+
+    assertTrue(iterator.hasNext());
+    assertEquals(w2, iterator.next());
+
+    assertFalse(iterator.hasNext());
+    try {
+      iterator.next();
+      fail("Expected NoSuchElementException");
+    } catch (NoSuchElementException e) {
+      // Expected.
+    }
+  }
+
+  /**
+   * Test that the iterator skips null widgets at the start of the list.
+   */
+  public void testNullWidgetStart() {
+    Widget w1 = new Widget();
+    Widget w2 = new Widget();
+    FiniteWidgetIterator iterator =
+        new FiniteWidgetIterator(new WidgetProviderImpl(null, w1, w2), 3);
+
+    assertTrue(iterator.hasNext());
+    assertEquals(w1, iterator.next());
+
+    assertTrue(iterator.hasNext());
+    assertEquals(w2, iterator.next());
+
+    assertFalse(iterator.hasNext());
+  }
+
+  /**
+   * Test that the iterator skips null widgets at the end of the list.
+   */
+  public void testNullWidgetEnd() {
+    Widget w0 = new Widget();
+    Widget w1 = new Widget();
+    FiniteWidgetIterator iterator =
+        new FiniteWidgetIterator(new WidgetProviderImpl(w0, w1, null), 3);
+
+    assertTrue(iterator.hasNext());
+    assertEquals(w0, iterator.next());
+
+    assertTrue(iterator.hasNext());
+    assertEquals(w1, iterator.next());
+
+    assertFalse(iterator.hasNext());
+  }
+
+  /**
+   * Test that the iterator skips null widgets in the middle of the list.
+   */
+  public void testNullWidgetMiddle() {
+    Widget w0 = new Widget();
+    Widget w2 = new Widget();
+    FiniteWidgetIterator iterator =
+        new FiniteWidgetIterator(new WidgetProviderImpl(w0, null, w2), 3);
+
+    assertTrue(iterator.hasNext());
+    assertEquals(w0, iterator.next());
+
+    assertTrue(iterator.hasNext());
+    assertEquals(w2, iterator.next());
+
+    assertFalse(iterator.hasNext());
+  }
+
+  public void testRemove() {
+    Widget w0 = new Label();
+    Widget w1 = new Label();
+    Widget w2 = new Label();
+    WidgetProviderImpl provider = new WidgetProviderImpl(w0, w1, w2);
+    FiniteWidgetIterator iterator = new FiniteWidgetIterator(provider, 3);
+
+    // Add the widgets to a panel.
+    FlowPanel panel = new FlowPanel();
+    panel.add(w0);
+    panel.add(w1);
+    panel.add(w2);
+
+    // Remove before getting first widget.
+    try {
+      iterator.remove();
+      fail("Expected IllegalStateException");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+
+    // Remove the widget.
+    assertEquals(w0, iterator.next());
+    assertEquals(panel, w0.getParent());
+    iterator.remove();
+    assertNull(w0.getParent());
+    provider.setWidget(0, null); // Update the provider.
+
+    // Try to remove again.
+    try {
+      iterator.remove();
+      fail("Expected IllegalStateException");
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+
+    // Get the next widget.
+    assertEquals(w1, iterator.next());
+  }
+}