Revise CellTableBuilder API. In the earlier version, a Utility class is used to
render the rows/cells. The utility performs various checks to ensure the proper
DOM structure. It also appends a cell id to each TD element so that it can
retrieve the Column quickly during event handling. These introduce additional
rendering latency and could become problematic for tables of extremely large
size and/or in slow browsers. This change removes the Utility class and replace
it with AbstractCellTableBuilder. This makes the CellTableBuilder API more
flexible. Users can choose to implement a CellTableBuilder without the above
mentioned overheads, or subclass AbstractCellTableBuilder, in a way almost
identical to the earlier table builder version.

A method getColumn(Context context, T rowValue, Element elem) is added to the
CellTableBuilder interface. User can implement this method to tell the CellTable
which column contains a particular element (e.g., the event target element). If
all columns are renderer, users may simply return
CellTable.getColumn(context.col). Or, users can "replay" the row building logic
to locate the column for the element. Of course the cell id based approach is
still supported and is now in AbstractCellTableBuilder. Two similar methods
isColumn and getColumns are also added. Please see javadoc for more details.

The buildRows method is refactored to remove the utility. Users can choose to
still use an element builder based approach (see AbstractCellTableBuilder), in
which each time a cell is rendered, checks are performed to ensure the proper
DOM structure. Or, users can choose to use a simple string builder based
approach and create a TableSectionBuilder in the new "finish()" method (e.g.,
using the new FastHtmlTableSectionBuilder in this change). This
will be much faster, won't be able to detect a malformed html.

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10550 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
index 2386841..0b6daa4 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCustomDataGrid.java
@@ -47,7 +47,7 @@
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.cellview.client.AbstractCellTable.Style;
-import com.google.gwt.user.cellview.client.CellTableBuilder;
+import com.google.gwt.user.cellview.client.AbstractCellTableBuilder;
 import com.google.gwt.user.cellview.client.Column;
 import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler;
 import com.google.gwt.user.cellview.client.DataGrid;
@@ -132,7 +132,7 @@
    * A custom version of {@link CellTableBuilder}.
    */
   @ShowcaseSource
