Implements UiBinder rendering for Cells.
Rendering values passed to the render() method.
Retrieving ui:fields using getters

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10434 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt23_24userApi.conf b/tools/api-checker/config/gwt23_24userApi.conf
index 14359f9..2801c61 100644
--- a/tools/api-checker/config/gwt23_24userApi.conf
+++ b/tools/api-checker/config/gwt23_24userApi.conf
@@ -106,6 +106,7 @@
 :user/src/com/google/gwt/user/client/rpc/core/**\
 :user/src/com/google/gwt/user/client/rpc/impl/**\
 :user/src/com/google/gwt/uibinder/attributeparsers/**\
+:user/src/com/google/gwt/uibinder/client/UiRendererUtils.java\
 :user/src/com/google/gwt/uibinder/elementparsers/**\
 :user/src/com/google/gwt/uibinder/testing/**\
 :user/src/com/google/gwt/util/**\
diff --git a/user/src/com/google/gwt/text/shared/UiRenderer.java b/user/src/com/google/gwt/text/shared/UiRenderer.java
deleted file mode 100644
index c2064b8..0000000
--- a/user/src/com/google/gwt/text/shared/UiRenderer.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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.text.shared;
-
-/**
- * Marker interface for SafeHtmlRenderer implementations to be code generated
- * from ui.xml files.
- * 
- * @param <T> the type to render
- */
-public interface UiRenderer<T> extends SafeHtmlRenderer<T> {
-}
diff --git a/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml b/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
index 3e2b6db..749c0c9 100644
--- a/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
+++ b/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
@@ -34,7 +34,7 @@
   <set-configuration-property name="UiBinder.useLazyWidgetBuilders" value="false"/>
 
   <generate-with class="com.google.gwt.uibinder.rebind.UiBinderGenerator">
-    <when-type-assignable class="com.google.gwt.text.shared.UiRenderer"/>
+    <when-type-assignable class="com.google.gwt.uibinder.client.UiRenderer"/>
   </generate-with>
 
   <generate-with class="com.google.gwt.uibinder.rebind.UiBinderGenerator">
diff --git a/user/src/com/google/gwt/uibinder/client/AbstractUiRenderer.java b/user/src/com/google/gwt/uibinder/client/AbstractUiRenderer.java
new file mode 100644
index 0000000..4dd7262
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/AbstractUiRenderer.java
@@ -0,0 +1,57 @@
+/*
+ * 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.uibinder.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+
+/**
+ * Abstract implementation of a safe HTML binder to make implementation of generated rendering
+ * simpler.
+ * 
+ * @param <T> the type to render
+ */
+public abstract class AbstractUiRenderer<T> implements UiRenderer<T> {
+
+  /**
+   * Marker attribute for DOM structures previously generated by UiRenderer.
+   */
+  public static final String RENDERED_ATTRIBUTE = "gwtuirendered";
+
+  /**
+   * Holds the part of the id attribute common to all elements being rendered.
+   */
+  protected String uiId;
+
+  @Override
+  public boolean isParentOrRenderer(Element parent) {
+    if (parent == null) {
+      return false;
+    }
+    
+    Element root;
+    try {
+      root = UiRendererUtilsImpl.findRootElement(parent, RENDERED_ATTRIBUTE);
+      return UiRendererUtilsImpl.isAttachedToDom(root)
+          && UiRendererUtilsImpl.isRenderedElementSingleChild(root);
+    } catch (IllegalArgumentException e) {
+      return false;
+    }
+  }
+
+  @Override
+  public void onBrowserEvent(T cell, Element parent, NativeEvent event) {
+    // Trivial implementation when no @UiHandlers
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiRenderer.java b/user/src/com/google/gwt/uibinder/client/UiRenderer.java
new file mode 100644
index 0000000..d8bfc0a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiRenderer.java
@@ -0,0 +1,46 @@
+/*
+ * 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.uibinder.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+
+/**
+ * Marker interface for classes whose implementation is to be provided via UiBinder code
+ * generation for SafeHtml rendering.
+ * 
+ * @param <T> the type to render
+ */
+public interface UiRenderer<T> {
+
+  /**
+   * Checks whether {@code parent} is a valid element to use as an argument for field getters.
+   * 
+   * @return {@code true} if parent contains or directly points to a previously rendered element.
+   *         In DevMode it also checks whether the parent is attached to the DOM
+   */
+  boolean isParentOrRenderer(Element parent);
+
+  /**
+   * Receives an event and dispatches it to the appropriate handler method annotated
+   * with {@code @UiHandler}.
+   *
+   * @param receiver of events
+   * @param parent element containing a previously rendered element
+   * @param event received by the browser
+   */
+  void onBrowserEvent(T receiver, Element parent, NativeEvent event);
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiRendererUtilsImpl.java b/user/src/com/google/gwt/uibinder/client/UiRendererUtilsImpl.java
new file mode 100644
index 0000000..b59a48c
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiRendererUtilsImpl.java
@@ -0,0 +1,167 @@
+/*
+ * 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.uibinder.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
+
+/**
+ * UiRenderer utilities.
+ */
+public class UiRendererUtilsImpl {
+
+  /**
+   * Build id strings used to identify DOM elements related to ui:fields.
+   * 
+   * @param fieldName name of the field that identifies the element
+   * @param uiId common part of the identifier for all elements in the rendered DOM structure
+   */
+  public static String buildInnerId(String fieldName, String uiId) {
+    return uiId + ":" + fieldName;
+  }
+
+  /**
+   * Retrieves a specific element within a previously rendered element.
+   * 
+   * @param parent parent element containing the element of interest
+   * @param fieldName name of the field to retrieve
+   * @param attribute that identifies the root element as such
+   * @return the element identified by {@code fieldName}
+   *
+   * @throws IllegalArgumentException if the {@code parent} does not point to or contains
+   *         a previously rendered element. In DevMode also when the root element is not
+   *         attached to the DOM
+   * @throws IllegalStateException parent does not contain an element matching
+   *         {@code filedName}
+   *
+   * @throws RuntimeException if the root element is not attached to the DOM and not running in
+   *         DevMode
+   * 
+   * @throws NullPointerException if {@code parent} == null
+   */
+  public static Element findInnerField(Element parent, String fieldName, String attribute) {
+    Element root = findRootElement(parent, attribute);
+
+    if (parent != root && !isRenderedElementSingleChild(root)) {
+      throw new IllegalArgumentException(
+          "Parent Element of previously rendered element contains more than one child"
+          + " while getting \"" + fieldName + "\"");
+    }
+
+    String uiId = root.getAttribute(attribute);
+    String renderedId = buildInnerId(fieldName, uiId);
+
+    Element elementById = Document.get().getElementById(renderedId);
+    if (elementById == null) {
+      if (!isAttachedToDom(root)) {
+        throw new RuntimeException("UiRendered element is not attached to DOM while getting \""
+            + fieldName + "\"");
+      } else if (!GWT.isProdMode()) {
+        throw new IllegalStateException("\"" + fieldName
+            + "\" not found within rendered element");
+      } else {
+        // In prod mode we do not distinguish between being unattached or not finding the element
+        throw new IllegalArgumentException("UiRendered element is not attached to DOM, or \""
+            + fieldName + "\" not found within rendered element");
+      }
+    }
+    return elementById;
+  }
+
+  /**
+   * Retrieves the root of a previously rendered element contained within the {@code parent}.
+   * The {@code parent} must either contain the previously rendered DOM structure as its only child,
+   * or point directly to the rendered element root.
+   * 
+   * @param parent element containing, or pointing to, a previously rendered DOM structure
+   * @param attribute attribute name that identifies the root of the DOM structure
+   * @return the root element of the previously rendered DOM structure
+   * 
+   * @throws NullPointerException if {@code parent} == null
+   * @throws IllegalArgumentException if {@code parent} does not contain a previously rendered
+   *         element
+   */
+  public static Element findRootElement(Element parent, String attribute) {
+    if (parent == null) {
+      throw new NullPointerException("parent argument is null");
+    }
+
+    Element rendered;
+    if (parent.hasAttribute(attribute)) {
+      // The parent is the root
+      return parent;
+    } else if ((rendered = parent.getFirstChildElement()) != null
+        && rendered.hasAttribute(attribute)) {
+      // The first child is the root
+      return rendered;
+    } else {
+      throw new IllegalArgumentException(
+          "Parent element does not contain a previously rendered element");
+    }
+  }
+
+  /**
+   * Walks up the parents of the {@code rendered} element to ascertain that it is attached to the
+   * document.
+   */
+  public static boolean isAttachedToDom(Element rendered) {
+    if (GWT.isProdMode()) {
+      return true;
+    }
+
+    Element body = Document.get().getBody();
+
+    while (rendered != null && rendered.hasParentElement() && !body.equals(rendered)) {
+      rendered = rendered.getParentElement();
+    }
+    return body.equals(rendered);
+  };
+
+  /**
+   * Checks that the parent of {@code rendered} has a single child.
+   */
+  public static boolean isRenderedElementSingleChild(Element rendered) {
+    return GWT.isProdMode() || rendered.getParentElement().getChildCount() == 1;
+  }
+
+  /**
+   * Inserts an attribute into the first tag found in a {@code safeHtml} template.
+   * This method assumes that the {@code safeHtml} template begins with an open HTML tag.
+   * {@code SafeHtml} templates produced by UiBinder always meet these conditions.
+   * <p>
+   * This method does not attempt to ensure {@code atributeName} and {@code attributeValue}
+   * contain safe values.
+   *
+   * @returns the {@code safeHtml} template with "{@code attributeName}={@code attributeValue}"
+   *          inserted as an attribute of the first tag found
+   */
+  public static SafeHtml stampUiRendererAttribute(SafeHtml safeHtml, String attributeName,
+      String attributeValue) {
+    String html = safeHtml.asString();
+    int endOfFirstTag = html.indexOf(">");
+
+    assert endOfFirstTag > 1 : "Safe html template does not start with an HTML open tag";
+    
+    if (html.charAt(endOfFirstTag - 1) == '/') {
+      endOfFirstTag--;
+    }
+
+    html = html.substring(0, endOfFirstTag) + " " + attributeName + "=\"" + attributeValue + "\""
+            + html.substring(endOfFirstTag);
+    return SafeHtmlUtils.fromTrustedString(html);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
index c10083c..9cb1032 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
@@ -82,7 +82,7 @@
     // that idHolder is a local variable reference, not a string id. We
     // have to generate the ids at runtime, not compile time, or else
     // we'll reuse ids for any template rendered more than once.
-    String idHolder = uiWriter.declareDomIdHolder();
+    String idHolder = uiWriter.declareDomIdHolder(null);
     uiWriter.ensureCurrentFieldAttached();
 
     FieldWriter childFieldWriter = uiWriter.parseElementToFieldWriter(elem);
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
index 5af8f3a..f335218 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
@@ -97,7 +97,7 @@
       name = "widget" + (++serial);
     }
 
-    String idHolder = uiWriter.declareDomIdHolder();
+    String idHolder = uiWriter.declareDomIdHolder(null);
     idToWidgetElement.put(idHolder, elem);
 
     if (oracle.findType(HasHTML.class.getName()).isAssignableFrom(type)) {
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
index 0960b06..610c07e 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JField;
 import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
@@ -41,7 +42,7 @@
  */
 public class UiBinderParser {
 
-  private enum Resource {
+  enum Resource {
     DATA {
       @Override
       void create(UiBinderParser parser, XMLElement elem)
@@ -153,7 +154,10 @@
 
   private JClassType consumeTypeAttribute(XMLElement elem)
       throws UnableToCompleteException {
-    String resourceTypeName = elem.consumeRequiredRawAttribute(TYPE_ATTRIBUTE);
+    if (!elem.hasAttribute(TYPE_ATTRIBUTE)) {
+      return null;
+    }
+    String resourceTypeName = elem.consumeRawAttribute(TYPE_ATTRIBUTE);
 
     JClassType resourceType = oracle.findType(resourceTypeName);
     if (resourceType == null) {
@@ -256,56 +260,122 @@
    */
   private void createResource(XMLElement elem) throws UnableToCompleteException {
     String resourceName = elem.consumeRequiredRawAttribute(FIELD_ATTRIBUTE);
+
     JClassType resourceType = consumeTypeAttribute(elem);
+
     if (elem.getAttributeCount() > 0) {
-      writer.die(elem, "Should only find attributes \"field\" and \"type\"");
+      writer.die(elem, "Should only find attributes \"%s\" and \"%s\".", FIELD_ATTRIBUTE,
+          TYPE_ATTRIBUTE);
     }
 
-    FieldWriter fieldWriter = fieldManager.registerField(
-        FieldWriterType.IMPORTED, resourceType, resourceName);
-    OwnerField ownerField = writer.getOwnerClass().getUiField(resourceName);
+    /* Is it a parameter passed to a render method? */
 
-    /* Perhaps it is provided via @UiField */
-
-    if (ownerField != null) {
-      if (!resourceType.getErasedType().equals(ownerField.getType().getRawType()
-          .getErasedType())) {
-        writer.die(elem, "Type must match %s", ownerField);
-      }
-      if (ownerField.isProvided()) {
-        String initializer;
-        if (writer.getDesignTime().isDesignTime()) {
-          String typeName = ownerField.getType().getRawType().getQualifiedSourceName();
-          initializer = writer.getDesignTime().getProvidedField(typeName,
-              ownerField.getName());
-        } else {
-          initializer = "owner." + ownerField.getName();
-        }
-        fieldWriter.setInitializer(initializer);
+    if (writer.isRenderer()) {
+      JClassType matchingResourceType = findRenderParameterType(resourceName);
+      if (matchingResourceType != null) {
+        createResourceUiRenderer(elem, resourceName, resourceType, matchingResourceType);
         return;
       }
     }
 
-    /* Nope. Maybe a @UiFactory will make it */
+    /* Perhaps it is provided via @UiField */
 
-    JMethod factoryMethod = writer.getOwnerClass().getUiFactoryMethod(
-        resourceType);
-    if (factoryMethod != null) {
-      String initializer;
-      if (writer.getDesignTime().isDesignTime()) {
-        String typeName = factoryMethod.getReturnType().getQualifiedSourceName();
-        initializer = writer.getDesignTime().getProvidedFactory(typeName,
-            factoryMethod.getName(), "");
-      } else {
-        initializer = String.format("owner.%s()", factoryMethod.getName());
+    if (writer.getOwnerClass() == null) {
+      writer.die("No owner provided for %s", writer.getBaseClass().getQualifiedSourceName());
+    }
+
+    if (writer.getOwnerClass().getUiField(resourceName) != null) {
+      // If the resourceType is present, is it the same as the one in the base class?
+      OwnerField ownerField = writer.getOwnerClass().getUiField(resourceName);
+
+      // If the resourceType was given, it must match the one declared with @UiField
+      if (resourceType != null && !resourceType.getErasedType()
+          .equals(ownerField.getType().getRawType().getErasedType())) {
+        writer.die(elem, "Type must match %s.", ownerField);
       }
-      fieldWriter.setInitializer(initializer);
+
+      if (ownerField.isProvided()) {
+        createResourceUiField(resourceName, resourceType, ownerField);
+        return;
+      } else {
+        // Let's keep trying, but we know the type at least.
+        resourceType = ownerField.getType().getRawType().getErasedType();
+      }
+    }
+
+    /* Nope. If we know the type, maybe a @UiFactory will make it */
+
+    if (resourceType != null && writer.getOwnerClass().getUiFactoryMethod(resourceType) != null) {
+      createResourceUiFactory(elem, resourceName, resourceType);
+      return;
     }
 
     /*
      * If neither of the above, the FieldWriter's default GWT.create call will
      * do just fine.
      */
+    if (resourceType != null) {
+      fieldManager.registerField(FieldWriterType.IMPORTED, resourceType, resourceName);
+    } else {
+      writer.die(elem, "Could not infer type for field %s.", resourceName);
+    }
+  }
+
+  private void createResourceUiFactory(XMLElement elem, String resourceName, JClassType resourceType)
+      throws UnableToCompleteException {
+    FieldWriter fieldWriter;
+    JMethod factoryMethod = writer.getOwnerClass().getUiFactoryMethod(resourceType);
+    JClassType methodReturnType = factoryMethod.getReturnType().getErasedType()
+        .isClassOrInterface();
+    if (!resourceType.getErasedType().equals(methodReturnType)) {
+      writer.die(elem, "Type must match %s.", methodReturnType);
+    }
+
+    String initializer;
+    if (writer.getDesignTime().isDesignTime()) {
+      String typeName = factoryMethod.getReturnType().getQualifiedSourceName();
+      initializer = writer.getDesignTime().getProvidedFactory(typeName,
+          factoryMethod.getName(), "");
+    } else {
+      initializer = String.format("owner.%s()", factoryMethod.getName());
+    }
+    fieldWriter = fieldManager.registerField(
+        FieldWriterType.IMPORTED, resourceType, resourceName);
+    fieldWriter.setInitializer(initializer);
+  }
+
+  private void createResourceUiField(String resourceName, JClassType resourceType,
+      OwnerField ownerField)
+      throws UnableToCompleteException {
+    FieldWriter fieldWriter;
+    String initializer;
+
+    if (writer.getDesignTime().isDesignTime()) {
+      String typeName = ownerField.getType().getRawType().getQualifiedSourceName();
+      initializer = writer.getDesignTime().getProvidedField(typeName, ownerField.getName());
+    } else {
+      initializer = "owner." + ownerField.getName();
+    }
+    fieldWriter = fieldManager.registerField(
+        FieldWriterType.IMPORTED,
+        ownerField.getType().getRawType().getErasedType(),
+        resourceName);
+    fieldWriter.setInitializer(initializer);
+  }
+
+  private void createResourceUiRenderer(XMLElement elem, String resourceName,
+      JClassType resourceType, JClassType matchingResourceType) throws UnableToCompleteException {
+    FieldWriter fieldWriter;
+    if (resourceType != null
+        && !resourceType.getErasedType().isAssignableFrom(matchingResourceType.getErasedType())) {
+      writer.die(elem, "Type must match the type of parameter %s in %s#render method.",
+          resourceName,
+          writer.getBaseClass().getQualifiedSourceName());
+    }
+
+    fieldWriter = fieldManager.registerField(
+        FieldWriterType.IMPORTED, matchingResourceType.getErasedType(), resourceName);
+    fieldWriter.setInitializer("this." + resourceName);
   }
 
   private void createSingleImport(XMLElement elem, JClassType enclosingType,
@@ -374,6 +444,27 @@
     return publicType;
   }
 
+  private JClassType findRenderParameterType(String resourceName) {
+    JMethod renderMethod = null;
+    for (JMethod method : writer.getBaseClass().getMethods()) {
+      if (method.getName().equals("render")) {
+        renderMethod = method;
+        break;
+      }
+    }
+    if (renderMethod == null) {
+      return null;
+    }
+    JClassType matchingResourceType = null;
+    for (JParameter jParameter : renderMethod.getParameters()) {
+      if (jParameter.getName().equals(resourceName)) {
+        matchingResourceType = jParameter.getType().isClassOrInterface();
+        break;
+      }
+    }
+    return matchingResourceType;
+  }
+
   private void findResources(XMLElement binderElement)
       throws UnableToCompleteException {
     binderElement.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 0649c5d..255fcf0 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -17,19 +17,24 @@
 
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.JPackage;
+import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JTypeParameter;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dom.client.TagName;
 import com.google.gwt.resources.client.ClientBundle;
-import com.google.gwt.text.shared.UiRenderer;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.uibinder.attributeparsers.AttributeParser;
 import com.google.gwt.uibinder.attributeparsers.AttributeParsers;
 import com.google.gwt.uibinder.attributeparsers.BundleAttributeParser;
 import com.google.gwt.uibinder.attributeparsers.BundleAttributeParsers;
+import com.google.gwt.uibinder.client.AbstractUiRenderer;
 import com.google.gwt.uibinder.client.LazyDomElement;
 import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiRenderer;
 import com.google.gwt.uibinder.elementparsers.AttributeMessageParser;
 import com.google.gwt.uibinder.elementparsers.BeanParser;
 import com.google.gwt.uibinder.elementparsers.ElementParser;
@@ -48,9 +53,11 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
+import java.beans.Introspector;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -169,6 +176,67 @@
     return propName.substring(0, 1).toUpperCase() + propName.substring(1);
   }
 
+  /**
+   * Scan the base class for the getter methods. Assumes getters begin with
+   * "get". See {@link #validateRendererGetters(JClassType)} for a method that
+   * guarantees this method will succeed.
+   */
+  private static List<JMethod> findGetterNames(JClassType owner) {
+    List<JMethod> ret = new ArrayList<JMethod>();
+    for (JMethod jMethod : owner.getMethods()) {
+      String getterName = jMethod.getName();
+      if (getterName.startsWith("get")) {
+        ret.add(jMethod);
+      }
+    }
+    return ret;
+  }
+
+  /**
+   * Scans a class for a method named "render". Returns its parameters except for the
+   * first one. See {@link #validateRenderParameters(JClassType)} for a method that
+   * guarantees this method will succeed.
+   */
+  private static JParameter[] findRenderParameters(JClassType owner) {
+    JMethod[] methods = owner.getMethods();
+    JMethod renderMethod = null;
+
+    for (JMethod jMethod : methods) {
+      if (jMethod.getName().equals("render")) {
+          renderMethod = jMethod;
+      }
+    }
+
+    JParameter[] parameters = renderMethod.getParameters();
+    return Arrays.copyOfRange(parameters, 1, parameters.length);
+  }
+
+  /**
+   * Determine the field name a getter is trying to retrieve. Assumes getters begin with
+   * "get".
+   */
+  private static String getterToFieldName(String name) {
+    String fieldName = name.substring(3);
+    return Introspector.decapitalize(fieldName);
+  }
+
+  private static String renderMethodParameters(JParameter[] renderParameters) {
+    StringBuilder builder = new StringBuilder();
+
+    for (int i = 0; i < renderParameters.length; i++) {
+      JParameter parameter = renderParameters[i];
+      builder.append("final ");
+      builder.append(parameter.getType().getQualifiedSourceName());
+      builder.append(" ");
+      builder.append(parameter.getName());
+      if (i < renderParameters.length - 1) {
+        builder.append(", ");
+      }
+    }
+
+    return builder.toString();
+  }
+
   private final MortalLogger logger;
 
   /**
@@ -412,7 +480,7 @@
   public String declareDomField(String fieldName, String ancestorField)
       throws UnableToCompleteException {
     ensureAttached();
-    String name = declareDomIdHolder();
+    String name = declareDomIdHolder(fieldName);
 
     if (useLazyWidgetBuilders) {
       // Create and initialize the dom field with LazyDomElement.
@@ -450,15 +518,23 @@
 
   /**
    * Declare a variable that will be filled at runtime with a unique id, safe
-   * for use as a dom element's id attribute.
+   * for use as a dom element's id attribute. For {@code UiRenderer} based code,
+   * elements corresponding to a ui:field, need and id initialized to a
+   * value that depends on the {@code fieldName}. For all other cases let {@code fieldName}
+   * be {@code null}.
    *
+   * @param fieldName name of the field corresponding to this variable.
    * @return that variable's name.
    */
-  public String declareDomIdHolder() throws UnableToCompleteException {
+  public String declareDomIdHolder(String fieldName) throws UnableToCompleteException {
     String domHolderName = "domId" + domId++;
     FieldWriter domField = fieldManager.registerField(FieldWriterType.DOM_ID_HOLDER,
         oracle.findType(String.class.getName()), domHolderName);
-    domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
+    if (isRenderer && fieldName != null) {
+      domField.setInitializer("UiRendererUtilsImpl.buildInnerId(\"" + fieldName + "\", uiId)");
+    } else {
+      domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
+    }
 
     return domHolderName;
   }
@@ -689,6 +765,13 @@
   }
 
   /**
+   * The type we have been asked to generated, e.g. MyUiBinder
+   */
+  public JClassType getBaseClass() {
+    return baseClass;
+  }
+
+  /**
    * Finds an attribute {@link BundleAttributeParser} for the given xml
    * attribute, if any, based on its namespace uri.
    *
@@ -814,6 +897,10 @@
     return findFieldType(elem).isAssignableTo(isRenderableClassType);
   }
 
+  public boolean isRenderer() {
+    return isRenderer;
+  }
+
   public boolean isWidgetElement(XMLElement elem) throws UnableToCompleteException {
     return isElementAssignableTo(elem, IsWidget.class);
   }
@@ -1326,6 +1413,74 @@
   }
 
   /**
+   * Scan the base class for the getter methods. Assumes getters begin with
+   * "get" and validates that each corresponds to a field declared with {@code ui:field},
+   * it has a single parameter, the parameter type is assignable to {@code Element} and
+   * its return type is assignable to {@code Element}.
+   */
+  private void validateRendererGetters(JClassType owner) throws UnableToCompleteException {
+    for (JMethod jMethod : owner.getMethods()) {
+      String getterName = jMethod.getName();
+      if (getterName.startsWith("get")) {
+        if (jMethod.getParameterTypes().length != 1) {
+          die("Getter %s must have exactly one parameter",
+              getterName);
+        }
+        String elementClassName = com.google.gwt.dom.client.Element.class.getCanonicalName();
+        JClassType elementType = oracle.findType(elementClassName);
+        JClassType getterParamType = jMethod.getParameterTypes()[0].getErasedType()
+            .isClassOrInterface();
+
+        if (!elementType.isAssignableFrom(getterParamType)) {
+          die("Getter %s must have exactly one parameter of type assignable to %s",
+              getterName,
+              elementClassName);
+        }
+        String fieldName = getterToFieldName(getterName);
+        FieldWriter field = fieldManager.lookup(fieldName);
+        if (field == null || !FieldWriterType.DEFAULT.equals(field.getFieldType())) {
+          die("%s does not match a \"ui:field='%s'\" declaration", getterName, fieldName);
+        }
+      } else if (!getterName.equals("render")) {
+        die("Unexpected method \"%s\" found", getterName);
+      }
+    }
+  }
+
+  /**
+   * Scans a class to validate that it contains a single method called render,
+   * which has a {@code void} return type, and its first parameter is of type
+   * {@code SafeHtmlBuilder}.
+   */
+  private void validateRenderParameters(JClassType owner) throws UnableToCompleteException {
+    JMethod[] methods = owner.getMethods();
+    JMethod renderMethod = null;
+
+    for (JMethod jMethod : methods) {
+      if (jMethod.getName().equals("render")) {
+        if (renderMethod == null) {
+          renderMethod = jMethod;
+        } else {
+          die("%s declares more than one method named render",
+              baseClass.getQualifiedSourceName());
+        }
+      }
+    }
+
+    if (renderMethod == null
+        || renderMethod.getParameterTypes().length < 1
+        || !renderMethod.getParameterTypes()[0].getErasedType().getQualifiedSourceName().equals(
+            SafeHtmlBuilder.class.getCanonicalName())) {
+      die("%s does not declare a render(SafeHtmlBuilder ...) method",
+          baseClass.getQualifiedSourceName());
+    }
+    if (!JPrimitiveType.VOID.equals(renderMethod.getReturnType())) {
+      die("%s#render(SafeHtmlBuilder ...) does not return void",
+          baseClass.getQualifiedSourceName());
+    }
+  }
+
+  /**
    * Write statements that parsers created via calls to {@link #addStatement}.
    * Such statements will assume that {@link #writeGwtFields} has already been
    * called.
@@ -1462,7 +1617,8 @@
           uiOwnerType.getParameterizedQualifiedSourceName(),
           baseClass.getParameterizedQualifiedSourceName());
     } else {
-      w.write("public class %s extends AbstractSafeHtmlRenderer<%s> implements %s {", implClassName,
+      w.write("public class %s extends %s<%s> implements %s {", implClassName,
+          AbstractUiRenderer.class.getName(),
           uiOwnerType.getParameterizedQualifiedSourceName(),
           baseClass.getParameterizedQualifiedSourceName());
     }
@@ -1537,6 +1693,7 @@
           uiRootType.getName());
     } else {
       w.write("import com.google.gwt.text.shared.AbstractSafeHtmlRenderer;");
+      w.write("import com.google.gwt.uibinder.client.UiRendererUtilsImpl;");
     }
   }
 
@@ -1600,11 +1757,13 @@
   }
 
   /**
-   * Writes the SafeHtmlRenderer's source for the renderable
+   * Writes the UiRenderer's source for the renderable
    * strategy.
    */
-  private void writeRenderer(
-      IndentedWriter w, String rootField) throws UnableToCompleteException {
+  private void writeRenderer(IndentedWriter w, String rootField) throws UnableToCompleteException {
+    validateRendererGetters(baseClass);
+    validateRenderParameters(baseClass);
+
     writePackage(w);
 
     writeImports(w);
@@ -1619,56 +1778,89 @@
 
     w.newline();
 
-    w.write("public SafeHtml render(final %s owner) {",
-        uiOwnerType.getParameterizedQualifiedSourceName());
+    JParameter[] renderParameters = findRenderParameters(baseClass);
+
+    writeRenderParameterDefinitions(w, renderParameters);
+
+    String renderParameterDeclarations = renderMethodParameters(renderParameters);
+    w.write("public void render(final %s sb%s%s) {", SafeHtmlBuilder.class.getName(),
+        renderParameterDeclarations.length() != 0 ? ", " : "",
+        renderParameterDeclarations);
     w.indent();
     w.newline();
 
-    w.write("return new Widgets(owner).getSafeHtml();");
-    w.outdent();
+    writeRenderParameterInitializers(w, renderParameters);
 
-    w.write("}");
+    w.write("uiId = com.google.gwt.dom.client.Document.get().createUniqueId();");
     w.newline();
 
-    // Writes the inner class Widgets.
-    w.newline();
-    w.write("/**");
-    w.write(" * Encapsulates the access to all inner widgets");
-    w.write(" */");
-    w.write("class Widgets {");
-    w.indent();
-
-    String ownerClassType = uiOwnerType.getParameterizedQualifiedSourceName();
-    w.write("private final %s owner;", ownerClassType);
-    w.newline();
-
-    w.write("public Widgets(final %s owner) {", ownerClassType);
-    w.indent();
-    w.write("this.owner = owner;");
     fieldManager.initializeWidgetsInnerClass(w, getOwnerClass());
+    w.newline();
+
+    // TODO(rchandia) Find a better way to get the root field name
+    String rootFieldName = rootField.substring(4, rootField.length() - 2);
+    String safeHtml = fieldManager.lookup(rootFieldName).getSafeHtml();
+
+    // TODO(rchandia) it should be possible to add the attribute when parsing the UiBinder file
+    w.write("sb.append(UiRendererUtilsImpl.stampUiRendererAttribute(%s, RENDERED_ATTRIBUTE, uiId));",
+        safeHtml);
     w.outdent();
+
     w.write("}");
     w.newline();
 
-    w.write("public SafeHtml getSafeHtml() {");
-    w.indent();
-    // TODO Find a better way to get the root field name
-    String safeHtml = fieldManager.lookup(rootField.substring(4, rootField.length() - 2)).getSafeHtml();
-    w.write("return %s;", safeHtml);
-    w.outdent();
-    w.write("}");
+    fieldManager.writeFieldDefinitions(w, getOracle(), getOwnerClass(), getDesignTime());
 
-    fieldManager.writeFieldDefinitions(
-        w, getOracle(), getOwnerClass(), getDesignTime());
-
-    w.outdent();
-    w.write("}");
+    writeRendererGetters(w, baseClass, rootFieldName);
 
     // Close class
     w.outdent();
     w.write("}");
   }
 
+  private void writeRendererGetters(IndentedWriter w, JClassType owner, String rootFieldName)
+      throws UnableToCompleteException {
+    List<JMethod> getters = findGetterNames(owner);
+
+    // For every requested getter
+    for (JMethod getter : getters) {
+      // public ElementSubclass getFoo(Element parent) {
+      w.write("%s {", getter.getReadableDeclaration(false, false, false, false, true));
+      w.indent();
+      String elementParameter = getter.getParameters()[0].getName();
+      String getterFieldName = getterToFieldName(getter.getName());
+      // The non-root elements are found by id
+      if (!getterFieldName.equals(rootFieldName)) {
+        // return (ElementSubclass) findUiField(parent);
+        w.write("return (%s) UiRendererUtilsImpl.findInnerField(%s, \"%s\", RENDERED_ATTRIBUTE);",
+            getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter,
+            getterFieldName);
+      } else {
+        // return (ElementSubclass) findPreviouslyRendered(parent);
+        w.write("return (%s) UiRendererUtilsImpl.findRootElement(%s, RENDERED_ATTRIBUTE);",
+            getter.getReturnType().getErasedType().getQualifiedSourceName(), elementParameter);
+      }
+      w.outdent();
+      w.write("}");
+    }
+  }
+
+  private void writeRenderParameterDefinitions(IndentedWriter w, JParameter[] renderParameters) {
+    for (int i = 0; i < renderParameters.length; i++) {
+      JParameter parameter = renderParameters[i];
+      w.write("private %s %s;", parameter.getType().getQualifiedSourceName(), parameter.getName());
+      w.newline();
+    }
+  }
+
+  private void writeRenderParameterInitializers(IndentedWriter w, JParameter[] renderParameters) {
+    for (int i = 0; i < renderParameters.length; i++) {
+      JParameter parameter = renderParameters[i];
+      w.write("this.%s = %s;", parameter.getName(), parameter.getName());
+      w.newline();
+    }
+  }
+
   /**
    * Write statements created by {@link HtmlTemplates#addSafeHtmlTemplate}. This
    * code must be placed after all instantiation code.
diff --git a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
index d6e2dc0..5bf6ee3 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
@@ -53,6 +53,8 @@
 import com.google.gwt.uibinder.rebind.HandlerEvaluatorTest;
 import com.google.gwt.uibinder.rebind.TokenatorTest;
 import com.google.gwt.uibinder.rebind.TypeOracleUtilsTest;
+import com.google.gwt.uibinder.rebind.UiBinderParserUiWithTest;
+import com.google.gwt.uibinder.rebind.UiRendererValidationTest;
 import com.google.gwt.uibinder.rebind.XMLElementTest;
 import com.google.gwt.uibinder.rebind.model.OwnerClassTest;
 import com.google.gwt.uibinder.rebind.model.OwnerFieldClassTest;
@@ -78,6 +80,8 @@
     suite.addTestSuite(XMLElementTest.class);
     suite.addTestSuite(DesignTimeUtilsTest.class);
     suite.addTestSuite(TypeOracleUtilsTest.class);
+    suite.addTestSuite(UiBinderParserUiWithTest.class);
+    suite.addTestSuite(UiRendererValidationTest.class);
 
     // model
     suite.addTestSuite(OwnerClassTest.class);
diff --git a/user/test/com/google/gwt/uibinder/rebind/AbstractUiBinderWriterTest.java b/user/test/com/google/gwt/uibinder/rebind/AbstractUiBinderWriterTest.java
new file mode 100644
index 0000000..52eb316
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/AbstractUiBinderWriterTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.uibinder.rebind;
+
+import com.google.gwt.codegen.server.AbortablePrintWriter;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationStateBuilder;
+import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
+import com.google.gwt.dev.javac.testing.impl.MockResourceOracle;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.util.collect.HashSet;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.uibinder.attributeparsers.AttributeParsers;
+import com.google.gwt.uibinder.attributeparsers.BundleAttributeParsers;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.model.OwnerClass;
+import com.google.gwt.uibinder.test.UiJavaResources;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXParseException;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Set;
+
+/**
+ * Base class for tests that exercise {@link UiBinderWriter}.
+ */
+public abstract class AbstractUiBinderWriterTest extends TestCase {
+
+  public static final MockJavaResource CLIENT_BUNDLE = new MockJavaResource(
+      "com.google.gwt.resources.client.ClientBundle") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.resources.client;\n");
+      code.append("public interface ClientBundle {");
+      code.append("}");
+      return code;
+    }
+  };
+  public static final MockJavaResource DIV_ELEMENT = new MockJavaResource(
+      "com.google.gwt.dom.client.DivElement") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.dom.client;\n");
+      code.append("public class DivElement extends Element {");
+      code.append("}");
+      return code;
+    }
+  };
+  public static final MockJavaResource FOO = new MockJavaResource("foo.Foo") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;");
+      code.append("public class Foo {");
+      code.append("}");
+      return code;
+    }
+  };
+
+  public static final MockJavaResource RENDERABLE_PANEL = new MockJavaResource(
+      "com.google.gwt.user.client.ui.RenderablePanel") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.user.client.ui;\n");
+      code.append("public class RenderablePanel {");
+      code.append("}");
+      return code;
+    }
+  };
+
+  public static final MockJavaResource SPAN_ELEMENT = new MockJavaResource(
+      "com.google.gwt.dom.client.SpanElement") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.dom.client;\n");
+      code.append("public class SpanElement extends Element {");
+      code.append("}");
+      return code;
+    }
+  };
+
+  protected static final String BINDER_URI = "urn:ui:com.google.gwt.uibinder";
+
+  protected static final W3cDomHelper docHelper = new W3cDomHelper(TreeLogger.NULL,
+        new MockResourceOracle());
+
+  protected static final String RENDERER_BASE_CLASS_NAME = "renderer.OwnerClass.Renderer";
+
+  protected static final String RENDERER_OWNER_CLASS_NAME = "renderer.OwnerClass";
+
+  private static TreeLogger createCompileLogger() {
+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(System.err, true));
+    logger.setMaxDetail(TreeLogger.ERROR);
+    return logger;
+  }
+
+  protected Document doc;
+  protected PrintWriter printWriter;
+  protected UiBinderWriter writer;
+  protected UiBinderParser parser;
+  protected BundleAttributeParsers attributeParsers;
+  protected XMLElementProvider elemProvider;
+  protected XMLElement elm;
+  protected FieldManager fieldManager;
+  protected Element item;
+  protected MockMortalLogger logger;
+  protected Set<Resource> resources = new HashSet<Resource>();
+  protected TypeOracle types;
+
+  public AbstractUiBinderWriterTest() {
+    super();
+  }
+
+  /**
+   * @param name
+   */
+  public AbstractUiBinderWriterTest(String name) {
+    super(name);
+  }
+
+  @SuppressWarnings("deprecation")
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    resources.addAll(UiJavaResources.getUiResources());
+    printWriter = new AbortablePrintWriter(new PrintWriter(new StringWriter()));
+  }
+
+  protected void init(String domString, MockJavaResource rendererClass) throws SAXParseException,
+      UnableToCompleteException {
+    resources.add(RENDERABLE_PANEL);
+    resources.add(CLIENT_BUNDLE);
+    resources.add(DIV_ELEMENT);
+    resources.add(SPAN_ELEMENT);
+    resources.add(FOO);
+    resources.add(rendererClass);
+    CompilationState state = CompilationStateBuilder.buildFrom(createCompileLogger(), resources);
+    types = state.getTypeOracle();
+    logger = new MockMortalLogger();
+    JClassType ownerType = types.findType(RENDERER_OWNER_CLASS_NAME);
+    UiBinderContext uiBinderCtx = new UiBinderContext();
+    attributeParsers =
+        new BundleAttributeParsers(types, logger, new OwnerClass(ownerType, logger, uiBinderCtx),
+            "templatePath", ownerType);
+    fieldManager = new FieldManager(types, logger, true);
+    String baseClass = RENDERER_BASE_CLASS_NAME;
+    DesignTimeUtils designTime = DesignTimeUtilsStub.EMPTY;
+    elemProvider =
+        new XMLElementProviderImpl(new AttributeParsers(types, fieldManager, logger),
+            attributeParsers, types, logger, designTime);
+    doc = docHelper.documentFor(domString, rendererClass.getPath());
+    item = (Element) doc.getDocumentElement().getChildNodes().item(0);
+    elm = elemProvider.get(item);
+    JClassType aClass = types.findType(baseClass);
+    MessagesWriter messages =
+        new MessagesWriter(types, BINDER_URI, logger, rendererClass.getPath(), "rendererPackage",
+            "rendererClassName");
+    writer =
+        new UiBinderWriter(aClass, "foo", "", types, logger, fieldManager, messages,
+            DesignTimeUtilsStub.EMPTY, uiBinderCtx, true, true, BINDER_URI);
+    parser = new UiBinderParser(writer, messages, fieldManager, types, null, BINDER_URI);
+    designTime.rememberPathForElements(doc);
+  }
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/uibinder/rebind/UiBinderParserUiWithTest.java b/user/test/com/google/gwt/uibinder/rebind/UiBinderParserUiWithTest.java
new file mode 100644
index 0000000..16a5e5d
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/UiBinderParserUiWithTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.uibinder.rebind;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationStateBuilder;
+import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
+import com.google.gwt.dev.javac.testing.impl.MockResourceOracle;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.util.collect.HashSet;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.uibinder.attributeparsers.AttributeParsers;
+import com.google.gwt.uibinder.attributeparsers.BundleAttributeParsers;
+import com.google.gwt.uibinder.test.UiJavaResources;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXParseException;
+
+import java.io.PrintWriter;
+import java.util.Set;
+
+/**
+ * Tests UiBinderParser behavior for <ui:with> tag.
+ */
+public class UiBinderParserUiWithTest extends TestCase {
+
+  public static final MockJavaResource BAR = new MockJavaResource("bar.Bar") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package bar;");
+      code.append("public class Bar {");
+      code.append("  public Bar(int a) { }");
+      code.append("}");
+      return code;
+    }
+  };
+
+  public static final MockJavaResource BINDER = new MockJavaResource("binder.OwnerClass") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package binder;\n");
+      code.append("import com.google.gwt.uibinder.client.UiBinder;\n");
+      code.append("import com.google.gwt.uibinder.client.UiField;\n");
+      code.append("import com.google.gwt.uibinder.client.UiFactory;\n");
+      code.append("import bar.Bar;\n");
+      code.append("import foo.Foo;\n");
+      code.append("public class OwnerClass {");
+      code.append("  public interface Binder");
+      code.append("      extends UiBinder<java.lang.String, OwnerClass> {");
+      code.append("  }");
+      code.append("  @UiField foo.Foo fieldName;");
+      code.append("  @UiFactory bar.Bar aFactory() { return new Bar(1); }");
+      code.append("}");
+      return code;
+    }
+  };
+
+  public static final MockJavaResource FOO = new MockJavaResource("foo.Foo") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;");
+      code.append("public class Foo {");
+      code.append("}");
+      return code;
+    }
+  };
+
+  public static final MockJavaResource FOOISH = new MockJavaResource("foo.Fooish") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package foo;");
+      code.append("public class Fooish extends Foo {");
+      code.append("}");
+      return code;
+    }
+  };
+
+  public static final MockJavaResource RENDERER = new MockJavaResource("renderer.OwnerClass") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package renderer;\n");
+      code.append("import com.google.gwt.safehtml.shared.SafeHtmlBuilder;\n");
+      code.append("import com.google.gwt.uibinder.client.UiRenderer;\n");
+      code.append("import foo.Foo;\n");
+      code.append("public class OwnerClass {");
+      code.append("  public interface Renderer");
+      code.append("      extends UiRenderer<OwnerClass> {");
+      code.append("    public void render(SafeHtmlBuilder sb, foo.Fooish fieldName);");
+      code.append("  }");
+      code.append("}");
+      return code;
+    }
+  };
+
+  private static final W3cDomHelper docHelper = new W3cDomHelper(TreeLogger.NULL,
+      new MockResourceOracle());
+
+  private static TreeLogger createCompileLogger() {
+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(System.err, true));
+    logger.setMaxDetail(TreeLogger.ERROR);
+    return logger;
+  }
+
+  UiBinderParser parser;
+  private BundleAttributeParsers attributeParsers;
+  private Document doc;
+  private XMLElementProvider elemProvider;
+
+  private XMLElement elm;
+
+  private FieldManager fieldManager;
+
+  private Element item;
+
+  private MockMortalLogger logger;
+
+  private Set<Resource> resources = new HashSet<Resource>();
+
+  private TypeOracle types;
+
+  private UiBinderWriter writer;
+
+  @SuppressWarnings("deprecation")
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    resources.addAll(UiJavaResources.getUiResources());
+    resources.add(FOO);
+    resources.add(FOOISH);
+    resources.add(BAR);
+    resources.add(RENDERER);
+    resources.add(BINDER);
+    CompilationState state = CompilationStateBuilder.buildFrom(createCompileLogger(), resources);
+    types = state.getTypeOracle();
+    logger = new MockMortalLogger();
+    attributeParsers = new BundleAttributeParsers(types, logger, null, "templatePath", null);
+    fieldManager = new FieldManager(types, logger, true);
+  }
+
+  public void testWithErrorMisTyped() throws SAXParseException {
+    try {
+      init("<doc><with field=\"fieldName\" type=\"bar.Bar\" bloop=\"\"/></doc>",
+          "renderer.OwnerClass.Renderer");
+      fail("Expecting UnabletoCompleteException");
+    } catch (UnableToCompleteException e) {
+      assertNotNull(logger.died);
+    }
+  }
+
+  public void testWithErrorNoField() throws SAXParseException {
+    try {
+      init("<doc><with type=\"bar.Bar\"/></doc>", "renderer.OwnerClass.Renderer");
+      fail("Expecting UnabletoCompleteException");
+    } catch (UnableToCompleteException e) {
+      assertNotNull(logger.died);
+    }
+  }
+
+  public void testWithErrorUiBinderBadUiFieldType() throws SAXParseException {
+    try {
+      init("<doc><with field=\"someName\" type=\"foo.Unknown\"/></doc>",
+          "binder.OwnerClass.Binder");
+      fail("Expecting UnabletoCompleteException");
+    } catch (UnableToCompleteException e) {
+      assertNotNull(logger.died);
+    }
+  }
+
+  public void testWithErrorUiBinderMisTypedUiField() throws SAXParseException {
+    try {
+      init("<doc><with field=\"fieldName\" type=\"bar.Bar\"/></doc>", "binder.OwnerClass.Binder");
+      fail("Expecting UnabletoCompleteException");
+    } catch (UnableToCompleteException e) {
+      assertNotNull(logger.died);
+    }
+  }
+
+  public void testWithErrorUiRendererMisTypedImport() throws SAXParseException {
+    try {
+      init("<doc><with field=\"fieldName\" type=\"bar.Bar\"/></doc>",
+          "renderer.OwnerClass.Renderer");
+      fail("Expecting UnabletoCompleteException");
+    } catch (UnableToCompleteException e) {
+      assertNotNull(logger.died);
+    }
+  }
+
+  public void testWithErrorUiRendererUnTypedNonExistingFieldName() throws SAXParseException {
+    try {
+      init("<doc><with field=\"nonExisting\"/></doc>", "renderer.OwnerClass.Renderer");
+      fail("Expecting UnabletoCompleteException");
+    } catch (UnableToCompleteException e) {
+      assertNotNull(logger.died);
+    }
+  }
+
+  public void testWithGwtCreated() throws UnableToCompleteException, SAXParseException {
+    init("<doc><with field=\"notAField\" type=\"foo.Foo\"/></doc>", "renderer.OwnerClass.Renderer");
+
+    assertNotNull(fieldManager.lookup("notAField"));
+    assertEquals("foo.Foo", fieldManager.lookup("notAField").getAssignableType()
+        .getQualifiedSourceName());
+  }
+
+  public void testWithUiBinderTypedUiField() throws UnableToCompleteException, SAXParseException {
+    init("<doc><with field=\"fieldName\" type=\"foo.Foo\"/></doc>", "binder.OwnerClass.Binder");
+
+    assertNotNull(fieldManager.lookup("fieldName"));
+    assertEquals("foo.Foo", fieldManager.lookup("fieldName").getAssignableType()
+        .getQualifiedSourceName());
+  }
+
+  public void testWithUiBinderUiFactory() throws UnableToCompleteException, SAXParseException {
+    init("<doc><with field=\"factoryProvided\" type=\"bar.Bar\"/></doc>",
+        "binder.OwnerClass.Binder");
+
+    assertNotNull(fieldManager.lookup("factoryProvided"));
+    assertEquals("bar.Bar", fieldManager.lookup("factoryProvided").getAssignableType()
+        .getQualifiedSourceName());
+  }
+
+  public void testWithUiBinderUntypedUiField() throws UnableToCompleteException, SAXParseException {
+    init("<doc><with field=\"fieldName\"/></doc>", "binder.OwnerClass.Binder");
+
+    assertNotNull(fieldManager.lookup("fieldName"));
+    assertEquals("foo.Foo", fieldManager.lookup("fieldName").getAssignableType()
+        .getQualifiedSourceName());
+  }
+
+  public void testWithUiRendererTypedImport() throws UnableToCompleteException, SAXParseException {
+    init("<doc><with field=\"fieldName\" type=\"foo.Foo\"/></doc>", "renderer.OwnerClass.Renderer");
+
+    assertNotNull(fieldManager.lookup("fieldName"));
+    assertEquals("foo.Fooish", fieldManager.lookup("fieldName").getAssignableType()
+        .getQualifiedSourceName());
+  }
+
+  public void testWithUiRendererUntypedImport()
+      throws UnableToCompleteException, SAXParseException {
+    init("<doc><with field=\"fieldName\"/></doc>", "renderer.OwnerClass.Renderer");
+
+    assertNotNull(fieldManager.lookup("fieldName"));
+    assertEquals("foo.Fooish", fieldManager.lookup("fieldName").getAssignableType()
+        .getQualifiedSourceName());
+  }
+
+  private void init(String domString, String baseClass) throws SAXParseException,
+      UnableToCompleteException {
+    DesignTimeUtils designTime = DesignTimeUtilsStub.EMPTY;
+    elemProvider =
+        new XMLElementProviderImpl(new AttributeParsers(types, null, logger), attributeParsers,
+            types, logger, designTime);
+    doc = docHelper.documentFor(domString, null);
+    item = (Element) doc.getDocumentElement().getElementsByTagName("with").item(0);
+    elm = elemProvider.get(item);
+    JClassType aClass = types.findType(baseClass);
+    writer =
+        new UiBinderWriter(aClass, "foo", "", types, logger, fieldManager, null,
+            DesignTimeUtilsStub.EMPTY, new UiBinderContext(), true, true, "");
+    parser = new UiBinderParser(writer, null, fieldManager, types, null, "");
+    designTime.rememberPathForElements(doc);
+    UiBinderParser.Resource.WITH.create(parser, elm);
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/UiRendererValidationTest.java b/user/test/com/google/gwt/uibinder/rebind/UiRendererValidationTest.java
new file mode 100644
index 0000000..22da576
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/UiRendererValidationTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.uibinder.rebind;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
+
+
+import org.xml.sax.SAXParseException;
+
+
+/**
+ * Tests UiBinderWriter validation of UiRender getters and render method.
+ */
+public class UiRendererValidationTest extends AbstractUiBinderWriterTest {
+
+  private static String UI_XML = "<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>"
+      + "<ui:with field='withField' />"
+      + "  <div ui:field='root'>"
+      + "    <span ui:field='someField'><ui:text from='{withField.toString}'/></span>"
+      + "  </div>"
+      + "</ui:UiBinder>";
+
+  private static String UI_XML_TYPED_WITH = "<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>"
+    + "<ui:with field='withField' type='foo.Foo' />"
+    + "  <div ui:field='root'>"
+    + "    <span ui:field='someField'><ui:text from='{withField.toString}'/></span>"
+    + "  </div>"
+    + "</ui:UiBinder>";
+
+  private StringBuffer declaredMethods = new StringBuffer();
+
+  public void testAGoodUiRendererInterface() throws UnableToCompleteException, SAXParseException {
+    declaredMethods.append("    public void render(SafeHtmlBuilder sb, foo.Foo withField);");
+    declaredMethods.append("    public DivElement getRoot(Element foo);");
+    declaredMethods.append("    public SpanElement getSomeField(Element bar);");
+    init(UI_XML, generateRendererResource(declaredMethods));
+    writer.parseDocument(doc, printWriter);
+  }
+
+  public void testGetterBadParameterType() throws SAXParseException, UnableToCompleteException {
+    initGetterTest();
+    declaredMethods.append("    public DivElement getRoot(String bar);");
+    init(UI_XML, generateRendererResource(declaredMethods));
+
+    assertParseFailure("Expected failure due to getter parameter not of type Element.",
+        "Getter getRoot must have exactly one parameter of type assignable to "
+            + "com.google.gwt.dom.client.Element");
+  }
+
+  public void testGetterParameterAssignableToElement()
+      throws SAXParseException, UnableToCompleteException {
+    initGetterTest();
+    declaredMethods.append("    public DivElement getRoot(DivElement bar);");
+    init(UI_XML, generateRendererResource(declaredMethods));
+
+    writer.parseDocument(doc, printWriter);
+  }
+
+  public void testGetterNoParameters() throws SAXParseException, UnableToCompleteException {
+    initGetterTest();
+    declaredMethods.append("    public DivElement getRoot();");
+    init(UI_XML, generateRendererResource(declaredMethods));
+
+    assertParseFailure("Expected failure due to getter with no parameters.",
+        "Getter getRoot must have exactly one parameter");
+  }
+
+  public void testGetterTooManyParameters() throws SAXParseException, UnableToCompleteException {
+    initGetterTest();
+    declaredMethods.append("    public DivElement getRoot(Element parent, Integer i);");
+    init(UI_XML, generateRendererResource(declaredMethods));
+
+    assertParseFailure("Expected failure due to bad getter signature.",
+        "Getter getRoot must have exactly one parameter");
+  }
+
+  public void testGetterUnknownField() throws SAXParseException, UnableToCompleteException {
+    initGetterTest();
+    declaredMethods.append("    public DivElement getQuux(Element parent);");
+    init(UI_XML, generateRendererResource(declaredMethods));
+    assertParseFailure("Expected failure due to getter for an unexpected field name.",
+        "getQuux does not match a \"ui:field='quux'\" declaration");
+  }
+
+  public void testRenderBadReturnType() throws SAXParseException, UnableToCompleteException {
+    declaredMethods.append("    public String render(SafeHtmlBuilder sb, foo.Foo withField);");
+    init(UI_XML, generateRendererResource(declaredMethods));
+    assertParseFailure("Expected failure due to bad render() return type.",
+        "renderer.OwnerClass.Renderer#render(SafeHtmlBuilder ...) does not return void");
+  }
+
+  public void testRenderExtraParametersOk() throws SAXParseException, UnableToCompleteException {
+    declaredMethods.append("    public void render("
+        + "SafeHtmlBuilder sb, foo.Foo withField, foo.Foo withUnknownField);");
+    init(UI_XML, generateRendererResource(declaredMethods));
+    writer.parseDocument(doc, printWriter);
+  }
+
+  public void testRenderMethodRepeated() throws SAXParseException, UnableToCompleteException {
+    declaredMethods.append("    public void render(SafeHtmlBuilder sb, foo.Foo withField);");
+    declaredMethods.append("    public void render(SafeHtmlBuilder sb);");
+    init(UI_XML, generateRendererResource(declaredMethods));
+    assertParseFailure("Expected failure due to more than one render method.",
+        "renderer.OwnerClass.Renderer declares more than one method named render");
+  }
+
+  public void testRenderMissing() throws SAXParseException, UnableToCompleteException {
+    // No render method here
+    init(UI_XML_TYPED_WITH, generateRendererResource(declaredMethods));
+    assertParseFailure("Expected failure due to missing render method.",
+        "renderer.OwnerClass.Renderer does not declare a render(SafeHtmlBuilder ...) method");
+  }
+
+  public void testRenderNoParameters() throws SAXParseException, UnableToCompleteException {
+    declaredMethods.append("    public void render();");
+    init(UI_XML_TYPED_WITH, generateRendererResource(declaredMethods));
+    assertParseFailure("Expected failure due to render with no parameters.",
+        "renderer.OwnerClass.Renderer does not declare a render(SafeHtmlBuilder ...) method");
+  }
+
+  public void testRenderNoParametersRepeated() throws SAXParseException, UnableToCompleteException {
+    declaredMethods.append("    public void render(SafeHtmlBuilder sb, foo.Foo withField);");
+    declaredMethods.append("    public void render();");
+    init(UI_XML_TYPED_WITH, generateRendererResource(declaredMethods));
+    assertParseFailure("Expected failure due to render method repeated.",
+        "renderer.OwnerClass.Renderer declares more than one method named render");
+  }
+
+  public void testUnknownMethodDeclaration() throws SAXParseException, UnableToCompleteException {
+    initGetterTest();
+    declaredMethods.append("    public void quux();");
+    init(UI_XML_TYPED_WITH, generateRendererResource(declaredMethods));
+    assertParseFailure("Expected failure due to unexpected method.",
+        "Unexpected method \"quux\" found");
+  }
+
+  private void assertParseFailure(String message, String expectedMessage) {
+    try {
+      writer.parseDocument(doc, printWriter);
+      fail(message);
+    } catch (UnableToCompleteException e) {
+      if (expectedMessage != null) {
+        assertEquals(expectedMessage, logger.died);
+      }
+    }
+  }
+
+  private MockJavaResource generateRendererResource(final StringBuffer declarations) {
+    return new MockJavaResource("renderer.OwnerClass") {
+      @Override
+      public CharSequence getContent() {
+        StringBuffer code = new StringBuffer();
+        code.append("package renderer;\n");
+        code.append("import com.google.gwt.safehtml.shared.SafeHtmlBuilder;\n");
+        code.append("import com.google.gwt.uibinder.client.UiRenderer;\n");
+        code.append("import com.google.gwt.dom.client.DivElement;\n");
+        code.append("import com.google.gwt.dom.client.Element;\n");
+        code.append("import com.google.gwt.dom.client.SpanElement;\n");
+        code.append("import foo.Foo;\n");
+        code.append("public class OwnerClass {");
+        code.append("  public interface Renderer");
+        code.append("      extends UiRenderer<OwnerClass> {");
+        code.append(declarations);
+        code.append("  }");
+        code.append("}");
+        return code;
+      }
+    };
+  }
+
+  private void initGetterTest() {
+    // Required, not part of test
+    declaredMethods.append("    public void render(SafeHtmlBuilder sb, foo.Foo withField);");
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/test/UiJavaResources.java b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
index 65ac2c8..e975dff 100644
--- a/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
+++ b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
@@ -492,6 +492,17 @@
       return code;
     }
   };
+  public static final MockJavaResource SAFEHTML_BUILDER = new MockJavaResource(
+      "com.google.gwt.safehtml.shared.SafeHtmlBuilder") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.safehtml.shared;\n");
+      code.append("public class SafeHtmlBuilder {");
+      code.append("}");
+      return code;
+    }
+  };
   public static final MockJavaResource SPLIT_LAYOUT_PANEL = new MockJavaResource(
       "com.google.gwt.user.client.ui.SplitLayoutPanel") {
     @Override
@@ -640,6 +651,26 @@
       return code;
     }
   };