-  private class CustomTableBuilder implements CellTableBuilder<ContactInfo> {
+  private class CustomTableBuilder extends AbstractCellTableBuilder<ContactInfo> {
 
     private final int todayMonth;
     private final Set<Integer> showingFriends = new HashSet<Integer>();
@@ -145,6 +145,7 @@
 
     @SuppressWarnings("deprecation")
     public CustomTableBuilder(ListHandler<ContactInfo> sortHandler) {
+      super(dataGrid);
       // Cache styles for faster access.
       Style style = dataGrid.getResources().style();
       rowStyle = style.evenRow();
@@ -343,14 +344,13 @@
 
     @SuppressWarnings("deprecation")
     @Override
-    public void buildRow(ContactInfo rowValue, int absRowIndex,
-        CellTableBuilder.Utility<ContactInfo> utility) {
-      buildContactRow(rowValue, absRowIndex, utility, false);
+    public void buildRowImpl(ContactInfo rowValue, int absRowIndex) {
+      buildContactRow(rowValue, absRowIndex, false);
 
       // Display information about the user in another row that spans the entire
       // table.
       if (rowValue.getAge() > 65) {
-        TableRowBuilder row = utility.startRow();
+        TableRowBuilder row = startRow();
         TableCellBuilder td = row.startTD().colSpan(7).className(cellStyle);
         td.style().trustedBackgroundColor("#ffa").outlineStyle(OutlineStyle.NONE).endStyle();
         td.text(rowValue.getFirstName() + " is elegible for retirement benefits").endTD();
@@ -361,7 +361,7 @@
       // table.
       Date dob = rowValue.getBirthday();
       if (dob.getMonth() == todayMonth) {
-        TableRowBuilder row = utility.startRow();
+        TableRowBuilder row = startRow();
         TableCellBuilder td = row.startTD().colSpan(7).className(cellStyle);
         td.style().trustedBackgroundColor("#ccf").endStyle();
         td.text(rowValue.getFirstName() + "'s birthday is this month!").endTD();
@@ -372,7 +372,7 @@
       if (showingFriends.contains(rowValue.getId())) {
         Set<ContactInfo> friends = ContactDatabase.get().queryFriends(rowValue);
         for (ContactInfo friend : friends) {
-          buildContactRow(friend, absRowIndex, utility, true);
+          buildContactRow(friend, absRowIndex, true);
         }
       }
     }
@@ -382,12 +382,10 @@
      * 
      * @param rowValue the contact info
      * @param absRowIndex the absolute row index
-     * @param utility the utility used to add rows and Cells
      * @param isFriend true if this is a subrow, false if a top level row
      */
     @SuppressWarnings("deprecation")
-    private void buildContactRow(ContactInfo rowValue, int absRowIndex,
-        CellTableBuilder.Utility<ContactInfo> utility, boolean isFriend) {
+    private void buildContactRow(ContactInfo rowValue, int absRowIndex, boolean isFriend) {
       // Calculate the row styles.
       SelectionModel<? super ContactInfo> selectionModel = dataGrid.getSelectionModel();
       boolean isSelected =
@@ -408,7 +406,7 @@
         cellStyles += childCell;
       }
 
-      TableRowBuilder row = utility.startRow();
+      TableRowBuilder row = startRow();
       row.className(trClasses.toString());
 
       /*
@@ -422,7 +420,7 @@
       td.className(cellStyles);
       td.style().outlineStyle(OutlineStyle.NONE).endStyle();
       if (!isFriend) {
-        utility.renderCell(td, utility.createContext(0), dataGrid.getColumn(0), rowValue);
+        renderCell(td, createContext(0), dataGrid.getColumn(0), rowValue);
       }
       td.endTD();
 
@@ -436,7 +434,7 @@
       td.className(cellStyles);
       if (!isFriend) {
         td.style().outlineStyle(OutlineStyle.NONE).endStyle();
-        utility.renderCell(td, utility.createContext(1), dataGrid.getColumn(1), rowValue);
+        renderCell(td, createContext(1), dataGrid.getColumn(1), rowValue);
       }
       td.endTD();
 
@@ -447,7 +445,7 @@
       if (isFriend) {
         td.text(rowValue.getFirstName());
       } else {
-        utility.renderCell(td, utility.createContext(2), dataGrid.getColumn(2), rowValue);
+        renderCell(td, createContext(2), dataGrid.getColumn(2), rowValue);
       }
       td.endTD();
 
@@ -458,7 +456,7 @@
       if (isFriend) {
         td.text(rowValue.getLastName());
       } else {
-        utility.renderCell(td, utility.createContext(3), dataGrid.getColumn(3), rowValue);
+        renderCell(td, createContext(3), dataGrid.getColumn(3), rowValue);
       }
       td.endTD();
 
@@ -475,7 +473,7 @@
       if (isFriend) {
         td.text(rowValue.getCategory().getDisplayName());
       } else {
-        utility.renderCell(td, utility.createContext(5), dataGrid.getColumn(5), rowValue);
+        renderCell(td, createContext(5), dataGrid.getColumn(5), rowValue);
       }
       td.endTD();
 
diff --git a/user/src/com/google/gwt/dom/builder/shared/HtmlOnlyTableSectionBuilder.java b/user/src/com/google/gwt/dom/builder/shared/HtmlOnlyTableSectionBuilder.java
new file mode 100644
index 0000000..abfabfc
--- /dev/null
+++ b/user/src/com/google/gwt/dom/builder/shared/HtmlOnlyTableSectionBuilder.java
@@ -0,0 +1,81 @@
+/*
+ * 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.dom.builder.shared;
+
+import com.google.gwt.safehtml.shared.SafeHtml;
+
+/**
+ * A subclass of standard {@link HtmlTableSectionBuilder} that allows directly appending html to
+ * a table section.
+ * <p>
+ * Some browsers do not support setting innerHTML on a table section element (such
+ * as a tbody), so both the DOM and HTML based implementations throw an error to
+ * ensure consistency. This class exists to allow users to append HTML to a
+ * TableSectionBuilder if they opt into the HTML version.
+ * </p>
+ */
+public final class HtmlOnlyTableSectionBuilder extends HtmlTableSectionBuilder {
+
+  /**
+   * Create and return a table body section builder.
+   */
+  public static HtmlOnlyTableSectionBuilder createTBodyBuilder() {
+    HtmlBuilderImpl htmlBuilderImpl = new HtmlBuilderImpl();
+    htmlBuilderImpl.startTBody();
+    return new HtmlOnlyTableSectionBuilder(htmlBuilderImpl);
+  }
+
+  /**
+   * Create and return a table footer section builder.
+   */
+  public static HtmlOnlyTableSectionBuilder createTFootBuilder() {
+    HtmlBuilderImpl htmlBuilderImpl = new HtmlBuilderImpl();
+    htmlBuilderImpl.startTFoot();
+    return new HtmlOnlyTableSectionBuilder(htmlBuilderImpl);
+  }
+    
+  /**
+   * Create and return a table header section builder.
+   */
+  public static HtmlOnlyTableSectionBuilder createTHeadBuilder() {
+    HtmlBuilderImpl htmlBuilderImpl = new HtmlBuilderImpl();
+    htmlBuilderImpl.startTHead();
+    return new HtmlOnlyTableSectionBuilder(htmlBuilderImpl);
+  }
+      
+  private HtmlBuilderImpl delegate;
+  
+  /**
+   * Construct a new html table section builder.
+   * 
+   * @param delegate delegate the html based delegate that builds the element
+   */
+  private HtmlOnlyTableSectionBuilder(HtmlBuilderImpl delegate) {
+    super(delegate);
+    this.delegate = delegate;
+  }
+  
+  /**
+   * Append html to the builder and validate the correctness of the html.
+   * 
+   * @param html the html for the table section
+   */
+  @Override
+  public TableSectionBuilder html(SafeHtml html) {
+    delegate.html(html);
+    return getReturnBuilder();
+  }
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java b/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
index 0cd4dbc..2653ae2 100644
--- a/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
+++ b/user/src/com/google/gwt/user/cellview/client/AbstractCellTable.java
@@ -25,11 +25,10 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.dom.builder.shared.DivBuilder;
-import com.google.gwt.dom.builder.shared.ElementBuilderBase;
-import com.google.gwt.dom.builder.shared.HtmlBuilderFactory;
 import com.google.gwt.dom.builder.shared.HtmlTableSectionBuilder;
 import com.google.gwt.dom.builder.shared.TableCellBuilder;
 import com.google.gwt.dom.builder.shared.TableRowBuilder;
+import com.google.gwt.dom.builder.shared.TableSectionBuilder;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.EventTarget;
@@ -250,9 +249,7 @@
    * 
    * @param <T> the data type of the rows.
    */
-  public static class DefaultCellTableBuilder<T> implements CellTableBuilder<T> {
-
-    private AbstractCellTable<T> cellTable;
+  public static class DefaultCellTableBuilder<T> extends AbstractCellTableBuilder<T> {
 
     private final String evenRowStyle;
     private final String oddRowStyle;
@@ -265,8 +262,7 @@
     private final String selectedCellStyle;
 
     public DefaultCellTableBuilder(AbstractCellTable<T> cellTable) {
-      this.cellTable = cellTable;
-
+      super(cellTable);
       // Cache styles for faster access.
       Style style = cellTable.getResources().style();
       evenRowStyle = style.evenRow();
@@ -279,9 +275,9 @@
       lastColumnStyle = " " + style.lastColumn();
       selectedCellStyle = " " + style.selectedRowCell();
     }
-
+    
     @Override
-    public void buildRow(T rowValue, int absRowIndex, CellTableBuilder.Utility<T> utility) {
+    public void buildRowImpl(T rowValue, int absRowIndex) {
 
       // Calculate the row styles.
       SelectionModel<? super T> selectionModel = cellTable.getSelectionModel();
@@ -304,7 +300,7 @@
       }
 
       // Build the row.
-      TableRowBuilder tr = utility.startRow();
+      TableRowBuilder tr = startRow();
       tr.className(trClasses.toString());
 
       // Build the columns.
@@ -349,7 +345,7 @@
         div.style().outlineStyle(OutlineStyle.NONE).endStyle();
 
         // Render the cell into the div.
-        utility.renderCell(div, context, column, rowValue);
+        renderCell(div, context, column, rowValue);
 
         // End the cell.
         div.endDiv();
@@ -671,8 +667,8 @@
           count++;
         }
       } else {
-        while (insertBefore != null
-            && table.getRowValueIndex(insertBefore.<TableRowElement> cast()) < absEndIndex) {
+        while (insertBefore != null && table.tableBuilder.getRowValueIndex(
+            insertBefore.<TableRowElement> cast()) < absEndIndex) {
           Element next = insertBefore.getNextSiblingElement();
           section.removeChild(insertBefore);
           insertBefore = next;
@@ -816,119 +812,7 @@
   }
 
   /**
-   * Implementation for {@link CellTableBuilder.Utility}.
-   */
-  private class UtilityImpl extends CellTableBuilder.Utility<T> {
 
-    private int rowIndex;
-    private int subrowIndex;
-    private Object rowValueKey;
-    private final HtmlTableSectionBuilder tbody;
-
-    private UtilityImpl() {
-      /*
-       * TODO(jlabanca): Test with DomBuilder.
-       * 
-       * DOM manipulation is sometimes faster than String concatenation and
-       * innerHTML, but not when mixing the two. Cells render as HTML strings,
-       * so its faster to render the entire table as a string.
-       */
-      tbody = HtmlBuilderFactory.get().createTBodyBuilder();
-    }
-
-    @Override
-    public Context createContext(int column) {
-      return new Context(rowIndex, column, rowValueKey);
-    }
-
-    @Override
-    public <C> void renderCell(ElementBuilderBase<?> builder, Context context,
-        HasCell<T, C> column, T rowValue) {
-      // Generate a unique ID for the cell.
-      String cellId = cellToIdMap.get(column);
-      if (cellId == null) {
-        cellId = "cell-" + Document.get().createUniqueId();
-        idToCellMap.put(cellId, column);
-        cellToIdMap.put(column, cellId);
-      }
-      builder.attribute(CELL_ATTRIBUTE, cellId);
-
-      // Render the cell into the builder.
-      SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
-      if (column instanceof Column) {
-        /*
-         * If the HasCell is a Column, let it render the Cell itself. This is
-         * here for legacy support.
-         */
-        Column<T, C> theColumn = (Column<T, C>) column;
-        theColumn.render(context, rowValue, cellBuilder);
-      } else {
-        column.getCell().render(context, column.getValue(rowValue), cellBuilder);
-      }
-      builder.html(cellBuilder.toSafeHtml());
-    }
-
-    @Override
-    public TableRowBuilder startRow() {
-      // End any dangling rows.
-      while (tbody.getDepth() > 1) {
-        tbody.end();
-      }
-
-      // Verify the depth.
-      if (tbody.getDepth() < 1) {
-        throw new IllegalStateException(
-            "Cannot start a row.  Did you call TableRowBuilder.end() too many times?");
-      }
-
-      // Start the next row.
-      TableRowBuilder row = tbody.startTR();
-      row.attribute(ROW_ATTRIBUTE, rowIndex);
-      row.attribute(SUBROW_ATTRIBUTE, subrowIndex);
-      subrowIndex++;
-      return row;
-    }
-
-    /**
-     * Get the {@link TableSectionElement} containing the children.
-     */
-    private SafeHtml asSafeHtml() {
-      // End dangling elements.
-      while (tbody.getDepth() > 0) {
-        tbody.endTBody();
-      }
-
-      // Strip the table section tags off of the tbody.
-      String rawHtml = tbody.asSafeHtml().asString();
-      assert rawHtml.startsWith("<tbody>") : "Malformed html";
-      assert rawHtml.endsWith("</tbody>") : "Malformed html";
-      rawHtml = rawHtml.substring(7, rawHtml.length() - 8);
-      return SafeHtmlUtils.fromTrustedString(rawHtml);
-    }
-
-    private void setRowInfo(int rowIndex, T rowValue) {
-      this.rowIndex = rowIndex;
-      this.rowValueKey = getValueKey(rowValue);
-      this.subrowIndex = 0; // Reset the subrow.
-    }
-  }
-
-  /**
-   * The attribute used to indicate that an element contains a cell.
-   */
-  private static final String CELL_ATTRIBUTE = "__gwt_cell";
-
-  /**
-   * The attribute used to specify the logical row index.
-   */
-  private static final String ROW_ATTRIBUTE = "__gwt_row";
-
-  /**
-   * The attribute used to specify the subrow within a logical row value.
-   */
-  private static final String SUBROW_ATTRIBUTE = "__gwt_subrow";
-
-  /**
    * The table specific {@link Impl}.
    */
   private static Impl TABLE_IMPL;
@@ -943,12 +827,6 @@
     return consumedEvents != null && consumedEvents.size() > 0;
   }
 
-  /**
-   * A mapping of unique cell IDs to the cell.
-   */
-  private final Map<String, HasCell<T, ?>> idToCellMap = new HashMap<String, HasCell<T, ?>>();
-  private final Map<HasCell<T, ?>, String> cellToIdMap = new HashMap<HasCell<T, ?>, String>();
-
   private boolean cellIsEditing;
   private final List<Column<T, ?>> columns = new ArrayList<Column<T, ?>>();
   private final Map<Column<T, ?>, String> columnWidths = new HashMap<Column<T, ?>, String>();
@@ -1172,7 +1050,7 @@
   public void flush() {
     getPresenter().flush();
   }
-
+  
   /**
    * Get the column at the specified index.
    * 
@@ -1790,7 +1668,6 @@
     TableSectionElement targetTableSection = null;
     TableCellElement targetTableCell = null;
     Element cellParent = null;
-    String cellId = null;
     {
       Element maybeTableCell = null;
       Element cur = target;
@@ -1819,9 +1696,8 @@
         }
 
         // Look for the most immediate cell parent if not already found.
-        String curCellId = isCellParent(cur);
-        if (cellParent == null && curCellId != null) {
-          cellId = curCellId;
+        boolean isColumn = tableBuilder.isColumn(cur);
+        if (cellParent == null && isColumn) {
           cellParent = cur;
         }
 
@@ -1883,9 +1759,9 @@
        * Get the row index of the data value. This may not correspond to the DOM
        * row index if the user specifies multiple table rows per row object.
        */
-      int absRow = getRowValueIndex(targetTableRow);
+      int absRow = tableBuilder.getRowValueIndex(targetTableRow);
       int relRow = absRow - getPageStart();
-      int subrow = getSubrowValueIndex(targetTableRow);
+      int subrow = tableBuilder.getSubrowValueIndex(targetTableRow);
       if ("mouseover".equals(eventType)) {
         // Unstyle the old row if it is still part of the table.
         if (hoveringRow != null && getTableBodyElement().isOrHasChild(hoveringRow)) {
@@ -1920,11 +1796,15 @@
 
       // Pass the event to the cell.
       if (cellParent != null && !previewEvent.isCanceled()) {
-        HasCell<T, ?> column = idToCellMap.get(cellId);
+        HasCell<T, ?> column;
         if (legacyRenderRowValues) {
           column = columns.get(col);
+        } else {
+          column = tableBuilder.getColumn(context, value, cellParent);
         }
-        fireEventToCell(event, eventType, cellParent, value, context, column);
+        if (column != null) {
+          fireEventToCell(event, eventType, cellParent, value, context, column);
+        }
       }
     }
   }
@@ -2082,9 +1962,7 @@
      * but still supported.
      */
     if (html == null) {
-      cellToIdMap.clear();
-      idToCellMap.clear();
-      html = buildRowValues(values, getPageStart());
+      html = buildRowValues(values, getPageStart(), true);
     }
 
     TABLE_IMPL.replaceAllRows(this, getTableBodyElement(), CellBasedWidgetImpl.get().processHtml(
@@ -2102,7 +1980,7 @@
      * but still supported.
      */
     if (html == null) {
-      html = buildRowValues(values, getPageStart() + start);
+      html = buildRowValues(values, getPageStart() + start, false);
     }
 
     TABLE_IMPL.replaceChildren(this, getTableBodyElement(), CellBasedWidgetImpl.get().processHtml(
@@ -2117,17 +1995,19 @@
       return false;
     }
 
-    String cellId = isCellParent(elem);
-    if (cellId == null) {
+    int row = getKeyboardSelectedRow();
+    int col = getKeyboardSelectedColumn();
+    T value = getVisibleItem(row);
+    Object key = getValueKey(value);
+    // TODO(pengzhuang): this doesn't support sub row selection?
+    Context context = new Context(row + getPageStart(), col, key);
+    HasCell<T, ?> column = tableBuilder.getColumn(context, value, elem);
+    if (column == null) {
       // The selected element does not contain a Cell.
       return false;
     }
 
-    HasCell<T, ?> column = idToCellMap.get(cellId);
-    if (column != null) {
-      resetFocusOnCellImpl(getKeyboardSelectedRow(), getKeyboardSelectedColumn(), column, elem);
-    }
-
+    resetFocusOnCellImpl(context, value, column, elem);
     return false;
   }
 
@@ -2212,10 +2092,10 @@
     int domIndex = Math.min(relRow, frameEnd);
     while (domIndex >= frameStart && domIndex <= frameEnd) {
       TableRowElement curRow = rows.getItem(domIndex);
-      int rowValueIndex = getRowValueIndex(curRow);
+      int rowValueIndex = tableBuilder.getRowValueIndex(curRow);
       if (rowValueIndex == absRow) {
         // Found a subrow in the row index.
-        int subrowValueIndex = getSubrowValueIndex(curRow);
+        int subrowValueIndex = tableBuilder.getSubrowValueIndex(curRow);
         if (subrow != subrowValueIndex) {
           // Shift to the correct subrow.
           int offset = subrow - subrowValueIndex;
@@ -2225,7 +2105,7 @@
             return null;
           }
           curRow = rows.getItem(subrowIndex);
-          if (getRowValueIndex(curRow) != absRow) {
+          if (tableBuilder.getRowValueIndex(curRow) != absRow) {
             // The "subrow" is actually part of the next row.
             return null;
           }
@@ -2252,22 +2132,31 @@
    * 
    * @param values the row values to render
    * @param start the absolute start index
+   * @param isRebuildingAllRows is this going to rebuild all rows
    * @return a {@link SafeHtml} string containing the row values
    */
-  private SafeHtml buildRowValues(List<T> values, int start) {
-    UtilityImpl utility = new UtilityImpl();
+  private SafeHtml buildRowValues(List<T> values, int start, boolean isRebuildingAllRows) {
     int length = values.size();
     int end = start + length;
+    tableBuilder.start(isRebuildingAllRows);
     for (int i = start; i < end; i++) {
       T value = values.get(i - start);
-      utility.setRowInfo(i, value);
-      tableBuilder.buildRow(value, i, utility);
+      tableBuilder.buildRow(value, i);
     }
 
     // Update the properties of the table.
     coalesceCellProperties();
-
-    return utility.asSafeHtml();
+    TableSectionBuilder tableSectionBuilder = tableBuilder.finish();
+    if (tableSectionBuilder instanceof HtmlTableSectionBuilder) {
+      // Strip the table section tags off of the tbody.
+      String rawHtml = ((HtmlTableSectionBuilder) tableSectionBuilder).asSafeHtml().asString();
+      assert rawHtml.startsWith("<tbody>") : "Malformed html";
+      assert rawHtml.endsWith("</tbody>") : "Malformed html";
+      rawHtml = rawHtml.substring(7, rawHtml.length() - 8);
+      return SafeHtmlUtils.fromTrustedString(rawHtml);
+    } else {
+      throw new IllegalStateException("Only HTML table section builder is supported at this time");
+    }
   }
 
   /**
@@ -2290,7 +2179,7 @@
     dependsOnSelection = false;
     handlesSelection = false;
     isInteractive = false;
-    for (HasCell<T, ?> column : idToCellMap.values()) {
+    for (HasCell<T, ?> column : tableBuilder.getColumns()) {
       Cell<?> cell = column.getCell();
       if (cell.dependsOnSelection()) {
         dependsOnSelection = true;
@@ -2495,7 +2384,7 @@
      * (including the tabIndex that we set) could be modified by its Cell. We
      * return the TD to be safe.
      */
-    if (isCellParent(td) != null) {
+    if (tableBuilder.isColumn(td)) {
       return td;
     }
 
@@ -2540,23 +2429,7 @@
     }
     return null;
   }
-
-  /**
-   * Get the index of the row value from the associated {@link TableRowElement}.
-   * 
-   * @param row the row element
-   * @return the row value index
-   */
-  private int getRowValueIndex(TableRowElement row) {
-    try {
-      return Integer.parseInt(row.getAttribute(ROW_ATTRIBUTE));
-    } catch (NumberFormatException e) {
-      // The attribute doesn't exist. Maybe the user is overriding
-      // renderRowValues().
-      return row.getSectionRowIndex() + getPageStart();
-    }
-  }
-
+  
   /**
    * Get the {@link IconCellDecorator} used to decorate sorted column headers.
    * 
@@ -2580,24 +2453,6 @@
   }
 
   /**
-   * Get the index of the subrow value from the associated
-   * {@link TableRowElement}. The sub row value starts at 0 for the first row
-   * that represents a row value.
-   * 
-   * @param row the row element
-   * @return the subrow value index, or 0 if not found
-   */
-  private int getSubrowValueIndex(TableRowElement row) {
-    try {
-      return Integer.parseInt(row.getAttribute(SUBROW_ATTRIBUTE));
-    } catch (NumberFormatException e) {
-      // The attribute doesn't exist. Maybe the user is overriding
-      // renderRowValues().
-      return 0;
-    }
-  }
-
-  /**
    * Initialize the widget.
    */
   private void init() {
@@ -2622,20 +2477,6 @@
   }
 
   /**
-   * Check if an element is the parent of a rendered cell.
-   * 
-   * @param elem the element to check
-   * @return the cellId if a cell parent, null if not
-   */
-  private String isCellParent(Element elem) {
-    if (elem == null) {
-      return null;
-    }
-    String cellId = elem.getAttribute(CELL_ATTRIBUTE);
-    return (cellId == null) || (cellId.length() == 0) ? null : cellId;
-  }
-
-  /**
    * Mark the column widths as dirty and redraw the table.
    */
   private void refreshColumnsAndRedraw() {
@@ -2664,13 +2505,10 @@
     }
   }
 
-  private <C> boolean resetFocusOnCellImpl(int row, int col, HasCell<T, C> column,
+  private <C> boolean resetFocusOnCellImpl(Context context, T value, HasCell<T, C> column,
       Element cellParent) {
-    T value = getVisibleItem(row);
-    Object key = getValueKey(value);
     C cellValue = column.getValue(value);
     Cell<C> cell = column.getCell();
-    Context context = new Context(row + getPageStart(), col, key);
     return cell.resetFocus(context, cellParent, cellValue);
   }
 
@@ -2707,3 +2545,4 @@
     }
   }
 }
+
diff --git a/user/src/com/google/gwt/user/cellview/client/AbstractCellTableBuilder.java b/user/src/com/google/gwt/user/cellview/client/AbstractCellTableBuilder.java
new file mode 100644
index 0000000..0e4fde0
--- /dev/null
+++ b/user/src/com/google/gwt/user/cellview/client/AbstractCellTableBuilder.java
@@ -0,0 +1,305 @@
+/*
+ * 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.cellview.client;
+
+import com.google.gwt.cell.client.Cell.Context;
+import com.google.gwt.cell.client.HasCell;
+import com.google.gwt.dom.builder.shared.ElementBuilderBase;
+import com.google.gwt.dom.builder.shared.HtmlBuilderFactory;
+import com.google.gwt.dom.builder.shared.HtmlTableSectionBuilder;
+import com.google.gwt.dom.builder.shared.TableRowBuilder;
+import com.google.gwt.dom.builder.shared.TableSectionBuilder;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Builder used to construct a CellTable.
+ * 
+ * @param <T> the row data type
+ */
+public abstract class AbstractCellTableBuilder<T> implements CellTableBuilder<T> {
+  
+  /**
+   * The attribute used to indicate that an element contains a cell.
+   */
+  private static final String CELL_ATTRIBUTE = "__gwt_cell";
+  
+  /**
+   * The attribute used to specify the logical row index.
+   */
+  private static final String ROW_ATTRIBUTE = "__gwt_row";
+
+  /**
+   * The attribute used to specify the subrow within a logical row value.
+   */
+  private static final String SUBROW_ATTRIBUTE = "__gwt_subrow";
+  
+  protected final AbstractCellTable<T> cellTable;
+  
+  /**
+   * A mapping of unique cell IDs to the cell.
+   */
+  private final Map<String, HasCell<T, ?>> idToCellMap = new HashMap<String, HasCell<T, ?>>();
+  private final Map<HasCell<T, ?>, String> cellToIdMap = new HashMap<HasCell<T, ?>, String>();
+
+  private HtmlTableSectionBuilder tbody;  
+  private int rowIndex;
+  private int subrowIndex;
+  private Object rowValueKey;
+
+  /**
+   * Construct a new table builder.
+   * 
+   * @param cellTable the table this builder will build rows for
+   */
+  public AbstractCellTableBuilder(AbstractCellTable<T> cellTable) {
+    this.cellTable = cellTable;
+  }
+  
+  /**
+   * Build zero or more table rows for the specified row value.
+   *
+   * @param rowValue the value for the row to render
+   * @param absRowIndex the absolute row index
+   */
+  @Override
+  public final void buildRow(T rowValue, int absRowIndex) {
+    setRowInfo(absRowIndex, rowValue);
+    buildRowImpl(rowValue, absRowIndex);
+  }
+  
+  /**
+   * Create the context for a column based on the current table building state.
+   * 
+   * @param column the column id
+   * @return the context that contains the column index, row/subrow indexes, and the row value key
+   */
+  public final Context createContext(int column) {
+    return new Context(rowIndex, column, rowValueKey, subrowIndex);
+  }
+  
+  /**
+   * Finish the building and get the {@link TableSectionBuilder} containing the children.
+   */
+  @Override
+  public final TableSectionBuilder finish() {
+    // End dangling elements.
+    while (tbody.getDepth() > 0) {
+      tbody.endTBody();
+    }
+    return tbody;
+  }
+  
+  /**
+   * Return the column containing an element.
+   *
+   * @param context the context for the element
+   * @param rowValue the value for the row corresponding to the element
+   * @param elem the element that the column contains
+   * @return the immediate column containing the element
+   */
+  @Override
+  public final HasCell<T, ?> getColumn(Context context, T rowValue, Element elem) {
+    return getColumn(elem);
+  }
+
+  /**
+   * Return all the columns that this table builder has renderred.
+   */
+  @Override
+  public final Collection<HasCell<T, ?>> getColumns() {
+    return idToCellMap.values();
+  }
+  
+  /**
+   * Get the index of the row value from the associated {@link TableRowElement}.
+   * 
+   * @param row the row element
+   * @return the row value index
+   */
+  @Override
+  public final int getRowValueIndex(TableRowElement row) {
+    try {
+      return Integer.parseInt(row.getAttribute(ROW_ATTRIBUTE));
+    } catch (NumberFormatException e) {
+      // The attribute doesn't exist. Maybe the user is overriding
+      // renderRowValues().
+      return row.getSectionRowIndex() + cellTable.getPageStart();
+    }
+  }
+  
+  /**
+   * Get the index of the subrow value from the associated
+   * {@link TableRowElement}. The sub row value starts at 0 for the first row
+   * that represents a row value.
+   * 
+   * @param row the row element
+   * @return the subrow value index, or 0 if not found
+   */
+  @Override
+  public final int getSubrowValueIndex(TableRowElement row) {
+    try {
+      return Integer.parseInt(row.getAttribute(SUBROW_ATTRIBUTE));
+    } catch (NumberFormatException e) {
+      // The attribute doesn't exist. Maybe the user is overriding
+      // renderRowValues() in {@link AbstractCellTable}.
+      return 0;
+    }
+  }
+  
+  /**
+   * Return if an element contains a cell. This may be faster to execute than {@link getColumn}.
+   *
+   * @param elem the element of interest
+   */
+  @Override
+  public final boolean isColumn(Element elem) {
+    return getCellId(elem) != null;
+  }
+  
+  /**
+   * Render the cell into an {@link ElementBuilderBase}.
+   * 
+   * @param builder the {@link ElementBuilderBase} that cell contents append to
+   * @param context the context for the element
+   * @param column the column containing the cell
+   * @param rowValue the value for the row corresponding to the element
+   */
+  public final <C> void renderCell(ElementBuilderBase<?> builder, Context context,
+      HasCell<T, C> column, T rowValue) {
+    // Generate a unique ID for the cell.
+    String cellId = cellToIdMap.get(column);
+    if (cellId == null) {
+      cellId = "cell-" + Document.get().createUniqueId();
+      idToCellMap.put(cellId, column);
+      cellToIdMap.put(column, cellId);
+    }
+    builder.attribute(CELL_ATTRIBUTE, cellId);
+
+    // Render the cell into the builder.
+    SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
+    if (column instanceof Column) {
+      /*
+       * If the HasCell is a Column, let it render the Cell itself. This is
+       * here for legacy support.
+       */
+      Column<T, C> theColumn = (Column<T, C>) column;
+      theColumn.render(context, rowValue, cellBuilder);
+    } else {
+      column.getCell().render(context, column.getValue(rowValue), cellBuilder);
+    }
+    builder.html(cellBuilder.toSafeHtml());
+  }
+  
+  /**
+   * 
+   */
+  /**
+   * Start building rows. Reset the internal table section builder. If the table builder is going
+   * to re-build all rows, the internal the maps associating the cells and ids will be cleared.
+   *
+   * @param isRebuildingAllRows is this start intended for rebuilding all rows
+   */
+  @Override
+  public final void start(boolean isRebuildingAllRows) {
+    /*
+     * TODO(jlabanca): Test with DomBuilder.
+     * 
+     * DOM manipulation is sometimes faster than String concatenation and
+     * innerHTML, but not when mixing the two. Cells render as HTML strings,
+     * so its faster to render the entire table as a string.
+     */
+    tbody = HtmlBuilderFactory.get().createTBodyBuilder();
+    if (isRebuildingAllRows) {
+      cellToIdMap.clear();
+      idToCellMap.clear();
+    }
+  }
+
+  /**
+   * Start a row and return the {@link TableRowBuilder} for this row.
+   */
+  public final TableRowBuilder startRow() {
+    // End any dangling rows.
+    while (tbody.getDepth() > 1) {
+      tbody.end();
+    }
+
+    // Verify the depth.
+    if (tbody.getDepth() < 1) {
+      throw new IllegalStateException(
+          "Cannot start a row.  Did you call TableRowBuilder.end() too many times?");
+    }
+
+    // Start the next row.
+    TableRowBuilder row = tbody.startTR();
+    row.attribute(ROW_ATTRIBUTE, rowIndex);
+    row.attribute(SUBROW_ATTRIBUTE, subrowIndex);
+    subrowIndex++;
+    return row;
+  }
+    
+  /**
+   * Build zero or more table rows for the specified row value.
+   * 
+   * @param rowValue the value for the row to render
+   * @param absRowIndex the absolute row index
+   */
+  protected abstract void buildRowImpl(T rowValue, int absRowIndex);
+  
+  /**
+   * Check if an element is the parent of a rendered cell.
+   * 
+   * @param elem the element to check
+   * @return the cellId if a cell parent, null if not
+   */
+  private String getCellId(Element elem) {
+    if (elem == null) {
+      return null;
+    }
+    String cellId = elem.getAttribute(CELL_ATTRIBUTE);
+    return (cellId == null) || (cellId.length() == 0) ? null : cellId;
+  }
+
+  /**
+   * Return the column containing an element.
+   *
+   * @param elem the elm that the column contains
+   * @return the column containing the element.
+   */
+  private HasCell<T, ?> getColumn(Element elem) {
+    String cellId = getCellId(elem);
+    return (cellId == null) ? null : idToCellMap.get(cellId);
+  }
+
+  /**
+   * Set the information for the current row to build.
+   *
+   * @param rowIndex the index of the row
+   * @param rowValue the value of this row
+   */
+  private void setRowInfo(int rowIndex, T rowValue) {
+    this.rowIndex = rowIndex;
+    this.rowValueKey = cellTable.getValueKey(rowValue);
+    this.subrowIndex = 0; // Reset the subrow.
+  }
+}
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTableBuilder.java b/user/src/com/google/gwt/user/cellview/client/CellTableBuilder.java
index 4ba4b1d..2b82062 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTableBuilder.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTableBuilder.java
@@ -17,8 +17,11 @@
 
 import com.google.gwt.cell.client.Cell.Context;
 import com.google.gwt.cell.client.HasCell;
-import com.google.gwt.dom.builder.shared.ElementBuilderBase;
-import com.google.gwt.dom.builder.shared.TableRowBuilder;
+import com.google.gwt.dom.builder.shared.TableSectionBuilder;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.TableRowElement;
+
+import java.util.Collection;
 
 /**
  * Builder used to construct a CellTable.
@@ -28,63 +31,66 @@
 public interface CellTableBuilder<T> {
 
   /**
-   * Utility to help build a table.
-   * 
-   * @param <T> the row data type
-   */
-  abstract static class Utility<T> {
-
-    /**
-     * Only instantiable by CellTable implementation.
-     */
-    Utility() {
-    }
-
-    /**
-     * Create a {@link Context} object for the specific column index that can be
-     * passed to a Cell.
-     * 
-     * @param column the column index of the context
-     * @return a {@link Context} object
-     */
-    public abstract Context createContext(int column);
-
-    /**
-     * Render a Cell into the specified {@link ElementBuilderBase}. Use this
-     * method to ensure that the Cell Widget properly handles events originating
-     * in the Cell.
-     * 
-     * <p>
-     * The {@link ElementBuilderBase} must be in a state where attributes and
-     * html can be appended. If the builder already contains a child element,
-     * this method will fail.
-     * </p>
-     * 
-     * @param <C> the data type of the cell
-     * @param builder the {@link ElementBuilderBase} to render into
-     * @param context the {@link Context} of the cell
-     * @param column the column or {@link HasCell} to render
-     * @param rowValue the row value to render
-     * @see #createContext(int)
-     */
-    public abstract <C> void renderCell(ElementBuilderBase<?> builder, Context context,
-        HasCell<T, C> column, T rowValue);
-
-    /**
-     * Add a row to the table.
-     * 
-     * @return the row to add
-     */
-    public abstract TableRowBuilder startRow();
-  }
-
-  /**
-   * Build zero or more table rows for the specified row value using the
-   * {@link Utility}.
-   * 
+   * Build zero or more table rows for the specified row value.
+   *
    * @param rowValue the value for the row to render
    * @param absRowIndex the absolute row index
-   * @param utility the utility used to build the table
    */
-  void buildRow(T rowValue, int absRowIndex, Utility<T> utility);
+  void buildRow(T rowValue, int absRowIndex);
+  
+  /**
+   * Finish the building of rows and return the table section builder. Currently only
+   * {@link HtmlTableSectionBuilder} and its subclasses are supported.
+   */
+  TableSectionBuilder finish();
+  
+  /**
+   * Return the column containing an element.
+   *
+   * @param context the context for the element
+   * @param rowValue the value for the row corresponding to the element
+   * @param elm the elm that the column contains
+   * @return the immediate column containing the element
+   */
+  HasCell<T, ?> getColumn(Context context, T rowValue, Element elem);
+
+  /**
+   * Return all the columns that this table builder has renderred.
+   */
+  Collection<HasCell<T, ?>> getColumns();
+  
+  /**
+   * Get the index of the primary row from the associated {@link TableRowElement} (an TR element).
+   * 
+   * @param row the row element
+   * @return the row value index
+   */
+  int getRowValueIndex(TableRowElement row);
+  
+  /**
+   * Get the index of the subrow value from the associated
+   * {@link TableRowElement} (an TR element). The sub row value starts at 0 for the first row
+   * that represents a row value.
+   * 
+   * @param row the row element
+   * @return the subrow value index, or 0 if not found
+   */
+  int getSubrowValueIndex(TableRowElement row);
+  
+  /**
+   * Return if an element contains a cell. This may be faster to execute than {@link getColumn}.
+   *
+   * @param elem the element of interest
+   */
+  boolean isColumn(Element elem);
+  
+  /**
+   * Start building rows. User may want to reset the internal state of the table builder (e.g., 
+   * reset the internal table section builder). A flag isRebuildingAllRows is used to mark whether
+   * the builder is going to rebuild all rows. User may want to have different reset logic given
+   * this flag.
+   *
+   * @param isRebuildingAllRows is this start intended for rebuilding all rows
+   */
+  void start(boolean isRebuildingAllRows);
 }
diff --git a/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java b/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
index 487f431..f828f2e 100644
--- a/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
+++ b/user/test/com/google/gwt/user/cellview/client/AbstractCellTableTestBase.java
@@ -110,16 +110,15 @@
     CellTableBuilder<String> builder =
         new AbstractCellTable.DefaultCellTableBuilder<String>(table) {
           @Override
-          public void buildRow(String rowValue, int absRowIndex,
-              CellTableBuilder.Utility<String> utility) {
+          public void buildRowImpl(String rowValue, int absRowIndex) {
             builtRows.add(absRowIndex);
-            TableRowBuilder tr = utility.startRow();
+            TableRowBuilder tr = startRow();
             tr.endTR(); // End the tr.
             tr.end(); // Accidentally end the table section.
 
             // Try to start another row.
             try {
-              utility.startRow();
+              startRow();
               fail("Expected IllegalStateException: tbody is already ended");
             } catch (IllegalStateException e) {
               // Expected.
@@ -143,14 +142,13 @@
     CellTableBuilder<String> builder =
         new AbstractCellTable.DefaultCellTableBuilder<String>(table) {
           @Override
-          public void buildRow(String rowValue, int absRowIndex,
-              CellTableBuilder.Utility<String> utility) {
-            super.buildRow(rowValue, absRowIndex, utility);
+          public void buildRowImpl(String rowValue, int absRowIndex) {
+            super.buildRowImpl(rowValue, absRowIndex);
 
             // Add child rows to row five.
             if (absRowIndex == 5) {
               for (int i = 0; i < 4; i++) {
-                TableRowBuilder tr = utility.startRow();
+                TableRowBuilder tr = startRow();
                 tr.startTD().colSpan(2).text("child " + i).endTD();
                 tr.endTR();
               }
@@ -188,11 +186,10 @@
     CellTableBuilder<String> builder =
         new AbstractCellTable.DefaultCellTableBuilder<String>(table) {
           @Override
-          public void buildRow(String rowValue, int absRowIndex,
-              CellTableBuilder.Utility<String> utility) {
+          public void buildRowImpl(String rowValue, int absRowIndex) {
             // Skip row index 5.
             if (absRowIndex != 5) {
-              super.buildRow(rowValue, absRowIndex, utility);
+              super.buildRowImpl(rowValue, absRowIndex);
             }
           }
         };
@@ -655,13 +652,12 @@
     CellTableBuilder<String> builder =
         new AbstractCellTable.DefaultCellTableBuilder<String>(table) {
           @Override
-          public void buildRow(String rowValue, int absRowIndex,
-              CellTableBuilder.Utility<String> utility) {
-            super.buildRow(rowValue, absRowIndex, utility);
+          public void buildRowImpl(String rowValue, int absRowIndex) {
+            super.buildRowImpl(rowValue, absRowIndex);
 
             // Add some children.
             for (int i = 0; i < 4; i++) {
-              TableRowBuilder tr = utility.startRow();
+              TableRowBuilder tr = startRow();
               tr.startTD().colSpan(2).text("child " + absRowIndex + ":" + i).endTD();
               tr.endTR();
             }