+  public static final MockJavaResource UI_FIELD = new MockJavaResource(
+      "com.google.gwt.uibinder.client.UiField") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.uibinder.client;");
+      code.append("import java.lang.annotation.Documented;");
+      code.append("import java.lang.annotation.ElementType;");
+      code.append("import java.lang.annotation.Retention;");
+      code.append("import java.lang.annotation.RetentionPolicy;");
+      code.append("import java.lang.annotation.Target;");
+      code.append("@Documented");
+      code.append("@Retention(RetentionPolicy.RUNTIME)");
+      code.append("@Target(ElementType.FIELD)");
+      code.append("public @interface UiField {");
+      code.append("  boolean provided() default false;");
+      code.append("}");
+      return code;
+    }
+  };
   public static final MockJavaResource UI_OBJECT = new MockJavaResource(
       "com.google.gwt.user.client.ui.UIObject") {
     @Override
@@ -651,6 +682,17 @@
       return code;
     }
   };
+  public static final MockJavaResource UI_RENDERER = new MockJavaResource(
+      "com.google.gwt.uibinder.client.UiRenderer") {
+    @Override
+    public CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.uibinder.client;\n");
+      code.append("public interface UiRenderer<T> {");
+      code.append("}");
+      return code;
+    }
+  };
   public static final MockJavaResource VALUE_LABEL = new MockJavaResource(
       "com.google.gwt.user.client.ui.ValueLabel") {
     @Override
@@ -720,6 +762,7 @@
     rtn.add(NUMBER_FORMAT);
     rtn.add(RENDERER);
     rtn.add(SAFEHTML);
+    rtn.add(SAFEHTML_BUILDER);
     rtn.add(SPLIT_LAYOUT_PANEL);
     rtn.add(STACK_LAYOUT_PANEL);
     rtn.add(STACK_PANEL);
@@ -734,6 +777,8 @@
     rtn.add(UI_OBJECT);
     rtn.add(UI_BINDER);
     rtn.add(UI_FACTORY);
+    rtn.add(UI_FIELD);
+    rtn.add(UI_RENDERER);
     rtn.add(VALUE_LABEL);
     rtn.add(WIDGET);
     return rtn;
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiRendererTest.java b/user/test/com/google/gwt/uibinder/test/client/UiRendererTest.java
index 15eabe0..8e4e6b0 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiRendererTest.java
+++ b/user/test/com/google/gwt/uibinder/test/client/UiRendererTest.java
@@ -15,16 +15,25 @@
  */
 package com.google.gwt.uibinder.test.client;
 
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
 import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.LabelElement;
 import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.SpanElement;
 import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.uibinder.test.client.UiRendererUi.HtmlRenderer;
 
 /**
  * Functional test of UiBinder.
  */
 public class UiRendererTest extends GWTTestCase {
+
+  private static final String RENDERED_VALUE = "bar";
+
+  private DivElement docDiv;
+  private SafeHtml renderedHtml;
+  private HtmlRenderer renderer;
   private UiRendererUi safeHtmlUi;
 
   @Override
@@ -37,15 +46,172 @@
     super.gwtSetUp();
     UiRendererTestApp app = UiRendererTestApp.getInstance();
     safeHtmlUi = app.getSafeHtmlUi();
+    renderedHtml = safeHtmlUi.render(RENDERED_VALUE);
+    renderer = safeHtmlUi.getRenderer();
+
+    docDiv = Document.get().createDivElement();
+    docDiv.setInnerHTML(renderedHtml.asString());
+    Document.get().getBody().appendChild(docDiv);
+  }
+
+  public void testFieldGetters() {
+    assertTrue(renderer.isParentOrRenderer(docDiv));
+    // Get root from parent
+    DivElement root = renderer.getRoot(docDiv);
+    assertTrue(renderer.isParentOrRenderer(root));
+    assertNotNull(root);
+
+    // For example, the rendered value should be inside
+    assertSpanContainsRenderedValue(root);
+
+    // Get nameSpan
+    SpanElement nameSpan = renderer.getNameSpan(docDiv);
+    assertSpanContainsRenderedValueText(nameSpan.getFirstChild());
+
+    // Getters also work from the root element
+    DivElement root2 = renderer.getRoot(root);
+    assertTrue(renderer.isParentOrRenderer(root2));
+    assertNotNull(root2);
+    assertSpanContainsRenderedValue(root2);
+    nameSpan = renderer.getNameSpan(root);
+    assertSpanContainsRenderedValueText(nameSpan.getFirstChild());
+  }
+
+  public void testFieldGettersDetachedRoot() {
+    // Detach root
+    DivElement root = renderer.getRoot(docDiv);
+    root.removeFromParent();
+
+    // Getting the root element is still fine
+    DivElement rootAgain = renderer.getRoot(root);
+    assertEquals(root, rootAgain);
+
+    if (GWT.isProdMode()) {
+      // In prod mode we avoid checking whether the parent is attached
+      try {
+        renderer.getNameSpan(root);
+        fail("Expected a IllegalArgumentException because root is not attached to the DOM");
+      } catch (IllegalArgumentException e) {
+        // Expected
+      }
+    } else {
+      // In dev Mode we explicitly check to see if parent is attached
+      try {
+        renderer.getNameSpan(root);
+        fail("Expected a RuntimeException because root is not attached to the DOM");
+      } catch (RuntimeException e) {
+        // Expected
+      }
+    }
+  }
+
+  public void testFieldGettersNoPreviouslyRenderedElement() {
+    assertFalse(renderer.isParentOrRenderer(null));
+
+    try {
+      renderer.getRoot(null);
+      fail("Expected NPE");
+    } catch (NullPointerException e) {
+      // Expected
+    }
+
+    try {
+      renderer.getNameSpan(null);
+      fail("Expected NPE");
+    } catch (NullPointerException e) {
+      // Expected
+    }
+
+    DivElement root = renderer.getRoot(docDiv);
+    SpanElement nameSpan = renderer.getNameSpan(docDiv);
+
+    // remove nameSpan
+    nameSpan.removeFromParent();
+    try {
+      renderer.getNameSpan(docDiv);
+      fail("Expected IllegalStateException because nameSpan was removed");
+    } catch (IllegalStateException e) {
+      // In dev mode this is different from not being attached
+      assertFalse(GWT.isProdMode());
+    } catch (IllegalArgumentException e) {
+      // Otherwise the same error as being not attached
+      assertTrue(GWT.isProdMode());
+    }
+
+    // Add a a sibling to the root element and remove the root from the parent altogether
+    SpanElement spanElement = Document.get().createSpanElement();
+    docDiv.appendChild(spanElement);
+    root.removeFromParent();
+
+    assertFalse(renderer.isParentOrRenderer(docDiv));
+    if (GWT.isProdMode()) {
+      // In prod mode no attempt is made to check whether root is still attached
+      assertTrue(renderer.isParentOrRenderer(root));
+    } else {
+      assertFalse(renderer.isParentOrRenderer(root));
+    }
+    try {
+      renderer.getRoot(docDiv);
+      fail("Expected an IllegalArgumentException to fail because parent does not contain the root");
+    } catch (IllegalArgumentException e) {
+      // Expected
+    }
+
+    try {
+      renderer.getNameSpan(docDiv);
+      fail("Expected an IllegalArgumentException to fail because parent does not contain the root");
+    } catch (IllegalArgumentException e) {
+      // Expected
+    }
+
+    // Finally remove the spanElement too
+    spanElement.removeFromParent();
+
+    assertFalse(renderer.isParentOrRenderer(docDiv));
+    try {
+      renderer.getRoot(docDiv);
+      fail("Expected an IllegalArgumentException to fail because parent does not contain the root");
+    } catch (IllegalArgumentException e) {
+      // Expected
+    }
+
+    try {
+      renderer.getNameSpan(docDiv);
+      fail("Expected an IllegalArgumentException to fail because parent does not contain the root");
+    } catch (IllegalArgumentException e) {
+      // Expected
+    }
+  }
+
+  public void testFieldGettersNotOnlyChild() {
+    DivElement root = renderer.getRoot(docDiv);
+
+    // Add a a sibling to the root element
+    docDiv.appendChild(Document.get().createSpanElement());
+
+    // Getting the root element is still fine
+    DivElement rootAgain = renderer.getRoot(docDiv);
+    assertEquals(root, rootAgain);
+
+    if (GWT.isProdMode()) {
+      // In prod mode we avoid checking for being the only child
+      assertTrue(renderer.isParentOrRenderer(docDiv));
+      SpanElement nameSpan = renderer.getNameSpan(docDiv);
+      assertSpanContainsRenderedValueText(nameSpan.getFirstChild());
+    } else {
+      // in dev mode an explicit check is made
+      assertFalse(renderer.isParentOrRenderer(docDiv));
+      try {
+        renderer.getNameSpan(docDiv);
+        fail("Expected an IllegalArgumentException to fail because root is not the only child");
+      } catch (IllegalArgumentException e) {
+        // Expected
+      }
+    }
   }
 
   public void testSafeHtmlRendererText() {
-    SafeHtml render = safeHtmlUi.render();
-
-    LabelElement renderedHtml = Document.get().createLabelElement();
-    renderedHtml.setInnerHTML(render.asString());
-
-    Node innerDiv = renderedHtml.getFirstChild();
+    Node innerDiv = docDiv.getFirstChild();
 
     // Was the first span rendered as a "HTML-safe" text string?
     Node spanWithConstantTextNode = innerDiv.getChild(0);
@@ -58,15 +224,38 @@
     assertEquals(Node.TEXT_NODE, firstRawTextNode.getNodeType());
     assertEquals(" Hello, ", firstRawTextNode.getNodeValue());
 
-    // Fields not present in owning class produce no content
-    Node firstFieldNode = innerDiv.getChild(2);
-    assertEquals(Node.ELEMENT_NODE, firstFieldNode.getNodeType());
-    assertEquals("span", firstFieldNode.getNodeName().toLowerCase());
-    assertFalse(firstFieldNode.hasChildNodes());
+    // The value passed to render() was rendered correctly
+    assertSpanContainsRenderedValue(innerDiv);
 
     // ui:msg tags get rendered but the "<ui:msg>" tag is not
     Node secondRawTextNode = innerDiv.getChild(3);
     assertEquals(Node.TEXT_NODE, secondRawTextNode.getNodeType());
     assertEquals(". How goes it? ", secondRawTextNode.getNodeValue());
+
+    // Fields not present in owning class produce no content
+    Node spanNode = innerDiv.getChild(4);
+    assertEquals(Node.ELEMENT_NODE, spanNode.getNodeType());
+    assertEquals("span", spanNode.getNodeName().toLowerCase());
+    assertFalse(spanNode.hasChildNodes());
+  }
+
+  @Override
+  protected void gwtTearDown() throws Exception {
+    docDiv.removeFromParent();
+    docDiv = null;
+  }
+
+  private void assertSpanContainsRenderedValue(Node root) {
+    Node firstFieldNode = root.getChild(2);
+    assertEquals(Node.ELEMENT_NODE, firstFieldNode.getNodeType());
+    assertEquals("span", firstFieldNode.getNodeName().toLowerCase());
+    assertTrue(firstFieldNode.hasChildNodes());
+    Node renderedValue = firstFieldNode.getFirstChild();
+    assertSpanContainsRenderedValueText(renderedValue);
+  }
+
+  private void assertSpanContainsRenderedValueText(Node renderedValue) {
+    assertEquals(Node.TEXT_NODE, renderedValue.getNodeType());
+    assertEquals(RENDERED_VALUE, renderedValue.getNodeValue());
   }
 }
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.java b/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.java
index 0b26d06..5127079 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.java
+++ b/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.java
@@ -1,31 +1,37 @@
 /*
  * 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
- *
+ * 
+ * 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
+ * 
+ * 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.uibinder.test.client;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.dom.client.TableColElement;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.dom.client.TableSectionElement;
 import com.google.gwt.resources.client.ClientBundle;
 import com.google.gwt.resources.client.CssResource;
 import com.google.gwt.safehtml.shared.SafeHtml;
-import com.google.gwt.text.shared.UiRenderer;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.uibinder.client.UiRenderer;
 
 /**
- * Sample use of a {@code SafeHtmlRenderer} with no dependency on
- * com.google.gwt.user.
+ * Sample use of a {@code UiRenderer} with no dependency on com.google.gwt.user.
  */
 public class UiRendererUi {
+
   /**
    * Resources for this template.
    */
@@ -39,16 +45,48 @@
    */
   public interface Style extends CssResource {
     String bodyColor();
+
     String bodyFont();
   }
 
-  interface HtmlRenderer extends UiRenderer<String> { }
+  static class Foo {
+    String bar;
+
+    public Foo(String bar) {
+      this.bar = bar;
+    }
+
+    String getBar() {
+      return bar;
+    }
+  }
+
+  interface HtmlRenderer extends UiRenderer<UiRendererUi> {
+    SpanElement getNameSpan(Element owner);
+    TableColElement getNarrowColumn(Element owner);
+    DivElement getRoot(Element owner);
+    TableSectionElement getTbody(Element owner);
+    TableCellElement getTh1(Element owner);
+    TableCellElement getTh2(Element owner);
+    TableCellElement getTh4(Element owner);
+    Element getTmElement(Element owner);
+    TableRowElement getTr(Element owner);
+
+    void render(SafeHtmlBuilder sb, Foo aValue);
+  }
+
   private static final HtmlRenderer renderer = GWT.create(HtmlRenderer.class);
 
+  public static HtmlRenderer getRenderer() {
+    return renderer;
+  }
+
   public UiRendererUi() {
   }
 
-  public SafeHtml render() {
-    return renderer.render(null);
+  public SafeHtml render(String value) {
+    SafeHtmlBuilder sb = new SafeHtmlBuilder();
+    getRenderer().render(sb, new Foo(value));
+    return sb.toSafeHtml();
   }
 }
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.ui.xml b/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.ui.xml
index 2dea655..e5e228c 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.ui.xml
+++ b/user/test/com/google/gwt/uibinder/test/client/UiRendererUi.ui.xml
@@ -16,12 +16,14 @@
   xmlns:res='urn:with:com.google.gwt.uibinder.test.client.DomBasedUi.Resources'
   >
   <ui:with field="constants" type="com.google.gwt.uibinder.test.client.Constants"/>
+  <ui:with field="aValue"/>
   <div ui:field='root' res:class="style.bodyColor style.bodyFont"
       title="The title of this div is localizable">
     <ui:attribute name='title'/>
     <span><ui:text from="{constants.getText}" /></span>
-    Hello, <span ui:field="nameSpan" />.
+    Hello, <span ui:field="nameSpan"><ui:text from="{aValue.getBar}"/></span>.
     <ui:msg>How goes it?</ui:msg>
+    <span/>
       <h2 res:class="style.bodyColor style.bodyFont">Placeholders in localizables</h2>
       <p><ui:msg>When you mark up your text for translation, there will be bits
       that you don't want the translators to mess with. You can protect