Hello UiBinder

Reviewed by: jgw et al.


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5896 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt16_20userApi.conf b/tools/api-checker/config/gwt16_20userApi.conf
index fd86b8d..0794a4b 100644
--- a/tools/api-checker/config/gwt16_20userApi.conf
+++ b/tools/api-checker/config/gwt16_20userApi.conf
@@ -67,6 +67,8 @@
 :user/src/com/google/gwt/rpc/linker\
 :user/src/com/google/gwt/user/client/rpc/core/java/util/LinkedHashMap_CustomFieldSerializer.java\
 :user/src/com/google/gwt/user/linker\
+:user/src/com/google/gwt/uibinder/parsers\
+:user/src/com/google/gwt/uibinder/testing\
 
 ##############################################
 #excluded packages
diff --git a/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml b/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
new file mode 100644
index 0000000..75f6535
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/UiBinder.gwt.xml
@@ -0,0 +1,23 @@
+<!--                                                                        -->
+<!-- Copyright 2008 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<!-- GWT UI Binder support.                                                 -->
+<module>
+  <!-- Inheriting the debug module -->
+  <inherits name="com.google.gwt.debug.Debug"/>
+  
+  <generate-with class="com.google.gwt.uibinder.rebind.UiBinderGenerator">
+    <when-type-assignable class="com.google.gwt.uibinder.client.UiBinder"/>
+  </generate-with>
+</module>
diff --git a/user/src/com/google/gwt/uibinder/client/AbstractUiBinder.java b/user/src/com/google/gwt/uibinder/client/AbstractUiBinder.java
new file mode 100644
index 0000000..1718aa5
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/AbstractUiBinder.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2008 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;
+
+/**
+ * Convenient base class for implementations of {@link UiBinder}, 
+ * generated or otherwise.
+ * 
+ * @param <U> The type of the UI's root object
+ * @param <O> The type of the owner of the UI
+ */
+public abstract class AbstractUiBinder<U,O> implements UiBinder<U, O> {
+  
+  /**
+   * An interface used by some generated implementations of UiBinder.
+   * 
+   * @param <U> The type of the UI's root object
+   * @param <O> The type of the owner of the UI
+   */
+  protected interface InstanceBinder<U, O> {
+    U makeUi();
+    void doBind(O owner);
+  }
+
+  public U createAndBindUi(O owner) {
+    U ui = createUiRoot(owner);
+    bindUi(ui, owner);
+    return ui;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/client/DomHolder.java b/user/src/com/google/gwt/uibinder/client/DomHolder.java
new file mode 100644
index 0000000..00d7306
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/DomHolder.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2008 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.user.client.ui.UIObject;
+
+/**
+ * Runtime helper for templates.
+ */
+public class DomHolder extends UIObject {
+  public DomHolder(Element element) {
+    setElement(element);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiBinder.java b/user/src/com/google/gwt/uibinder/client/UiBinder.java
new file mode 100644
index 0000000..bfff7f6
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiBinder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2008 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;
+
+/**
+ * Interface implemented by classes that generate DOM or Widget structures from
+ * ui.xml template files, and which inject portions of the generated UI into the
+ * fields of an owner.
+ * <p>
+ * The generated UiBinder implementation will be based on an xml file resource
+ * in the same package as the owner class, with the same name and a "ui.xml"
+ * suffix. For example, a UI owned by class {@code bar.baz.Foo} will be sought
+ * in {@code /bar/baz/Foo.ui.xml}. (To use a different template file, put the
+ * {@link UiTemplate} annotation on your UiBinder interface declaration to point
+ * the code generator at it.)
+ * 
+ * @param <U> The type of the root object of the generated UI, typically a
+ *          subclass of {@link com.google.gwt.dom.client.Element} or
+ *          {@link com.google.gwt.user.client.ui.UIObject}
+ * @param <O> The type of the object that will own the generated UI
+ */
+public interface UiBinder<U, O> {
+
+  /**
+   * Creates and returns the root object of the UI. If possible the creation of
+   * the rest of the UI is deferred until {@link #bindUi} is called.
+   * 
+   * @deprecated The use case for this complexity never materialized. Use
+   *             {@link #createAndBindUi}
+   */
+  @Deprecated
+  U createUiRoot(O owner);
+
+  /**
+   * Completes the creation of a UI started with a call to {@link #createUiRoot}, 
+   * and fills any owner fields tagged with {@link UiField}.
+   * 
+   * @deprecated The use case for this complexity never materialized. Use
+   *             {@link #createAndBindUi}
+   */
+  @Deprecated
+  void bindUi(U root, O owner);
+
+  /**
+   * Creates and returns the root object of the UI, and fills any fields of owner
+   * tagged with {@link UiField}.
+   * 
+   * @param owner the object whose {@literal @}UiField needs will be filled
+   */
+  U createAndBindUi(O owner);
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java b/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
new file mode 100644
index 0000000..55763d5
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2008 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.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.UIObject;
+
+/**
+ * Static helper methods used by UiBinder. These methods are likely to move,
+ * so please don't use them for non-UiBinder code.
+ */
+public class UiBinderUtil {
+  private static Element hiddenDiv;
+
+  public static Element attachToDomAndGetChild(Element element, String id) {
+    // TODO(rjrjr) This is copied from HTMLPanel. Reconcile
+    ensureHiddenDiv();
+
+    // Hang on to the panel's original parent and sibling elements so that it
+    // can be replaced.
+    Element origParent = element.getParentElement();
+    Element origSibling = element.getNextSiblingElement();
+
+    // Attach the panel's element to the hidden div.
+    hiddenDiv.appendChild(element);
+
+    // Now that we're attached to the DOM, we can use getElementById.
+    Element child = Document.get().getElementById(id);
+
+    // Put the panel's element back where it was.
+    if (origParent != null) {
+      origParent.insertBefore(element, origSibling);
+    } else {
+      orphan(element);
+    }
+
+    return child;
+  }
+
+  public static Element fromHtml(String html) {
+    ensureHiddenDiv();
+    hiddenDiv.setInnerHTML(html);
+    Element newbie = hiddenDiv.getFirstChildElement();
+    orphan(newbie);
+    return newbie;
+  }
+
+  private static void ensureHiddenDiv() {
+    // If the hidden DIV has not been created, create it.
+    if (hiddenDiv == null) {
+      hiddenDiv = Document.get().createDivElement();
+      UIObject.setVisible(hiddenDiv, false);
+      RootPanel.getBodyElement().appendChild(hiddenDiv);
+    }
+  }
+
+  private static void orphan(Node node) {
+    node.getParentNode().removeChild(node);
+  }
+
+  /**
+   * Not to be instantiated.
+   */
+  private UiBinderUtil() { }
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiConstructor.java b/user/src/com/google/gwt/uibinder/client/UiConstructor.java
new file mode 100644
index 0000000..78463b6
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiConstructor.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2008 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 java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a constructor that may be used as an alternative to a widget's
+ * zero args construtor in a {@link UiBinder} template. The parameter names
+ * of the constructor may be filled as xml element attribute values.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.CONSTRUCTOR)
+public @interface UiConstructor {
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiFactory.java b/user/src/com/google/gwt/uibinder/client/UiFactory.java
new file mode 100644
index 0000000..148aae0
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2008 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 java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method that may be called as an alternative to a GWT.create call in a
+ * {@link UiBinder} template. The parameter names of the constructor are treated
+ * as required xml element attribute values.
+ * <p>
+ * It is an error to apply this annotation to more than one method of a given
+ * return type.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface UiFactory {
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiField.java b/user/src/com/google/gwt/uibinder/client/UiField.java
new file mode 100644
index 0000000..e0e9d8d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiField.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 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 java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks fields in a UiBinder client that must be filled by the binder's
+ * {@link UiBinder#bindUi} method. If provided is true the field creation is
+ * delegated to the client (owner).
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface UiField {
+  boolean provided() default false;
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiHandler.java b/user/src/com/google/gwt/uibinder/client/UiHandler.java
new file mode 100644
index 0000000..43d1aeb
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiHandler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2009 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 java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method to be automatically bound as an event handler. See examples
+ * in {@link com.google.gwt.uibinder.sample.client.HandlerDemo}.
+ *
+ * <p>The annotation values must be declared in the "ui:field"
+ * template attribute.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface UiHandler {
+  String[] value();
+}
diff --git a/user/src/com/google/gwt/uibinder/client/UiTemplate.java b/user/src/com/google/gwt/uibinder/client/UiTemplate.java
new file mode 100644
index 0000000..f677d44
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/client/UiTemplate.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2008 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 java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates the template from which to generate a {@link UiBinder}.
+ */
+@Documented
+@Target(ElementType.TYPE)
+public @interface UiTemplate {
+
+  /**
+   * @return The template name
+   */
+  String value();
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/AttributeMessageInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/AttributeMessageInterpreter.java
new file mode 100644
index 0000000..4b034b5
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/AttributeMessageInterpreter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.AttributeMessage;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+
+/**
+ * Examines each element for child &lt;m:attr/&gt; elements, and replaces the
+ * corresponding attributes of the examinee with references to the translatable
+ * messages created.
+ * <p>
+ * That is, when examining element foo in
+ * <pre>
+ *   &lt;foo bar="baz"&gt;
+ *     &lt;m:attr name="baz"&gt;
+ *   &lt;/foo&gt;</pre>
+ * cosume the m:attr element, and declare a method on the Messages interface
+ * with {@literal @}Default("baz")
+ */
+ class AttributeMessageInterpreter implements XMLElement.Interpreter<String> {
+
+  private final UiBinderWriter writer;
+
+  public AttributeMessageInterpreter(UiBinderWriter writer) {
+    this.writer = writer;
+  }
+
+  public String interpretElement(XMLElement elem)
+      throws UnableToCompleteException {
+    MessagesWriter messages = writer.getMessages();
+    for (AttributeMessage am : messages.consumeAttributeMessages(elem)) {
+      elem.setAttribute(am.getAttribute(),
+        writer.tokenForExpression(am.getMessageAsHtmlAttribute()));
+    }
+
+    /*
+     * Return null because we don't want to replace the dom element with any
+     * particular string (though we may have consumed its id or gwt:field)
+     */
+    return null;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/AttributeMessageParser.java b/user/src/com/google/gwt/uibinder/parsers/AttributeMessageParser.java
new file mode 100644
index 0000000..e6cacda
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/AttributeMessageParser.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * This parser is not tied to a particular class of element, but rather is run
+ * as the first parser in any parser stack. It looks for attribute values that
+ * are set as calls to the template's generated Messages interface, by calling
+ * {@link com.google.gwt.uibinder.rebind.messages.MessagesWriter#storeMessageAttributesFor}
+ */
+public class AttributeMessageParser implements ElementParser {
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    writer.getMessages().consumeAndStoreMessageAttributesFor(elem);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/AttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/AttributeParser.java
new file mode 100644
index 0000000..a39a779
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/AttributeParser.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Attribute parsers are classes that parse xml attribute values, turning them
+ * into valid Java expressions.
+ */
+public interface AttributeParser {
+
+  /**
+   * Parse the given attribute value.
+   *
+   * @param value the attribute value to be parsed
+   * @param writer the writer
+   * @return a valid Java expression
+   * @throws UnableToCompleteException
+   */
+  String parse(String value, UiBinderWriter writer)
+    throws UnableToCompleteException;
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/BeanParser.java b/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
new file mode 100644
index 0000000..dec5021
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/BeanParser.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+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.NotFoundException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLAttribute;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.AttributeMessage;
+import com.google.gwt.uibinder.rebind.model.OwnerFieldClass;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Utility methods for discovering bean-like properties and generating code to
+ * initialize them.
+ */
+public class BeanParser implements ElementParser {
+
+  /**
+   * Generates code to initialize all bean attributes on the given element.
+   * Includes support for &lt;m:attribute /&gt; children that will apply
+   * to setters
+   * @throws UnableToCompleteException
+   */
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    final Map<String, String> setterValues = new HashMap<String, String>();
+    final Map<String, String> localizedValues =
+        fetchLocalizedAttributeValues(elem, writer);
+
+    final Map<String, String> requiredValues = new HashMap<String, String>();
+    final Map<String, JParameter> unfilledRequiredParams =
+        new HashMap<String, JParameter>();
+
+    final OwnerFieldClass ownerFieldClass = OwnerFieldClass.getFieldClass(type);
+
+    // See if there's a factory method
+    JAbstractMethod creator = writer.getOwnerClass().getUiFactoryMethod(type);
+    if (creator == null) {
+      // If not, see if there's a @UiConstructor
+      creator = ownerFieldClass.getUiConstructor();
+    }
+
+    if (creator != null) {
+      for (JParameter param : creator.getParameters()) {
+        unfilledRequiredParams.put(param.getName(), param);
+      }
+    }
+
+    // Work through the localized attribute values and assign them
+    // to appropriate constructor params or setters (which had better be
+    // ready to accept strings)
+
+    for (Entry<String, String> property : localizedValues.entrySet()) {
+      String key = property.getKey();
+      String value = property.getValue();
+
+      JParameter param = unfilledRequiredParams.get(key);
+      if (param != null) {
+        if (!isString(writer, param)) {
+          writer.die("In %s, cannot appply message attribute to non-string "
+              + "constructor argument %s %s.", elem,
+              param.getType().getSimpleSourceName(), key);
+        }
+
+        requiredValues.put(key, value);
+        unfilledRequiredParams.remove(key);
+     } else {
+        JMethod setter = ownerFieldClass.getSetter(key);
+        JParameter[] params = setter == null ? null : setter.getParameters();
+
+        if (setter == null || !(params.length == 1)
+            || !isString(writer, params[0])) {
+          writer.die("In %s, no method found to apply message attribute %s",
+              elem, key);
+        } else {
+          setterValues.put(key, value);
+        }
+      }
+    }
+
+    // Now go through the element and dispatch its attributes, remembering
+    // that constructor arguments get first dibs
+    for (int i = elem.getAttributeCount() - 1; i >= 0; i--) {
+      // Backward traversal b/c we're deleting attributes from the xml element
+
+      XMLAttribute attribute = elem.getAttribute(i);
+
+      // Ignore xmlns attributes
+      if (attribute.getName().startsWith("xmlns:")) {
+        continue;
+      }
+
+      String propertyName = attribute.getLocalName();
+      if (setterValues.keySet().contains(propertyName)
+          || requiredValues.containsKey(propertyName)) {
+        // TODO(rjrjr) A fine example of why res: namespace hack must die
+        writer.die("Duplicate attribute name: %s", propertyName);
+      }
+
+      if (unfilledRequiredParams.keySet().contains(propertyName)) {
+        JParameter parameter = unfilledRequiredParams.get(propertyName);
+        AttributeParser parser =
+            writer.getAttributeParser(attribute, parameter);
+        if (parser == null) {
+          writer.die("In %s, unable to parse %s as constructor argument "
+              + "of type %s", elem, attribute,
+              parameter.getType().getSimpleSourceName());
+        }
+        requiredValues.put(propertyName, parser.parse(attribute.consumeValue(),
+            writer));
+        unfilledRequiredParams.remove(propertyName);
+      } else {
+        JMethod setter = ownerFieldClass.getSetter(propertyName);
+        if (setter == null) {
+          writer.die("In %s, class %s has no appropriate set%s() method", elem,
+              elem.getLocalName(), initialCap(propertyName));
+        }
+
+        JParameter[] params = setter.getParameters();
+
+        AttributeParser parser =
+            writer.getAttributeParser(attribute, params);
+        if (parser == null) {
+          if (params.length == 1 && JPrimitiveType.INT == params[0].getType()) {
+            writer.die("In %s, %s cannot be parsed for %s.",
+                    elem, attribute, asString(setter));
+          }
+        } else {
+          setterValues.put(propertyName, parser.parse(attribute.consumeValue(),
+              writer));
+        }
+      }
+    }
+
+    if (!unfilledRequiredParams.isEmpty()) {
+      StringBuilder b =
+          new StringBuilder(String.format("%s missing required arguments:",
+              elem));
+      for (String name : unfilledRequiredParams.keySet()) {
+        b.append(" ").append(name);
+      }
+      writer.die(b.toString());
+    }
+
+    if (creator != null) {
+      String[] args = makeArgsList(requiredValues, creator);
+      if (creator instanceof JMethod) { // Factory method
+        String factoryMethod = String.format("owner.%s(%s)", creator.getName(),
+            UiBinderWriter.asCommaSeparatedList(args));
+        writer.setFieldInitializer(fieldName, factoryMethod);
+      } else { // Annotated Constructor
+        writer.setFieldInitializerAsConstructor(fieldName, type, args);
+      }
+    }
+
+    for (String propertyName : setterValues.keySet()) {
+      writer.addStatement("%s.set%s(%s);", fieldName,
+          initialCap(propertyName), setterValues.get(propertyName));
+    }
+  }
+
+  private String asString(JMethod setter) {
+    String sig = String.format("%s#%s(%s)", setter.getEnclosingType().getName(),
+        setter.getName(), getParamTypes(setter));
+    return sig;
+  }
+
+  /**
+   * Fetch the localized attributes that were stored by the
+   * AttributeMessageParser.
+   */
+  private Map<String, String> fetchLocalizedAttributeValues(XMLElement elem,
+      UiBinderWriter writer) {
+    final Map<String, String> localizedValues = new HashMap<String, String>();
+
+    Collection<AttributeMessage> attributeMessages =
+      writer.getMessages().retrieveMessageAttributesFor(elem);
+
+    if (attributeMessages != null) {
+      for (AttributeMessage att : attributeMessages) {
+        String propertyName = att.getAttribute();
+        localizedValues.put(propertyName, att.getMessageUnescaped());
+      }
+    }
+    return localizedValues;
+  }
+
+  private StringBuilder getParamTypes(JMethod setter) {
+    StringBuilder args = new StringBuilder();
+    for (Iterator<JParameter> i =
+        Arrays.asList(setter.getParameters()).iterator(); i.hasNext();) {
+      args.append(i.next().getType().getSimpleSourceName());
+      if (i.hasNext()) {
+        args.append(", ");
+      }
+    }
+    return args;
+  }
+
+  private String initialCap(String propertyName) {
+    return propertyName.substring(0, 1).toUpperCase() +
+    propertyName.substring(1);
+  }
+
+  private boolean isString(UiBinderWriter writer, JParameter param) {
+    JClassType stringType;
+    try {
+      stringType = writer.getOracle().getType(String.class.getName());
+    } catch (NotFoundException e) {
+      return false; // Inconceivable!
+    }
+    return stringType.equals(param.getType());
+  }
+
+  private String[] makeArgsList(final Map<String, String> valueMap,
+      JAbstractMethod method) {
+    JParameter[] params = method.getParameters();
+    String[] args = new String[params.length];
+    int i = 0;
+    for (JParameter param : params) {
+      args[i++] = valueMap.get(param.getName());
+    }
+    return args;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java
new file mode 100644
index 0000000..3c48775
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/BooleanAttributeParser.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Parses a single boolean attribute.
+ */
+public class BooleanAttributeParser implements AttributeParser {
+
+  public String parse(String value, UiBinderWriter writer) {
+    // TODO(jgw): check validity.
+    return value;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java
new file mode 100644
index 0000000..2dce510
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/BundleAttributeParser.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Interprets an attribute's contents as a method call on a resource class (one
+ * tied to an xmnls prefix via a "with://" url).
+ */
+public class BundleAttributeParser implements AttributeParser {
+
+  private final JClassType bundleClass;
+  private String bundleInstance;
+  private boolean isBundleStatic;
+
+  public BundleAttributeParser(JClassType bundleClass, String bundleInstance,
+      boolean isBundleStatic) {
+    this.bundleClass = bundleClass;
+    this.bundleInstance = bundleInstance;
+    this.isBundleStatic = isBundleStatic;
+  }
+
+  public JClassType bundleClass() {
+    return bundleClass;
+  }
+
+  public String bundleInstance() {
+    return bundleInstance;
+  }
+
+  public String fullBundleClassName() {
+    return bundleClass.getPackage().getName() + "." + bundleClass.getName();
+  }
+
+  public boolean isBundleStatic() {
+    return isBundleStatic;
+  }
+
+  public String parse(String attribute, UiBinderWriter writer) {
+    StringBuilder b = new StringBuilder();
+    String[] values = attribute.split(" ");
+    boolean first = true;
+    for (String value : values) {
+      if (first) {
+        first = false;
+      } else {
+        b.append(" + \" \" + ");
+      }
+      b.append(bundleInstance() + "." + parenthesizeDots(value) + "()");
+    }
+    return b.toString();
+  }
+
+  private String parenthesizeDots(String value) {
+    return value.replaceAll("\\.", "().");
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java
new file mode 100644
index 0000000..4a91e84
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/CellPanelParser.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.CellPanel} widgets.
+ */
+public class CellPanelParser implements ElementParser {
+
+  private static final String HALIGN_ATTR = "horizontalAlignment";
+  private static final String VALIGN_ATTR = "verticalAlignment";
+  private static final String WIDTH_ATTR = "width";
+  private static final String HEIGHT_ATTR = "height";
+  private static final String CELL_TAG = "Cell";
+
+  private static HorizontalAlignmentConstantParser halignParser =
+      new HorizontalAlignmentConstantParser();
+  private static VerticalAlignmentConstantParser valignParser =
+      new VerticalAlignmentConstantParser();
+  private static StringAttributeParser stringParser =
+      new StringAttributeParser();
+
+  /**
+   * Parses the alignment and size attributes common to all CellPanels.
+   *
+   * This is exposed publicly because there is a DockPanelParser that overrides
+   * the default behavior, but still needs to parse these attributes.
+   *
+   * @throws UnableToCompleteException
+   */
+  public static void parseCellAttributes(XMLElement cellElem, String fieldName,
+      String childFieldName, UiBinderWriter writer) throws UnableToCompleteException {
+    // Parse horizontal and vertical alignment attributes.
+    if (cellElem.hasAttribute(HALIGN_ATTR)) {
+      String value =
+          halignParser.parse(cellElem.consumeAttribute(HALIGN_ATTR), writer);
+      writer.addStatement("%1$s.setCellHorizontalAlignment(%2$s, %3$s);", fieldName,
+          childFieldName, value);
+    }
+
+    if (cellElem.hasAttribute(VALIGN_ATTR)) {
+      String value =
+          valignParser.parse(cellElem.consumeAttribute(VALIGN_ATTR), writer);
+      writer.addStatement("%1$s.setCellVerticalAlignment(%2$s, %3$s);", fieldName,
+          childFieldName, value);
+    }
+
+    // Parse width and height attributes.
+    if (cellElem.hasAttribute(WIDTH_ATTR)) {
+      String value =
+          stringParser.parse(cellElem.consumeAttribute(WIDTH_ATTR), writer);
+      writer.addStatement("%1$s.setCellWidth(%2$s, %3$s);", fieldName, childFieldName,
+          value);
+    }
+
+    if (cellElem.hasAttribute(HEIGHT_ATTR)) {
+      String value =
+          stringParser.parse(cellElem.consumeAttribute(HEIGHT_ATTR), writer);
+      writer.addStatement("%1$s.setCellHeight(%2$s, %3$s);", fieldName, childFieldName,
+          value);
+    }
+  }
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    for (XMLElement child : elem.consumeChildElements()) {
+      String ns = child.getNamespaceUri();
+      String tagName = child.getLocalName();
+      if (ns.equals(elem.getNamespaceUri()) && tagName.equals(CELL_TAG)) {
+        // It's a cell element, so parse its single child as a widget.
+        XMLElement widget = child.consumeSingleChildElement();
+        if (widget == null) {
+          writer.die("Cell must contain a single child widget");
+        }
+        String childFieldName = writer.parseWidget(widget);
+        writer.addStatement("%1$s.add(%2$s);", fieldName, childFieldName);
+
+        // Parse the cell tag's alignment & size attributes.
+        parseCellAttributes(child, fieldName, childFieldName, writer);
+      } else {
+        // It's just a normal child, so parse it as a widget.
+        String childFieldName = writer.parseWidget(child);
+        writer.addStatement("%1$s.add(%2$s);", fieldName, childFieldName);
+      }
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java
new file mode 100644
index 0000000..f9ebf80
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/ComputedAttributeInterpreter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLAttribute;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Assigns computed values to element attributes, e.g.
+ * res:styleName="style.pretty" which will become something like
+ * myWidget.setStyleName(resources.style().pretty()) in the generated code.
+ */
+ class ComputedAttributeInterpreter implements XMLElement.Interpreter<String> {
+
+  private final UiBinderWriter writer;
+
+  public ComputedAttributeInterpreter(UiBinderWriter writer) {
+    this.writer = writer;
+  }
+
+  public String interpretElement(XMLElement elem)
+      throws UnableToCompleteException {
+    Map<String, String> attNameToToken = new HashMap<String, String>();
+
+    for (int i = elem.getAttributeCount() - 1; i >= 0; i--) {
+      XMLAttribute att = elem.getAttribute(i);
+
+      AttributeParser parser = writer.getAttributeParser(att);
+      if (parser != null) {
+        String parsedValue = parser.parse(att.consumeValue(), writer);
+        String attToken = writer.tokenForExpression(parsedValue);
+        attNameToToken.put(att.getLocalName(), attToken);
+      }
+    }
+
+    for (Map.Entry<String, String> attr : attNameToToken.entrySet()) {
+      elem.setAttribute(attr.getKey(), attr.getValue());
+    }
+
+    // Return null because we don't want to replace the dom element
+    return null;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/CustomButtonParser.java b/user/src/com/google/gwt/uibinder/parsers/CustomButtonParser.java
new file mode 100644
index 0000000..70c428b
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/CustomButtonParser.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Parses CustomButton widgets.
+ */
+public class CustomButtonParser implements ElementParser {
+
+  private static final Set<String> faceNames = new HashSet<String>();
+  private static final Object IMAGE_CLASS =
+      "com.google.gwt.user.client.ui.Image";
+
+  static {
+    faceNames.add("UpFace");
+    faceNames.add("DownFace");
+    faceNames.add("UpHoveringFace");
+    faceNames.add("DownHoveringFace");
+    faceNames.add("UpDisabledFace");
+    faceNames.add("DownDisabledFace");
+  }
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    // Parse children.
+    for (XMLElement child : elem.consumeChildElements()) {
+      // CustomButton can only contain Face elements.
+      String ns = child.getNamespaceUri();
+      String faceName = child.getLocalName();
+
+      if (!ns.equals(elem.getNamespaceUri())) {
+        writer.die("Invalid CustomButton child namespace: " + ns);
+      }
+      if (!faceNames.contains(faceName)) {
+        writer.die("Invalid CustomButton face: " + faceName);
+      }
+
+      // Look for innerHTML first.
+      HtmlInterpreter interpreter =
+          HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
+      String innerHtml = child.consumeInnerHtml(interpreter).trim();
+      if (innerHtml.length() > 0) {
+        writer.addStatement("%1$s.get%2$s().setHTML(\"%3$s\");", fieldName, faceName,
+            innerHtml);
+      }
+
+      // Then look for html, text, and image attributes.
+      if (child.hasAttribute("html")) {
+        String html = child.consumeAttribute("html");
+        writer.addStatement("%1$s.get%2$s().setHTML(\"%3$s\");", fieldName, faceName, html);
+      }
+
+      if (child.hasAttribute("text")) {
+        String text = child.consumeAttribute("text");
+        writer.addStatement("%1$s.get%2$s().setText(\"%3$s\");", fieldName, faceName, text);
+      }
+
+      if (child.hasAttribute("image")) {
+        String image = child.consumeAttribute("image");
+        writer.addStatement("%1$s.get%2$s().setImage(new %3$s(\"%4$s\"));", fieldName,
+            faceName, IMAGE_CLASS, image);
+      }
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/DisclosurePanelParser.java b/user/src/com/google/gwt/uibinder/parsers/DisclosurePanelParser.java
new file mode 100644
index 0000000..722db79
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/DisclosurePanelParser.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.user.client.ui.DisclosurePanel;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.DisclosurePanel} widgets.
+ */
+public class DisclosurePanelParser implements ElementParser {
+
+  private static final String ATTRIBUTE_HEADER_WIDGET = "DisclosurePanel-header";
+
+  private static final String ATTRIBUTE_HEADER_BUNDLE = "imageBundle";
+
+  private static final String ATTRIBUTE_INITIALLY_OPEN = "initiallyOpen";
+
+  private static final String ATTRIBUTE_ENABLE_ANIMATION = "enableAnimation";
+
+  /**
+   * @return the type oracle's DisclosurePanel class
+   */
+  private static JClassType getDisclosurePanelClass(UiBinderWriter w) {
+    return w.getOracle().findType(DisclosurePanel.class.getName());
+  }
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+
+    String text = null;
+    // They must specify a label
+    if (elem.hasAttribute("text")) {
+      text = elem.consumeAttribute("text");
+      text = '"' + UiBinderWriter.escapeTextForJavaStringLiteral(text) + '"';
+    }
+
+    // They may specify an image bundle
+    String imageBundle = null;
+    if (elem.hasAttribute(ATTRIBUTE_HEADER_BUNDLE)) {
+      imageBundle = elem.consumeAttribute(ATTRIBUTE_HEADER_BUNDLE);
+    }
+
+    // They may specify an initial closed state
+    String initiallyOpen = "false";
+    if (elem.hasAttribute(ATTRIBUTE_INITIALLY_OPEN)) {
+      initiallyOpen = elem.consumeAttribute(ATTRIBUTE_INITIALLY_OPEN);
+    }
+
+    // They may enable animation
+    String enableAnimation = "true";
+    if (elem.hasAttribute(ATTRIBUTE_ENABLE_ANIMATION)) {
+      enableAnimation = elem.consumeAttribute(ATTRIBUTE_ENABLE_ANIMATION);
+    }
+
+    String childFieldName = null;
+    String headerFieldName = null;
+
+    for (XMLElement child : elem.consumeChildElements()) {
+      // Disclosure panel header optionally comes from the DisclosurePanel-header attribute of the
+      // child
+      boolean childIsHeader = false;
+      String headerAttributeName = elem.getPrefix() + ":" + ATTRIBUTE_HEADER_WIDGET;
+      if (child.hasAttribute(headerAttributeName)) {
+        if (headerFieldName != null) {
+          writer.die("In %s, DisclosurePanel cannot contain more than one header widget.", elem);
+        }
+        child.consumeAttribute(headerAttributeName);
+        headerFieldName = writer.parseWidget(child);
+        childIsHeader = true;
+      }
+      if (!childIsHeader) {
+        if (childFieldName != null) {
+          writer.die("In %s, DisclosurePanel cannot contain more than one content widget.", elem);
+        }
+        childFieldName = writer.parseWidget(child);
+      }
+    }
+
+    // To use the image bundle, you must provide a text header.
+    if (imageBundle != null) {
+      writer.setFieldInitializerAsConstructor(fieldName,
+          getDisclosurePanelClass(writer), imageBundle, (text != null ? text : "\"\""),
+          initiallyOpen);
+    } else {
+      JClassType panelClass = getDisclosurePanelClass(writer);
+      if (text != null) {
+        writer.setFieldInitializerAsConstructor(fieldName, panelClass, text);
+      } else {
+        writer.setFieldInitializerAsConstructor(fieldName, panelClass);
+      }
+    }
+    if (childFieldName != null) {
+      writer.addStatement("%1$s.setContent(%2$s);", fieldName, childFieldName);
+    }
+    if (headerFieldName != null) {
+      writer.addStatement("%1$s.setHeader(%2$s);", fieldName, headerFieldName);
+    }
+    writer.addStatement("%1$s.setAnimationEnabled(%2$s);", fieldName, enableAnimation);
+    writer.addStatement("%1$s.setOpen(%2$s);", fieldName, initiallyOpen);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java
new file mode 100644
index 0000000..1d4be30
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/DockPanelParser.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+import java.util.HashMap;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.DockPanel} widgets.
+ */
+public class DockPanelParser implements ElementParser {
+
+  private static final String TAG_DOCK = "Dock";
+  private static final HashMap<String, String> values =
+      new HashMap<String, String>();
+
+  static {
+    values.put("NORTH", "com.google.gwt.user.client.ui.DockPanel.NORTH");
+    values.put("SOUTH", "com.google.gwt.user.client.ui.DockPanel.SOUTH");
+    values.put("EAST", "com.google.gwt.user.client.ui.DockPanel.EAST");
+    values.put("WEST", "com.google.gwt.user.client.ui.DockPanel.WEST");
+    values.put("CENTER", "com.google.gwt.user.client.ui.DockPanel.CENTER");
+  }
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    // Parse children.
+    for (XMLElement child : elem.consumeChildElements()) {
+      // DockPanel can only contain Dock elements.
+      String ns = child.getNamespaceUri();
+      String tagName = child.getLocalName();
+
+      if (!ns.equals(elem.getNamespaceUri())) {
+        writer.die("Invalid DockPanel child namespace: " + ns);
+      }
+      if (!tagName.equals(TAG_DOCK)) {
+        writer.die("Invalid DockPanel child element: " + tagName);
+      }
+
+      // And they must specify a direction.
+      if (!child.hasAttribute("direction")) {
+        writer.die("Dock must specify the 'direction' attribute");
+      }
+      String value = child.consumeAttribute("direction");
+      String translated = values.get(value);
+      if (translated == null) {
+        writer.die("Invalid value: dockDirection='" + value + "'");
+      }
+
+      // And they can only have a single child widget.
+      XMLElement widget = child.consumeSingleChildElement();
+      if (widget == null) {
+        writer.die("Dock must contain a single child widget.");
+      }
+      String childFieldName = writer.parseWidget(widget);
+      writer.addStatement("%1$s.add(%2$s, %3$s);", fieldName, childFieldName, translated);
+
+      // And they can optionally have a width.
+      if (child.hasAttribute("width")) {
+        writer.addStatement("%1$s.setCellWidth(%2$s, \"%3$s\");",
+            fieldName, childFieldName, child.consumeAttribute("width"));
+      }
+
+      // And they can optionally have a height.
+      if (child.hasAttribute("height")) {
+        writer.addStatement("%1$s.setCellHeight(%2$s, \"%3$s\");",
+            fieldName, childFieldName, child.consumeAttribute("height"));
+      }
+
+      // Parse the CellPanel-specific attributes on the Dock element.
+      CellPanelParser.parseCellAttributes(child, fieldName, childFieldName, writer);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/DomElementParser.java b/user/src/com/google/gwt/uibinder/parsers/DomElementParser.java
new file mode 100644
index 0000000..ba2619a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/DomElementParser.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * Parses a raw dom element, as opposed to a widget or uiobject. Note that this
+ * parser does not get a crack at every dom element--it only handles the case of
+ * a dom element as ui root. Note also that it is intended to be used only by
+ * {@link UiBinderWriter}, and will generate cast class exceptions if used
+ * otherwise.
+ */
+public class DomElementParser implements ElementParser {
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    HtmlInterpreter interpreter =
+        new HtmlInterpreter(writer, "root", new HtmlMessageInterpreter(writer,
+            "root"));
+
+    writer.setFieldInitializer(fieldName, "null");
+
+    interpreter.interpretElement(elem);
+    String rootHtml = elem.consumeOpeningTag() + elem.getClosingTag();
+    String rootType = getFQTypeName(type);
+    writer.genRoot(String.format(
+        "(%1$s) UiBinderUtil.fromHtml(\"%2$s\")", rootType, rootHtml));
+
+    String innerHtml = elem.consumeInnerHtml(interpreter);
+
+    if (innerHtml.trim().length() > 0) { // TODO(rjrjr) Really want this check?
+      writer.addStatement("root.setInnerHTML(\"%s\");", innerHtml);
+    }
+  }
+
+  private String getFQTypeName(JClassType type) {
+    return type.getPackage().getName() + "." + type.getName();
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/ElementParser.java b/user/src/com/google/gwt/uibinder/parsers/ElementParser.java
new file mode 100644
index 0000000..e1b629d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/ElementParser.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * Element parsers are classes that parse xml elements, using the context to
+ * generate widget initialization code.
+ */
+public interface ElementParser {
+
+  /**
+   * Parse the given element, generating the code to initialize it from the
+   * element's attributes and children.
+   *
+   * @param elem the element to be parsed
+   * @param fieldName the name of the widget field to be initialized
+   * @param type TODO
+   * @param writer the writer
+   * @throws UnableToCompleteException on error
+   */
+  void parse(XMLElement elem, String fieldName,
+      JClassType type, UiBinderWriter writer) throws UnableToCompleteException;
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java
new file mode 100644
index 0000000..433cea0
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/FieldInterpreter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * Generates fields to hold named dom elements (e.g. &lt;div
+ * ui:field="importantDiv"&gt;)
+ */
+ class FieldInterpreter implements XMLElement.Interpreter<String> {
+
+  private final String element;
+  private final UiBinderWriter writer;
+
+  public FieldInterpreter(UiBinderWriter writer, String ancestorExpression) {
+    this.writer = writer;
+    this.element = ancestorExpression;
+  }
+
+  public String interpretElement(XMLElement elem)
+      throws UnableToCompleteException {
+    String fieldName = writer.declareFieldIfNeeded(elem);
+    if (fieldName != null) {
+      String token = writer.declareDomField(fieldName, element);
+
+      if (elem.hasAttribute("id")) {
+        writer.die(String.format(
+            "Cannot declare id=\"%s\" and %s=\"%s\" on the same element",
+            elem.consumeAttribute("id"), writer.getGwtFieldAttributeName(),
+            fieldName));
+      }
+
+      elem.setAttribute("id", token);
+    }
+
+    /*
+     * Return null because we don't want to replace the dom element with any
+     * particular string (though we may have consumed its id or gwt:field)
+     */
+    return null;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java
new file mode 100644
index 0000000..48b4003
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HTMLPanelParser.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.parsers.HtmlMessageInterpreter.PlaceholderInterpreterProvider;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.PlaceholderInterpreter;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.HTMLPanel} widgets.
+ */
+public class HTMLPanelParser implements ElementParser {
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      final UiBinderWriter writer) throws UnableToCompleteException {
+    String customTag =
+      UiBinderWriter.escapeTextForJavaStringLiteral(elem.consumeAttribute("tag"));
+    
+    /*
+     * Gathers up elements that indicate nested widgets (but only those
+     * that are not inside msg elements).
+     */
+    WidgetInterpreter widgetInterpreter = new WidgetInterpreter(fieldName, writer);
+
+    /* 
+     * Handles non-widget elements like msg, and dom elements with ui:field
+     * attributes. There may be widgets inside a msg, which is why
+     * the construction in makeHtmlInterpreter is so complicated.
+     */
+    HtmlInterpreter htmlInterpreter = makeHtmlInterpreter(fieldName, writer);
+
+    String html = elem.consumeInnerHtml(InterpreterPipe.newPipe(widgetInterpreter,
+        htmlInterpreter));
+
+    /*
+     * HTMLPanel has no no-arg ctor, so we have to generate our own, using the
+     * element's innerHTML and perhaps its tag attribute. Do this in a way that
+     * will not break subclasses if they happen to have the same constructor
+     * signature (by passing in type).
+     */
+    if ("".equals(customTag)) {
+      writer.setFieldInitializerAsConstructor(fieldName, type, "\"" + html + "\"");
+    } else {
+      writer.setFieldInitializerAsConstructor(fieldName, type, "\"" + customTag + "\"",
+          "\"" + html + "\"");
+    }
+  }
+
+  /**
+   * Creates an HtmlInterpreter with our specialized placeholder interpreter,
+   * which will allow widget instances to be declared inside of m:msg elements.
+   */
+  private HtmlInterpreter makeHtmlInterpreter(final String fieldName,
+      final UiBinderWriter uiWriter) {
+    final String ancestorExpression = fieldName + ".getElement()";
+
+    PlaceholderInterpreterProvider placeholderInterpreterProvider =
+        new PlaceholderInterpreterProvider() {
+          public PlaceholderInterpreter get(MessageWriter message) {
+            return new WidgetPlaceholderInterpreter(fieldName, uiWriter,
+                message, ancestorExpression);
+          }
+        };
+
+    HtmlInterpreter htmlInterpreter =
+        new HtmlInterpreter(uiWriter, ancestorExpression,
+            new HtmlMessageInterpreter(uiWriter, placeholderInterpreterProvider));
+
+    return htmlInterpreter;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HasHTMLParser.java b/user/src/com/google/gwt/uibinder/parsers/HasHTMLParser.java
new file mode 100644
index 0000000..5d06369
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HasHTMLParser.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * Parses widgets that implement {@link com.google.gwt.user.client.ui.HasHTML}.
+ */
+public class HasHTMLParser implements ElementParser {
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+
+    HtmlInterpreter interpreter =
+      HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
+    String html = elem.consumeInnerHtml(interpreter);
+
+    // TODO(jgw): throw an error if there's a conflicting 'html' attribute.
+    if (html.trim().length() > 0) {
+      writer.genStringPropertySet(fieldName, "HTML", html);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HasTextParser.java b/user/src/com/google/gwt/uibinder/parsers/HasTextParser.java
new file mode 100644
index 0000000..742205b
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HasTextParser.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * Parses widgets that implement {@link com.google.gwt.user.client.ui.HasText}.
+ */
+public class HasTextParser implements ElementParser {
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    // Widgets that implement HasText will use their elements' inner text.
+    String text = elem.consumeInnerText(new TextInterpreter(writer));
+    if (text.trim().length() > 0) {
+      writer.genStringPropertySet(fieldName, "text", text);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HasWidgetsParser.java b/user/src/com/google/gwt/uibinder/parsers/HasWidgetsParser.java
new file mode 100644
index 0000000..808bb38
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HasWidgetsParser.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * Parses any widgets that implement
+ * {@link com.google.gwt.user.client.ui.HasWidgets}.
+ *
+ * This handles all panels that support a single-argument add() method.
+ */
+public class HasWidgetsParser implements ElementParser {
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    // Parse children.
+    for (XMLElement child : elem.consumeChildElements()) {
+      String childFieldName = writer.parseWidget(child);
+      writer.addStatement("%1$s.add(%2$s);", fieldName, childFieldName);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java b/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java
new file mode 100644
index 0000000..230abfc
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HorizontalAlignmentConstantParser.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+import java.util.HashMap;
+
+/**
+ * Parses a
+ * {@link com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant}.
+ */
+public class HorizontalAlignmentConstantParser implements AttributeParser {
+
+  private static final HashMap<String, String> values =
+    new HashMap<String, String>();
+
+  static {
+    values.put("ALIGN_LEFT",
+      "com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_LEFT");
+    values.put("ALIGN_RIGHT",
+      "com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_RIGHT");
+    values.put("ALIGN_CENTER",
+      "com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_CENTER");
+  }
+
+  public String parse(String value, UiBinderWriter writer)
+  throws UnableToCompleteException {
+    String translated = values.get(value);
+    if (translated == null) {
+      writer.die("Invalid value: horizontalAlignment='" + value + "'");
+    }
+    return translated;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HtmlInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/HtmlInterpreter.java
new file mode 100644
index 0000000..18e1d6c
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HtmlInterpreter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.XMLElement.Interpreter;
+
+/**
+ * This is the most generally useful interpreter, and the most likely to be used
+ * by a custom parser when calling {@link XMLElement#consumeInnerHtml}
+ * <ul>
+ * <li>Assigns computed values to element attributes (e.g.
+ * res:class="style.pretty")
+ * <li>Generates fields to hold named dom elements (e.g. &lt;div
+ * gwt:field="importantDiv"&gt;)
+ * <li>Turns &lt;m:msg&gt; and &lt;m:attr&gt; elements into methods on a
+ * generated Messages interface
+ * </ul>
+ */
+public class HtmlInterpreter implements XMLElement.Interpreter<String> {
+
+  /**
+   * A convenience factory method for the most common use of this class, to work
+   * with HTML that will eventually be rendered under a
+   * {@link com.google.gwt.user.client.ui.UIObject} (or really, any object that
+   * responds to <code>getElement()</code>). Uses an instance of
+   * {@link HtmlMessageInterpreter} to process message elements.
+   *
+   * @param uiExpression An expression that can be evaluated at runtime to find
+   *          an object whose getElement() method can be called to get an
+   *          ancestor of all Elements generated from the interpreted HTML.
+   */
+  public static HtmlInterpreter newInterpreterForUiObject(
+      UiBinderWriter writer, String uiExpression) {
+    String ancestorExpression = uiExpression + ".getElement()";
+    return new HtmlInterpreter(writer, ancestorExpression,
+        new HtmlMessageInterpreter(writer, ancestorExpression));
+  }
+
+  private final InterpreterPipe<String> pipe;
+
+  /**
+   * Rather than using this constructor, you probably want to use the
+   * {@link #newInterpreterForUiObject} factory method.
+   *
+   * @param ancestorExpression An expression that can be evaluated at runtime to
+   *          find an Element that will be an ancestor of all Elements generated
+   *          from the interpreted HTML.
+   * @param messageInterpreter an interpreter to handle msg and ph elements, typically an instance
+   *          of {@link HtmlMessageInterpreter}. This interpreter gets last
+   *          crack
+   */
+  public HtmlInterpreter(UiBinderWriter writer, String ancestorExpression,
+      Interpreter<String> messageInterpreter) {
+    this.pipe = new InterpreterPipe<String>();
+
+    pipe.add(new FieldInterpreter(writer, ancestorExpression));
+    pipe.add(new ComputedAttributeInterpreter(writer));
+    pipe.add(new AttributeMessageInterpreter(writer));
+    pipe.add(messageInterpreter);
+  }
+
+  public String interpretElement(XMLElement elem)
+      throws UnableToCompleteException {
+    return pipe.interpretElement(elem);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java
new file mode 100644
index 0000000..4c9d48d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HtmlMessageInterpreter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.messages.PlaceholderInterpreter;
+
+/**
+ * Processes <m:msg> elements inside HTML values, which themselves
+ * are allowed to contain HTML. That HTML may hold elements with
+ * ui:field attributes and computed attributes, which must be
+ * replaced by placeholders in the generated message.
+ */
+public class HtmlMessageInterpreter implements XMLElement.Interpreter<String> {
+  /**
+   * Provides instances of {@link PlaceholderInterpreter}, to allow
+   * customized handling of the innards of a msg element.
+   */
+  public interface PlaceholderInterpreterProvider {
+    PlaceholderInterpreter get(MessageWriter message);
+  }
+
+  private final UiBinderWriter uiWriter;
+  private final PlaceholderInterpreterProvider phiProvider;
+
+  /**
+   * Build a HtmlMessageInterpreter that uses customized placeholder interpreter
+   * instances.
+   */
+  public HtmlMessageInterpreter(UiBinderWriter uiWriter,
+      PlaceholderInterpreterProvider phiProvider) {
+    this.uiWriter = uiWriter;
+    this.phiProvider = phiProvider;
+ }
+
+  /**
+   * Build a normally configured HtmlMessageInterpreter, able to handle
+   * put placeholders around dom elements with m:ph attributes and computed
+   * attributes.
+   */
+  public HtmlMessageInterpreter(final UiBinderWriter uiWriter,
+      final String ancestorExpression) {
+    this(uiWriter, new PlaceholderInterpreterProvider() {
+      public PlaceholderInterpreter get(MessageWriter message) {
+        return new HtmlPlaceholderInterpreter(uiWriter, message, ancestorExpression);
+      }
+    });
+  }
+
+  public String interpretElement(XMLElement elem)
+      throws UnableToCompleteException {
+    MessagesWriter messages = uiWriter.getMessages();
+    if (messages.isMessage(elem)) {
+      if (!elem.hasChildNodes()) {
+        uiWriter.die("Empty message: " + elem);
+      }
+
+      MessageWriter message = messages.newMessage(elem);
+      message.setDefaultMessage(elem.consumeInnerHtml(phiProvider.get(message)));
+      return uiWriter.tokenForExpression(messages.declareMessage(message));
+    }
+
+    return null;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java
new file mode 100644
index 0000000..81d2f15
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/HtmlPlaceholderInterpreter.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.messages.PlaceholderInterpreter;
+
+/**
+ * Interprets the interior of an html message. Extends the basic
+ * PlaceholderInterpreter to handle dom elements with field names and computed
+ * attributes--basically any dom attribute that cannot live in a static value
+ * and so must be wrapped in a placeholder. (Note that the interior of such an
+ * element is processed with a recursive call to
+ * {@link XMLElement#consumeInnerHtml}.)
+ */
+class HtmlPlaceholderInterpreter extends PlaceholderInterpreter {
+  private int serial = 0;
+  private final XMLElement.Interpreter<String> fieldAndComputed;
+
+  HtmlPlaceholderInterpreter(UiBinderWriter writer,
+      MessageWriter message, String ancestorExpression) {
+    super(writer, message);
+    fieldAndComputed =
+        InterpreterPipe.newPipe(new FieldInterpreter(writer,
+            ancestorExpression), new ComputedAttributeInterpreter(writer));
+  }
+
+  @Override
+  public String interpretElement(XMLElement elem)
+      throws UnableToCompleteException {
+    fieldAndComputed.interpretElement(elem);
+
+    if (isDomPlaceholder(elem)) {
+      MessagesWriter mw = uiWriter.getMessages();
+
+      String name = mw.consumeMessageAttribute("ph", elem);
+      if ("".equals(name)) {
+        name = "htmlElement" + (++serial);
+      }
+
+      String openTag = elem.consumeOpeningTag();
+      String openPlaceholder =
+          nextPlaceholder(name + "Begin", stripTokens(openTag),
+              uiWriter.detokenate(openTag));
+
+      String body = elem.consumeInnerHtml(this);
+
+      String closeTag = elem.getClosingTag();
+      String closePlaceholder =
+          nextPlaceholder(name + "End", closeTag, closeTag);
+
+      return openPlaceholder + body + closePlaceholder;
+    }
+
+    return super.interpretElement(elem);
+  }
+
+  @Override
+  protected String consumePlaceholderInnards(XMLElement elem)
+      throws UnableToCompleteException {
+    return elem.consumeInnerHtml(fieldAndComputed);
+  }
+
+  /**
+   * @return true if it has an m:ph attribute, or has a token in any attribute
+   */
+  private boolean isDomPlaceholder(XMLElement elem) {
+    MessagesWriter mw = uiWriter.getMessages();
+    if (mw.hasMessageAttribute("ph", elem)) {
+      return true;
+    }
+    for (int i = elem.getAttributeCount() - 1; i >= 0; i--) {
+      if (elem.getAttribute(i).hasToken()) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/IntPairParser.java b/user/src/com/google/gwt/uibinder/parsers/IntPairParser.java
new file mode 100644
index 0000000..070718c
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/IntPairParser.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Parses a pair of integer values.
+ */
+public class IntPairParser implements AttributeParser {
+
+  public String parse(String value, UiBinderWriter writer) {
+    // TODO(jgw): parse & validate
+    return value;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/IntParser.java b/user/src/com/google/gwt/uibinder/parsers/IntParser.java
new file mode 100644
index 0000000..11a85ee
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/IntParser.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Parses an integer value.
+ */
+public class IntParser implements AttributeParser {
+
+  public String parse(String value, UiBinderWriter writer) {
+    // TODO(jgw): validate
+    return value;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/InterpreterPipe.java b/user/src/com/google/gwt/uibinder/parsers/InterpreterPipe.java
new file mode 100644
index 0000000..f9adbce
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/InterpreterPipe.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.XMLElement.Interpreter;
+import com.google.gwt.uibinder.rebind.XMLElement.PostProcessingInterpreter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Pairs {@link XMLElement.Interpreter} instances.
+ *
+ * @param <T> The type returned by all members of the pipe
+ */
+ class InterpreterPipe<T> implements PostProcessingInterpreter<T> {
+  public static <T>InterpreterPipe<T> newPipe(Interpreter<T> a, Interpreter<T> b) {
+    InterpreterPipe<T> rtn = new InterpreterPipe<T>();
+    rtn.add(a);
+    rtn.add(b);
+    return rtn;
+  }
+
+  private final List<Interpreter<T>> pipe =
+    new ArrayList<Interpreter<T>>();
+
+  public void add(Interpreter<T> i) {
+    pipe.add(i);
+  }
+
+  /**
+   * Interpreters are fired in the order they were handed to the constructor. If
+   * an interpretter gives a non-null result, downstream interpretters don't
+   * fire.
+   *
+   * @return The T or null returned by the last pipelined interpretter to run
+   * @throws UnableToCompleteException on error
+   */
+  public T interpretElement(XMLElement elem) throws UnableToCompleteException {
+    T rtn = null;
+    for (XMLElement.Interpreter<T> i : pipe) {
+      rtn = i.interpretElement(elem);
+      if (null != rtn) {
+        break;
+      }
+    }
+    return rtn;
+  }
+
+  /**
+   * Called by various {@link XMLElement} consumeInner*() methods after all
+   * elements have been handed to {@link #interpretElement}. Passes the
+   * text to be post processed to each pipe member that is instanceof
+   * {@link PostProcessingInterpreter}.
+   */
+  public String postProcess(String consumedText) throws UnableToCompleteException {
+    for (XMLElement.Interpreter<T> i : pipe) {
+      if (i instanceof PostProcessingInterpreter) {
+        consumedText = ((PostProcessingInterpreter<T>) i).postProcess(consumedText);
+      }
+    }
+    return consumedText;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/MenuBarParser.java b/user/src/com/google/gwt/uibinder/parsers/MenuBarParser.java
new file mode 100644
index 0000000..0325997
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/MenuBarParser.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.user.client.ui.MenuBar;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.MenuBar} widgets.
+ */
+public class MenuBarParser implements ElementParser {
+  private static final String TAG_MENUITEM = "MenuItem";
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    // Generate instantiation (Vertical MenuBars require a ctor param).
+    if (elem.hasAttribute("vertical")) {
+      boolean vertical = elem.consumeBooleanAttribute("vertical");
+      if (vertical) {
+        writer.setFieldInitializerAsConstructor(fieldName,
+            writer.getOracle().findType(MenuBar.class.getName()), "true");
+      }
+    }
+
+    // Parse children.
+    for (XMLElement child : elem.consumeChildElements()) {
+      // MenuBar can only contain MenuItem elements.
+      {
+        String ns = child.getNamespaceUri();
+        String tagName = child.getLocalName();
+
+        if (!elem.getNamespaceUri().equals(ns) || !tagName.equals(TAG_MENUITEM)) {
+          writer.die("In %s, only <%s:%s> are valid children", elem,
+              elem.getPrefix(), TAG_MENUITEM);
+        }
+      }
+
+      String itemFieldName = writer.parseWidget(child);
+
+      writer.addStatement("%1$s.addItem(%2$s);", fieldName, itemFieldName);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/MenuItemParser.java b/user/src/com/google/gwt/uibinder/parsers/MenuItemParser.java
new file mode 100644
index 0000000..128aa96
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/MenuItemParser.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2009 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.user.client.ui.MenuItem;
+
+/**
+ * A parser for menu items.
+ */
+public class MenuItemParser implements ElementParser {
+
+  /**
+   * Used by {@link XMLElement#consumeInnerHtml}. Gets to examine
+   * each dom element. Removes gwt:MenuBar elements and hands them
+   * to the template writer to be interpreted as widgets. Replaces
+   * gwt:MenuItemHTML elements with their consumeInnerHtml contents,
+   * and warns that they are deprecated.
+   */
+  private static class MenuItemGutsInterpreter
+      implements XMLElement.Interpreter<String> {
+    private final String namespaceUri;
+    private final HtmlInterpreter htmlInterpreter;
+    private final UiBinderWriter writer;
+    private final String errorContext;
+
+    private String menuBarField;
+
+    public MenuItemGutsInterpreter(UiBinderWriter writer, String namespaceUri,
+        String errorContext, HtmlInterpreter htmlInterpreter) {
+      this.writer = writer;
+      this.errorContext = errorContext;
+      this.namespaceUri = namespaceUri;
+      this.htmlInterpreter = htmlInterpreter;
+    }
+
+    public String interpretElement(XMLElement elem)
+        throws UnableToCompleteException {
+      if (isMenuHtml(elem)) {
+        writer.warn("In %s, the MenuItemHTML element is no longer required, "
+            + "and its contents have been inlined. This will become an error.",
+            errorContext);
+        return elem.consumeInnerHtml(htmlInterpreter);
+      }
+
+      if (isMenuBar(elem)) {
+        if (menuBarField != null) {
+          writer.die("In %s, only one MenuBar may be contained in a MenuItem",
+              errorContext);
+        }
+        menuBarField = writer.parseWidget(elem);
+        return "";
+      }
+
+      return null;
+    }
+
+    String getMenuBarField() {
+      return menuBarField;
+    }
+
+    private boolean isMenuBar(XMLElement child) {
+      return namespaceUri.equals(child.getNamespaceUri())
+          && child.getLocalName().equals(TAG_MENUBAR);
+    }
+
+    private boolean isMenuHtml(XMLElement child) {
+      return namespaceUri.equals(child.getNamespaceUri())
+          && child.getLocalName().equals(TAG_MENUITEMHTML);
+    }
+  }
+  private static final String TAG_MENUBAR = "MenuBar";
+
+  private static final String TAG_MENUITEMHTML = "MenuItemHTML";
+
+  public void parse(final XMLElement elem, String fieldName, JClassType type,
+      final UiBinderWriter writer) throws UnableToCompleteException {
+    writer.setFieldInitializerAsConstructor(fieldName,
+        writer.getOracle().findType(MenuItem.class.getName()),
+        "\"\"", "(com.google.gwt.user.client.Command) null");
+
+    InterpreterPipe<String> interpreter = new InterpreterPipe<String>();
+
+    // Build an interpreter pipeline to handle MenuItemHTML and MenuBar
+    // children, and to interpret everything other than those as HTML.
+    // TODO(rjrjr) Once MenuItemHTML goes away, we can reduce this to
+    // just handling MenuBar, and rely on HasHTMLParser to do the
+    // "everything other than those" bit.
+
+    final HtmlInterpreter htmlInterpreter =
+        HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
+    MenuItemGutsInterpreter guts =
+        new MenuItemGutsInterpreter(writer, elem.getNamespaceUri(),
+            elem.toString(), htmlInterpreter);
+
+    interpreter.add(guts);
+    interpreter.add(htmlInterpreter);
+
+    String html = elem.consumeInnerHtml(interpreter);
+    if (html.trim().length() > 0) {
+      writer.genStringPropertySet(fieldName, "HTML", html);
+    }
+    if (guts.getMenuBarField() != null) {
+      writer.genPropertySet(fieldName, "subMenu", guts.getMenuBarField());
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/NullInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/NullInterpreter.java
new file mode 100644
index 0000000..e302db2
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/NullInterpreter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * An no-op interpreter.
+ * @param <T> The type of null to return
+ */
+public final class NullInterpreter<T> implements XMLElement.Interpreter<T> {
+  public T interpretElement(XMLElement elem) {
+    return null;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/RadioButtonParser.java b/user/src/com/google/gwt/uibinder/parsers/RadioButtonParser.java
new file mode 100644
index 0000000..0c8d6d6
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/RadioButtonParser.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.user.client.ui.RadioButton;
+
+/**
+ * Parses {@link RadioButton} widgets.
+ */
+public class RadioButtonParser implements ElementParser {
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    if (!elem.hasAttribute("name")) {
+      writer.die("RadioButton requires a 'name' attribute: " + elem);
+    }
+
+    if (type.equals(writer.getOracle().findType(RadioButton.class.getName()))) {
+      writer.setFieldInitializerAsConstructor(fieldName, type,
+          '"' + elem.consumeAttribute("name") + '"');
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/StackPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/StackPanelParser.java
new file mode 100644
index 0000000..3a406b9
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/StackPanelParser.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.StackPanel} widgets.
+ */
+public class StackPanelParser implements ElementParser {
+
+  private static final String ATTRIBUTE_TEXT = "StackPanel-text";
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    // Parse children.
+    for (XMLElement child : elem.consumeChildElements()) {
+
+      // Stack panel label comes from the StackPanel-text attribute of the child
+      String stackItemLabel = null;
+      String variableAttributeName = elem.getPrefix() + ":" + ATTRIBUTE_TEXT;
+      if (child.hasAttribute(variableAttributeName)) {
+        stackItemLabel = child.consumeAttribute(variableAttributeName);
+      }
+
+      String childFieldName = writer.parseWidget(child);
+      if (stackItemLabel == null) {
+        writer.addStatement("%1$s.add(%2$s);", fieldName, childFieldName);
+      } else {
+        writer.addStatement("%1$s.add(%2$s, \"%3$s\");", fieldName, childFieldName, stackItemLabel);
+      }
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java b/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java
new file mode 100644
index 0000000..e289991
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/StringAttributeParser.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+/**
+ * Parses a string attribute.
+ */
+public class StringAttributeParser implements AttributeParser {
+
+  public String parse(String value, UiBinderWriter writer) {
+    return "\"" + UiBinderWriter.escapeTextForJavaStringLiteral(value) + "\"";
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/TabPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/TabPanelParser.java
new file mode 100644
index 0000000..6965740
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/TabPanelParser.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * Parses {@link com.google.gwt.user.client.ui.TabPanel} widgets.
+ */
+public class TabPanelParser implements ElementParser {
+
+  private static final String TAG_TAB = "Tab";
+  private static final String TAG_TABHTML = "TabHTML";
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    // Parse children.
+    for (XMLElement child : elem.consumeChildElements()) {
+      // TabPanel can only contain Tab elements.
+      String ns = child.getNamespaceUri();
+      String tagName = child.getLocalName();
+
+      if (!ns.equals(elem.getNamespaceUri())) {
+        writer.die("Invalid TabPanel child namespace: " + ns);
+      }
+      if (!tagName.equals(TAG_TAB)) {
+        writer.die("Invalid TabPanel child element: " + tagName);
+      }
+
+      // Get the caption, if any.
+      String tabCaption = "";
+      if (child.hasAttribute("text")) {
+        tabCaption = child.consumeAttribute("text");
+      }
+
+      // Get the single required child widget.
+      String tabHTML = null;
+      String childFieldName = null;
+      for (XMLElement tabChild : child.consumeChildElements()) {
+        if (tabChild.getLocalName().equals(TAG_TABHTML)) {
+          HtmlInterpreter interpreter =
+            HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
+          tabHTML = tabChild.consumeInnerHtml(interpreter);
+        } else {
+          if (childFieldName != null) {
+            writer.die("gwt:Tab may only have a single child widget");
+          }
+          childFieldName = writer.parseWidget(tabChild);
+        }
+      }
+
+      if (tabHTML != null) {
+        writer.addStatement("%1$s.add(%2$s, \"%3$s\", true);", fieldName,
+            childFieldName, tabHTML);
+      } else {
+        writer.addStatement("%1$s.add(%2$s, \"%3$s\");", fieldName,
+            childFieldName, tabCaption);
+      }
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/TextInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/TextInterpreter.java
new file mode 100644
index 0000000..342dfd7
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/TextInterpreter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.messages.PlaceholderInterpreter;
+
+/**
+ * The interpreter of choice for calls to {@link XMLElement#consumeInnerText}.
+ * It handles &lt;m:msg/&gt; elements, replacing them with references to Messages
+ * interface calls.
+ * <P>
+ * Calls to {@link XMLElement#consumeInnerHtml} should instead use
+ * {@link HtmlInterpreter}
+ */
+public class TextInterpreter implements XMLElement.Interpreter<String> {
+
+  private final UiBinderWriter writer;
+
+  public TextInterpreter(UiBinderWriter writer) {
+    this.writer = writer;
+  }
+
+  public String interpretElement(XMLElement elem)
+      throws UnableToCompleteException {
+    MessagesWriter messages = writer.getMessages();
+    if (messages.isMessage(elem)) {
+      String messageInvocation = consumeAsTextMessage(elem, messages);
+      return writer.tokenForExpression(messageInvocation);
+    }
+
+    return null;
+  }
+
+  private String consumeAsTextMessage(XMLElement elem, MessagesWriter messages)
+      throws UnableToCompleteException {
+    if (!elem.hasChildNodes()) {
+      writer.die("Empty message: " + elem);
+    }
+
+    MessageWriter message = messages.newMessage(elem);
+    PlaceholderInterpreter interpreter =
+        new TextPlaceholderInterpreter(writer, message);
+    message.setDefaultMessage(elem.consumeInnerText(interpreter));
+    return messages.declareMessage(message);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/TextPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/TextPlaceholderInterpreter.java
new file mode 100644
index 0000000..fadb67b
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/TextPlaceholderInterpreter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.PlaceholderInterpreter;
+
+/**
+ * Interprets the interior of text message. Any ph elements in the message will
+ * be consumed by a call to their {@link XMLElement#consumeInnerText} method.
+ * A ph inside a text message may contain no other elements.
+ */
+public final class TextPlaceholderInterpreter extends PlaceholderInterpreter {
+  public TextPlaceholderInterpreter(UiBinderWriter writer,
+      MessageWriter message) {
+    super(writer, message);
+  }
+
+  @Override protected String consumePlaceholderInnards(XMLElement elem)
+      throws UnableToCompleteException {
+    return elem.consumeInnerText(new NullInterpreter<String>());
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/UIObjectParser.java b/user/src/com/google/gwt/uibinder/parsers/UIObjectParser.java
new file mode 100644
index 0000000..4ce0858
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/UIObjectParser.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * Parser of all UIObject types. Basically bats cleanup if more
+ * specialized parsers have left things around, or haven't been run.
+ */
+public class UIObjectParser implements ElementParser {
+
+  private static final String ATTRIBUTE_DEBUG_ID = "debugId";
+  private static final String ATTRIBUTE_ADD_STYLE_NAMES = "addStyleNames";
+  private static final String ATTRIBUTE_ADD_STYLE_DEPENDENT_NAMES = "addStyleDependentNames";
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+
+    addCustomAttribute(elem, fieldName, writer, "ensureDebugId", ATTRIBUTE_DEBUG_ID, false);
+    addCustomAttribute(elem, fieldName, writer, "addStyleName", ATTRIBUTE_ADD_STYLE_NAMES, true);
+    addCustomAttribute(elem, fieldName, writer, "addStyleDependentName",
+        ATTRIBUTE_ADD_STYLE_DEPENDENT_NAMES, true);
+
+    HtmlInterpreter interpreter =
+      HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
+
+    String html = elem.consumeInnerHtml(interpreter);
+    if (html.trim().length() > 0) {
+      writer.setFieldInitializer(fieldName,
+          String.format("new DomHolder(UiBinderUtil.fromHtml(\"%s\"))", html));
+    }
+  }
+
+  /**
+   * @param elem The actual XmlElement which the parser is inspecting
+   * @param fieldName The object/type to which a custom attribute will be added
+   * @param writer The TemplateWriter for generating the code
+   * @param targetSetterMethod The real method/setter used by UIObject to set
+   *        the attribute
+   * @param attribute The attribute as it will appear in the binder template
+   * @param isMultiValue Specifies if the attributes value can have multiple
+   *        values that are comma separated
+   * @throws UnableToCompleteException
+   */
+  private void addCustomAttribute(XMLElement elem, String fieldName, UiBinderWriter writer,
+      String targetSetterMethod, String attribute, boolean isMultiValue)
+      throws UnableToCompleteException {
+    String attributeValue = null;
+    if (elem.hasAttribute(attribute)) {
+      attributeValue = elem.consumeAttribute(attribute);
+
+      if ("".equals(attributeValue)) {
+        writer.die("In %s, value for attribute %s cannot be empty", elem, attribute);
+      }
+
+      // Check if the value is comma separated
+      if (isMultiValue && attributeValue.contains(",")) {
+        String[] values = attributeValue.split(",");
+        for (String value : values) {
+          value = value.trim();
+          if ("".equals(value)) {
+            writer.die("In %s, value for attribute %s cannot be empty", elem, attribute);
+          }
+          writer.addStatement("%1$s.%2$s(\"%3$s\");", fieldName, targetSetterMethod, value);
+        }
+      } else {
+        writer.addStatement("%1$s.%2$s(\"%3$s\");", fieldName, targetSetterMethod, attributeValue);
+      }
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/VerticalAlignmentConstantParser.java b/user/src/com/google/gwt/uibinder/parsers/VerticalAlignmentConstantParser.java
new file mode 100644
index 0000000..70ba1ca
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/VerticalAlignmentConstantParser.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 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.parsers;
+
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+
+import java.util.HashMap;
+
+/**
+ * Parses a
+ * {@link com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant}.
+ */
+public class VerticalAlignmentConstantParser implements AttributeParser {
+
+  private static final HashMap<String, String> values =
+    new HashMap<String, String>();
+
+  static {
+    values.put("ALIGN_TOP",
+      "com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_TOP");
+    values.put("ALIGN_MIDDLE",
+      "com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_MIDDLE");
+    values.put("ALIGN_BOTTOM",
+      "com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_BOTTOM");
+  }
+
+  public String parse(String value, UiBinderWriter writer) {
+    String translated = values.get(value);
+    if (translated == null) {
+      throw new RuntimeException("Invalid value: vorizontalAlignment='"
+        + value + "'");
+    }
+    return translated;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/WidgetInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/WidgetInterpreter.java
new file mode 100644
index 0000000..78f7a22
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/WidgetInterpreter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2008 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.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.XMLElement.PostProcessingInterpreter;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Used by {@link HTMLPanelParser} to interpret elements that call for widget
+ * instances. Declares the appropriate widget, and replaces them in the markup
+ * with a &lt;span&gt;.
+ */
+class WidgetInterpreter implements PostProcessingInterpreter<String> {
+
+  private static final Map<String, String> LEGAL_CHILD_ELEMENTS;
+  private static final String DEFAULT_CHILD_ELEMENT = "span";
+  static {
+    // Other cases?
+    HashMap<String, String> map = new HashMap<String, String>();
+    map.put("table", "tbody");
+    map.put("thead", "tr");
+    map.put("tbody", "tr");
+    map.put("tfoot", "tr");
+    map.put("ul", "li");
+    map.put("ol", "li");
+    map.put("dl", "dt");
+    LEGAL_CHILD_ELEMENTS = Collections.unmodifiableMap(map);
+  }
+
+  private static String getLegalPlaceholderTag(XMLElement elem) {
+    XMLElement parent = elem.getParent();
+    String tag = null;
+    if (parent != null) {
+      tag = LEGAL_CHILD_ELEMENTS.get(parent.getLocalName());
+    }
+    if (tag == null) {
+      tag = DEFAULT_CHILD_ELEMENT;
+    }
+    return tag;
+  }
+
+  /**
+   * A map of widget idHolder field names to the element it references.
+   */
+  private final Map<String, XMLElement> idToWidgetElement =
+    new HashMap<String, XMLElement>();
+  private final String fieldName;
+
+  private final UiBinderWriter uiWriter;
+
+  public WidgetInterpreter(String fieldName, UiBinderWriter writer) {
+    this.fieldName = fieldName;
+    this.uiWriter = writer;
+  }
+
+  public String interpretElement(XMLElement elem) throws UnableToCompleteException {
+    if (uiWriter.isWidget(elem)) {
+      // Allocate a local variable to hold the dom id for this widget. Note
+      // 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();
+      idToWidgetElement.put(idHolder, elem);
+
+      // Create an element to hold the widget.
+      String tag = getLegalPlaceholderTag(elem);
+      return "<" + tag + " id='\" + " + idHolder + " + \"'></" + tag + ">";
+    }
+    return null;
+  }
+
+  /**
+   * Called by {@link XMLElement#consumeInnerHtml} after all elements have
+   * been handed to {@link #interpretElement}. Parses each widget element
+   * that was seen, and generates a
+   * {@link com.google.gwt.user.client.ui.HTMLPanel#addAndReplaceElement} for
+   * each.
+   */
+  public String postProcess(String consumedHtml) throws UnableToCompleteException {
+    /*
+     * Generate an addAndReplaceElement(widget, idHolder) for each widget found
+     * while parsing the element's inner HTML.
+     */
+    for (String idHolder : idToWidgetElement.keySet()) {
+      XMLElement childElem = idToWidgetElement.get(idHolder);
+      String childField = uiWriter.parseWidget(childElem);
+      uiWriter.addInitStatement("%1$s.addAndReplaceElement(%2$s, %3$s);", fieldName,
+          childField, idHolder);
+    }
+    return consumedHtml;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/WidgetPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/parsers/WidgetPlaceholderInterpreter.java
new file mode 100644
index 0000000..b337b4a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/WidgetPlaceholderInterpreter.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2008 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.parsers;
+
+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.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.messages.MessageWriter;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.user.client.ui.HasHTML;
+import com.google.gwt.user.client.ui.HasText;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Used by {@link HTMLPanelParser}. Refines {@link HtmlPlaceholderInterpreter}
+ * to allow widgets to appear inside msg elements in an HTMLPanel.
+ * <p>
+ * HasText and HasHTML get special treatment, where their innerText or innerHTML
+ * become part of the {@literal @}Default value of the message being generated.
+ * E.g., this markup in an HTMLPanel:
+ *
+ * <pre>
+ * <m:msg>Hello &lt;gwt:HyperLink&gt;click here&lt;/gwt:HyperLink&gt; thank you.</m:msg></pre>
+ *
+ * becomes a message like this: <pre>
+ * {@literal @}Default("Hello {0}click here{1} thank you.")
+ * String getMessage1(
+ *   {@literal @}Example("&lt;span&gt;") String widget1Begin,
+ *   {@literal @}Example("&lt;/span&gt;") String widget1End
+ * );</pre>
+ *
+ * <p>
+ * The contents of other widget types are opaque to the message, and are covered
+ * by a single placeholder. One implication of this is that the content of an
+ * HTMLPanel inside a msg in another HTMLPanel must always be in a separate
+ * message.
+ */
+class WidgetPlaceholderInterpreter extends HtmlPlaceholderInterpreter {
+  // Could break this up into three further classes, for HasText, HasHTML
+  // and Other, but that seems more trouble than it's worth.
+
+  private int serial = 0;
+  private final String ancestorExpression;
+  private final String fieldName;
+  private final Map<String, XMLElement> idToWidgetElement =
+    new HashMap<String, XMLElement>();
+  private final Set<String> idIsHasHTML = new HashSet<String>();
+  private final Set<String> idIsHasText = new HashSet<String>();
+
+  WidgetPlaceholderInterpreter(String fieldName, UiBinderWriter writer,
+      MessageWriter message, String ancestorExpression) {
+    super(writer, message, ancestorExpression);
+    this.fieldName = fieldName;
+    this.ancestorExpression = ancestorExpression;
+ }
+
+  @Override
+  public String interpretElement(XMLElement elem)
+      throws UnableToCompleteException {
+
+    if (!uiWriter.isWidget(elem)) {
+      return super.interpretElement(elem);
+    }
+
+    JClassType type = uiWriter.findWidgetOrElementType(elem);
+    TypeOracle oracle = uiWriter.getOracle();
+
+    MessagesWriter mw = uiWriter.getMessages();
+    String name = mw.consumeMessageAttribute("ph", elem);
+    if ("".equals(name)) {
+      name = "widget" + (++serial);
+    }
+
+    String idHolder = uiWriter.declareDomIdHolder();
+    idToWidgetElement.put(idHolder, elem);
+
+    if (oracle.findType(HasHTML.class.getName()).isAssignableFrom(type)) {
+      return handleHasHTMLPlaceholder(elem, name, idHolder);
+    }
+
+    if (oracle.findType(HasText.class.getName()).isAssignableFrom(type)) {
+      return handleHasTextPlaceholder(elem, name, idHolder);
+   }
+
+    return handleOpaqueWidgetPlaceholder(elem, name, idHolder);
+  }
+
+  /**
+   * Called by {@link XMLElement#consumeInnerHtml} after all elements
+   * have been handed to {@link #interpretElement}.
+   */
+  @Override
+  public String postProcess(String consumed) throws UnableToCompleteException {
+    for (String idHolder : idToWidgetElement.keySet()) {
+      XMLElement childElem = idToWidgetElement.get(idHolder);
+      String childField = uiWriter.parseWidget(childElem);
+
+      genSetWidgetTextCall(idHolder, childField);
+      uiWriter.addInitStatement("%1$s.addAndReplaceElement(%2$s, %3$s);",
+          fieldName, childField, idHolder);
+    }
+    return super.postProcess(consumed);
+  }
+
+  private String genCloseTag(String name) {
+    String closePlaceholder =
+        nextPlaceholder(name + "End", "</span>", "</span>");
+    return closePlaceholder;
+  }
+
+  private String genOpenTag(String name, String idHolder) {
+    String openTag = String.format("<span id='\" + %s + \"'>", idHolder);
+    String openPlaceholder =
+        nextPlaceholder(name + "Begin", "<span>", openTag);
+    return openPlaceholder;
+  }
+
+  private void genSetWidgetTextCall(String idHolder, String childField) {
+    if (idIsHasText.contains(idHolder)) {
+      uiWriter.addInitStatement(
+          "%s.setText(%s.getElementById(%s).getInnerText());", childField,
+          fieldName, idHolder);
+    }
+    if (idIsHasHTML.contains(idHolder)) {
+      uiWriter.addInitStatement(
+          "%s.setHTML(%s.getElementById(%s).getInnerHTML());", childField,
+          fieldName, idHolder);
+    }
+  }
+
+  private String handleHasHTMLPlaceholder(XMLElement elem, String name,
+      String idHolder) throws UnableToCompleteException {
+    idIsHasHTML.add(idHolder);
+    String openPlaceholder = genOpenTag(name, idHolder);
+
+    String body =
+        elem.consumeInnerHtml(new HtmlPlaceholderInterpreter(uiWriter,
+            message, ancestorExpression));
+    String bodyToken = tokenator.nextToken(body);
+
+    String closePlaceholder = genCloseTag(name);
+    return openPlaceholder + bodyToken + closePlaceholder;
+  }
+
+  private String handleHasTextPlaceholder(XMLElement elem, String name,
+      String idHolder) throws UnableToCompleteException {
+    idIsHasText.add(idHolder);
+    String openPlaceholder = genOpenTag(name, idHolder);
+
+    String body =
+        elem.consumeInnerText(new TextPlaceholderInterpreter(uiWriter,
+            message));
+    String bodyToken = tokenator.nextToken(body);
+
+    String closePlaceholder = genCloseTag(name);
+    return openPlaceholder + bodyToken + closePlaceholder;
+  }
+
+  private String handleOpaqueWidgetPlaceholder(XMLElement elem, String name,
+      String idHolder) throws UnableToCompleteException {
+    String tag = String.format("<span id='\" + %s + \"'></span>", idHolder);
+    String placeholder = nextPlaceholder(name, "<span></span>", tag);
+    return placeholder;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/ChildWalker.java b/user/src/com/google/gwt/uibinder/rebind/ChildWalker.java
new file mode 100644
index 0000000..fc6ed3c
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/ChildWalker.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2008 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 org.w3c.dom.CDATASection;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+/**
+ * Traverses the children of a {@link Node}.
+ */
+class ChildWalker {
+
+  /**
+   * Take a {@link NodeVisitor} and show it each child of the given {@link Node}
+   * that is of a type relevant to our templates.
+   * <p>
+   * Note that this is not a recursive call, though the visitor itself may
+   * choose to recurse
+   *
+   * @throws UnableToCompleteException
+   */
+  void accept(Node n, NodeVisitor v) throws UnableToCompleteException {
+
+    NodeList childNodes = n.getChildNodes();
+    for (int i = 0; i < childNodes.getLength(); ++i) {
+      Node child = childNodes.item(i);
+
+      switch (child.getNodeType()) {
+        case Node.ELEMENT_NODE:
+          v.visitElement((Element) child);
+          break;
+
+        case Node.TEXT_NODE:
+          v.visitText((Text) child);
+          break;
+
+        case Node.COMMENT_NODE:
+          // Ditch comment nodes.
+          break;
+
+        case Node.CDATA_SECTION_NODE:
+          v.visitCData((CDATASection) child);
+          break;
+
+        case Node.ENTITY_NODE:
+        case Node.ENTITY_REFERENCE_NODE:
+        case Node.ATTRIBUTE_NODE:
+        case Node.DOCUMENT_NODE:
+        case Node.DOCUMENT_FRAGMENT_NODE:
+        case Node.NOTATION_NODE:
+        case Node.PROCESSING_INSTRUCTION_NODE:
+        default: {
+          // None of these are expected node types.
+          throw new RuntimeException("Unexpected XML node");
+        }
+      }
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldManager.java b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
new file mode 100644
index 0000000..cdeb811
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldManager.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2009 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 java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+
+/**
+ * This class handles all {@link FieldWriter} instances created for the
+ * current template.
+ */
+class FieldManager {
+
+  private static final String NO_DEFAULT_CTOR_ERROR =
+      "%1$s has no default (zero args) constructor. To fix this, you can define"
+      + " a @UiFactory method on %2$s, or annotate a constructor of %3$s with"
+      + " @UiConstructor.";
+
+  private static final String DUPLICATED_FIELD_ERROR =
+      "Duplicate declaration of field %1$s.";
+
+  /**
+   * Map of field name to FieldWriter. Note its a LinkedHashMap--we want to
+   * write these out in the order they're declared.
+   */
+  private final LinkedHashMap<String, FieldWriter> fieldsMap =
+      new LinkedHashMap<String, FieldWriter>();
+
+  /**
+   * A stack of the fields.
+   */
+  private final LinkedList<FieldWriter> parsedFieldStack =
+      new LinkedList<FieldWriter>();
+
+  private TypeOracle oracle;
+  private TreeLogger logger;
+
+  /**
+   * Basic constructor just injects an oracle instance.
+   */
+  public FieldManager(TypeOracle oracle, TreeLogger logger) {
+    this.oracle = oracle;
+    this.logger = logger;
+  }
+
+  /**
+   * Post an error message and halt processing. This method always throws an
+   * {@link UnableToCompleteException}
+   */
+  public void die(String message, Object... params)
+      throws UnableToCompleteException {
+    logger.log(TreeLogger.ERROR, String.format(message, params));
+    throw new UnableToCompleteException();
+  }
+
+  /**
+   * @param fieldName the name of the {@link FieldWriter} to find
+   * @return the {@link FieldWriter} instance indexed by fieldName or
+   *         <b>null</b> in case fieldName is not found
+   */
+  public FieldWriter lookup(String fieldName) {
+    return fieldsMap.get(fieldName);
+  }
+
+  /**
+   * Remove the field at the top of the {@link #parsedFieldStack}.
+   */
+  public void pop() {
+    parsedFieldStack.removeFirst();
+  }
+
+  /**
+   * @param fieldWriter the field to push on the top of the
+   *        {@link #parsedFieldStack}
+   */
+  public void push(FieldWriter fieldWriter) {
+    parsedFieldStack.addFirst(fieldWriter);
+  }
+
+  /**
+   * This is where {@link FieldWriter} instances come from. When making a field
+   * we peek at the {@link #parsedFieldStack} to make sure that the field that
+   * holds the widget currently being parsed will depended upon the field being
+   * declared. This ensures, for example, that dom id fields (see
+   * {@link #declareDomIdHolder()}) used by an HTMLPanel will be declared
+   * before it is.
+   *
+   * @param typeName the full qualified name for the class associated with the
+   *        field
+   * @param fieldName the name of the field
+   * @return a new {@link FieldWriter} instance, <b>null</b> in case fieldName
+   *         is already indexed
+   */
+  public FieldWriter registerField(String typeName, String fieldName)
+      throws UnableToCompleteException {
+    JClassType fieldType = oracle.findType(typeName);
+    if (fieldsMap.containsKey(fieldName)) {
+      die(DUPLICATED_FIELD_ERROR, fieldName);
+    }
+
+    FieldWriter field = new FieldWriter(fieldType, fieldName);
+    fieldsMap.put(fieldName, field);
+
+    if (parsedFieldStack.size() > 0) {
+      parsedFieldStack.getFirst().needs(field);
+    }
+
+    return field;
+  }
+
+  /**
+   * Writes all stored gwt fields.
+   *
+   * @param writer the writer to output
+   * @param ownerTypeName the name of the class being processed
+   */
+  public void writeGwtFieldsDeclaration(IndentedWriter writer,
+      String ownerTypeName) throws UnableToCompleteException {
+    Collection<FieldWriter> fields = fieldsMap.values();
+    for (FieldWriter field : fields) {
+      if (!field.write(writer)) {
+        die(NO_DEFAULT_CTOR_ERROR,
+          field.getType().getQualifiedSourceName(),
+          ownerTypeName,
+          field.getType().getName());
+      }
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
new file mode 100644
index 0000000..28172e9
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/FieldWriter.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2009 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.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Models a field to be written in the generated binder code. Note that this is
+ * not necessarily a field that the user has declared. It's basically any
+ * instance variable the generator will need.
+ * <p>
+ * A field can have a custom initialization statement, set via
+ * {@link #setInitializer}. Without one it will be initialized via a
+ * {@link GWT#create} call. (In the rare case that you need a field not to be
+ * initialized, initialize it to "null".)
+ * <p>
+ * Dependencies can be declared between fields via {@link #needs}, to ensure
+ * that one can be initialized via reference to another. Circular references are
+ * not supported, nor detected.
+ */
+public class FieldWriter {
+  private final JClassType type;
+  private final String name;
+  private final Set<FieldWriter> needs = new HashSet<FieldWriter>();
+
+  private String initializer;
+  private boolean written;
+
+  /**
+   * Package protected, only TemplateWriter is allowed to instantiate
+   * FieldWriters.
+   * <p>
+   * Public for testing only
+   */
+  FieldWriter(JClassType type, String name) {
+    this.type = type;
+    this.name = name;
+  }
+
+  /**
+   * @return the fully qualified name of this field's type
+   */
+  public String getFullTypeName() {
+    return type.getPackage().getName() + "." + type.getName();
+  }
+
+  /**
+   * @return the type of this field
+   */
+  public JClassType getType() {
+    return type;
+  }
+
+  /**
+   * Declares that the receiver depends upon the give field.
+   */
+  public void needs(FieldWriter f) {
+    needs.add(f);
+  }
+
+  /**
+   * Used to provide an initializer string to use instead of a
+   * {@link com.google.gwt.core.client.GWT#create()} call.
+   * Note that this is an RHS expression. Don't include the leading '=', and
+   * don't end it with ';'.
+   */
+  public void setInitializer(String initializer) {
+    if (this.initializer != null) {
+      throw new IllegalStateException(String.format(
+          "Second attempt to set initializer for field \"%s\", "
+              + "from \"%s\" to \"%s\"", name, this.initializer, initializer));
+    }
+    this.initializer = initializer;
+  }
+
+  /**
+   * Write the field delcaration.
+   *
+   * @return false if unable to write for lack of a default constructor
+   */
+  // TODO(rjrjr) This return code thing is silly. We should
+  // just be calling {@link TemplateWriter#die}, but that's made complicated
+  // by an unfortunate override in UiBinderWriter. Fix this when that
+  // subclassing goes away.
+  public boolean write(IndentedWriter w) {
+    if (written) {
+      return true;
+    }
+
+    for (FieldWriter f : needs) {
+      // TODO(rdamazio, rjrjr) This is simplistic, and will fail when
+      // we support more interesting contexts (e.g. the same need being used
+      // inside two different
+      // LazyPanels)
+      f.write(w);
+    }
+
+    if (initializer == null) {
+      if (type.findConstructor(new JType[0]) == null) {
+        return false;
+      }
+      initializer =
+          String.format("(%1$s) GWT.create(%1$s.class)", getFullTypeName());
+    }
+
+    w.write("%s %s = %s;", getFullTypeName(), name, initializer);
+
+    this.written = true;
+    return true;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java b/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
new file mode 100644
index 0000000..1f7d7a4
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2008 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.uibinder.rebind.XMLElement.Interpreter;
+
+import org.w3c.dom.Element;
+
+class GetInnerHtmlVisitor extends GetInnerTextVisitor {
+
+  /**
+   * Recursively gathers an HTML representation of the children of the given
+   * Elem, and stuffs it into the given StringBuffer. Applies the interpreter to
+   * each descendant, and uses the writer to report errors.
+   */
+  public static void getEscapedInnerHtml(Element elem, StringBuffer buffer,
+      Interpreter<String> interpreter, UiBinderWriter writer)
+      throws UnableToCompleteException {
+    new ChildWalker().accept(elem, new GetInnerHtmlVisitor(buffer, interpreter,
+        writer));
+  }
+
+  private GetInnerHtmlVisitor(StringBuffer buffer,
+      Interpreter<String> interpreter, UiBinderWriter writer) {
+    super(buffer, interpreter, writer);
+  }
+
+  @Override
+  public void visitElement(Element elem) throws UnableToCompleteException {
+    XMLElement xmlElement = new XMLElement(elem, writer);
+    String replacement = interpreter.interpretElement(xmlElement);
+    if (replacement != null) {
+      buffer.append(replacement);
+      return;
+    }
+
+    // TODO(jgw): Ditch the closing tag when there are no children.
+    buffer.append(xmlElement.consumeOpeningTag());
+    getEscapedInnerHtml(elem, buffer, interpreter, writer);
+    buffer.append(xmlElement.getClosingTag());
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/GetInnerTextVisitor.java b/user/src/com/google/gwt/uibinder/rebind/GetInnerTextVisitor.java
new file mode 100644
index 0000000..4cf561a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/GetInnerTextVisitor.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2008 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.uibinder.rebind.XMLElement.Interpreter;
+
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+
+class GetInnerTextVisitor implements NodeVisitor {
+
+  /**
+   * Gathers a text representation of the children of the given Elem, and stuffs
+   * it into the given StringBuffer. Applies the interpreter to each descendant,
+   * and uses the writer to report errors.
+   */
+  public static void getEscapedInnerText(Element elem, StringBuffer buffer,
+      Interpreter<String> interpreter, UiBinderWriter writer)
+      throws UnableToCompleteException {
+    new ChildWalker().accept(elem, new GetInnerTextVisitor(buffer, interpreter,
+        writer));
+  }
+
+  protected final StringBuffer buffer;
+  protected final Interpreter<String> interpreter;
+  protected final UiBinderWriter writer;
+
+  protected GetInnerTextVisitor(StringBuffer buffer,
+      Interpreter<String> interpreter, UiBinderWriter writer) {
+    this.buffer = buffer;
+    this.interpreter = interpreter;
+    this.writer = writer;
+  }
+
+  public void visitCData(CDATASection d) {
+    // TODO(jgw): write this back just as it came in.
+  }
+
+  public void visitElement(Element e) throws UnableToCompleteException {
+    String replacement =
+        interpreter.interpretElement(new XMLElement(e, writer));
+
+    if (replacement != null) {
+      buffer.append(replacement);
+    }
+  }
+
+  public void visitText(Text t) {
+    String escaped =
+        UiBinderWriter.escapeText(t.getTextContent(), preserveWhiteSpace(t));
+    buffer.append(escaped);
+  }
+
+  private boolean preserveWhiteSpace(Text t) {
+    Element parent = Node.ELEMENT_NODE == t.getParentNode().getNodeType()
+      ? (Element) t.getParentNode() : null;
+
+    boolean preserveWhitespace =
+        parent != null && "pre".equals(parent.getTagName());
+    return preserveWhitespace;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/GwtResourceEntityResolver.java b/user/src/com/google/gwt/uibinder/rebind/GwtResourceEntityResolver.java
new file mode 100644
index 0000000..c93dbd3
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/GwtResourceEntityResolver.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2009 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 org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Does special handling of external entities encountered by sax xml parser,
+ * e.g. the uri in
+ * 
+ * <pre>
+ * &lt;!DOCTYPE gwt:UiBinder 
+  SYSTEM "http://google-web-toolkit.googlecode.com/svn/resources/xhtml.ent"></pre>
+ * <p>
+ * Specifically, if the requested uri starts with
+ * <code>http://google-web-toolkit.googlecode.com/svn/resources</code>, provide
+ * the contents from a built in resource rather than allowing sax to make a
+ * network request.
+ */
+class GwtResourceEntityResolver implements EntityResolver {
+  interface ResourceLoader {
+    InputStream fetch(String name);
+  }
+
+  private static final String EXTERNAL_ENTITY_PREFIX = "http://google-web-toolkit.googlecode.com/svn/resources";
+
+  private static final String RESOURCES = "com/google/gwt/uibinder/resources";
+
+  private final ResourceLoader resourceLoader;
+
+  public GwtResourceEntityResolver() {
+    this(new ResourceLoader() {
+      public InputStream fetch(String name) {
+        return UiBinderGenerator.class.getClassLoader().getResourceAsStream(
+            name);
+      }
+    });
+  }
+
+  /**
+   * For testing.
+   */
+  GwtResourceEntityResolver(ResourceLoader resourceLoader) {
+    this.resourceLoader = resourceLoader;
+  }
+
+  public InputSource resolveEntity(String publicId, String systemId)
+      throws SAXException, IOException {
+    if (!systemId.startsWith(EXTERNAL_ENTITY_PREFIX)) {
+      // Not our problem, return null and sax will find it
+      return null;
+    }
+
+    String name = RESOURCES
+        + systemId.substring(EXTERNAL_ENTITY_PREFIX.length());
+    InputStream resourceStream = resourceLoader.fetch(name);
+
+    if (resourceStream == null) {
+      /*
+       * Maybe, just maybe they're asking for something that has been added to
+       * svn but hasn't reached their local gwt install yet. Return null to
+       * allow sax to try to fetch it.
+       */
+      return null;
+    }
+
+    InputSource inputSource = new InputSource(resourceStream);
+    inputSource.setPublicId(publicId);
+    inputSource.setSystemId(systemId);
+    return inputSource;
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java b/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
new file mode 100644
index 0000000..5237375
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/HandlerEvaluator.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2009 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.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.uibinder.rebind.model.OwnerClass;
+
+/**
+ * This class implements an easy way to bind widget event handlers to methods
+ * annotated with {@link com.google.gwt.uibinder.client.UiHandler} so that the
+ * user doesn't need to worry about writing code to implement these bindings.
+ *
+ * <p>For instance, the class defined below:
+ * <pre>
+ *   public class MyClass {
+ *     @UiField Label label;
+ *
+ *     @UiBinder({"label", "link"})
+ *     public void doClick(ClickEvent e) {
+ *       // do something
+ *     }
+ *   }
+ * </pre>
+ *
+ * will generate a piece of code like:
+ * <pre>
+ *    ClickHandler handler0 = new ClickHandler() {
+ *      @Override
+ *      public void onClick(ClickEvent event) {
+ *        owner.doClick(event);
+ *      }
+ *   });
+ *   label.addClickHandler(handler0);
+ *   link.addClickHandler(handler0);
+ * </pre>
+ *
+ * Notice that the <b>link</b> object doesn't need to be annotated with
+ * {@link com.google.gwt.uibinder.client.UiField} as long as it exists
+ * (annotated with ui:field) in the template.
+ */
+class HandlerEvaluator {
+
+  private static final String HANDLER_BASE_NAME = "handler";
+
+  private int varCounter = 0;
+
+  private final TreeLogger logger;
+
+  private final JClassType handlerRegistrationJClass;
+  private final JClassType eventHandlerJClass;
+  private final OwnerClass ownerClass;
+
+  /**
+   * The verbose testable constructor.
+   *
+   * @param ownerClass a descriptor of the UI owner class
+   * @param logger the logger for warnings and errors
+   * @param oracle the type oracle
+   */
+  HandlerEvaluator(OwnerClass ownerClass, TreeLogger logger, TypeOracle oracle) {
+    this.ownerClass = ownerClass;
+    this.logger = logger;
+
+    handlerRegistrationJClass = oracle.findType(HandlerRegistration.class.getName());
+    eventHandlerJClass = oracle.findType(EventHandler.class.getName());
+  }
+
+  /**
+   * Post an error message and throws an {@link UnableToCompleteException}.
+   *
+   * @param message the error message
+   * @param params the format params
+   */
+  public void die(String message, Object... params) throws UnableToCompleteException {
+    logger.log(TreeLogger.ERROR, String.format(message, params));
+    throw new UnableToCompleteException();
+  }
+
+  /**
+   * Runs the evaluator in the given class according to the valid fields
+   * extracted from the template (via attribute ui:field).
+   *
+   * @param writer the writer used to output the results
+   * @param fieldManager the field manager instance
+   * @param uiOwner the name of the class evaluated here that owns the template
+   */
+  public void run(IndentedWriter writer, FieldManager fieldManager,
+      String uiOwner) throws UnableToCompleteException {
+
+    // Iterate through all methods defined in the class.
+    for (JMethod method : ownerClass.getUiHandlers()) {
+      // Evaluate the method.
+      String boundMethod = method.getName();
+      if (method.isPrivate()) {
+        die("Method '%s' cannot be private.", boundMethod);
+      }
+
+      // Retrieves both event and handler types.
+      JParameter[] parameters = method.getParameters();
+      if (parameters.length != 1) {
+        die("Method '%s' must have a single event parameter defined.", boundMethod);
+      }
+      JClassType eventType = parameters[0].getType().isClass();
+
+      JClassType handlerType = getHandlerForEvent(eventType);
+      if (handlerType == null) {
+        die("Parameter '%s' is not an event (subclass of GwtEvent).", eventType.getName());
+      }
+
+      // Cool to add the handler in the output.
+      String handlerVarName = HANDLER_BASE_NAME + (++varCounter);
+      writeHandler(writer, uiOwner, handlerVarName, handlerType, eventType, boundMethod);
+
+      // Adds the handler created above.
+      UiHandler annotation = method.getAnnotation(UiHandler.class);
+      for (String objectName : annotation.value()) {
+        // Is the field object valid?
+        FieldWriter fieldWriter = fieldManager.lookup(objectName);
+        if (fieldWriter == null) {
+          die("Method '%s' can not be bound. You probably missed ui:field='%s' "
+              + "in the template.", boundMethod, objectName);
+        }
+
+        // Retrieves the "add handler" method in the object.
+        JMethod addHandlerMethodType = getAddHandlerMethodForObject(
+            fieldWriter.getType(), handlerType);
+        if (addHandlerMethodType == null) {
+          die("Field '%s' does not have an 'add%s' method associated.",
+              objectName, handlerType.getName());
+        }
+
+        // Cool to tie the handler into the object.
+        writeAddHandler(writer, handlerVarName,
+            addHandlerMethodType.getName(), objectName);
+      }
+    }
+  }
+
+  /**
+   * Post a warning message.
+   *
+   * @param message the error message
+   * @param params the format params
+   */
+  public void warn(String message, Object... params) {
+    logger.log(TreeLogger.WARN, String.format(message, params));
+  }
+
+  /**
+   * Writes a handler entry using the given writer.
+   *
+   * @param writer the writer used to output the results
+   * @param uiOwner the name of the class evaluated here that owns the template
+   * @param handlerVarName the name of the handler variable
+   * @param handlerType the handler we want to create
+   * @param eventType the event associated with the handler
+   * @param boundMethod the method bound in the handler
+   */
+  protected void writeHandler(IndentedWriter writer, String uiOwner, String handlerVarName,
+      JClassType handlerType, JClassType eventType, String boundMethod)
+      throws UnableToCompleteException {
+
+    // Retrieves the single method (usually 'onSomething') related to all
+    // handlers. Ex: onClick in ClickHandler, onBlur in BlurHandler ...
+    JMethod[] methods = handlerType.getMethods();
+    if (methods.length != 1) {
+      die("'%s' has more than one method defined.", handlerType.getName());
+    }
+
+    // Checks if the method has an Event as parameter. Ex: ClickEvent in
+    // onClick, BlurEvent in onBlur ...
+    JParameter[] parameters = methods[0].getParameters();
+    if (parameters.length != 1 || parameters[0].getType() != eventType) {
+      die("Method '%s' needs '%s' as parameter", methods[0].getName(), eventType.getName());
+    }
+
+    writer.newline();
+    writer.write("final %1$s %2$s = new %1$s() {",
+        handlerType.getParameterizedQualifiedSourceName(), handlerVarName);
+    writer.indent();
+    writer.write("public void %1$s(%2$s event) {",
+        methods[0].getName(), eventType.getParameterizedQualifiedSourceName());
+    writer.indent();
+    writer.write("%1$s.%2$s(event);", uiOwner, boundMethod);
+    writer.outdent();
+    writer.write("}");
+    writer.outdent();
+    writer.write("};");
+  }
+
+  /**
+   * Adds the created handler to the given object (field).
+   *
+   * @param writer the writer used to output the results
+   * @param handlerVarName the name of the handler variable
+   * @param addHandlerMethodName the "add handler" method name associated
+   *        with the object
+   * @param objectName the name of the object we want to tie the handler
+   */
+  void writeAddHandler(IndentedWriter writer, String handlerVarName,
+      String addHandlerMethodName, String objectName) {
+    writer.write("this.%1$s.%2$s(%3$s);", objectName,
+        addHandlerMethodName, handlerVarName);
+  }
+
+  /**
+   * Checks if a specific handler is valid for a given object and return the
+   * method that ties them. The object must override a method that returns
+   * {@link com.google.gwt.event.shared.HandlerRegistration} and receives a
+   * single input parameter of the same type of handlerType.
+   *
+   * <p>Output an error in case more than one method match the conditions
+   * described above.</p>
+   *
+   * <pre>
+   *   <b>Examples:</b>
+   *    - HandlerRegistration addClickHandler(ClickHandler handler)
+   *    - HandlerRegistration addMouseOverHandler(MouseOverHandler handler)
+   *    - HandlerRegistration addSubmitCompleteHandler(
+   *          FormPanel.SubmitCompleteHandler handler)
+   * </pre>
+   *
+   * @param objectType the object type we want to check
+   * @param handlerType the handler type we want to check in the object
+   *
+   * @return the method that adds handlerType into objectType, or <b>null</b>
+   *         if no method was found
+   */
+  private JMethod getAddHandlerMethodForObject(JClassType objectType,
+        JClassType handlerType) throws UnableToCompleteException {
+    JMethod handlerMethod = null;
+    for (JMethod method : objectType.getOverridableMethods()) {
+
+      // Condition 1: returns HandlerRegistration?
+      if (method.getReturnType() == handlerRegistrationJClass) {
+
+        // Condition 2: single parameter of the same type of handlerType?
+        JParameter[] parameters = method.getParameters();
+        if ((parameters.length == 1) && handlerType.equals(parameters[0].getType())) {
+
+          // Condition 3: does more than one method match the condition?
+          if (handlerMethod != null) {
+            die("This handler cannot be generated. Methods '%s' and '%s' are "
+                + "ambiguous. Which one to pick?", method, handlerMethod);
+          }
+
+          handlerMethod = method;
+        }
+      }
+    }
+    return handlerMethod;
+  }
+
+  /**
+   * Retrieves the handler associated with the event.
+   *
+   * @param eventType the given event
+   * @return the associated handler, <code>null</code> if not found
+   */
+  private JClassType getHandlerForEvent(JClassType eventType) {
+
+    // All handlers event must have an overrided method getAssociatedType().
+    // We take advantage of this information to get the associated handler.
+    // Ex:
+    //  com.google.gwt.event.dom.client.ClickEvent
+    //    ---> com.google.gwt.event.dom.client.ClickHandler
+    //
+    //  com.google.gwt.event.dom.client.BlurEvent
+    //    ---> com.google.gwt.event.dom.client.BlurHandler
+
+    if (eventType == null) {
+      return null;
+    }
+
+    JMethod method = eventType.findMethod("getAssociatedType", new JType[0]);
+    if (method == null) {
+      warn("Method 'getAssociatedType()' could not be found in the event '%s'.",
+          eventType.getName());
+      return null;
+    }
+
+    JType returnType = method.getReturnType();
+    if (returnType == null) {
+      warn("The method 'getAssociatedType()' in the event '%s' returns void.",
+          eventType.getName());
+      return null;
+    }
+
+    JParameterizedType isParameterized = returnType.isParameterized();
+    if (isParameterized == null) {
+      warn("The method 'getAssociatedType()' in '%s' does not return Type<? extends EventHandler>.",
+        eventType.getName());
+      return null;
+    }
+
+    JClassType[] argTypes = isParameterized.getTypeArgs();
+    if ((argTypes.length != 1) && !argTypes[0].isAssignableTo(eventHandlerJClass)) {
+      warn("The method 'getAssociatedType()' in '%s' does not return Type<? extends EventHandler>.",
+        eventType.getName());
+      return null;
+    }
+
+    return argTypes[0];
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/IndentedWriter.java b/user/src/com/google/gwt/uibinder/rebind/IndentedWriter.java
new file mode 100644
index 0000000..08ce64f
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/IndentedWriter.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2008 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 java.io.PrintWriter;
+
+/**
+ * Pleasant wrapper for PrintWriter, manages indentation levels.
+ * Name is a misnomer, as this doesn't implement Writer.
+ */
+public class IndentedWriter {
+  private final PrintWriter pw;
+
+  private int indent;
+
+  public IndentedWriter(PrintWriter pw) {
+    super();
+    this.pw = pw;
+  }
+
+  /**
+   * Indents the generated code.
+   */
+  public void indent() {
+    ++indent;
+  }
+
+  /**
+   * Outputs a new line.
+   */
+  public void newline() {
+    pw.println();
+  }
+
+  /**
+   * Un-indents the generated code.
+   */
+  public void outdent() {
+    if (indent == 0) {
+      throw new IllegalStateException("Tried to outdent below zero");
+    }
+    --indent;
+  }
+
+  /**
+   * Outputs the given string.
+   */
+  public void write(String format) {
+    printIndent();
+    pw.println(format);
+  }
+
+  /**
+   * Outputs the given string with replacements, using the Java message format.
+   */
+  public void write(String format, Object... args) {
+    printIndent();
+    pw.printf(format, args);
+    pw.println();
+  }
+
+  private void printIndent() {
+    for (int i = 0; i < indent; ++i) {
+      pw.print("  ");
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/NodeVisitor.java b/user/src/com/google/gwt/uibinder/rebind/NodeVisitor.java
new file mode 100644
index 0000000..6ee212a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/NodeVisitor.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 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 org.w3c.dom.CDATASection;
+import org.w3c.dom.Element;
+import org.w3c.dom.Text;
+
+/**
+ * Implements methods to interpret the {@link org.w3c.dom.Node} types
+ * we actually care about.
+ */
+interface NodeVisitor {
+  void visitCData(CDATASection d) throws UnableToCompleteException;
+
+  void visitElement(Element e) throws UnableToCompleteException;
+
+  void visitText(Text t) throws UnableToCompleteException;
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/Tokenator.java b/user/src/com/google/gwt/uibinder/rebind/Tokenator.java
new file mode 100644
index 0000000..60824bc
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/Tokenator.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2008 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 java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Methods to dispense unique text tokens to be stitched into text, and
+ * to help replace the tokens with arbitrary content.
+ */
+public class Tokenator {
+  /**
+   * Resolves a token to its literal value.
+   */
+  public interface Resolver {
+    String resolveToken(String token);
+  }
+
+  private static final String TOKEN = "--token--";
+  private static final String TOKEN_REGEXP = "\\-\\-token\\-\\-";
+  private static int curId = 0;
+
+  public static String detokenate(String betokened, Resolver resolver) {
+    StringBuilder detokenated = new StringBuilder();
+
+    int index = 0, nextToken = 0;
+    while ((nextToken = betokened.indexOf(TOKEN, index)) > -1) {
+      detokenated.append(betokened.substring(index, nextToken));
+
+      int endToken = betokened.indexOf(TOKEN, nextToken + TOKEN.length());
+      String token = betokened.substring(nextToken, endToken + TOKEN.length());
+      detokenated.append(resolver.resolveToken(token));
+
+      index = endToken + TOKEN.length();
+    }
+
+    detokenated.append(betokened.substring(index));
+    return detokenated.toString();
+  }
+
+  public static boolean hasToken(String s) {
+    return s.matches(".*" + TOKEN_REGEXP + "\\d+" + TOKEN_REGEXP + ".*");
+  }
+
+  private static String nextToken() {
+    return TOKEN + (curId++) + TOKEN;
+  }
+
+  private Map<String, String> tokenToResolved =
+    new HashMap<String, String>();
+
+  public String detokenate(String betokened) {
+    return detokenate(betokened, new Resolver() {
+      public String resolveToken(String token) {
+        return tokenToResolved.get(token);
+      }
+    });
+  }
+
+  public String nextToken(final String resolved) {
+    String token = nextToken();
+    tokenToResolved.put(token, resolved);
+    return token;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
new file mode 100644
index 0000000..6b1dd49
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderGenerator.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2008 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.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+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.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.uibinder.client.UiTemplate;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.Enumeration;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Generator for implementations of
+ * {@link com.google.gwt.uibinder.client.UiBinder}.
+ */
+public class UiBinderGenerator extends Generator {
+
+  private static final String TEMPLATE_SUFFIX = ".ui.xml";
+
+  private static void commit(TreeLogger logger, GeneratorContext genCtx,
+      String packageName, PrintWriter pw, UiBinderWriter uiWriter) {
+    uiWriter.write(pw);
+    genCtx.commit(logger, pw);
+
+    MessagesWriter messages = uiWriter.getMessages();
+    if (messages.hasMessages()) {
+      PrintWriter mpw = genCtx.tryCreate(logger, packageName,
+          messages.getMessagesClassName());
+      if (mpw == null) {
+        throw new RuntimeException("Tried to gen messages twice.");
+      }
+
+      messages.write(mpw);
+      genCtx.commit(logger, mpw);
+    }
+  }
+
+  @Override
+  public String generate(TreeLogger logger, GeneratorContext genCtx,
+      String fqInterfaceName) throws UnableToCompleteException {
+    TypeOracle oracle = genCtx.getTypeOracle();
+    JClassType interfaceType;
+    try {
+      interfaceType = oracle.getType(fqInterfaceName);
+    } catch (NotFoundException e) {
+      throw new RuntimeException(e);
+    }
+
+    String implName = interfaceType.getName();
+    implName = implName.replace('.', '_') + "Impl";
+
+    String packageName = interfaceType.getPackage().getName();
+    PrintWriter printWriter = genCtx.tryCreate(logger, packageName, implName);
+
+    if (printWriter != null) {
+      String templateName = deduceTemplateFile(logger, interfaceType);
+
+      Document document;
+      try {
+        document = parseXmlResource(logger, templateName);
+      } catch (SAXParseException e) {
+        logger.log(TreeLogger.ERROR, "Error parsing XML (line "
+            + e.getLineNumber() + "): " + e.getMessage(), e);
+        throw new UnableToCompleteException();
+      }
+
+      UiBinderWriter uiBinderWriter = new UiBinderWriter(interfaceType,
+          implName, templateName, oracle, logger);
+      uiBinderWriter.parseDocument(document);
+      commit(logger, genCtx, packageName, printWriter, uiBinderWriter);
+    }
+    return packageName + "." + implName;
+  }
+
+  /**
+   * Given a UiBinder interface, return the path to its ui.xml file, suitable
+   * for any classloader to find it as a resource.
+   */
+  private String deduceTemplateFile(TreeLogger logger, JClassType interfaceType)
+      throws UnableToCompleteException {
+    String templateName = null;
+    UiTemplate annotation = interfaceType.getAnnotation(UiTemplate.class);
+    if (annotation == null) {
+      // if the interface is defined as a nested class, use the name of the
+      // enclosing type
+      if (interfaceType.getEnclosingType() != null) {
+        interfaceType = interfaceType.getEnclosingType();
+      }
+      return slashify(interfaceType.getQualifiedSourceName()) + TEMPLATE_SUFFIX;
+    } else {
+      templateName = annotation.value();
+      if (!templateName.endsWith(TEMPLATE_SUFFIX)) {
+        logger.log(TreeLogger.ERROR, "Template file name must end with "
+            + TEMPLATE_SUFFIX);
+        throw new UnableToCompleteException();
+      }
+
+      /*
+       * If the template file name (minus suffix) has no dots, make it relative
+       * to the binder's package, otherwise slashify the dots
+       */
+      String unsuffixed = templateName.substring(0,
+          templateName.lastIndexOf(TEMPLATE_SUFFIX));
+      if (!unsuffixed.contains(".")) {
+        templateName = slashify(interfaceType.getPackage().getName()) + "/"
+            + templateName;
+      } else {
+        templateName = slashify(unsuffixed) + TEMPLATE_SUFFIX;
+      }
+    }
+    return templateName;
+  }
+
+  private Document parseXmlResource(TreeLogger logger, final String resourcePath)
+      throws SAXParseException, UnableToCompleteException {
+    // Get the document builder. We need namespaces, and automatic expanding
+    // of entity references (the latter of which makes life somewhat easier
+    // for XMLElement).
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    factory.setNamespaceAware(true);
+    factory.setExpandEntityReferences(true);
+    DocumentBuilder builder;
+    try {
+      builder = factory.newDocumentBuilder();
+    } catch (ParserConfigurationException e) {
+      throw new RuntimeException(e);
+    }
+
+    try {
+      ClassLoader classLoader = UiBinderGenerator.class.getClassLoader();
+      Enumeration<URL> urls = classLoader.getResources(resourcePath);
+      if (!urls.hasMoreElements()) {
+        logger.log(TreeLogger.ERROR, "Unable to find resource: " + resourcePath);
+        throw new UnableToCompleteException();
+      }
+      URL url = urls.nextElement();
+
+      InputStream stream = url.openStream();
+      InputSource input = new InputSource(stream);
+      input.setSystemId(url.toExternalForm());
+
+      builder.setEntityResolver(new GwtResourceEntityResolver());
+
+      return builder.parse(input);
+    } catch (SAXParseException e) {
+      // Let SAXParseExceptions through.
+      throw e;
+    } catch (SAXException e) {
+      throw new RuntimeException(e);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private String slashify(String s) {
+    return s.replace(".", "/");
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
new file mode 100644
index 0000000..723db3e
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -0,0 +1,1234 @@
+/*
+ * Copyright 2008 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.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPackage;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dom.client.TagName;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.parsers.AttributeMessageParser;
+import com.google.gwt.uibinder.parsers.AttributeParser;
+import com.google.gwt.uibinder.parsers.BeanParser;
+import com.google.gwt.uibinder.parsers.BundleAttributeParser;
+import com.google.gwt.uibinder.parsers.ElementParser;
+import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
+import com.google.gwt.uibinder.rebind.model.OwnerClass;
+import com.google.gwt.uibinder.rebind.model.OwnerField;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Writer for UiBinder generated classes.
+ *
+ * TODO(rdamazio): Refactor this, extract model classes, improve ordering
+ *                 guarantees, etc.
+ * TODO(rjrjr): Improve error messages
+ */
+public class UiBinderWriter {
+  private static final String GWT_URI = "urn:ui:com.google.gwt.uibinder";
+  private static final String BUNDLE_URI_SCHEME = "urn:with:";
+  private static final String PACKAGE_URI_SCHEME = "urn:import:";
+
+  private static int domId = 0;
+
+  public static String asCommaSeparatedList(String... args) {
+    StringBuilder b = new StringBuilder();
+    for (String arg : args) {
+      if (b.length() > 0) {
+        b.append(", ");
+      }
+      b.append(arg);
+    }
+
+    return b.toString();
+  }
+
+  /**
+   * Escape text that will be part of a string literal to be interpreted at
+   * runtime as an HTML attribute value.
+   */
+  public static String escapeAttributeText(String text) {
+    text = escapeText(text, false);
+
+    /*
+     * Escape single-quotes to make them safe to be interpreted at runtime as an
+     * HTML attribute value (for which we by convention use single quotes).
+     */
+    text = text.replaceAll("'", "&#39;");
+    return text;
+  }
+
+  /**
+   * Escape text that will be part of a string literal to be interpreted at
+   * runtime as HTML, optionally preserving whitespace.
+   */
+  public static String escapeText(String text, boolean preserveWhitespace) {
+    // Replace reserved XML characters with entities. Note that we *don't*
+    // replace single- or double-quotes here, because they're safe in text
+    // nodes.
+    text = text.replaceAll("&", "&amp;");
+    text = text.replaceAll("<", "&lt;");
+    text = text.replaceAll(">", "&gt;");
+
+    if (!preserveWhitespace) {
+      text = text.replaceAll("\\s+", " ");
+    }
+
+    return escapeTextForJavaStringLiteral(text);
+  }
+  /**
+   * Escape characters that would mess up interpretation of this string as a
+   * string literal in generated code (that is, protect \n and " ).
+   */
+  public static String escapeTextForJavaStringLiteral(String text) {
+    text = text.replaceAll("\"", "\\\\\"");
+    text = text.replaceAll("\n", "\\\\n");
+
+    return text;
+  }
+
+  private static String capitalizePropName(String propName) {
+    return propName.substring(0, 1).toUpperCase() + propName.substring(1);
+  }
+  private static AttributeParser getAttributeParserByClassName(
+      String parserClassName) {
+    try {
+      Class<? extends AttributeParser> parserClass =
+          Class.forName(parserClassName).asSubclass(AttributeParser.class);
+      return parserClass.newInstance();
+    } catch (ClassNotFoundException e) {
+      throw new RuntimeException("Unable to instantiate parser", e);
+    } catch (ClassCastException e) {
+      throw new RuntimeException(parserClassName
+          + " must extend AttributeParser");
+    } catch (InstantiationException e) {
+      throw new RuntimeException("Unable to instantiate parser", e);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException("Unable to instantiate parser", e);
+    }
+  }
+  /**
+   * Returns a list of the given type and all its superclasses and implemented
+   * interfaces in a breadth-first traversal.
+   *
+   * @param type the base type
+   * @return a breadth-first collection of its type hierarchy
+   */
+  private static Iterable<JClassType> getClassHierarchyBreadthFirst(
+      JClassType type) {
+    LinkedList<JClassType> list = new LinkedList<JClassType>();
+    LinkedList<JClassType> q = new LinkedList<JClassType>();
+
+    q.add(type);
+    while (!q.isEmpty()) {
+      // Pop the front of the queue and add it to the result list.
+      JClassType curType = q.removeFirst();
+      list.add(curType);
+
+      // Add the superclass and implemented interfaces to the back of the queue.
+      JClassType superClass = curType.getSuperclass();
+      if (superClass != null) {
+        q.add(superClass);
+      }
+      for (JClassType intf : curType.getImplementedInterfaces()) {
+        q.add(intf);
+      }
+    }
+
+    return list;
+  }
+
+  private static String getFullTypeName(JClassType type) {
+    StringBuilder b = new StringBuilder(type.getPackage().getName());
+    if (b.length() > 0) {
+      b.append(".");
+    }
+    b.append(type.getName());
+    return b.toString();
+  }
+  /**
+   * Class names of parsers for values of attributes with no namespace prefix,
+   * keyed by method parameter signatures.
+   *
+   * TODO(rjrjr) Seems like the attribute parsers belong in BeanParser, which is
+   * the only thing that uses them.
+   */
+  private final Map<String, String> attributeParsers =
+      new HashMap<String, String>();
+
+  /**
+   * Class names of parsers for various ui types, keyed by the classname of the
+   * UI class they can build.
+   */
+  private final Map<String, String> elementParsers =
+      new HashMap<String, String>();
+  /**
+   * Map of bundle parsers, keyed by bundle class name.
+   */
+  private final Map<String, BundleAttributeParser> bundleParsers =
+      new HashMap<String, BundleAttributeParser>();
+
+  private final List<String> initStatements = new ArrayList<String>();
+
+  private final List<String> statements = new ArrayList<String>();
+  private final HandlerEvaluator handlerEvaluator;
+  private final MessagesWriter messages;
+  private final Tokenator tokenator = new Tokenator();
+  private final TreeLogger logger;
+
+  private final String templatePath;
+  private final TypeOracle oracle;
+  /**
+   * The type we have been asked to generated, e.g. MyUiBinder
+   */
+  private final JClassType baseClass;
+  /**
+   * The name of the class we're creating, e.g. MyUiBinderImpl
+   */
+  private final String implClassName;
+  private final JClassType uiOwnerType;
+
+  private final JClassType uiRootType;
+
+  private final OwnerClass ownerClass;
+
+  private final FieldManager fieldManager;
+
+  private int fieldIndex;
+
+  private String gwtPrefix;
+
+  private String rendered;
+
+  private String rootDeclaration;
+
+  UiBinderWriter(JClassType baseClass, String implClassName,
+      String templatePath, TypeOracle oracle, TreeLogger logger)
+      throws UnableToCompleteException {
+    this.baseClass = baseClass;
+    this.implClassName = implClassName;
+    this.oracle = oracle;
+    this.logger = logger;
+    this.templatePath = templatePath;
+    this.messages = createMessagesWriter(templatePath, logger);
+
+    JClassType uiBinderType = baseClass.getImplementedInterfaces()[0];
+    JClassType[] typeArgs = uiBinderType.isParameterized().getTypeArgs();
+    uiRootType = typeArgs[0];
+    uiOwnerType = typeArgs[1];
+
+    ownerClass = new OwnerClass(uiOwnerType);
+    handlerEvaluator = new HandlerEvaluator(ownerClass, logger, oracle);
+    fieldManager = new FieldManager(oracle, logger);
+  }
+
+  /**
+   * Add a statement to be run after everything has been instantiated.
+   */
+  public void addInitStatement(String format, Object... params) {
+    initStatements.add(String.format(format, params));
+  }
+
+  // TODO(rjrjr) These declare* methods should be returning FieldWriter
+  // instances, not strings.
+
+  /**
+   * Adds a statement to the block run after fields are declared.
+   */
+  public void addStatement(String statement) {
+    statements.add(statement);
+  }
+
+  /**
+   * Adds a statement to the block run after fields are declared, using the Java
+   * message format.
+   */
+  public void addStatement(String format, Object... args) {
+    statements.add(String.format(format, args));
+  }
+
+  /**
+   * Declare a field that will hold an Element instance. Returns a token that
+   * the caller must set as the id attribute of that element in whatever
+   * innerHTML expression will reproduce it at runtime.
+   * <P>
+   * In the generated code, this token will be replaced by an expression to
+   * generate a unique dom id at runtime. Further code will be generated to be
+   * run after widgets are instantiated, to use that dom id in a getElementById
+   * call and assign the Element instance to its field.
+   *
+   * @param fieldName The name of the field being declared
+   * @param parentElementExpression an expression to be evaluated at runtime,
+   *          which will return an Element that is an ancestor of this one
+   *          (needed by the getElementById call mentioned above).
+   */
+  public String declareDomField(String fieldName,
+      String parentElementExpression) throws UnableToCompleteException {
+    String name = declareDomIdHolder();
+    setFieldInitializer(fieldName, "null");
+    addInitStatement(
+        "%s = UiBinderUtil.attachToDomAndGetChild(%s, %s).cast();", fieldName,
+        parentElementExpression, name);
+    addInitStatement("%s.removeAttribute(\"id\");", fieldName);
+    return tokenForExpression(name);
+  }
+
+  /**
+   * Declare a variable that will be filled at runtime with a unique id, safe
+   * for use as a dom element's id attribute.
+   *
+   * @return that variable's name.
+   */
+  public String declareDomIdHolder() throws UnableToCompleteException {
+    String domHolderName = "domId" + domId++;
+    FieldWriter domField = fieldManager.registerField(
+        "java.lang.String", domHolderName);
+    domField.setInitializer(
+        "com.google.gwt.dom.client.Document.get().createUniqueId()");
+    return domHolderName;
+  }
+
+  /**
+   * Declares a field of the given type name, returning the name of the declared
+   * field. If the element has a field or id attribute, use its value.
+   * Otherwise, create and return a new, private field name for it.
+   */
+  public String declareField(String typeName, XMLElement elem)
+      throws UnableToCompleteException {
+    String fieldName = getFieldName(elem);
+    if (fieldName == null) {
+      fieldName = ("f_" + elem.getLocalName() + (++fieldIndex));
+    }
+    fieldManager.registerField(typeName, fieldName);
+    return fieldName;
+  }
+
+  /**
+   * If this element has a gwt:field attribute, create a field for it of the
+   * appropriate type, and return the field name. If no gwt:field attribute is
+   * found, do nothing and return null
+   *
+   * @return The new field name, or null if no field is created
+   */
+  public String declareFieldIfNeeded(XMLElement elem)
+      throws UnableToCompleteException {
+    String fieldName = getFieldName(elem);
+    if (fieldName != null) {
+      String typeName = findWidgetOrElementType(elem).getQualifiedSourceName();
+      fieldManager.registerField(typeName, fieldName);
+    }
+    return fieldName;
+  }
+
+  /**
+   * Given a string containing tokens returned by {@link #tokenForExpression} or
+   * {@link #declareDomField}, return a string with those tokens replaced by the
+   * appropriate expressions. (It is not normally necessary for an
+   * {@link XMLElement.Interpreter} or {@link ElementParser} to make this call,
+   * as the tokens are typically replaced by the TemplateWriter itself.)
+   */
+  public String detokenate(String betokened) {
+    return tokenator.detokenate(betokened);
+  }
+
+  /**
+   * Post an error message and halt processing. This method always throws an
+   * {@link UnableToCompleteException}
+   */
+  public void die(String message) throws UnableToCompleteException {
+    logger.log(TreeLogger.ERROR, message);
+    throw new UnableToCompleteException();
+  }
+
+  /**
+   * Post an error message and halt processing. This method always throws an
+   * {@link UnableToCompleteException}
+   */
+  public void die(String message, Object... params)
+      throws UnableToCompleteException {
+    logger.log(TreeLogger.ERROR, String.format(message, params));
+    throw new UnableToCompleteException();
+  }
+
+  /**
+   * Finds the JClassType that corresponds to this XMLElement, which must be a
+   * Widget or an Element.
+   *
+   * @throws UnableToCompleteException If no such widget class exists
+   * @throws RuntimeException if asked to handle a non-widget, non-DOM element
+   */
+  public JClassType findWidgetOrElementType(XMLElement elem)
+      throws UnableToCompleteException {
+    String tagName = elem.getLocalName();
+
+    if (!isWidget(elem)) {
+      return findElementTypeForTag(tagName);
+    }
+
+    String ns = elem.getNamespaceUri();
+
+    JPackage pkg = parseNamespacePackage(ns);
+    if (pkg == null) {
+      throw new RuntimeException("No such package: " + ns);
+    }
+
+    JClassType rtn = null;
+    if (pkg != null) {
+      rtn = pkg.findType(tagName);
+      if (rtn == null) {
+        die("No class matching \"%s\" in %s",tagName, ns);
+      }
+    }
+
+    return rtn;
+  }
+
+  /**
+   * Generates the code to set a property value (assumes that 'value' is a valid
+   * Java expression).
+   */
+  public void genPropertySet(String fieldName, String propName, String value) {
+    addStatement("%1$s.set%2$s(%3$s);", fieldName,
+        capitalizePropName(propName), value);
+  }
+
+  /**
+   * Provide the expression to generate only the root element of the UI. If this
+   * is called, the code to create the bulk of the ui will be in the
+   * {@link UiBinder#bindUi} method. If it is not called, all code to create the
+   * ui will be in the {@link UiBinder#createUiRoot} method.
+   */
+  public void genRoot(String initString) {
+    rootDeclaration = initString;
+  }
+
+  /**
+   * Generates the code to set a string property.
+   */
+  public void genStringPropertySet(String fieldName, String propName,
+      String value) {
+    genPropertySet(fieldName, propName, "\"" + value + "\"");
+  }
+
+  /**
+   * Find and return an appropriate attribute parser for a set of parameters, or
+   * return null.
+   */
+  public AttributeParser getAttributeParser(JParameter... params) {
+    String paramTypeNames = getParametersKey(params);
+    String parserClassName = attributeParsers.get(paramTypeNames);
+
+    if (parserClassName != null) {
+      return getAttributeParserByClassName(parserClassName);
+    }
+
+    return null;
+  }
+
+  /**
+   * Finds an attribute parser for the given xml attribute.
+   */
+  public AttributeParser getAttributeParser(XMLAttribute attribute)
+      throws UnableToCompleteException {
+    if (attribute.getNamespaceUri() == null) {
+      return null;
+    }
+
+    String attributePrefixUri = attribute.getNamespaceUri();
+    if (!attributePrefixUri.startsWith(BUNDLE_URI_SCHEME)) {
+      return null;
+    }
+
+    String bundleClassName =
+        attributePrefixUri.substring(BUNDLE_URI_SCHEME.length());
+    BundleAttributeParser parser = bundleParsers.get(bundleClassName);
+    if (parser == null) {
+      JClassType bundleClass = getOracle().findType(bundleClassName);
+      if (bundleClass == null) {
+        die("No such bundle class: " + bundleClassName);
+      }
+      parser = createBundleParser(bundleClass, attribute);
+
+      bundleParsers.put(bundleClassName, parser);
+    }
+
+    return parser;
+  }
+
+  /**
+   * Find and return an appropriate attribute parser for an attribute and set of
+   * parameters, or return null.
+   */
+  public AttributeParser getAttributeParser(XMLAttribute attribute,
+      JParameter... params) throws UnableToCompleteException {
+    AttributeParser parser = getAttributeParser(attribute);
+    if (parser == null) {
+      parser = getAttributeParser(params);
+    }
+    return parser;
+  }
+
+  public String getGwtFieldAttributeName() {
+    return gwtPrefix + ":field";
+  }
+
+  /**
+   * Get the {@link MessagesWriter} for this UI, generating it if necessary.
+   */
+  public MessagesWriter getMessages() {
+    return messages;
+  }
+
+  /**
+   * Gets the type oracle.
+   */
+  public TypeOracle getOracle() {
+    return oracle;
+  }
+
+  public OwnerClass getOwnerClass() {
+    return ownerClass;
+  }
+
+  public boolean isWidget(XMLElement elem) {
+    String uri = elem.getNamespaceUri();
+    return uri != null && uri.startsWith(PACKAGE_URI_SCHEME);
+  }
+
+  /**
+   * Parses the widget associated with the specified element, and returns the
+   * name of the field (possibly private) that will hold the object it
+   * describes.
+   *
+   * @param elem the xml element to be parsed
+   * @return the name of the field containing the parsed widget
+   */
+  public String parseWidget(XMLElement elem) throws UnableToCompleteException {
+    if (elementParsers.isEmpty()) {
+      registerParsers();
+    }
+
+    // Get the class associated with this element.
+    JClassType type = findWidgetOrElementType(elem);
+
+    // Declare its field.
+    String fieldName = declareField(type.getQualifiedSourceName(), elem);
+
+    FieldWriter field = fieldManager.lookup(fieldName);
+
+    // Push the field that will hold this widget to the top of parsedFieldStack
+    // to ensure that fields registered by its parsers will be noted as
+    // dependencies of the new widget. See registerField.
+    fieldManager.push(field);
+
+    // Give all the parsers a chance to generate their code.
+    for (ElementParser parser : getParsersForClass(type)) {
+      parser.parse(elem, fieldName, type, this);
+    }
+    fieldManager.pop();
+    return fieldName;
+  }
+
+  /**
+   * Gives the writer the initializer to use for this field instead of the
+   * default GWT.create call.
+   *
+   * @throws IllegalStateException if an initializer has already been set
+   */
+  public void setFieldInitializer(String fieldName, String factoryMethod) {
+    fieldManager.lookup(fieldName).setInitializer(factoryMethod);
+  }
+
+  /**
+   * Instructs the writer to initialize the field with a specific
+   * contructor invocaction, instead of the default GWT.create call.
+   */
+  public void setFieldInitializerAsConstructor(String fieldName,
+      JClassType type, String... args) {
+    setFieldInitializer(fieldName,
+        String.format("new %s(%s)", getFullTypeName(type),
+            asCommaSeparatedList(args)));
+  }
+
+  /**
+   * Returns a string token that can be used in place the given expression
+   * inside any string literals. Before the generated code is written, the
+   * expression will be stiched back into the generated code in place of the
+   * token, surrounded by plus signs. This is useful in strings to be handed to
+   * setInnerHTML() and setText() calls, to allow a unique dom id attribute or
+   * other runtime expression in the string.
+   *
+   * @param expression
+   */
+  public String tokenForExpression(String expression) {
+    return tokenator.nextToken(("\" + " + expression + " + \""));
+  }
+
+  /**
+   * Post a warning message.
+   */
+  public void warn(String message) {
+    logger.log(TreeLogger.WARN, message);
+  }
+
+  /**
+   * Post a warning message.
+   */
+  public void warn(String message, Object... params) {
+    logger.log(TreeLogger.WARN, String.format(message, params));
+  }
+
+  /**
+   * Entry point for the code generation logic. It generates the
+   * implementation's superstructure, and parses the root widget (leading to all
+   * of its children being parsed as well).
+   */
+  void parseDocument(Document doc) throws UnableToCompleteException {
+    Element documentElement = doc.getDocumentElement();
+    this.rendered = tokenator.detokenate(parseElement(documentElement));
+  }
+
+  void write(PrintWriter printWriter) {
+    printWriter.print(rendered);
+  }
+
+  private void addAttributeParser(String signature, String className) {
+    attributeParsers.put(signature, className);
+  }
+
+  private void addParser(String gwtClass, String parser) {
+    elementParsers.put(gwtClass, parser);
+  }
+
+  private void addWidgetParser(String className) {
+    String gwtClass = "com.google.gwt.user.client.ui." + className;
+    String parser = "com.google.gwt.uibinder.parsers." + className + "Parser";
+    addParser(gwtClass, parser);
+  }
+
+  /**
+   * Creates a parser for the given bundle class.
+   */
+  private BundleAttributeParser createBundleParser(JClassType bundleClass,
+      XMLAttribute attribute) throws UnableToCompleteException {
+
+    // Try to find any bundle instance created with @UiFactory.
+    JMethod method = getOwnerClass().getUiFactoryMethod(bundleClass);
+    if (method != null) {
+      return new BundleAttributeParser(bundleClass, "owner." + method.getName()
+          + "()", false);
+    }
+
+    // Try to find any bundle instance created with UiField.
+    OwnerField field = getOwnerClass().getUiFieldForType(bundleClass);
+    if (field != null) {
+      String[] tmp = attribute.getName().split(":");
+      String templateName = tmp[0];
+      if (!templateName.equals(field.getName())) {
+         die("Template %s has no \"xmlns:%s='urn:with:%s'\" for %s.%s#%s",
+            templatePath,
+            field.getName(),
+            bundleClass.getQualifiedSourceName(),
+            uiOwnerType.getPackage().getName(),
+            uiOwnerType.getName(),
+            field.getName());
+      }
+
+      if (field.isProvided()) {
+        return new BundleAttributeParser(bundleClass,
+            "owner." + field.getName(), false);
+      }
+    }
+
+    return new BundleAttributeParser(bundleClass, "my"
+        + bundleClass.getName().replace('.', '_') + "Instance", true);
+  }
+
+  private MessagesWriter createMessagesWriter(String templatePath,
+      TreeLogger logger) {
+    return new MessagesWriter(GWT_URI, logger, templatePath, getPackageName(),
+        this.implClassName);
+  }
+
+  /**
+   * Outputs a bundle resource for a given bundle attribute parser.
+   */
+  private String declareStaticField(BundleAttributeParser parser) {
+    if (!parser.isBundleStatic()) {
+      return null;
+    }
+
+    String fullBundleClassName = parser.fullBundleClassName();
+
+    StringBuilder b = new StringBuilder();
+    b.append("static ").append(fullBundleClassName).append(" ").append(
+        parser.bundleInstance()).append(" = GWT.create(").append(
+        fullBundleClassName).append(".class);");
+
+    return b.toString();
+  }
+
+  /**
+   * Given a DOM tag name, return the corresponding Element subclass.
+   */
+  private JClassType findElementTypeForTag(String tag) {
+    JClassType elementClass =
+        oracle.findType("com.google.gwt.dom.client.Element");
+    JClassType[] types = elementClass.getSubtypes();
+    for (JClassType type : types) {
+      TagName annotation = type.getAnnotation(TagName.class);
+      if (annotation != null) {
+        for (String annotationTag : annotation.value()) {
+          if (annotationTag.equals(tag)) {
+            return type;
+          }
+        }
+      }
+    }
+
+    return elementClass;
+  }
+
+  /**
+   * Inspects this element for a gwt:field attribute. If one is found, the
+   * attribute is consumed and its value returned.
+   *
+   * @return The field name declared by an element, or null if none is declared
+   */
+  private String getFieldName(XMLElement elem)
+      throws UnableToCompleteException {
+    String fieldName = null;
+    boolean hasOldSchoolId = false;
+    if (elem.hasAttribute("id") && isWidget(elem)) {
+      hasOldSchoolId = true;
+      // If an id is specified on the element, use that.
+      fieldName = elem.consumeAttribute("id");
+      logger.log(TreeLogger.WARN, String.format(
+          "Deprecated use of id=\"%1$s\" for field name. "
+              + "Please switch to gwt:field=\"%1$s\" instead. "
+              + "This will soon be a compile error!", fieldName));
+    }
+    if (elem.hasAttribute(getGwtFieldAttributeName())) {
+      if (hasOldSchoolId) {
+        die("Cannot declare both id and field on the same element: " + elem);
+      }
+      fieldName = elem.consumeAttribute(getGwtFieldAttributeName());
+    }
+    return fieldName;
+  }
+
+  private String getPackageName() {
+    return baseClass.getPackage().getName();
+  }
+
+  /**
+   * Given a parameter array, return a key for the attributeParsers table.
+   */
+  private String getParametersKey(JParameter[] params) {
+    String paramTypeNames = "";
+    for (int i = 0; i < params.length; ++i) {
+      paramTypeNames +=
+          params[i].getType().getParameterizedQualifiedSourceName();
+      if (i != params.length - 1) {
+        paramTypeNames += ",";
+      }
+    }
+    return paramTypeNames;
+  }
+
+  private Class<? extends ElementParser> getParserForClass(JClassType uiClass) {
+    // Find the associated parser.
+    String uiClassName = uiClass.getQualifiedSourceName();
+    String parserClassName = elementParsers.get(uiClassName);
+    if (parserClassName == null) {
+      return null;
+    }
+
+    // And instantiate it.
+    try {
+      return Class.forName(parserClassName).asSubclass(ElementParser.class);
+    } catch (ClassNotFoundException e) {
+      throw new RuntimeException("Unable to instantiate parser", e);
+    } catch (ClassCastException e) {
+      throw new RuntimeException(
+          parserClassName + " must extend ElementParser");
+    }
+  }
+
+  /**
+   * Find a set of element parsers for the given ui type.
+   *
+   * The list of parsers will be returned in order from most- to least-specific.
+   */
+  private Iterable<ElementParser> getParsersForClass(JClassType type) {
+    List<ElementParser> parsers = new ArrayList<ElementParser>();
+
+    /*
+     * Let this non-widget parser go first (it finds <m:attribute/> elements).
+     * Any other such should land here too.
+     *
+     * TODO(rjrjr) Need a scheme to associate these with a namespace uri or
+     * something?
+     */
+    parsers.add(new AttributeMessageParser());
+
+    for (JClassType curType : getClassHierarchyBreadthFirst(type)) {
+      try {
+        Class<? extends ElementParser> cls = getParserForClass(curType);
+        if (cls != null) {
+          ElementParser parser = cls.newInstance();
+          parsers.add(parser);
+        }
+      } catch (InstantiationException e) {
+        throw new RuntimeException(
+            "Unable to instantiate " + curType.getName(), e);
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(
+            "Unable to instantiate " + curType.getName(), e);
+      }
+    }
+
+    parsers.add(new BeanParser());
+
+    return parsers;
+  }
+
+  private String instanceBinderType() {
+    return String.format("AbstractUiBinder.InstanceBinder<%s, %s>",
+        uiRootType.getName(), uiOwnerType.getName());
+  }
+
+  /**
+   * Writes a field setter if the field is not provided and the field class
+   * is compatible with its respective template field.
+   */
+  private void maybeWriteFieldSetter(IndentedWriter niceWriter,
+      OwnerField ownerField, JClassType templateClass, String templateField)
+      throws UnableToCompleteException {
+    JClassType fieldType = ownerField.getType().getRawType();
+
+    if (!templateClass.isAssignableTo(fieldType)) {
+      die("Template field and owner field types don't match: %s != %s",
+          templateClass.getQualifiedSourceName(),
+          fieldType.getQualifiedSourceName());
+    }
+
+    if (!ownerField.isProvided()) {
+      niceWriter.write("owner.%1$s = %2$s;",
+          ownerField.getName(), templateField);
+    }
+  }
+
+  private String parseElement(Element documentElement)
+      throws UnableToCompleteException {
+    gwtPrefix = documentElement.lookupPrefix(GWT_URI);
+
+    JClassType uiBinderClass = getOracle().findType(UiBinder.class.getName());
+    if (!baseClass.isAssignableTo(uiBinderClass)) {
+      die(baseClass.getName() + " must implement UiBinder");
+    }
+
+    XMLElement elem = new XMLElement(documentElement, this);
+    messages.findMessagesConfig(elem);
+    elem = elem.consumeSingleChildElement();
+    String rootField = parseWidget(elem);
+
+    StringWriter stringWriter = new StringWriter();
+    IndentedWriter niceWriter =
+        new IndentedWriter(new PrintWriter(stringWriter));
+
+    if (rootDeclaration != null) {
+       writeBifurcatedBinder(niceWriter);
+    } else {
+       writeEagerBinder(niceWriter, rootField);
+    }
+
+    return stringWriter.toString();
+  }
+
+  /**
+   * Parses a package uri (i.e. package://com.google...).
+   *
+   * @throws UnableToCompleteException on bad package name
+   */
+  private JPackage parseNamespacePackage(String ns)
+      throws UnableToCompleteException {
+    if (ns.startsWith(PACKAGE_URI_SCHEME)) {
+      String pkgName = ns.substring(PACKAGE_URI_SCHEME.length());
+
+      JPackage pkg = oracle.findPackage(pkgName);
+      if (pkg == null) {
+        die("Package not found: " + pkgName);
+      }
+
+      return pkg;
+    }
+
+    return null;
+  }
+
+  private void registerParsers() {
+    // TODO(rjrjr): Allow third-party parsers to register themselves
+    //              automagically, according to http://b/issue?id=1867118
+    // Parses the root element in UiBinder, for non-widget templates
+    addParser("com.google.gwt.dom.client.Element",
+        "com.google.gwt.uibinder.parsers.DomElementParser");
+
+    // Register widget parsers.
+    addWidgetParser("UIObject");
+    addWidgetParser("HasText");
+    addWidgetParser("HasHTML");
+    addWidgetParser("HasWidgets");
+    addWidgetParser("HTMLPanel");
+    addWidgetParser("DockPanel");
+    addWidgetParser("StackPanel");
+    addWidgetParser("DisclosurePanel");
+    addWidgetParser("TabPanel");
+    addWidgetParser("MenuItem");
+    addWidgetParser("MenuBar");
+    addWidgetParser("RadioButton");
+    addWidgetParser("CellPanel");
+    addWidgetParser("CustomButton");
+
+    addAttributeParser("boolean",
+        "com.google.gwt.uibinder.parsers.BooleanAttributeParser");
+
+    addAttributeParser("java.lang.String",
+        "com.google.gwt.uibinder.parsers.StringAttributeParser");
+
+    addAttributeParser("int", "com.google.gwt.uibinder.parsers.IntParser");
+
+    addAttributeParser("int,int",
+        "com.google.gwt.uibinder.parsers.IntPairParser");
+
+    addAttributeParser(
+        "com.google.gwt.user.client.ui.HasHorizontalAlignment."
+            + "HorizontalAlignmentConstant",
+        "com.google.gwt.uibinder.parsers.HorizontalAlignmentConstantParser");
+  }
+
+  /**
+   * Write statements that parsers created via calls to
+   * {@link #addInitStatement}. Such statements will assume that
+   * {@link #writeGwtFields} has already been called.
+   */
+  private void writeAddedStatements(IndentedWriter niceWriter) {
+    for (String s : statements) {
+      niceWriter.write(s);
+    }
+  }
+
+  /**
+   * Writes a binder that creates a root element in
+   * {@link UiBinder#createUiRoot}, and the rest of the dom in
+   * {@link UiBinder#bindUi}. See a sample in {@link "http://go/gwt-bif-binder"}
+   */
+  private void writeBifurcatedBinder(IndentedWriter w)
+      throws UnableToCompleteException {
+    writePackage(w);
+    writeImports(w);
+    w.newline();
+
+    writeClassOpen(w);
+    writeStatics(w);
+    w.newline();
+
+    // createUiRoot method
+    w.write("public %s createUiRoot(final %s owner) {", uiRootType.getName(),
+        uiOwnerType.getName());
+    w.indent();
+    w.write("return %s;", rootDeclaration);
+    w.outdent();
+    w.write("}");
+    w.newline();
+
+    // bindUi method
+    w.write("public void bindUi(%s root, %s owner) {", uiRootType.getName(),
+        uiOwnerType.getName());
+    w.indent();
+
+    writeGwtFields(w);
+    writeAddedStatements(w);
+    writeInitStatements(w);
+    writeFieldSetters(w);
+    writeHandlers(w);
+    w.newline();
+
+    // close bindUi method
+    w.outdent();
+    w.write("}");
+    w.newline();
+
+    // Close class
+    w.outdent();
+    w.write("}");
+  }
+
+  private void writeClassOpen(IndentedWriter w) {
+    w.write("public class %s extends AbstractUiBinder<%s, %s> \n"
+        + "implements %s {",
+        implClassName, uiRootType.getName(), uiOwnerType.getName(),
+        baseClass.getName());
+    w.indent();
+  }
+
+  /**
+   * Writes a binder that creates the entire ui in {@link UiBinder#createUiRoot}
+   * and does nothing but fill the clients fields in {@link UiBinder#bindUi}.
+   * <p>
+   * Because UiBinder instances tend to be static and reused, createUiRoot
+   * creates an object with references to the generated ui's fields, and stores
+   * it in a hash map keyed by the root UI object. This binding object is
+   * removed from the map during the call to bindUi and used to fill the
+   * client's <code>{@literal @}UiField</code>s. I would prefer to find a
+   * way to do without the map, but am stumped.
+   * <p>
+   * See a sample in {@link "http://go/gwt-eager-binder"}
+   *
+   * @throws UnableToCompleteException
+   */
+  private void writeEagerBinder(IndentedWriter w, String rootField)
+      throws UnableToCompleteException {
+    writePackage(w);
+
+    // Add a few extra imports
+    w.write("import java.util.HashMap;");
+    w.write("import java.util.Map;");
+    writeImports(w);
+    w.newline();
+
+    writeClassOpen(w);
+    writeStatics(w);
+    w.newline();
+
+    // Map from root objects to instance binders
+    w.write("final Map<%1$s, %2$s> rootToBindCommand "
+        + "= new HashMap<%1$s, %2$s>();", uiRootType.getName(),
+        instanceBinderType());
+    w.newline();
+
+    // createUiRoot method
+    w.write("public %s createUiRoot(final %s owner) {", uiRootType.getName(),
+        uiOwnerType.getName());
+    w.indent();
+    w.newline();
+
+    // write instance binder anon class
+    w.write("%1$s instanceBinder = new %1$s() {", instanceBinderType());
+    w.indent();
+    writeGwtFields(w);
+    w.newline();
+
+    w.write("public %s makeUi() {", uiRootType.getName());
+    w.indent();
+    writeAddedStatements(w);
+    w.newline();
+    writeInitStatements(w);
+    w.newline();
+    writeHandlers(w);
+    w.newline();
+    w.write("return %s;", rootField);
+    w.outdent();
+    w.write("}");
+    w.newline();
+
+    w.write("public void doBind(%s owner) {", uiOwnerType.getName());
+    w.indent();
+    writeFieldSetters(w);
+    w.outdent();
+    w.write("}");
+
+    // close instance binder anon class
+    w.outdent();
+    w.write("};");
+    w.newline();
+
+    // use instance binder to make the ui, and store the binder away
+    // to wait for the call to  bindUi
+    w.write("%s root = instanceBinder.makeUi();", uiRootType.getName());
+    w.write("rootToBindCommand.put(root, instanceBinder);");
+    w.write("return root;");
+
+    // close createUiRoot method
+    w.outdent();
+    w.write("}");
+    w.newline();
+
+    // bindUi method
+    w.write("public void bindUi(%s root, %s owner) {", uiRootType.getName(),
+        uiOwnerType.getName());
+    w.indent();
+
+    // fetch command and run it
+    w.write("%s instanceBinder = rootToBindCommand.remove(root);",
+        instanceBinderType());
+    w.write("assert instanceBinder != null : "
+          + "\"Attempted to bind an unknown UI, or to bind the same one twice\""
+          + ";");
+    w.write("instanceBinder.doBind(owner);");
+
+    // close bindUiMethod
+    w.outdent();
+    w.write("}");
+    w.newline();
+
+    // Close class
+    w.outdent();
+    w.write("}");
+  }
+
+  /**
+   * Write the statements to fill in the fields of the UI owner.
+   */
+  private void writeFieldSetters(IndentedWriter niceWriter)
+      throws UnableToCompleteException {
+    // For each owner field
+    Collection<OwnerField> ownerFields = getOwnerClass().getUiFields();
+    for (OwnerField ownerField : ownerFields) {
+      FieldWriter fieldWriter = fieldManager.lookup(ownerField.getName());
+
+      BundleAttributeParser bundleParser = bundleParsers.get(
+          ownerField.getType().getRawType().getQualifiedSourceName());
+
+      if (bundleParser != null) {
+        // ownerField is a bundle resource.
+        maybeWriteFieldSetter(niceWriter, ownerField,
+            bundleParser.bundleClass(), bundleParser.bundleInstance());
+
+      } else if (fieldWriter != null) {
+        // ownerField is a widget.
+        maybeWriteFieldSetter(niceWriter, ownerField,
+            fieldWriter.getType(), ownerField.getName());
+
+      } else {
+        // ownerField was not found as bundle resource or widget, must die.
+        die("Template %s has no %s attribute for %s.%s#%s",
+            templatePath,
+            getGwtFieldAttributeName(),
+            uiOwnerType.getPackage().getName(),
+            uiOwnerType.getName(),
+            ownerField.getName());
+      }
+    }
+  }
+
+  /**
+   * Write declarations for variables or fields to hold elements declared with
+   * gwt:field in the template. For those that have not had constructor
+   * generation suppressed, emit GWT.create() calls instantiating them (or die
+   * if they have no default constructor).
+   *
+   * @throws UnableToCompleteException on constructor problem
+   */
+  private void writeGwtFields(IndentedWriter niceWriter)
+      throws UnableToCompleteException {
+    // For each provided field in the owner class, initialize from the owner
+    Collection<OwnerField> ownerFields = getOwnerClass().getUiFields();
+    for (OwnerField ownerField : ownerFields) {
+      if (ownerField.isProvided()) {
+        String fieldName = ownerField.getName();
+        FieldWriter fieldWriter = fieldManager.lookup(fieldName);
+
+        // TODO(hermes) This can be null due to http://b/1836504. If that
+        // is fixed properly, a null fieldWriter will be an error
+        // (would that be a user error or a runtime error? Not sure)
+        if (fieldWriter != null) {
+          setFieldInitializer(fieldName,
+              String.format("owner.%1$s", fieldName));
+        }
+      }
+    }
+
+    // Write gwt field declarations.
+    fieldManager.writeGwtFieldsDeclaration(niceWriter, uiOwnerType.getName());
+  }
+
+  private void writeHandlers(IndentedWriter w)
+      throws UnableToCompleteException {
+    handlerEvaluator.run(w, fieldManager, "owner");
+  }
+
+  private void writeImports(IndentedWriter w) {
+    w.write("import com.google.gwt.core.client.GWT;");
+    w.write("import com.google.gwt.dom.client.Element;");
+    w.write("import com.google.gwt.uibinder.client.AbstractUiBinder;");
+    w.write("import com.google.gwt.uibinder.client.DomHolder;");
+    w.write("import com.google.gwt.uibinder.client.UiBinderUtil;");
+    w.write("import %s.%s;", uiRootType.getPackage().getName(),
+        uiRootType.getName());
+  }
+
+  /**
+   * Write statements created by {@link #addInitStatement}. This code must be
+   * placed after all instnatiation code.
+   */
+  private void writeInitStatements(IndentedWriter niceWriter) {
+    for (String s : initStatements) {
+      niceWriter.write(s);
+    }
+  }
+
+  private void writePackage(IndentedWriter w) {
+    String packageName = baseClass.getPackage().getName();
+    if (packageName.length() > 0) {
+      w.write("package %1$s;", packageName);
+      w.newline();
+    }
+  }
+
+  /**
+   * Generates instances of any bundle classes that have been referenced by a
+   * namespace entry in the top level element. This must be called *after* all
+   * parsing is through, as the bundle list is generated lazily as dom elements
+   * are parsed.
+   */
+  private void writeStaticBundleInstances(IndentedWriter niceWriter) {
+    // TODO(rjrjr) It seems bad that this method has special
+    // knowledge of BundleAttributeParser
+    for (String key : bundleParsers.keySet()) {
+      String declaration = declareStaticField(bundleParsers.get(key));
+      if (declaration != null) {
+        niceWriter.write(declaration);
+      }
+    }
+  }
+
+  private void writeStaticMessagesInstance(IndentedWriter niceWriter) {
+    if (messages.hasMessages()) {
+      niceWriter.write(messages.getDeclaration());
+    }
+  }
+
+  private void writeStatics(IndentedWriter w) {
+    writeStaticMessagesInstance(w);
+    writeStaticBundleInstances(w);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java b/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java
new file mode 100644
index 0000000..e14e5d9
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLAttribute.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2008 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 org.w3c.dom.Attr;
+
+/**
+ * Like {@link XMLElement}, a wrapper around {@link Attr} to keep
+ * parser writers out of trouble.
+ */
+public class XMLAttribute {
+
+  private XMLElement xmlElem;
+  private Attr w3cAttr;
+
+  public XMLAttribute(XMLElement element, Attr attr) {
+    this.xmlElem = element;
+    this.w3cAttr = attr;
+  }
+
+  public String consumeValue() {
+    return xmlElem.consumeAttribute(w3cAttr.getName());
+  }
+
+  public String getLocalName() {
+    return w3cAttr.getLocalName();
+  }
+
+  public String getName() {
+    return w3cAttr.getName();
+  }
+
+  public String getNamespaceUri() {
+    return w3cAttr.getNamespaceURI();
+  }
+
+  public boolean hasToken() {
+    return Tokenator.hasToken(w3cAttr.getValue());
+  }
+
+  public boolean isConsumed() {
+    return !xmlElem.hasAttribute(w3cAttr.getName());
+  }
+
+  @Override
+  public String toString() {
+    return w3cAttr.getName() + "='" + w3cAttr.getValue() + "'";
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
new file mode 100644
index 0000000..07e4002
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2008 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 org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A wrapper for {@link Element} that limits the way parsers can interact with
+ * the XML document, and provides some convenience methods.
+ * <p>
+ * The main function of this wrapper is to ensure that parsers can only read
+ * elements and attributes by 'consuming' them, which removes the given value.
+ * This allows for a natural hierarchy among parsers -- more specific parsers
+ * will run first, and if they consume a value, less-specific parsers will not
+ * see it.
+ */
+public class XMLElement {
+  private static final Set<String> NO_END_TAG = new HashSet<String>();
+
+  /**
+   * Callback interface used by {@link #consumeInnerHtml(Interpreter)} and
+   * {@link #consumeChildElements(Interpreter)}.
+   */
+  public interface Interpreter<T> {
+    /**
+     * Given an XMLElement, return its filtered value.
+     *
+     * @throws UnableToCompleteException on error
+     */
+    T interpretElement(XMLElement elem) throws UnableToCompleteException;
+  }
+
+  /**
+   * Extends {@link Interpreter} with a method to be called after
+   * all elements have been processed.
+   */
+  public interface PostProcessingInterpreter<T> extends Interpreter<T> {
+    String postProcess(String consumedText) throws UnableToCompleteException;
+  }
+
+  private static class NoBrainInterpeter<T> implements Interpreter<T> {
+    private final T rtn;
+
+    public NoBrainInterpeter(T rtn) {
+      this.rtn = rtn;
+    }
+
+    public T interpretElement(XMLElement elem) {
+      return rtn;
+    }
+  }
+
+  private static void clearChildren(Element elem) {
+    Node child;
+    while ((child = elem.getFirstChild()) != null) {
+      elem.removeChild(child);
+    }
+  }
+
+  private final UiBinderWriter writer;
+
+  private final Element elem;
+  
+  private final String debugString;
+
+  {
+    // from com/google/gxp/compiler/schema/html.xml
+    NO_END_TAG.add("area");
+    NO_END_TAG.add("base");
+    NO_END_TAG.add("basefont");
+    NO_END_TAG.add("br");
+    NO_END_TAG.add("col");
+    NO_END_TAG.add("frame");
+    NO_END_TAG.add("hr");
+    NO_END_TAG.add("img");
+    NO_END_TAG.add("input");
+    NO_END_TAG.add("isindex");
+    NO_END_TAG.add("link");
+    NO_END_TAG.add("meta");
+    NO_END_TAG.add("param");
+    NO_END_TAG.add("wbr");
+  }
+
+  public XMLElement(Element elem, UiBinderWriter writer) {
+    this.elem = elem;
+    this.writer = writer;
+    this.debugString = getOpeningTag();
+  }
+
+  /**
+   * Consumes the given attribute and returns its trimmed value, or null
+   * if it was undeclared. The returned string is not escaped.
+   *
+   * @param name the attribute's full name (including prefix)
+   * @return the attribute's value, or null
+   */
+  public String consumeAttribute(String name) {
+    String value = elem.getAttribute(name);
+    elem.removeAttribute(name);
+    return value.trim();
+  }
+
+  /**
+   * Consumes the given attribute as a boolean value.
+   *
+   * @throws UnableToCompleteException
+   */
+  public boolean consumeBooleanAttribute(String attr)
+      throws UnableToCompleteException {
+    String value = consumeAttribute(attr);
+    if (value.equals("true")) {
+      return true;
+    } else if (value.equals("false")) {
+      return false;
+    }
+    writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
+        + "as a boolean value", attr, this));
+    return false; // unreachable line for happy compiler
+  }
+
+  /**
+   * Consumes and returns all child elements, and erases any text nodes.
+   */
+  public Iterable<XMLElement> consumeChildElements() {
+    try {
+      Iterable<XMLElement> rtn =
+          consumeChildElements(new NoBrainInterpeter<Boolean>(true));
+      clearChildren(elem);
+      return rtn;
+    } catch (UnableToCompleteException e) {
+      throw new RuntimeException("Impossible exception", e);
+    }
+  }
+
+  /**
+   * Consumes and returns all child elements selected by the interpreter. Note
+   * that text nodes are not elements, and so are not presented for
+   * interpretation, and are not consumed.
+   *
+   * @param interpreter Should return true for any child that should be consumed
+   *          and returned.
+   * @throws UnableToCompleteException
+   */
+  public Collection<XMLElement> consumeChildElements(
+      Interpreter<Boolean> interpreter) throws UnableToCompleteException {
+    List<XMLElement> elements = new ArrayList<XMLElement>();
+    List<Node> doomed = new ArrayList<Node>();
+
+    NodeList childNodes = elem.getChildNodes();
+    for (int i = 0; i < childNodes.getLength(); ++i) {
+      Node childNode = childNodes.item(i);
+      if (childNode.getNodeType() == Node.ELEMENT_NODE) {
+        XMLElement childElement = new XMLElement((Element) childNode, writer);
+        if (interpreter.interpretElement(childElement)) {
+          elements.add(childElement);
+          doomed.add(childNode);
+        }
+      }
+    }
+
+    for (Node n : doomed) {
+      elem.removeChild(n);
+    }
+    return elements;
+  }
+
+  /**
+   * Consumes all child elements, and returns an HTML interpretation of them.
+   * Trailing and leading whitespace is trimmed.
+   * <p>
+   * Each element encountered will be passed to the given Interpreter for
+   * possible replacement. Escaping is performed to allow the returned text to
+   * serve as a Java string literal used as input to a setInnerHTML call.
+   * <p>
+   * This call requires an interpreter to make sense of any special children.
+   * The odds are you want to use
+   * {@link com.google.gwt.templates.parsers.HtmlInterpreter} for an HTML value,
+   * or {@link com.google.gwt.templates.parsers.TextInterpreter} for text.
+   *
+   * @param interpreter Called for each element, expected to return a string
+   *          replacement for it, or null if it should be left as is
+   */
+  public String consumeInnerHtml(Interpreter<String> interpreter)
+      throws UnableToCompleteException {
+    if (interpreter == null) {
+      throw new NullPointerException("interpreter must not be null");
+    }
+    StringBuffer buf = new StringBuffer();
+    GetInnerHtmlVisitor.getEscapedInnerHtml(elem, buf, interpreter, writer);
+
+    clearChildren(elem);
+    return buf.toString().trim();
+  }
+
+  /**
+   * Refines {@link #consumeInnerHtml(Interpreter)} to handle
+   * PostProcessingInterpreter.
+   */
+  public String consumeInnerHtml(PostProcessingInterpreter<String> interpreter)
+      throws UnableToCompleteException {
+    String html = consumeInnerHtml((Interpreter<String>) interpreter);
+    return interpreter.postProcess(html);
+  }
+
+  /**
+   * Consumes all child text nodes, and asserts that this element held only
+   * text. Trailing and leading whitespace is trimmed.
+   * <p>
+   * This call requires an interpreter to make sense of any special children.
+   * The odds are you want to use
+   * {@link com.google.gwt.templates.parsers.TextInterpreter}
+   *
+   * @throws UnableToCompleteException If any elements present are not consumed
+   *           by the interpreter
+   */
+  public String consumeInnerText(Interpreter<String> interpreter)
+      throws UnableToCompleteException {
+    if (interpreter == null) {
+      throw new NullPointerException("interpreter must not be null");
+    }
+    StringBuffer buf = new StringBuffer();
+
+    GetInnerTextVisitor.getEscapedInnerText(elem, buf, interpreter, writer);
+
+    // Make sure there are no children left but empty husks
+    for (XMLElement child : consumeChildElements()) {
+      if (child.hasChildNodes() || child.getAttributeCount() > 0) {
+        // TODO(rjrjr) This is not robust enough, and consumeInnerHtml needs
+        // a similar check
+        writer.die("Text value of \"%s\" has illegal child \"%s\"", this,
+            child);
+      }
+    }
+
+    clearChildren(elem);
+    return buf.toString().trim();
+  }
+
+  /**
+   * Refines {@link #consumeInnerText(Interpreter)} to handle
+   * PostProcessingInterpreter.
+   */
+  public String consumeInnerText(PostProcessingInterpreter<String> interpreter)
+      throws UnableToCompleteException {
+    String text = consumeInnerText((Interpreter<String>) interpreter);
+    return interpreter.postProcess(text);
+  }
+
+ /**
+   * Consumes all attributes, and returns a string representing the
+   * entire opening tag. E.g., "<div able='baker'>"
+   */
+  public String consumeOpeningTag() {
+    String rtn = getOpeningTag();
+
+    for (int i = getAttributeCount() - 1; i >= 0; i--) {
+      getAttribute(i).consumeValue();
+    }
+    return rtn;
+  }
+
+  /**
+   * Consumes a single child element, ignoring any text nodes and throwing an
+   * exception if more than one child element is found.
+   */
+  public XMLElement consumeSingleChildElement() {
+    XMLElement ret = null;
+    for (XMLElement child : consumeChildElements()) {
+      if (ret != null) {
+        throw new RuntimeException(getLocalName()
+            + " may only contain a single child element.");
+      }
+
+      ret = child;
+    }
+
+    return ret;
+  }
+
+  /**
+   * Get the attribute at the given index. If you are consuming attributes,
+   * remember to traverse them in reverse.
+   */
+  public XMLAttribute getAttribute(int i) {
+    return new XMLAttribute(XMLElement.this,
+        (Attr) elem.getAttributes().item(i));
+  }
+
+  /**
+   * @return The number of attributes this element has
+   */
+  public int getAttributeCount() {
+    return elem.getAttributes().getLength();
+  }
+
+  public String getClosingTag() {
+    if (NO_END_TAG.contains(elem.getTagName())) {
+      return "";
+    }
+    return String.format("</%s>", elem.getTagName());
+  }
+
+  /**
+   * Gets this element's local name (sans namespace prefix).
+   */
+  public String getLocalName() {
+    return elem.getLocalName();
+  }
+
+  /**
+   * Gets this element's namespace URI.
+   */
+  public String getNamespaceUri() {
+    return elem.getNamespaceURI();
+  }
+
+  public String getNamespaceUriForAttribute(String fieldName) {
+    Attr attr = elem.getAttributeNode(fieldName);
+    return attr.getNamespaceURI();
+  }
+
+  /**
+   * @return the parent element, or null if parent is null or a node type
+   * other than Element
+   */
+  public XMLElement getParent() {
+    Node parent = elem.getParentNode();
+    if (parent == null || Node.ELEMENT_NODE != parent.getNodeType()) {
+      return null;
+    }
+    return new XMLElement((Element) parent, writer);
+  }
+
+  public String getPrefix() {
+    return elem.getPrefix();
+  }
+
+  /**
+   * Determines whether the element has a given attribute.
+   */
+  public boolean hasAttribute(String name) {
+    return elem.hasAttribute(name);
+  }
+
+  public boolean hasChildNodes() {
+    return elem.hasChildNodes();
+  }
+
+  public String lookupPrefix(String prefix) {
+    return elem.lookupPrefix(prefix);
+  }
+
+  public void setAttribute(String name, String value) {
+    elem.setAttribute(name, value);
+  }
+
+  @Override
+  public String toString() {
+    return debugString;
+  }
+
+  private String getOpeningTag() {
+    StringBuilder b = new StringBuilder().append("<").append(elem.getTagName());
+
+    NamedNodeMap attrs = elem.getAttributes();
+    for (int i = 0; i < attrs.getLength(); i++) {
+      Attr attr = (Attr) attrs.item(i);
+      b.append(String.format(" %s='%s'", attr.getName(),
+          UiBinderWriter.escapeAttributeText(attr.getValue())));
+    }
+    b.append(">");
+
+    return b.toString();
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/AttributeMessage.java b/user/src/com/google/gwt/uibinder/rebind/messages/AttributeMessage.java
new file mode 100644
index 0000000..123704d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/AttributeMessage.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2008 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.messages;
+
+/**
+ * Associates an attribute name and a message expression. Can generate
+ * code that refers to the message.
+ */
+public class AttributeMessage {
+  private final String attribute;
+  private final String message;
+
+  public AttributeMessage(String attribute, String message) {
+    super();
+    this.attribute = attribute;
+    this.message = message;
+  }
+
+  public String getAttribute() {
+    return attribute;
+  }
+
+  /**
+   * Return an expression to fetch the message and escape it, suitable
+   * for concatenation into the middle of an innerHTML string.
+   */
+  public String getMessageAsHtmlAttribute() {
+    return message
+    + ".replaceAll(\"&\", \"&amp;\").replaceAll(\"'\", \"&#39;\")";
+  }
+
+  public String getMessageUnescaped() {
+    return message;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/GenerateAnnotationWriter.java b/user/src/com/google/gwt/uibinder/rebind/messages/GenerateAnnotationWriter.java
new file mode 100644
index 0000000..69a089a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/GenerateAnnotationWriter.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2008 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.messages;
+
+import com.google.gwt.uibinder.rebind.IndentedWriter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a {@literal @}Generate annotation in a Messages interface,
+ * and can write it out at code gen time.
+ */
+class GenerateAnnotationWriter {
+  private static String toArgsList(List<String> strings) {
+    StringBuilder b = new StringBuilder();
+    boolean first = true;
+    for (String s : strings) {
+      if (first)  {
+        first = false;
+      } else {
+        b.append(",\n  ");
+      }
+      b.append(s);
+    }
+    return b.toString();
+  }
+
+  private static String toArrayLiteral(String[] strings) {
+    StringBuilder b = new StringBuilder("{");
+    for (String s : strings) {
+      b.append(String.format("\"%s\", ", s));
+    }
+    b.append('}');
+    return b.toString();
+  }
+
+  private final String[] formats;
+  private final String fileName;
+  private final String[] locales;
+
+  public GenerateAnnotationWriter(String[] formats, String fileName,
+      String[] locales) {
+    this.formats = formats;
+    this.fileName = fileName;
+    this.locales = locales;
+  }
+
+  public void write(IndentedWriter w) {
+    boolean hasFormats = formats.length > 0;
+    boolean hasFileName = fileName.length() > 0;
+    boolean hasLocales = locales.length > 0;
+
+    if (hasFormats || hasFileName || hasLocales) {
+      List<String> args = new ArrayList<String>();
+      if (hasFormats) {
+        args.add(String.format("format = %s", toArrayLiteral(formats)));
+      }
+      if (hasFileName) {
+        args.add(String.format("fileName = \"%s\"", fileName));
+      }
+      if (hasLocales) {
+        args.add(String.format("locales = %s", toArrayLiteral(locales)));
+      }
+
+      w.write("@Generate(");
+      w.indent();
+      w.write(toArgsList(args));
+      w.outdent();
+      w.write(")");
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/MessageWriter.java b/user/src/com/google/gwt/uibinder/rebind/messages/MessageWriter.java
new file mode 100644
index 0000000..9c0ff70
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/MessageWriter.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2008 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.messages;
+
+import com.google.gwt.uibinder.rebind.IndentedWriter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a method in a Messages interface. Can write both the method
+ * declaration and its invocation.
+ */
+public class MessageWriter {
+  /**
+   * Escapes ' and { chars, which have special meaning to Messages
+   * interfaces.
+   */
+  public static String escapeMessageFormat(String messageFormatStyleText) {
+    StringBuilder b = new StringBuilder();
+    for (int i = 0; i < messageFormatStyleText.length(); i++) {
+      char c = messageFormatStyleText.charAt(i);
+      if (c == '\'') {
+        b.append("''");
+      } else if (c == '{') {
+        b.append("'{'");
+      } else {
+        b.append(c);
+      }
+    }
+    return b.toString();
+  }
+  private String defaultMessage;
+  private final String description;
+  private final String key;
+  private final String name;
+  private final List<PlaceholderWriter> placeholders =
+    new ArrayList<PlaceholderWriter>();
+
+  private final String meaning;
+
+  MessageWriter(String description, String key, String meaning,
+      String name) {
+    this.description = description;
+    this.key = key;
+    this.meaning = meaning;
+    this.name = name;
+  }
+
+  public void addPlaceholder(PlaceholderWriter placeholder) {
+    this.placeholders.add(placeholder);
+  }
+
+  public String getInvocation() {
+    StringBuilder b = new StringBuilder(String.format("%s(", name));
+    int countdown = placeholders.size();
+    for (PlaceholderWriter ph : placeholders) {
+      b.append(ph.getValue());
+      if (--countdown > 0) {
+        b.append(",");
+      }
+    }
+    b.append(")");
+    return b.toString();
+  }
+
+  public int getPlaceholderCount() {
+    return placeholders.size();
+  }
+
+  public void setDefaultMessage(String defaultMessage) {
+    this.defaultMessage = defaultMessage;
+  }
+
+  public void writeDeclaration(IndentedWriter pw) {
+    pw.write("@DefaultMessage(\"%s\")", defaultMessage);
+    if (description.length() > 0) {
+      pw.write("@Description(\"%s\")", description);
+    }
+    if (key.length() > 0) {
+      pw.write("@Key(\"%s\")", key);
+    }
+    if (meaning.length() > 0) {
+      pw.write("@Meaning(\"%s\")", meaning);
+    }
+    if (placeholders.isEmpty()) {
+      pw.write("String %s();", name);
+    } else {
+      pw.write("String %s(", name);
+      pw.indent();
+
+      int countdown = placeholders.size();
+      for (PlaceholderWriter ph : placeholders) {
+        String comma = --countdown > 0 ? "," : "";
+        pw.write(ph.getDeclaration() + comma);
+      }
+      pw.write(");");
+      pw.outdent();
+    }
+    pw.newline();
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java b/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
new file mode 100644
index 0000000..31b5fee
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/MessagesWriter.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2008 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.messages;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.IndentedWriter;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * TemplateWriter for messages.
+ */
+public class MessagesWriter {
+
+  public static final String ATTRIBUTE = "attribute";
+
+  private static final String MESSAGES_URI = "urn:messages:com.google.gwt.i18n";
+  private static final String NAME = "name";
+  private static final String[] EMPTY_ARRAY = {};
+
+  private final String messagesNamespaceURI;
+  private final String packageName;
+  private final String messagesClassName;
+  private final TreeLogger logger;
+  private final List<MessageWriter> messages = new ArrayList<MessageWriter>();
+  private final String generatedFrom;
+
+  private String defaultLocale;
+  private String messagesPrefix;
+  private String generateKeys;
+  private GenerateAnnotationWriter generate;
+
+  private Map<XMLElement, Collection<AttributeMessage>> elemToAttributeMessages =
+      new HashMap<XMLElement, Collection<AttributeMessage>>();
+
+  public MessagesWriter(String nameSpaceUri, TreeLogger logger, String generatedFrom,
+      String packageName, String implClassName) {
+    this.messagesNamespaceURI = nameSpaceUri;
+    this.generatedFrom = generatedFrom;
+    this.packageName = packageName;
+
+    // Localizable classes cannot have underscores in their names.
+    this.messagesClassName = implClassName.replaceAll("_", "") + "GenMessages";
+
+    this.logger = logger;
+  }
+
+  public MessagesWriter(TreeLogger logger, String generatedFrom,
+      String packageName, String implClassName) {
+    this(MESSAGES_URI, logger, generatedFrom, packageName, implClassName);
+  }
+
+  /**
+   * Call {@link #consumeAttributeMessages}, but instead of returning the
+   * results store them for retrieval by a later call to
+   * {@link #retrieveMessageAttributesFor}.
+   *
+   * @throws UnableToCompleteException on error
+   */
+  public void consumeAndStoreMessageAttributesFor(XMLElement elem)
+      throws UnableToCompleteException {
+    Collection<AttributeMessage> attributeMessages =
+        consumeAttributeMessages(elem);
+    if (!attributeMessages.isEmpty()) {
+      elemToAttributeMessages.put(elem, attributeMessages);
+    }
+  }
+
+  /**
+   * Examine the children of the given element. Consume those tagged m:attribute
+   * and return a set of {@link AttributeMessage} instances. E.g.:
+   * <p>
+   *
+   * <pre>
+   * &lt;img src="blueSky.jpg" alt="A blue sky">
+   *   &lt;m:attribute m:name="alt" description="blue sky image alt text"/>
+   * &lt;/img>
+   * </pre>
+   *
+   * <p>
+   */
+  public Collection<AttributeMessage> consumeAttributeMessages(XMLElement elem)
+      throws UnableToCompleteException {
+    Collection<XMLElement> messageChildren = getAttributeMessageChildren(elem);
+    if (messageChildren.isEmpty()) {
+      return Collections.emptySet();
+    }
+
+    Set<AttributeMessage> attributeMessages = new HashSet<AttributeMessage>();
+    for (XMLElement child : messageChildren) {
+      String attributeName = consumeMessageElementAttribute(NAME, child);
+      if (attributeName.length() == 0) {
+        die(String.format("Missing name attribute in %s", child));
+      }
+      if (!elem.hasAttribute(attributeName)) {
+        die(String.format("%s has no attribute matching %s", elem, child));
+      }
+
+      String defaultMessage =
+          MessageWriter.escapeMessageFormat(elem.consumeAttribute(attributeName));
+      defaultMessage =
+          UiBinderWriter.escapeTextForJavaStringLiteral(defaultMessage);
+      attributeMessages.add(new AttributeMessage(attributeName, declareMessage(
+          child, defaultMessage)));
+    }
+    return attributeMessages;
+  }
+
+  /**
+   * Consume an m:blah attribute on a non-message element, e.g.
+   * {@code <span m:ph="fnord"/>}
+   */
+  public String consumeMessageAttribute(String attName, XMLElement elem) {
+    String fullAttName = getMessagesPrefix() + ":" + attName;
+    return elem.consumeAttribute(fullAttName);
+  }
+
+  /**
+   * Declares a message created by a previous call to {@link #newMessage}, and
+   * returns its invocation expression to be stitched into an innerHTML block.
+   */
+  public String declareMessage(MessageWriter newMessage) {
+    messages.add(newMessage);
+    return String.format("messages.%s", newMessage.getInvocation());
+  }
+
+  /**
+   * Expected to be called with the root element, to allow configuration from
+   * various messages related attributes.
+   */
+  public void findMessagesConfig(XMLElement elem) {
+    String prefix = elem.lookupPrefix(getMessagesUri());
+    if (prefix != null) {
+      messagesPrefix = prefix;
+      defaultLocale = consumeMessageAttribute("defaultLocale", elem);
+      generateKeys = consumeMessageAttribute("generateKeys", elem);
+      generate =
+          new GenerateAnnotationWriter(getMessageAttributeStringArray(
+              "generateFormat", elem), consumeMessageAttribute(
+              "generateFilename", elem), getMessageAttributeStringArray(
+              "generateLocales", elem));
+    }
+  }
+
+  /**
+   * @return the expression that will instantiate the Messages interface
+   */
+  public String getDeclaration() {
+    return String.format(
+        "static %1$s messages = (%1$s) GWT.create(%1$s.class);",
+        getMessagesClassName());
+  }
+
+  public String getMessagesClassName() {
+    return messagesClassName;
+  }
+
+  /**
+   * @return The namespace prefix (not including :) declared by the template for
+   *         message elements and attributes
+   */
+  public String getMessagesPrefix() {
+    return messagesPrefix;
+  }
+
+  /**
+   * Confirm existence of an m:blah attribute on a non-message element, e.g.
+   * {@code <span m:ph="fnord"/>}
+   */
+  public boolean hasMessageAttribute(String attName, XMLElement elem) {
+    String fullAttName = getMessagesPrefix() + ":" + attName;
+    return elem.hasAttribute(fullAttName);
+  }
+
+  /**
+   * @return true iff any messages have been declared
+   */
+  public boolean hasMessages() {
+    return !messages.isEmpty();
+  }
+
+  public boolean isMessage(XMLElement elem) {
+    return isMessagePrefixed(elem) && "msg".equals(elem.getLocalName());
+  }
+
+  /**
+   * Creates a new MessageWriter instance with description, key and meaning
+   * values consumed from the given XMLElement. Note that this message will not
+   * be written in the generated code unless it is later declared via
+   * {@link #declareMessage(MessageWriter)}
+   */
+  public MessageWriter newMessage(XMLElement elem) {
+    MessageWriter newMessage =
+        new MessageWriter(consumeMessageElementAttribute("description", elem),
+            consumeMessageElementAttribute("key", elem),
+            consumeMessageElementAttribute("meaning", elem),
+            nextMessageName());
+    return newMessage;
+  }
+
+  /**
+   * @return The set of AttributeMessages that were found in elem and stored by
+   *         a previous call to {@link #consumeAndStoreMessageAttributesFor}
+   */
+  public Collection<AttributeMessage> retrieveMessageAttributesFor(
+      XMLElement elem) {
+    return elemToAttributeMessages.get(elem);
+  }
+
+  public void write(PrintWriter printWriter) {
+    IndentedWriter writer = new IndentedWriter(printWriter);
+
+    // Package declaration.
+    if (packageName.length() > 0) {
+      writer.write("package %1$s;", packageName);
+      writer.newline();
+    }
+
+    // Imports.
+    writer.write("import com.google.gwt.i18n.client.Messages;");
+    writer.write("import static com.google.gwt.i18n.client.LocalizableResource.*;");
+    writer.newline();
+
+    // Open interface.
+    genInterfaceAnnotations(writer);
+    writer.write("public interface %s extends Messages {", getMessagesClassName());
+    writer.newline();
+    writer.indent();
+
+    // Write message methods
+    for (MessageWriter m : messages) {
+      m.writeDeclaration(writer);
+    }
+
+    // Close interface.
+    writer.outdent();
+    writer.write("}");
+  }
+
+  /**
+   * Consume an attribute on a messages related element (as oppposed to a
+   * messages attribute in some other kind of element), e.g. the description in
+   * {@code <m:msg description="described!">}
+   */
+  String consumeMessageElementAttribute(String attName, XMLElement elem) {
+    if (elem.hasAttribute(attName)) {
+      return UiBinderWriter.escapeTextForJavaStringLiteral(elem.consumeAttribute(attName));
+    }
+
+    String fullAttName = getMessagesPrefix() + ":" + attName;
+    if (elem.hasAttribute(fullAttName)) {
+      String value = elem.consumeAttribute(fullAttName);
+      logger.log(TreeLogger.WARN, String.format(
+          "In %s, deprecated prefix \"%s:\" on \"%s\". Use \"%s\" instead.",
+          elem, getMessagesPrefix(), fullAttName, attName));
+      return value;
+    }
+
+    return "";
+  }
+
+  String consumeRequiredMessageElementAttribute(String attName,
+      XMLElement elem) throws UnableToCompleteException {
+    String value = consumeMessageElementAttribute(attName, elem);
+    if ("".equals(value)) {
+      die("%s does not have required attribute %s", elem, attName);
+    }
+    return value;
+  }
+
+  boolean isMessagePrefixed(XMLElement elem) {
+    String uri = elem.getNamespaceUri();
+    return uri != null && uri.startsWith(getMessagesUri());
+  }
+
+  private String declareMessage(XMLElement elem, String defaultMessage)
+      throws UnableToCompleteException {
+    List<PlaceholderWriter> emptyList = Collections.emptyList();
+    MessageWriter newMessage = newMessage(elem);
+    newMessage.setDefaultMessage(defaultMessage);
+    for (PlaceholderWriter placeholder : emptyList) {
+      newMessage.addPlaceholder(placeholder);
+    }
+    return declareMessage(newMessage);
+  }
+
+  private void die(String message) throws UnableToCompleteException {
+    // TODO(rjrjr) copied from TemplateWriter. Move to common superclass or
+    // something
+    logger.log(TreeLogger.ERROR, message);
+    throw new UnableToCompleteException();
+  }
+
+  /**
+   * Post an error message and halt processing. This method always throws an
+   * {@link UnableToCompleteException}
+   */
+  private void die(String message, Object... params)
+      throws UnableToCompleteException {
+    // TODO(rjrjr) copied from TemplateWriter. Move to common superclass or
+    // something
+    logger.log(TreeLogger.ERROR, String.format(message, params));
+    throw new UnableToCompleteException();
+  }
+
+  private void genInterfaceAnnotations(IndentedWriter pw) {
+    pw.write("@GeneratedFrom(\"%s\")", generatedFrom);
+    if (defaultLocale.length() > 0) {
+      pw.write("@DefaultLocale(\"%s\")", defaultLocale);
+    }
+    if (generateKeys.length() > 0) {
+      pw.write("@GenerateKeys(\"%s\")", generateKeys);
+    }
+    generate.write(pw);
+  }
+
+  private Collection<XMLElement> getAttributeMessageChildren(
+      final XMLElement elem) throws UnableToCompleteException {
+    return elem.consumeChildElements(new XMLElement.Interpreter<Boolean>() {
+      public Boolean interpretElement(XMLElement child)
+          throws UnableToCompleteException {
+        if (isAttributeMessage(child)) {
+          if (child.hasChildNodes()) {
+            die(String.format("Illegal body for %s in %s.", child, elem));
+          }
+          return true;
+        }
+
+        return false;
+      }
+    });
+  }
+
+  private String[] getMessageAttributeStringArray(String attName,
+      XMLElement elem) {
+    String value = consumeMessageAttribute(attName, elem);
+    if (value == null) {
+      return EMPTY_ARRAY;
+    }
+    return value.split("\\s*,\\s*");
+  }
+
+  private String getMessagesUri() {
+    return messagesNamespaceURI;
+  }
+
+  private boolean isAttributeMessage(XMLElement elem) {
+    return isMessagePrefixed(elem) && ATTRIBUTE.equals(elem.getLocalName());
+  }
+
+  private String nextMessageName() {
+    return String.format("message%d", messages.size() + 1);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java
new file mode 100644
index 0000000..3d55938
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderInterpreter.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2008 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.messages;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.uibinder.rebind.Tokenator;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.uibinder.rebind.Tokenator.Resolver;
+import com.google.gwt.uibinder.rebind.XMLElement.PostProcessingInterpreter;
+
+/**
+ * Abstract class performs the heavy lifting for processing ph children of a msg
+ * element.
+ */
+public abstract class PlaceholderInterpreter implements
+    PostProcessingInterpreter<String> {
+
+  protected final UiBinderWriter uiWriter;
+  protected final MessageWriter message;
+  protected final Tokenator tokenator = new Tokenator();
+
+  public PlaceholderInterpreter(UiBinderWriter writer, MessageWriter message) {
+    this.uiWriter = writer;
+    this.message = message;
+  }
+
+  public String interpretElement(XMLElement elem)
+      throws UnableToCompleteException {
+
+    if (isPlaceholderElement(elem)) {
+      /*
+       * The innerHTML or innerText of the <m:ph> will be provided as the value
+       * of the appropriate parameter when the Messages method is called.
+       *
+       * E.g.
+       *
+       *   <m:msg>Able <m:ph name="foo" example"baz">baker</m:ph> charlie</m:msg>
+       *
+       * becomes
+       *
+       *   @Default("Able {0} charlie)
+       *   String message1(@Example("baz") String foo)
+       *
+       * and in the generated innerHTML is invoked
+       *
+       *   message1("baker")
+       */
+
+      MessagesWriter mw = getMessagesWriter();
+      String name = mw.consumeRequiredMessageElementAttribute("name", elem);
+      String example = mw.consumeMessageElementAttribute("example", elem);
+      String value = consumePlaceholderInnards(elem);
+
+      // Use the value as the example if none is provided
+      if ("".equals(example)) {
+
+        /*
+         * The value may contain tokens vended by the TemplateWriter, which will
+         * be substituted for runtime-computed values (like unique dom ids). We
+         * don't want those in the example text shown to the translator, so snip
+         * them
+         */
+        example = stripTokens(value);
+      }
+
+      /*
+       * Again, if there are TemplateWriter tokens in the value string, we need
+       * it to have it replace them with the real expresions.
+       */
+      value = uiWriter.detokenate(value);
+
+      return nextPlaceholder(name, example, value);
+    }
+
+    if (uiWriter.isWidget(elem)) {
+      uiWriter.die("Found %s in a message that cannot contain widgets", elem);
+    }
+    return null;
+  }
+
+  /**
+   * Called by various {@link XMLElement} consumeInner*() methods after all
+   * elements have been handed to {@link #interpretElement}. Performs escaping
+   * on the consumed text to make it safe for use as a Messages {@literal @}Default
+   * value.
+   */
+  public String postProcess(String consumed) throws UnableToCompleteException {
+    return tokenator.detokenate(MessageWriter.escapeMessageFormat(consumed));
+  }
+
+  protected abstract String consumePlaceholderInnards(XMLElement elem)
+      throws UnableToCompleteException;
+
+  protected String nextPlaceholder(String name, String example, String value) {
+    message.addPlaceholder(new PlaceholderWriter(name, example, value));
+    return tokenator.nextToken(String.format("{%d}",
+        message.getPlaceholderCount() - 1));
+  }
+
+  protected String stripTokens(String value) {
+    String rtn = Tokenator.detokenate(value, new Resolver() {
+      public String resolveToken(String token) {
+        return "";
+      }
+    });
+    return rtn;
+  }
+
+  private MessagesWriter getMessagesWriter() {
+    return uiWriter.getMessages();
+  }
+
+  private boolean isPlaceholderElement(XMLElement elem) {
+    return getMessagesWriter().isMessagePrefixed(elem)
+        && "ph".equals(elem.getLocalName());
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderWriter.java b/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderWriter.java
new file mode 100644
index 0000000..127b3d3
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/messages/PlaceholderWriter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2008 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.messages;
+
+/**
+ * Represents a parameter in a Messages interface method, and
+ * can write out its declaration.
+ */
+class PlaceholderWriter {
+  private final String name;
+  private final String example;
+  private final String value;
+
+  /**
+   * @param name Parameter name for this placeholder
+   * @param example Contents of the {@literal @}Example annotation
+   * @param value The value to provide for this param when writing
+   * an invocation of its message method.
+   */
+  public PlaceholderWriter(String name, String example, String value) {
+    this.name = name;
+    this.example = inQuotes(example);
+    this.value = inQuotes(value);
+  }
+
+  public String getDeclaration() {
+    return String.format("@Example(%s) String %s", example, name);
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  private String inQuotes(String s) {
+    return String.format("\"%s\"", s);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
new file mode 100644
index 0000000..00381df
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerClass.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2009 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.model;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+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.uibinder.client.UiFactory;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Model class with all attributes of the owner class.
+ * This includes factories, fields and handlers.
+ */
+public class OwnerClass {
+
+  /**
+   * Map from field name to model.
+   */
+  private final Map<String, OwnerField> uiFields =
+      new HashMap<String, OwnerField>();
+
+  /**
+   * Map from field type to model.
+   *
+   * This is used for binding resources - for widgets, there may be multiple
+   * widgets with the same type, in which case this becomes meaningless.
+   */
+  private final Map<JClassType, OwnerField> uiFieldTypes =
+      new HashMap<JClassType, OwnerField>();
+
+  /**
+   * Map from type to the method that produces it.
+   */
+  private final Map<JClassType, JMethod> uiFactories =
+      new HashMap<JClassType, JMethod>();
+
+  /**
+   * List of all @UiHandler methods in the owner class.
+   */
+  private final List<JMethod> uiHandlers = new ArrayList<JMethod>();
+
+  /**
+   * Constructor.
+   *
+   * @param ownerType the type of the owner class
+   */
+  public OwnerClass(JClassType ownerType) throws UnableToCompleteException {
+    findUiFields(ownerType);
+    findUiFactories(ownerType);
+    findUiHandlers(ownerType);
+  }
+
+  /**
+   * Returns the method annotated with @UiFactory which returns the given type.
+   *
+   * @param forType the type to look for a factory of
+   * @return the factory method, or null if none exists
+   */
+  public JMethod getUiFactoryMethod(JClassType forType) {
+    return uiFactories.get(forType);
+  }
+
+  /**
+   * Returns the method annotated with @UiFactory which returns the given type.
+   *
+   * @param forType the type to look for a factory of
+   * @return the factory method, or null if none exists
+   */
+  public JMethod getUiFactoryMethod(OwnerFieldClass forType) {
+    return getUiFactoryMethod(forType.getRawType());
+  }
+
+  /**
+   * Gets a field with the given name.
+   * It's important to notice that a field may not exist on the owner class even
+   * if it has a name in the XML and even has handlers attached to it -  such a
+   * field will only exist in the generated binder class.
+   *
+   * @param name the name of the field to get
+   * @return the field descriptor, or null if the owner doesn't have that field
+   */
+  public OwnerField getUiField(String name) {
+    return uiFields.get(name);
+  }
+
+  /**
+   * Gets the field with the given type.
+   * Note that multiple fields can have the same type, so it only makes sense to
+   * call this to retrieve resource fields, such as messages and image bundles,
+   * for which only one instance is expected.
+   *
+   * @param type the type of the field
+   * @return the field descriptor
+   */
+  public OwnerField getUiFieldForType(JClassType type) {
+    return uiFieldTypes.get(type);
+  }
+
+  /**
+   * Returns a collection of all fields in the owner class.
+   */
+  public Collection<OwnerField> getUiFields() {
+    return uiFields.values();
+  }
+
+  /**
+   * Returns all the UiHandler methods defined in the owner class.
+   */
+  public List<JMethod> getUiHandlers() {
+    return uiHandlers;
+  }
+
+  /**
+   * Scans the owner class to find all methods annotated with @UiFactory, and
+   * puts them in {@link #uiFactories}.
+   * 
+   * @param ownerType the type of the owner class
+   * @throws UnableToCompleteException 
+   */
+  private void findUiFactories(JClassType ownerType)
+      throws UnableToCompleteException {
+    JMethod[] methods = ownerType.getMethods();
+    for (JMethod method : methods) {
+      if (method.isAnnotationPresent(UiFactory.class)) {
+        JClassType factoryType = method.getReturnType().isClassOrInterface();
+
+        if (factoryType == null) {
+          // TODO(rdamazio): proper logging
+          System.out.println("Factory return type is not a class in method "
+              + method.getName());
+          throw new UnableToCompleteException();
+        }
+
+        if (uiFactories.containsKey(factoryType)) {
+          // TODO(rdamazio): proper logging
+          System.out.println("Duplicate factory in class "
+              + method.getEnclosingType().getName() + " for type "
+              + factoryType.getName());
+          throw new UnableToCompleteException();
+        }
+
+        uiFactories.put(factoryType, method);
+      }
+    }
+
+    // Recurse to superclass
+    JClassType superclass = ownerType.getSuperclass();
+    if (superclass != null) {
+      findUiFactories(superclass);
+    }
+  }
+
+  /**
+   * Scans the owner class to find all fields annotated with @UiField, and puts
+   * them in {@link #uiFields} and {@link #uiFieldTypes}.
+   * 
+   * @param ownerType the type of the owner class
+   */
+  private void findUiFields(JClassType ownerType)
+      throws UnableToCompleteException {
+    JField[] fields = ownerType.getFields();
+    for (JField field : fields) {
+      if (field.isAnnotationPresent(UiField.class)) {
+        JClassType ownerFieldType = field.getType().isClassOrInterface();
+
+        if (ownerFieldType == null) {
+          // TODO(rdamazio): proper logging
+          System.out.println("Field type is not a class in field "
+              + field.getName());
+          throw new UnableToCompleteException();
+        }
+
+        OwnerField ownerField = new OwnerField(field);
+        String ownerFieldName = field.getName();
+
+        uiFields.put(ownerFieldName, ownerField);
+        uiFieldTypes.put(ownerFieldType, ownerField);
+      }
+    }
+
+    // Recurse to superclass
+    JClassType superclass = ownerType.getSuperclass();
+    if (superclass != null) {
+      findUiFields(superclass);
+    }
+  }
+
+  /**
+   * Scans the owner class to find all methods annotated with @UiHandler, and
+   * adds them to their respective fields.
+   *
+   * @param ownerType the type of the owner class
+   */
+  private void findUiHandlers(JClassType ownerType) {
+    JMethod[] methods = ownerType.getMethods();
+    for (JMethod method : methods) {
+      if (method.isAnnotationPresent(UiHandler.class)) {
+        uiHandlers.add(method);
+      }
+    }
+
+    // Recurse to superclass
+    JClassType superclass = ownerType.getSuperclass();
+    if (superclass != null) {
+      findUiHandlers(superclass);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
new file mode 100644
index 0000000..73e7edd
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerField.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2009 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.model;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.uibinder.client.UiField;
+
+/**
+ * Descriptor for a field of the owner class.
+ *
+ * Please notice that some fields defined in the XML and in the generated binder
+ * class may not be present in the owner class - for instance, they may not be
+ * relevant to the code of the owner class.
+ * The fields in the binder class are instead represented by an instance of
+ * {@link com.google.gwt.uibinder.rebind.FieldWriter}.
+ */
+public class OwnerField {
+  private final String name;
+  private final OwnerFieldClass fieldType;
+  private final boolean isProvided;
+
+  /**
+   * Constructor.
+   *
+   * @param field the field of the owner class
+   */
+  public OwnerField(JField field) throws UnableToCompleteException {
+    this.name = field.getName();
+
+    // Get the field type and ensure it's a class or interface
+    JClassType fieldClassType = field.getType().isClassOrInterface();
+
+    if (fieldClassType == null) {
+      // TODO(rdamazio): proper logging
+      System.out.println("Type for field " + name + " is not a class: "
+          + field.getType().getSimpleSourceName());
+      throw new UnableToCompleteException();
+    }
+
+    // TODO(rdamazio): For non-widget classes (resources), this will be useless
+    //                 since there are no setters, no uiconstructor, etc.
+    this.fieldType = OwnerFieldClass.getFieldClass(fieldClassType);
+
+    // Get the UiField annotation and process it
+    UiField annotation = field.getAnnotation(UiField.class);
+
+    if (annotation == null) {
+      // TODO(rdamazio): proper logging
+      System.out.println("Field " + name + " is not annotated with @UiField");
+      throw new UnableToCompleteException();
+    }
+
+    isProvided = annotation.provided();
+  }
+
+  /**
+   * Returns the name of the field in the owner class.
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Returns a descriptor for the type of the field. 
+   */
+  public OwnerFieldClass getType() {
+    return fieldType;
+  }
+
+  /**
+   * Returns whether this field's value is provided by owner class.
+   * If it's not provided, then it's the binder's responsibility to assign it.
+   */
+  public boolean isProvided() {
+    return isProvided;
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java b/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java
new file mode 100644
index 0000000..f102a6f
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/rebind/model/OwnerFieldClass.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2009 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.model;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JConstructor;
+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.uibinder.client.UiConstructor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Descriptor for a class which can be used as a @UiField.
+ * This is usually a widget, but can also be a resource (such as Messages or
+ * an ImageBundle). Also notice that the existence of an OwnerFieldClass doesn't
+ * mean the class is actually present as a field in the owner.
+ */
+public class OwnerFieldClass {
+
+  /**
+   * Global map of field classes.
+   * This serves as a cache so each class is only processed once.
+   */
+  private static final Map<JClassType, OwnerFieldClass> FIELD_CLASSES =
+      new HashMap<JClassType, OwnerFieldClass>();
+
+  /**
+   * Gets or creates the descriptor for the given field class.
+   *
+   * @param forType the field type to get a descriptor for
+   * @return the descriptor
+   */
+  public static OwnerFieldClass getFieldClass(JClassType forType)
+      throws UnableToCompleteException {
+    OwnerFieldClass clazz = FIELD_CLASSES.get(forType);
+    if (clazz == null) {
+      clazz = new OwnerFieldClass(forType);
+      FIELD_CLASSES.put(forType, clazz); 
+    }
+    return clazz;
+  }
+
+  private final JClassType rawType;
+  private final Map<String, JMethod> setters = new HashMap<String, JMethod>();
+  private Set<String> ambiguousSetters;
+  private JConstructor uiConstructor;
+
+  /**
+   * Default constructor.
+   * This is package-visible for testing only.
+   *
+   * @param forType the type of the field class
+   * @throws UnableToCompleteException if the class is not valid
+   */
+  OwnerFieldClass(JClassType forType) throws UnableToCompleteException {
+    this.rawType = forType;
+
+    findUiConstructor(forType);
+    findSetters(forType);
+  }
+
+  /**
+   * Returns the field's raw type.
+   */
+  public JClassType getRawType() {
+    return rawType;
+  }
+
+  /**
+   * Finds the setter method for a given property.
+   *
+   * @param propertyName the name of the property
+   * @return the setter method, or null if none exists
+   */
+  public JMethod getSetter(String propertyName)
+      throws UnableToCompleteException {
+    if (ambiguousSetters != null && ambiguousSetters.contains(propertyName)) {
+      // TODO(rdamazio): proper logging
+      System.out.println("Ambiguous setter requested: " + rawType.getName()
+          + "." + propertyName);
+      throw new UnableToCompleteException();
+    }
+
+    return setters.get(propertyName);
+  }
+
+  /**
+   * Returns the constructor annotated with @UiConstructor, or null if none
+   * exists.
+   */
+  public JConstructor getUiConstructor() {
+    return uiConstructor;
+  }
+
+  /**
+   * Given a collection of setters for the same property, picks which one to
+   * use.
+   * Not having a proper setter is not an error unless of course the user tries
+   * to use it.
+   *
+   * @param propertySetters the collection of setters
+   * @return the setter to use, or null if none is good enough
+   */
+  private JMethod disambiguateSetters(Collection<JMethod> propertySetters) {
+    if (propertySetters.size() == 1) {
+      return propertySetters.iterator().next();
+    }
+
+    // Pick the string setter, if there's one
+    for (JMethod method : propertySetters) {
+      JParameter[] parameters = method.getParameters();
+      if (parameters.length == 1
+          && parameters[0].getType().getQualifiedSourceName().equals(
+              "java.lang.String")) {
+        return method;
+      }
+    }
+
+    // Check if all setters aren't just the same one being overridden in parent
+    // classes.
+    JMethod firstMethod = null;
+    for (JMethod method : propertySetters) {
+      if (firstMethod == null) {
+        firstMethod = method;
+        continue;
+      }
+
+      // If the method is not the same as the first one, there's still an
+      // ambiguity. Being equal means having the same parameter types.
+      if (!sameParameterTypes(method, firstMethod)) {
+        return null;
+      }
+    }
+
+    return firstMethod;
+  }
+
+  /**
+   * Recursively finds all setters for the given class and its superclasses.
+   *
+   * @param fieldType the leaf type to look at
+   * @return a multimap of property name to the setter methods
+   */
+  private Map<String, Collection<JMethod>> findAllSetters(
+      JClassType fieldType) {
+    Map<String, Collection<JMethod>> allSetters;
+
+    // First, get all setters from the parent class, recursively.
+    JClassType superClass = fieldType.getSuperclass();
+    if (superClass != null) {
+      allSetters = findAllSetters(superClass);
+    } else {
+      // Stop recursion - deepest level creates return value
+      allSetters = new HashMap<String, Collection<JMethod>>();
+    }
+
+    JMethod[] methods = fieldType.getMethods();
+    for (JMethod method : methods) {
+      if (!isSetterMethod(method)) {
+        continue;
+      }
+
+      // Take out "set"
+      String propertyName = method.getName().substring(3);
+
+      // turn "PropertyName" into "propertyName"
+      propertyName = propertyName.substring(0, 1).toLowerCase()
+          + propertyName.substring(1);
+
+      Collection<JMethod> propertyMethods = allSetters.get(propertyName);
+      if (propertyMethods == null) {
+        propertyMethods = new ArrayList<JMethod>();
+        allSetters.put(propertyName, propertyMethods);
+      }
+
+      propertyMethods.add(method);
+    }
+
+    return allSetters;
+  }
+
+  /**
+   * Finds all setters in the class, and puts them in the {@link #setters}
+   * field.
+   *
+   * @param fieldType the type of the field
+   */
+  private void findSetters(JClassType fieldType) {
+    // Pass one - get all setter methods
+    Map<String, Collection<JMethod>> allSetters = findAllSetters(fieldType);
+
+    // Pass two - disambiguate
+    for (String propertyName : allSetters.keySet()) {
+      Collection<JMethod> propertySetters = allSetters.get(propertyName);
+      JMethod setter = disambiguateSetters(propertySetters);
+      
+      // If no setter could be disambiguated for this property, add it to the
+      // set of ambiguous setters. This is later consulted if and only if the
+      // setter is used.
+      if (setter == null) {
+        if (ambiguousSetters == null) {
+          ambiguousSetters = new HashSet<String>();
+        }
+
+        ambiguousSetters.add(propertyName);
+      }
+
+      setters.put(propertyName, setter);
+    }
+  }
+
+  /**
+   * Finds the constructor annotated with @UiConcontructor if there is one, and
+   * puts it in the {@link #uiConstructor} field.
+   *
+   * @param fieldType the type of the field
+   */
+  private void findUiConstructor(JClassType fieldType)
+      throws UnableToCompleteException {
+    for (JConstructor ctor : fieldType.getConstructors()) {
+      if (ctor.getAnnotation(UiConstructor.class) != null) {
+        if (uiConstructor != null) {
+          // TODO(rdamazio): proper logging
+          System.out.println(fieldType.getName()
+              + " has more than one constructor annotated with @UiConstructor");
+          throw new UnableToCompleteException();
+        }
+        uiConstructor = ctor;
+      }
+    }
+  }
+
+  /**
+   * Checks whether the given method qualifies as a setter.
+   * This looks at the method qualifiers, name and return type, but not at the
+   * parameter types.
+   *
+   * @param method the method to look at
+   * @return whether it's a setter
+   */
+  private boolean isSetterMethod(JMethod method) {
+    // All setter methods should be public void setSomething(...)
+    return method.isPublic() && !method.isStatic()
+        && method.getName().startsWith("set")
+        && method.getReturnType() == JPrimitiveType.VOID;
+  }
+
+  /**
+   * Checks whether two methods have the same parameter types.
+   *
+   * @param m1 the first method to compare
+   * @param m2 the second method to compare
+   * @return whether the methods have the same parameter types
+   */
+  private boolean sameParameterTypes(JMethod m1, JMethod m2) {
+    JParameter[] p1 = m1.getParameters();
+    JParameter[] p2 = m2.getParameters();
+
+    if (p1.length != p2.length) {
+      return false;
+    }
+
+    for (int i = 0; i < p1.length; i++) {
+      JType type1 = p1[i].getType();
+      JType type2 = p2[i].getType();
+
+      if (!type1.equals(type2)) {
+        return false;
+      }
+    }
+
+    // No types were different
+    return true;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/resources/xhtml.ent b/user/src/com/google/gwt/uibinder/resources/xhtml.ent
new file mode 100644
index 0000000..aa80147
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/resources/xhtml.ent
@@ -0,0 +1,488 @@
+<!--
+  Portions Copyright 1986 International Organization for Standardization.
+  Permission to copy in any form is granted for use with conforming SGML
+  systems and applications as defined in ISO 8879, provided this notice is
+  included in all copies.
+-->
+
+<!--
+  This is the complete set of named character entites defined in XHTML1.0. It's
+  essentially a union of:
+
+    http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent
+    http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent
+    http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent
+-->
+
+<!-- Latin-1 characters -->
+
+  <!ENTITY nbsp   "&#160;"> <!-- no-break space = non-breaking space,
+                                    U+00A0 ISOnum -->
+  <!ENTITY iexcl  "&#161;"> <!-- inverted exclamation mark, U+00A1 ISOnum -->
+  <!ENTITY cent   "&#162;"> <!-- cent sign, U+00A2 ISOnum -->
+  <!ENTITY pound  "&#163;"> <!-- pound sign, U+00A3 ISOnum -->
+  <!ENTITY curren "&#164;"> <!-- currency sign, U+00A4 ISOnum -->
+  <!ENTITY yen    "&#165;"> <!-- yen sign = yuan sign, U+00A5 ISOnum -->
+  <!ENTITY brvbar "&#166;"> <!-- broken bar = broken vertical bar,
+                                    U+00A6 ISOnum -->
+  <!ENTITY sect   "&#167;"> <!-- section sign, U+00A7 ISOnum -->
+  <!ENTITY uml    "&#168;"> <!-- diaeresis = spacing diaeresis,
+                                    U+00A8 ISOdia -->
+  <!ENTITY copy   "&#169;"> <!-- copyright sign, U+00A9 ISOnum -->
+  <!ENTITY ordf   "&#170;"> <!-- feminine ordinal indicator, U+00AA ISOnum -->
+  <!ENTITY laquo  "&#171;"> <!-- left-pointing double angle quotation mark
+                                    = left pointing guillemet, U+00AB ISOnum -->
+  <!ENTITY not    "&#172;"> <!-- not sign = angled dash,
+                                    U+00AC ISOnum -->
+  <!ENTITY shy    "&#173;"> <!-- soft hyphen = discretionary hyphen,
+                                    U+00AD ISOnum -->
+  <!ENTITY reg    "&#174;"> <!-- registered sign = registered trade mark sign,
+                                    U+00AE ISOnum -->
+  <!ENTITY macr   "&#175;"> <!-- macron = spacing macron = overline
+                                    = APL overbar, U+00AF ISOdia -->
+  <!ENTITY deg    "&#176;"> <!-- degree sign, U+00B0 ISOnum -->
+  <!ENTITY plusmn "&#177;"> <!-- plus-minus sign = plus-or-minus sign,
+                                    U+00B1 ISOnum -->
+  <!ENTITY sup2   "&#178;"> <!-- superscript two = superscript digit two
+                                    = squared, U+00B2 ISOnum -->
+  <!ENTITY sup3   "&#179;"> <!-- superscript three = superscript digit three
+                                    = cubed, U+00B3 ISOnum -->
+  <!ENTITY acute  "&#180;"> <!-- acute accent = spacing acute,
+                                    U+00B4 ISOdia -->
+  <!ENTITY micro  "&#181;"> <!-- micro sign, U+00B5 ISOnum -->
+  <!ENTITY para   "&#182;"> <!-- pilcrow sign = paragraph sign,
+                                    U+00B6 ISOnum -->
+  <!ENTITY middot "&#183;"> <!-- middle dot = Georgian comma
+                                    = Greek middle dot, U+00B7 ISOnum -->
+  <!ENTITY cedil  "&#184;"> <!-- cedilla = spacing cedilla, U+00B8 ISOdia -->
+  <!ENTITY sup1   "&#185;"> <!-- superscript one = superscript digit one,
+                                    U+00B9 ISOnum -->
+  <!ENTITY ordm   "&#186;"> <!-- masculine ordinal indicator,
+                                    U+00BA ISOnum -->
+  <!ENTITY raquo  "&#187;"> <!-- right-pointing double angle quotation mark
+                                    = right pointing guillemet, U+00BB ISOnum -->
+  <!ENTITY frac14 "&#188;"> <!-- vulgar fraction one quarter
+                                    = fraction one quarter, U+00BC ISOnum -->
+  <!ENTITY frac12 "&#189;"> <!-- vulgar fraction one half
+                                    = fraction one half, U+00BD ISOnum -->
+  <!ENTITY frac34 "&#190;"> <!-- vulgar fraction three quarters
+                                    = fraction three quarters, U+00BE ISOnum -->
+  <!ENTITY iquest "&#191;"> <!-- inverted question mark
+                                    = turned question mark, U+00BF ISOnum -->
+  <!ENTITY Agrave "&#192;"> <!-- latin capital letter A with grave
+                                    = latin capital letter A grave,
+                                    U+00C0 ISOlat1 -->
+  <!ENTITY Aacute "&#193;"> <!-- latin capital letter A with acute,
+                                    U+00C1 ISOlat1 -->
+  <!ENTITY Acirc  "&#194;"> <!-- latin capital letter A with circumflex,
+                                    U+00C2 ISOlat1 -->
+  <!ENTITY Atilde "&#195;"> <!-- latin capital letter A with tilde,
+                                    U+00C3 ISOlat1 -->
+  <!ENTITY Auml   "&#196;"> <!-- latin capital letter A with diaeresis,
+                                    U+00C4 ISOlat1 -->
+  <!ENTITY Aring  "&#197;"> <!-- latin capital letter A with ring above
+                                    = latin capital letter A ring,
+                                    U+00C5 ISOlat1 -->
+  <!ENTITY AElig  "&#198;"> <!-- latin capital letter AE
+                                    = latin capital ligature AE,
+                                    U+00C6 ISOlat1 -->
+  <!ENTITY Ccedil "&#199;"> <!-- latin capital letter C with cedilla,
+                                    U+00C7 ISOlat1 -->
+  <!ENTITY Egrave "&#200;"> <!-- latin capital letter E with grave,
+                                    U+00C8 ISOlat1 -->
+  <!ENTITY Eacute "&#201;"> <!-- latin capital letter E with acute,
+                                    U+00C9 ISOlat1 -->
+  <!ENTITY Ecirc  "&#202;"> <!-- latin capital letter E with circumflex,
+                                    U+00CA ISOlat1 -->
+  <!ENTITY Euml   "&#203;"> <!-- latin capital letter E with diaeresis,
+                                    U+00CB ISOlat1 -->
+  <!ENTITY Igrave "&#204;"> <!-- latin capital letter I with grave,
+                                    U+00CC ISOlat1 -->
+  <!ENTITY Iacute "&#205;"> <!-- latin capital letter I with acute,
+                                    U+00CD ISOlat1 -->
+  <!ENTITY Icirc  "&#206;"> <!-- latin capital letter I with circumflex,
+                                    U+00CE ISOlat1 -->
+  <!ENTITY Iuml   "&#207;"> <!-- latin capital letter I with diaeresis,
+                                    U+00CF ISOlat1 -->
+  <!ENTITY ETH    "&#208;"> <!-- latin capital letter ETH, U+00D0 ISOlat1 -->
+  <!ENTITY Ntilde "&#209;"> <!-- latin capital letter N with tilde,
+                                    U+00D1 ISOlat1 -->
+  <!ENTITY Ograve "&#210;"> <!-- latin capital letter O with grave,
+                                    U+00D2 ISOlat1 -->
+  <!ENTITY Oacute "&#211;"> <!-- latin capital letter O with acute,
+                                    U+00D3 ISOlat1 -->
+  <!ENTITY Ocirc  "&#212;"> <!-- latin capital letter O with circumflex,
+                                    U+00D4 ISOlat1 -->
+  <!ENTITY Otilde "&#213;"> <!-- latin capital letter O with tilde,
+                                    U+00D5 ISOlat1 -->
+  <!ENTITY Ouml   "&#214;"> <!-- latin capital letter O with diaeresis,
+                                    U+00D6 ISOlat1 -->
+  <!ENTITY times  "&#215;"> <!-- multiplication sign, U+00D7 ISOnum -->
+  <!ENTITY Oslash "&#216;"> <!-- latin capital letter O with stroke
+                                    = latin capital letter O slash,
+                                    U+00D8 ISOlat1 -->
+  <!ENTITY Ugrave "&#217;"> <!-- latin capital letter U with grave,
+                                    U+00D9 ISOlat1 -->
+  <!ENTITY Uacute "&#218;"> <!-- latin capital letter U with acute,
+                                    U+00DA ISOlat1 -->
+  <!ENTITY Ucirc  "&#219;"> <!-- latin capital letter U with circumflex,
+                                    U+00DB ISOlat1 -->
+  <!ENTITY Uuml   "&#220;"> <!-- latin capital letter U with diaeresis,
+                                    U+00DC ISOlat1 -->
+  <!ENTITY Yacute "&#221;"> <!-- latin capital letter Y with acute,
+                                    U+00DD ISOlat1 -->
+  <!ENTITY THORN  "&#222;"> <!-- latin capital letter THORN,
+                                    U+00DE ISOlat1 -->
+  <!ENTITY szlig  "&#223;"> <!-- latin small letter sharp s = ess-zed,
+                                    U+00DF ISOlat1 -->
+  <!ENTITY agrave "&#224;"> <!-- latin small letter a with grave
+                                    = latin small letter a grave,
+                                    U+00E0 ISOlat1 -->
+  <!ENTITY aacute "&#225;"> <!-- latin small letter a with acute,
+                                    U+00E1 ISOlat1 -->
+  <!ENTITY acirc  "&#226;"> <!-- latin small letter a with circumflex,
+                                    U+00E2 ISOlat1 -->
+  <!ENTITY atilde "&#227;"> <!-- latin small letter a with tilde,
+                                    U+00E3 ISOlat1 -->
+  <!ENTITY auml   "&#228;"> <!-- latin small letter a with diaeresis,
+                                    U+00E4 ISOlat1 -->
+  <!ENTITY aring  "&#229;"> <!-- latin small letter a with ring above
+                                    = latin small letter a ring,
+                                    U+00E5 ISOlat1 -->
+  <!ENTITY aelig  "&#230;"> <!-- latin small letter ae
+                                    = latin small ligature ae, U+00E6 ISOlat1 -->
+  <!ENTITY ccedil "&#231;"> <!-- latin small letter c with cedilla,
+                                    U+00E7 ISOlat1 -->
+  <!ENTITY egrave "&#232;"> <!-- latin small letter e with grave,
+                                    U+00E8 ISOlat1 -->
+  <!ENTITY eacute "&#233;"> <!-- latin small letter e with acute,
+                                    U+00E9 ISOlat1 -->
+  <!ENTITY ecirc  "&#234;"> <!-- latin small letter e with circumflex,
+                                    U+00EA ISOlat1 -->
+  <!ENTITY euml   "&#235;"> <!-- latin small letter e with diaeresis,
+                                    U+00EB ISOlat1 -->
+  <!ENTITY igrave "&#236;"> <!-- latin small letter i with grave,
+                                    U+00EC ISOlat1 -->
+  <!ENTITY iacute "&#237;"> <!-- latin small letter i with acute,
+                                    U+00ED ISOlat1 -->
+  <!ENTITY icirc  "&#238;"> <!-- latin small letter i with circumflex,
+                                    U+00EE ISOlat1 -->
+  <!ENTITY iuml   "&#239;"> <!-- latin small letter i with diaeresis,
+                                    U+00EF ISOlat1 -->
+  <!ENTITY eth    "&#240;"> <!-- latin small letter eth, U+00F0 ISOlat1 -->
+  <!ENTITY ntilde "&#241;"> <!-- latin small letter n with tilde,
+                                    U+00F1 ISOlat1 -->
+  <!ENTITY ograve "&#242;"> <!-- latin small letter o with grave,
+                                    U+00F2 ISOlat1 -->
+  <!ENTITY oacute "&#243;"> <!-- latin small letter o with acute,
+                                    U+00F3 ISOlat1 -->
+  <!ENTITY ocirc  "&#244;"> <!-- latin small letter o with circumflex,
+                                    U+00F4 ISOlat1 -->
+  <!ENTITY otilde "&#245;"> <!-- latin small letter o with tilde,
+                                    U+00F5 ISOlat1 -->
+  <!ENTITY ouml   "&#246;"> <!-- latin small letter o with diaeresis,
+                                    U+00F6 ISOlat1 -->
+  <!ENTITY divide "&#247;"> <!-- division sign, U+00F7 ISOnum -->
+  <!ENTITY oslash "&#248;"> <!-- latin small letter o with stroke,
+                                    = latin small letter o slash,
+                                    U+00F8 ISOlat1 -->
+  <!ENTITY ugrave "&#249;"> <!-- latin small letter u with grave,
+                                    U+00F9 ISOlat1 -->
+  <!ENTITY uacute "&#250;"> <!-- latin small letter u with acute,
+                                    U+00FA ISOlat1 -->
+  <!ENTITY ucirc  "&#251;"> <!-- latin small letter u with circumflex,
+                                    U+00FB ISOlat1 -->
+  <!ENTITY uuml   "&#252;"> <!-- latin small letter u with diaeresis,
+                                    U+00FC ISOlat1 -->
+  <!ENTITY yacute "&#253;"> <!-- latin small letter y with acute,
+                                    U+00FD ISOlat1 -->
+  <!ENTITY thorn  "&#254;"> <!-- latin small letter thorn,
+                                    U+00FE ISOlat1 -->
+  <!ENTITY yuml   "&#255;"> <!-- latin small letter y with diaeresis,
+                                    U+00FF ISOlat1 -->
+
+<!-- Special characters -->
+
+  <!-- Relevant ISO entity set is given unless names are newly introduced.
+       New names (i.e., not in ISO 8879 list) do not clash with any
+       existing ISO 8879 entity names. ISO 10646 character numbers
+       are given for each character, in hex. values are decimal
+       conversions of the ISO 10646 values and refer to the document
+       character set. Names are Unicode names. 
+  -->
+
+  <!-- C0 Controls and Basic Latin -->
+  <!ENTITY quot    "&#34;"> <!--  quotation mark, U+0022 ISOnum -->
+  <!ENTITY amp     "&#38;#38;"> <!--  ampersand, U+0026 ISOnum -->
+  <!ENTITY lt      "&#38;#60;"> <!--  less-than sign, U+003C ISOnum -->
+  <!ENTITY gt      "&#62;"> <!--  greater-than sign, U+003E ISOnum -->
+  <!ENTITY apos	 "&#39;"> <!--  apostrophe = APL quote, U+0027 ISOnum -->
+
+  <!-- Latin Extended-A -->
+  <!ENTITY OElig   "&#338;"> <!--  latin capital ligature OE,
+                                      U+0152 ISOlat2 -->
+  <!ENTITY oelig   "&#339;"> <!--  latin small ligature oe, U+0153 ISOlat2 -->
+  <!-- ligature is a misnomer, this is a separate character in some languages -->
+  <!ENTITY Scaron  "&#352;"> <!--  latin capital letter S with caron,
+                                      U+0160 ISOlat2 -->
+  <!ENTITY scaron  "&#353;"> <!--  latin small letter s with caron,
+                                      U+0161 ISOlat2 -->
+  <!ENTITY Yuml    "&#376;"> <!--  latin capital letter Y with diaeresis,
+                                      U+0178 ISOlat2 -->
+
+  <!-- Spacing Modifier Letters -->
+  <!ENTITY circ    "&#710;"> <!--  modifier letter circumflex accent,
+                                      U+02C6 ISOpub -->
+  <!ENTITY tilde   "&#732;"> <!--  small tilde, U+02DC ISOdia -->
+
+  <!-- General Punctuation -->
+  <!ENTITY ensp    "&#8194;"> <!-- en space, U+2002 ISOpub -->
+  <!ENTITY emsp    "&#8195;"> <!-- em space, U+2003 ISOpub -->
+  <!ENTITY thinsp  "&#8201;"> <!-- thin space, U+2009 ISOpub -->
+  <!ENTITY zwnj    "&#8204;"> <!-- zero width non-joiner,
+                                      U+200C NEW RFC 2070 -->
+  <!ENTITY zwj     "&#8205;"> <!-- zero width joiner, U+200D NEW RFC 2070 -->
+  <!ENTITY lrm     "&#8206;"> <!-- left-to-right mark, U+200E NEW RFC 2070 -->
+  <!ENTITY rlm     "&#8207;"> <!-- right-to-left mark, U+200F NEW RFC 2070 -->
+  <!ENTITY ndash   "&#8211;"> <!-- en dash, U+2013 ISOpub -->
+  <!ENTITY mdash   "&#8212;"> <!-- em dash, U+2014 ISOpub -->
+  <!ENTITY lsquo   "&#8216;"> <!-- left single quotation mark,
+                                      U+2018 ISOnum -->
+  <!ENTITY rsquo   "&#8217;"> <!-- right single quotation mark,
+                                      U+2019 ISOnum -->
+  <!ENTITY sbquo   "&#8218;"> <!-- single low-9 quotation mark, U+201A NEW -->
+  <!ENTITY ldquo   "&#8220;"> <!-- left double quotation mark,
+                                      U+201C ISOnum -->
+  <!ENTITY rdquo   "&#8221;"> <!-- right double quotation mark,
+                                      U+201D ISOnum -->
+  <!ENTITY bdquo   "&#8222;"> <!-- double low-9 quotation mark, U+201E NEW -->
+  <!ENTITY dagger  "&#8224;"> <!-- dagger, U+2020 ISOpub -->
+  <!ENTITY Dagger  "&#8225;"> <!-- double dagger, U+2021 ISOpub -->
+  <!ENTITY permil  "&#8240;"> <!-- per mille sign, U+2030 ISOtech -->
+  <!ENTITY lsaquo  "&#8249;"> <!-- single left-pointing angle quotation mark,
+                                      U+2039 ISO proposed -->
+  <!-- lsaquo is proposed but not yet ISO standardized -->
+  <!ENTITY rsaquo  "&#8250;"> <!-- single right-pointing angle quotation mark,
+                                      U+203A ISO proposed -->
+  <!-- rsaquo is proposed but not yet ISO standardized -->
+
+  <!-- Currency Symbols -->
+  <!ENTITY euro   "&#8364;"> <!--  euro sign, U+20AC NEW -->
+
+<!-- Mathematical, Greek and Symbolic characters for XHTML -->
+
+  <!-- Latin Extended-B -->
+  <!ENTITY fnof     "&#402;"> <!-- latin small letter f with hook = function
+                                      = florin, U+0192 ISOtech -->
+
+  <!-- Greek -->
+  <!ENTITY Alpha    "&#913;"> <!-- greek capital letter alpha, U+0391 -->
+  <!ENTITY Beta     "&#914;"> <!-- greek capital letter beta, U+0392 -->
+  <!ENTITY Gamma    "&#915;"> <!-- greek capital letter gamma,
+                                      U+0393 ISOgrk3 -->
+  <!ENTITY Delta    "&#916;"> <!-- greek capital letter delta,
+                                      U+0394 ISOgrk3 -->
+  <!ENTITY Epsilon  "&#917;"> <!-- greek capital letter epsilon, U+0395 -->
+  <!ENTITY Zeta     "&#918;"> <!-- greek capital letter zeta, U+0396 -->
+  <!ENTITY Eta      "&#919;"> <!-- greek capital letter eta, U+0397 -->
+  <!ENTITY Theta    "&#920;"> <!-- greek capital letter theta,
+                                      U+0398 ISOgrk3 -->
+  <!ENTITY Iota     "&#921;"> <!-- greek capital letter iota, U+0399 -->
+  <!ENTITY Kappa    "&#922;"> <!-- greek capital letter kappa, U+039A -->
+  <!ENTITY Lambda   "&#923;"> <!-- greek capital letter lamda,
+                                      U+039B ISOgrk3 -->
+  <!ENTITY Mu       "&#924;"> <!-- greek capital letter mu, U+039C -->
+  <!ENTITY Nu       "&#925;"> <!-- greek capital letter nu, U+039D -->
+  <!ENTITY Xi       "&#926;"> <!-- greek capital letter xi, U+039E ISOgrk3 -->
+  <!ENTITY Omicron  "&#927;"> <!-- greek capital letter omicron, U+039F -->
+  <!ENTITY Pi       "&#928;"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->
+  <!ENTITY Rho      "&#929;"> <!-- greek capital letter rho, U+03A1 -->
+  <!-- there is no Sigmaf, and no U+03A2 character either -->
+  <!ENTITY Sigma    "&#931;"> <!-- greek capital letter sigma,
+                                      U+03A3 ISOgrk3 -->
+  <!ENTITY Tau      "&#932;"> <!-- greek capital letter tau, U+03A4 -->
+  <!ENTITY Upsilon  "&#933;"> <!-- greek capital letter upsilon,
+                                      U+03A5 ISOgrk3 -->
+  <!ENTITY Phi      "&#934;"> <!-- greek capital letter phi,
+                                      U+03A6 ISOgrk3 -->
+  <!ENTITY Chi      "&#935;"> <!-- greek capital letter chi, U+03A7 -->
+  <!ENTITY Psi      "&#936;"> <!-- greek capital letter psi,
+                                      U+03A8 ISOgrk3 -->
+  <!ENTITY Omega    "&#937;"> <!-- greek capital letter omega,
+                                      U+03A9 ISOgrk3 -->
+
+  <!ENTITY alpha    "&#945;"> <!-- greek small letter alpha,
+                                      U+03B1 ISOgrk3 -->
+  <!ENTITY beta     "&#946;"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->
+  <!ENTITY gamma    "&#947;"> <!-- greek small letter gamma,
+                                      U+03B3 ISOgrk3 -->
+  <!ENTITY delta    "&#948;"> <!-- greek small letter delta,
+                                      U+03B4 ISOgrk3 -->
+  <!ENTITY epsilon  "&#949;"> <!-- greek small letter epsilon,
+                                      U+03B5 ISOgrk3 -->
+  <!ENTITY zeta     "&#950;"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->
+  <!ENTITY eta      "&#951;"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->
+  <!ENTITY theta    "&#952;"> <!-- greek small letter theta,
+                                      U+03B8 ISOgrk3 -->
+  <!ENTITY iota     "&#953;"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->
+  <!ENTITY kappa    "&#954;"> <!-- greek small letter kappa,
+                                      U+03BA ISOgrk3 -->
+  <!ENTITY lambda   "&#955;"> <!-- greek small letter lamda,
+                                      U+03BB ISOgrk3 -->
+  <!ENTITY mu       "&#956;"> <!-- greek small letter mu, U+03BC ISOgrk3 -->
+  <!ENTITY nu       "&#957;"> <!-- greek small letter nu, U+03BD ISOgrk3 -->
+  <!ENTITY xi       "&#958;"> <!-- greek small letter xi, U+03BE ISOgrk3 -->
+  <!ENTITY omicron  "&#959;"> <!-- greek small letter omicron, U+03BF NEW -->
+  <!ENTITY pi       "&#960;"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->
+  <!ENTITY rho      "&#961;"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->
+  <!ENTITY sigmaf   "&#962;"> <!-- greek small letter final sigma,
+                                      U+03C2 ISOgrk3 -->
+  <!ENTITY sigma    "&#963;"> <!-- greek small letter sigma,
+                                      U+03C3 ISOgrk3 -->
+  <!ENTITY tau      "&#964;"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->
+  <!ENTITY upsilon  "&#965;"> <!-- greek small letter upsilon,
+                                      U+03C5 ISOgrk3 -->
+  <!ENTITY phi      "&#966;"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->
+  <!ENTITY chi      "&#967;"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->
+  <!ENTITY psi      "&#968;"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->
+  <!ENTITY omega    "&#969;"> <!-- greek small letter omega,
+                                      U+03C9 ISOgrk3 -->
+  <!ENTITY thetasym "&#977;"> <!-- greek theta symbol,
+                                      U+03D1 NEW -->
+  <!ENTITY upsih    "&#978;"> <!-- greek upsilon with hook symbol,
+                                      U+03D2 NEW -->
+  <!ENTITY piv      "&#982;"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->
+
+  <!-- General Punctuation -->
+  <!ENTITY bull     "&#8226;"> <!-- bullet = black small circle,
+                                       U+2022 ISOpub  -->
+  <!-- bullet is NOT the same as bullet operator, U+2219 -->
+  <!ENTITY hellip   "&#8230;"> <!-- horizontal ellipsis = three dot leader,
+                                       U+2026 ISOpub  -->
+  <!ENTITY prime    "&#8242;"> <!-- prime = minutes = feet, U+2032 ISOtech -->
+  <!ENTITY Prime    "&#8243;"> <!-- double prime = seconds = inches,
+                                       U+2033 ISOtech -->
+  <!ENTITY oline    "&#8254;"> <!-- overline = spacing overscore,
+                                       U+203E NEW -->
+  <!ENTITY frasl    "&#8260;"> <!-- fraction slash, U+2044 NEW -->
+
+  <!-- Letterlike Symbols -->
+  <!ENTITY weierp   "&#8472;"> <!-- script capital P = power set
+                                       = Weierstrass p, U+2118 ISOamso -->
+  <!ENTITY image    "&#8465;"> <!-- black-letter capital I = imaginary part,
+                                       U+2111 ISOamso -->
+  <!ENTITY real     "&#8476;"> <!-- black-letter capital R = real part symbol,
+                                       U+211C ISOamso -->
+  <!ENTITY trade    "&#8482;"> <!-- trade mark sign, U+2122 ISOnum -->
+  <!ENTITY alefsym  "&#8501;"> <!-- alef symbol = first transfinite cardinal,
+                                       U+2135 NEW -->
+  <!-- alef symbol is NOT the same as hebrew letter alef,
+       U+05D0 although the same glyph could be used to depict both characters -->
+
+  <!-- Arrows -->
+  <!ENTITY larr     "&#8592;"> <!-- leftwards arrow, U+2190 ISOnum -->
+  <!ENTITY uarr     "&#8593;"> <!-- upwards arrow, U+2191 ISOnum-->
+  <!ENTITY rarr     "&#8594;"> <!-- rightwards arrow, U+2192 ISOnum -->
+  <!ENTITY darr     "&#8595;"> <!-- downwards arrow, U+2193 ISOnum -->
+  <!ENTITY harr     "&#8596;"> <!-- left right arrow, U+2194 ISOamsa -->
+  <!ENTITY crarr    "&#8629;"> <!-- downwards arrow with corner leftwards
+                                       = carriage return, U+21B5 NEW -->
+  <!ENTITY lArr     "&#8656;"> <!-- leftwards double arrow, U+21D0 ISOtech -->
+  <!-- Unicode does not say that lArr is the same as the 'is implied by' arrow
+      but also does not have any other character for that function. So lArr can
+      be used for 'is implied by' as ISOtech suggests -->
+  <!ENTITY uArr     "&#8657;"> <!-- upwards double arrow, U+21D1 ISOamsa -->
+  <!ENTITY rArr     "&#8658;"> <!-- rightwards double arrow,
+                                       U+21D2 ISOtech -->
+  <!-- Unicode does not say this is the 'implies' character but does not have 
+       another character with this function so rArr can be used for 'implies'
+       as ISOtech suggests -->
+  <!ENTITY dArr     "&#8659;"> <!-- downwards double arrow, U+21D3 ISOamsa -->
+  <!ENTITY hArr     "&#8660;"> <!-- left right double arrow,
+                                       U+21D4 ISOamsa -->
+
+  <!-- Mathematical Operators -->
+  <!ENTITY forall   "&#8704;"> <!-- for all, U+2200 ISOtech -->
+  <!ENTITY part     "&#8706;"> <!-- partial differential, U+2202 ISOtech  -->
+  <!ENTITY exist    "&#8707;"> <!-- there exists, U+2203 ISOtech -->
+  <!ENTITY empty    "&#8709;"> <!-- empty set = null set, U+2205 ISOamso -->
+  <!ENTITY nabla    "&#8711;"> <!-- nabla = backward difference,
+                                       U+2207 ISOtech -->
+  <!ENTITY isin     "&#8712;"> <!-- element of, U+2208 ISOtech -->
+  <!ENTITY notin    "&#8713;"> <!-- not an element of, U+2209 ISOtech -->
+  <!ENTITY ni       "&#8715;"> <!-- contains as member, U+220B ISOtech -->
+  <!ENTITY prod     "&#8719;"> <!-- n-ary product = product sign,
+                                       U+220F ISOamsb -->
+  <!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though
+       the same glyph might be used for both -->
+  <!ENTITY sum      "&#8721;"> <!-- n-ary summation, U+2211 ISOamsb -->
+  <!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'
+       though the same glyph might be used for both -->
+  <!ENTITY minus    "&#8722;"> <!-- minus sign, U+2212 ISOtech -->
+  <!ENTITY lowast   "&#8727;"> <!-- asterisk operator, U+2217 ISOtech -->
+  <!ENTITY radic    "&#8730;"> <!-- square root = radical sign,
+                                       U+221A ISOtech -->
+  <!ENTITY prop     "&#8733;"> <!-- proportional to, U+221D ISOtech -->
+  <!ENTITY infin    "&#8734;"> <!-- infinity, U+221E ISOtech -->
+  <!ENTITY ang      "&#8736;"> <!-- angle, U+2220 ISOamso -->
+  <!ENTITY and      "&#8743;"> <!-- logical and = wedge, U+2227 ISOtech -->
+  <!ENTITY or       "&#8744;"> <!-- logical or = vee, U+2228 ISOtech -->
+  <!ENTITY cap      "&#8745;"> <!-- intersection = cap, U+2229 ISOtech -->
+  <!ENTITY cup      "&#8746;"> <!-- union = cup, U+222A ISOtech -->
+  <!ENTITY int      "&#8747;"> <!-- integral, U+222B ISOtech -->
+  <!ENTITY there4   "&#8756;"> <!-- therefore, U+2234 ISOtech -->
+  <!ENTITY sim      "&#8764;"> <!-- tilde operator = varies with = similar to,
+                                       U+223C ISOtech -->
+  <!-- tilde operator is NOT the same character as the tilde, U+007E,
+       although the same glyph might be used to represent both  -->
+  <!ENTITY cong     "&#8773;"> <!-- approximately equal to, U+2245 ISOtech -->
+  <!ENTITY asymp    "&#8776;"> <!-- almost equal to = asymptotic to,
+                                       U+2248 ISOamsr -->
+  <!ENTITY ne       "&#8800;"> <!-- not equal to, U+2260 ISOtech -->
+  <!ENTITY equiv    "&#8801;"> <!-- identical to, U+2261 ISOtech -->
+  <!ENTITY le       "&#8804;"> <!-- less-than or equal to, U+2264 ISOtech -->
+  <!ENTITY ge       "&#8805;"> <!-- greater-than or equal to,
+                                       U+2265 ISOtech -->
+  <!ENTITY sub      "&#8834;"> <!-- subset of, U+2282 ISOtech -->
+  <!ENTITY sup      "&#8835;"> <!-- superset of, U+2283 ISOtech -->
+  <!ENTITY nsub     "&#8836;"> <!-- not a subset of, U+2284 ISOamsn -->
+  <!ENTITY sube     "&#8838;"> <!-- subset of or equal to, U+2286 ISOtech -->
+  <!ENTITY supe     "&#8839;"> <!-- superset of or equal to,
+                                       U+2287 ISOtech -->
+  <!ENTITY oplus    "&#8853;"> <!-- circled plus = direct sum,
+                                       U+2295 ISOamsb -->
+  <!ENTITY otimes   "&#8855;"> <!-- circled times = vector product,
+                                       U+2297 ISOamsb -->
+  <!ENTITY perp     "&#8869;"> <!-- up tack = orthogonal to = perpendicular,
+                                       U+22A5 ISOtech -->
+  <!ENTITY sdot     "&#8901;"> <!-- dot operator, U+22C5 ISOamsb -->
+  <!-- dot operator is NOT the same character as U+00B7 middle dot -->
+
+  <!-- Miscellaneous Technical -->
+  <!ENTITY lceil    "&#8968;"> <!-- left ceiling = APL upstile,
+                                       U+2308 ISOamsc  -->
+  <!ENTITY rceil    "&#8969;"> <!-- right ceiling, U+2309 ISOamsc  -->
+  <!ENTITY lfloor   "&#8970;"> <!-- left floor = APL downstile,
+                                       U+230A ISOamsc  -->
+  <!ENTITY rfloor   "&#8971;"> <!-- right floor, U+230B ISOamsc  -->
+  <!ENTITY lang     "&#9001;"> <!-- left-pointing angle bracket = bra,
+                                       U+2329 ISOtech -->
+  <!-- lang is NOT the same character as U+003C 'less than sign' 
+       or U+2039 'single left-pointing angle quotation mark' -->
+  <!ENTITY rang     "&#9002;"> <!-- right-pointing angle bracket = ket,
+                                       U+232A ISOtech -->
+  <!-- rang is NOT the same character as U+003E 'greater than sign' 
+       or U+203A 'single right-pointing angle quotation mark' -->
+
+  <!-- Geometric Shapes -->
+  <!ENTITY loz      "&#9674;"> <!-- lozenge, U+25CA ISOpub -->
+
+  <!-- Miscellaneous Symbols -->
+  <!ENTITY spades   "&#9824;"> <!-- black spade suit, U+2660 ISOpub -->
+  <!-- black here seems to mean filled as opposed to hollow -->
+  <!ENTITY clubs    "&#9827;"> <!-- black club suit = shamrock,
+                                       U+2663 ISOpub -->
+  <!ENTITY hearts   "&#9829;"> <!-- black heart suit = valentine,
+                                       U+2665 ISOpub -->
+  <!ENTITY diams    "&#9830;"> <!-- black diamond suit, U+2666 ISOpub -->
+
diff --git a/user/src/com/google/gwt/uibinder/sample/UiBinderDemo.gwt.xml b/user/src/com/google/gwt/uibinder/sample/UiBinderDemo.gwt.xml
new file mode 100644
index 0000000..948c463
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/UiBinderDemo.gwt.xml
@@ -0,0 +1,19 @@
+<!-- Copyright 2008 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<!-- GWT UiBinder support.                                                  -->
+<module>
+  <inherits name="com.google.gwt.resources.Resources" />
+  <inherits name="com.google.gwt.uibinder.UiBinder" />
+  <entry-point class="com.google.gwt.uibinder.sample.client.UiBinderDemo" />
+</module>
diff --git a/user/src/com/google/gwt/uibinder/sample/client/AnnotatedStrictLabel.java b/user/src/com/google/gwt/uibinder/sample/client/AnnotatedStrictLabel.java
new file mode 100644
index 0000000..ed5fab4
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/AnnotatedStrictLabel.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2008 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.sample.client;
+
+import com.google.gwt.uibinder.client.UiConstructor;
+
+/**
+ * To prove that a bound Ui can use a widget that is not
+ * default instantiable, via @UiConstructor.
+ */
+public class AnnotatedStrictLabel extends StrictLabel {
+  @UiConstructor public AnnotatedStrictLabel(String text) {
+    super(text);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/ClickyLink.java b/user/src/com/google/gwt/uibinder/sample/client/ClickyLink.java
new file mode 100644
index 0000000..60e2381
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/ClickyLink.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2008 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.sample.client;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * A silly little widget to make sure that silly custom widgets
+ * actually work.
+ */
+
+public class ClickyLink extends Widget implements HasText {
+
+  /**
+   * This static method exists purely to ensure BeanParser handles overloaded
+   * methods correctly.
+   */
+  public static void setPopupText(HasText t, String text) {
+    t.setText(text);
+  }
+
+  private String popupText;
+
+  /**
+   * Creates an empty hyperlink.
+   */
+  public ClickyLink() {
+    setElement(DOM.createAnchor());
+    DOM.setElementAttribute(getElement(), "href", "#");
+    sinkEvents(Event.ONCLICK);
+
+    addDomHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        if (popupText != null) {
+          Window.alert(popupText);
+        }
+        event.preventDefault();
+      }
+    }, ClickEvent.getType());
+  }
+
+  /**
+   * Creates a hyperlink with its text specified.
+   *
+   * @param text the hyperlink's text
+   */
+  public ClickyLink(String text) {
+    this();
+    setText(text);
+  }
+
+  public String getPopupText() {
+    return popupText;
+  }
+
+  public String getText() {
+    return DOM.getInnerText(getElement());
+  }
+
+  public void setPopupText(String text) {
+    popupText = text;
+  }
+
+  public void setText(String text) {
+    DOM.setInnerText(getElement(), text);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.css b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.css
new file mode 100644
index 0000000..d277a99
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.css
@@ -0,0 +1,8 @@
+.bodyColor {
+  color: indigo;
+}
+
+.bodyFont {
+  font-family: Helvetica, Arial, sans-serif;
+  font-size: small;
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.java b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.java
new file mode 100644
index 0000000..398e4f2
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2008 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.sample.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.StyleInjector;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.CssResource.Strict;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiTemplate;
+
+/**
+ * Sample use of a {@code UiBinder} with no dependency on
+ * com.google.gwt.user.
+ */
+public class DomBasedUi {
+  /**
+   * Resources for this template.
+   */
+  public interface Resources extends ClientBundle {
+    @Strict
+    @Source("DomBasedUi.css")
+    Style style();
+  }
+
+  /**
+   * CSS for this template.
+   */
+  public interface Style extends CssResource {
+    String bodyColor();
+    String bodyFont();
+  }
+
+  @UiTemplate("DomBasedUi.ui.xml")
+  // Actually, it will default to this name w/o annotation
+  interface Binder extends UiBinder<Element, DomBasedUi> {
+  }
+
+  private static final Resources res = GWT.create(Resources.class);
+  private static final Binder binder = GWT.create(Binder.class);
+
+  private static boolean stylesInjected = false;
+
+  @UiField SpanElement nameSpan;
+  @UiField Element tmElement;
+
+  private final Element root;
+  private final String yourNameHere;
+
+  private boolean gotRoot = false;
+
+  public DomBasedUi(String yourNameHere) {
+    this.yourNameHere = yourNameHere;
+    root = binder.createUiRoot(this);
+
+    // Inject only once.
+    if (!stylesInjected) {
+      StyleInjector.injectStylesheet(res.style().getText());
+      stylesInjected = true;
+    }
+
+    // Or if you don't care about deferred rendering...
+//    root = binder.createAndBindUi(this);
+//    nameSpan.setInnerText(yourNameHere);
+    // ...and delete gotRoot and the bulk of #getElement
+  }
+
+  public Element getRoot() {
+    if (!gotRoot) {
+      binder.bindUi(root, this);
+      nameSpan.setInnerText(yourNameHere);
+      gotRoot = true;
+    }
+    return root;
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.ui.xml
new file mode 100644
index 0000000..776910d
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/DomBasedUi.ui.xml
@@ -0,0 +1,33 @@
+<!--                                                                        -->
+<!-- Copyright 2008 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:res='urn:with:com.google.gwt.uibinder.sample.client.DomBasedUi.Resources'
+
+  ui:defaultLocale="en_us"
+  ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator"
+  ui:generateFormat="com.google.gwt.i18n.rebind.format.PropertiesFormat"
+  ui:generateFilename="myapp_translate_source"
+  ui:generateLocales="default"
+  >
+  <div res:class="style.bodyColor style.bodyFont">
+    Hello, <span ui:field="nameSpan" />.
+    <ui:msg>How goes it?</ui:msg>
+      <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
+      these with <span style="font-weight:bold"
+        ui:ph="boldSpan">placeholders</span><ui:ph name="tm"><sup ui:field="tmElement">TM</sup></ui:ph>.</ui:msg></p>
+  </div>
+</ui:UiBinder>
diff --git a/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementPanel.java b/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementPanel.java
new file mode 100644
index 0000000..07c6374
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementPanel.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 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.sample.client;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Used to test UiBinder, a widget that can be based on arbitrary element
+ * tags at the whim of its user.
+ */
+public class ExplicitElementPanel extends ComplexPanel {
+  @UiConstructor
+  ExplicitElementPanel(String tag) {
+    setElement(Document.get().createElement(tag));
+  }
+  
+  @Override
+  public void add(Widget child) {
+    add(child, getElement());
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementWidget.java b/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementWidget.java
new file mode 100644
index 0000000..4028b45
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/ExplicitElementWidget.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2009 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.sample.client;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.user.client.ui.HasHTML;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Used to test UiBinder, a widget that can be based on arbitrary element
+ * tags at the whim of its user.
+ */
+public class ExplicitElementWidget extends Widget implements HasHTML {
+  @UiConstructor
+  ExplicitElementWidget(String tag) {
+    setElement(Document.get().createElement(tag));
+  }
+  
+  public String getHTML() {
+    return getElement().getInnerHTML();
+  }
+
+  public String getText() {
+    return getElement().getInnerText();
+  }
+
+  public void setHTML(String html) {
+    getElement().setInnerHTML(html);
+  }
+
+  public void setText(String text) {
+    getElement().setInnerText(text);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/FakeBundle.java b/user/src/com/google/gwt/uibinder/sample/client/FakeBundle.java
new file mode 100644
index 0000000..1b64c58
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/FakeBundle.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2008 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.sample.client;
+
+/**
+ * Faux bundle used by test.
+ */
+public class FakeBundle {
+  public Foo foo() {
+    return  new Foo();
+  }
+
+  public String helloText() {
+    return "hello";
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/Foo.java b/user/src/com/google/gwt/uibinder/sample/client/Foo.java
new file mode 100644
index 0000000..c858727
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/Foo.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2009 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.sample.client;
+
+/**
+ * Used when testing non-primitive property setting in templates.
+ */
+public class Foo {
+  @Override
+  public String toString() {
+    return "I am a foo!";
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/FooLabel.java b/user/src/com/google/gwt/uibinder/sample/client/FooLabel.java
new file mode 100644
index 0000000..b859688
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/FooLabel.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2009 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.sample.client;
+
+import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Label;
+
+/**
+ * A widget tied to a non-primitive type, used to show that
+ * such properties can be set via templates.
+ */
+public class FooLabel extends Composite {
+  @UiConstructor
+  public FooLabel() {
+    Label l = new Label();
+    initWidget(l);
+  }
+
+  /**
+   * @return the text
+   */
+  public String getText() {
+    return getLabel().getText();
+  }
+
+  public void setFoo(Foo foo) {
+    getLabel().setText("This widget has non primitive properties: "
+        + foo.toString());
+  }
+
+  private Label getLabel() {
+    return (Label) getWidget();
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.java b/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.java
new file mode 100644
index 0000000..062f2e6
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2009 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.sample.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.uibinder.client.UiTemplate;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
+
+/**
+ * A simple demo showing how UiHandler works.
+ */
+public class HandlerDemo extends Composite {
+
+  @UiTemplate("HandlerDemo.ui.xml")
+  interface MyUiBinder extends UiBinder<Panel, HandlerDemo> {
+  }
+  private static final MyUiBinder binder = GWT.create(MyUiBinder.class);
+
+  @UiField FormPanel panelForm;
+  @UiField TextBox textBoxValueChange;
+
+  public HandlerDemo() {
+    initWidget(binder.createAndBindUi(this));
+  }
+
+  @UiHandler("panelForm")
+  public void doSubmit(SubmitEvent event) {
+    eventMessage(event);
+    event.cancel();
+  }
+
+  @UiHandler("textBoxValueChange")
+  protected void doChangeValue(ValueChangeEvent<String> event) {
+    eventMessage(event);
+  }
+
+  @UiHandler({"buttonClick", "labelClick"})
+  void doClick(ClickEvent event) {
+    eventMessage(event);
+  }
+
+  @UiHandler("buttonSubmit")
+  void doClickSubmit(ClickEvent event) {
+    panelForm.submit();
+  }
+
+  @UiHandler("buttonMouseOut")
+  void doMouseOut(MouseOutEvent event) {
+    eventMessage(event);
+  }
+
+  @UiHandler("buttonMouseOver")
+  void doMouseOver(MouseOverEvent event) {
+    eventMessage(event);
+  }
+
+  private void eventMessage(GwtEvent<?> event) {
+    Window.alert(event.toDebugString());
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.ui.xml
new file mode 100644
index 0000000..6e9f727
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/HandlerDemo.ui.xml
@@ -0,0 +1,19 @@
+<gwt:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:gwt='urn:import:com.google.gwt.user.client.ui'>
+
+  <gwt:FlowPanel>
+    <gwt:Button ui:field="buttonClick" text="ClickEvent test for Button"/>
+    <gwt:Button ui:field="buttonMouseOver" text="MouseOverEvent test for Button"/>
+    <gwt:Button ui:field="buttonMouseOut" text="MouseOutEvent test for Button"/>
+    <gwt:Label ui:field="labelClick" text="ClickEvent test for Label"/>
+
+    <gwt:FormPanel ui:field="panelForm">
+      <gwt:Button ui:field="buttonSubmit" text="SubmitEvent test" />
+    </gwt:FormPanel>
+
+    <gwt:Label text="ValueChangeEvent test" />
+    <gwt:TextBox ui:field="textBoxValueChange"/>
+  </gwt:FlowPanel>
+
+</gwt:UiBinder>
diff --git a/user/src/com/google/gwt/uibinder/sample/client/MyDatePicker.java b/user/src/com/google/gwt/uibinder/sample/client/MyDatePicker.java
new file mode 100644
index 0000000..1d329a1
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/MyDatePicker.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2008 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.sample.client;
+
+import com.google.gwt.user.datepicker.client.DatePicker;
+
+import java.util.Date;
+
+/**
+ * A custom widget used by UiBinder samples to make sure that custom widgets
+ * actually work.
+ */
+public class MyDatePicker extends DatePicker {
+
+  @SuppressWarnings("deprecation")
+  public MyDatePicker() {
+    Date importantDate = new Date(1997, 4, 19);
+    setValue(importantDate);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/MyEntities.ent b/user/src/com/google/gwt/uibinder/sample/client/MyEntities.ent
new file mode 100644
index 0000000..7c068fe
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/MyEntities.ent
@@ -0,0 +1,23 @@
+<!--
+  Copyright 2009 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
+  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. License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<!-- 
+  Sample custom entity definitions, demonstrating that such
+  things work with UiBinder. 
+-->
+
+  <!ENTITY point-right  "&#x261e;"> <!-- right pointing hand -->
+  <!ENTITY point-left  "&#x261c;"> <!-- left pointing hand -->
+  
\ No newline at end of file
diff --git a/user/src/com/google/gwt/uibinder/sample/client/NeedlesslyAnnotatedLabel.java b/user/src/com/google/gwt/uibinder/sample/client/NeedlesslyAnnotatedLabel.java
new file mode 100644
index 0000000..4ac51bb
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/NeedlesslyAnnotatedLabel.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2008 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.sample.client;
+
+import com.google.gwt.uibinder.client.UiConstructor;
+import com.google.gwt.user.client.ui.Label;
+
+/**
+ * A widget with a UiConstructor annotation that it doesn't need,
+ * to confirm these work.
+ */
+public class NeedlesslyAnnotatedLabel extends Label {
+  @UiConstructor public NeedlesslyAnnotatedLabel() {
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/PointlessRadioButtonSubclass.java b/user/src/com/google/gwt/uibinder/sample/client/PointlessRadioButtonSubclass.java
new file mode 100644
index 0000000..62b8163
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/PointlessRadioButtonSubclass.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 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.sample.client;
+
+import com.google.gwt.user.client.ui.RadioButton;
+
+/**
+ * Just to prove we can subclass radio button.
+ */
+public class PointlessRadioButtonSubclass extends RadioButton {
+
+  public PointlessRadioButtonSubclass() {
+    super("");
+  }
+
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/StrictLabel.java b/user/src/com/google/gwt/uibinder/sample/client/StrictLabel.java
new file mode 100644
index 0000000..331f7ba4
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/StrictLabel.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2008 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.sample.client;
+
+import com.google.gwt.user.client.ui.InlineLabel;
+
+/**
+ * To prove that a bound Ui can use a widget that is not
+ * default instantiable.
+ */
+public class StrictLabel extends InlineLabel {
+  public StrictLabel(String text) {
+    super(text);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/UiBinderDemo.java b/user/src/com/google/gwt/uibinder/sample/client/UiBinderDemo.java
new file mode 100644
index 0000000..8275c62
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/UiBinderDemo.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2008 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.sample.client;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Demonstration of templated UI. Used by UiBinderTest
+ */
+public class UiBinderDemo implements EntryPoint {
+
+  public void onModuleLoad() {
+    DomBasedUi boundUi = new DomBasedUi("Mr. User Man");
+    Document.get().getBody().appendChild((boundUi).getRoot());
+    
+    RootPanel.get().add((Widget) new WidgetBasedUi());
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css
new file mode 100644
index 0000000..599ea37
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css
@@ -0,0 +1,14 @@
+.prettyText {
+  color: green;
+  font-family: Helvetica, Arial, sans-serif;
+}
+
+.tmText {
+  color: purple;
+  font-family: serif;
+}
+
+.menuBar {
+  border:solid;
+  background-color:white;
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
new file mode 100644
index 0000000..310e037
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2008 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.sample.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DListElement;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.OListElement;
+import com.google.gwt.dom.client.ParagraphElement;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.StyleInjector;
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiFactory;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiTemplate;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DisclosurePanel;
+import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.MenuBar;
+import com.google.gwt.user.client.ui.MenuItem;
+import com.google.gwt.user.client.ui.RadioButton;
+import com.google.gwt.user.client.ui.StackPanel;
+import com.google.gwt.user.client.ui.Tree;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Sample use of a {@code UiBinder} with the com.google.gwt.user Widget set, and
+ * custom widgets.
+ */
+public class WidgetBasedUi extends Composite {
+  @UiTemplate("WidgetBasedUi.ui.xml")
+  // Actually, it will default to this name w/o annotation
+  interface Binder extends UiBinder<Widget, WidgetBasedUi> {
+  }
+
+  private static final Binder binder = GWT.create(Binder.class);
+
+  private static boolean stylesInjected = false;
+
+  @UiField(provided = true)
+  final WidgetBasedUiResources res;
+
+  @UiField(provided = true)
+  final Label bundledLabel;
+
+  @UiField
+  ClickyLink customLinkWidget;
+  @UiField
+  PointlessRadioButtonSubclass emptyRadio;
+  @UiField
+  ClickyLink funnyCharsAttributeWidget;
+  @UiField
+  ParagraphElement funnyCharsDomAttributeParagraph;
+  @UiField
+  ClickyLink funnyCharsMessageAttributeWidget;
+  @UiField
+  ParagraphElement funnyCharsMessageDomAttributeParagraph;
+  @UiField
+  ParagraphElement funnyCharsMessageParagraph;
+  @UiField
+  ParagraphElement funnyCharsParagraph;
+  @UiField
+  Label gwtFieldLabel;
+  @UiField
+  ParagraphElement main;
+  @UiField
+  Button myButton;
+  @UiField
+  RadioButton myRadioAble;
+  @UiField
+  RadioButton myRadioBaker;
+  @UiField
+  StackPanel myStackPanel;
+  @UiField
+  Widget myStackPanelItem;
+  @UiField
+  DisclosurePanel myDisclosurePanel;
+  @UiField
+  Widget myDisclosurePanelItem;
+  @UiField
+  Tree myTree;
+  @UiField
+  Element nonStandardElement;
+  @UiField
+  DockPanel root;
+  @UiField
+  DivElement sideBar;
+  @UiField
+  SpanElement spanInMsg;
+  @UiField
+  Element tmElement;
+  @UiField
+  Element tmElementJr;
+  @UiField
+  SpanElement trimmedMessage;
+  @UiField
+  NeedlesslyAnnotatedLabel needlessLabel;
+  @UiField
+  AnnotatedStrictLabel strictLabel;
+  @UiField
+  AnnotatedStrictLabel translatedStrictLabel;
+  @UiField
+  StrictLabel veryStrictLabel;
+  @UiField
+  StrictLabel translatedVeryStrictLabel;
+  @UiField
+  FooLabel theFoo;
+  @UiField
+  MenuBar dropdownMenuBar;
+  @UiField
+  MenuItem menuItemMop;
+  @UiField
+  MenuItem menuItemLegacy;
+  @UiField
+  SpanElement messageInMain;
+  @UiField
+  TableElement widgetCrazyTable;
+  @UiField
+  OListElement widgetCrazyOrderedList;
+  @UiField
+  DListElement widgetCrazyDefinitionList;
+  @UiField
+  HTMLPanel customTagHtmlPanel;
+
+  public WidgetBasedUi() {
+    this.bundledLabel = new Label();
+    this.res = GWT.create(WidgetBasedUiResources.class);
+
+    // Inject only once.
+    if (!stylesInjected) {
+      StyleInjector.injectStylesheet(res.style().getText());
+      stylesInjected = true;
+    }
+
+    initWidget(binder.createAndBindUi(this));
+  }
+
+  public Label getBundledLabel() {
+    return bundledLabel;
+  }
+
+  public ClickyLink getCustomLinkWidget() {
+    return customLinkWidget;
+  }
+
+  public HTMLPanel getCustomTagHtmlPanel() {
+    return customTagHtmlPanel;
+  }
+
+  public MenuBar getDropdownMenuBar() {
+    return dropdownMenuBar;
+  }
+
+  public ClickyLink getFunnyCharsAttributeWidget() {
+    return funnyCharsAttributeWidget;
+  }
+
+  public ParagraphElement getFunnyCharsDomAttributeParagraph() {
+    return funnyCharsDomAttributeParagraph;
+  }
+
+  public ClickyLink getFunnyCharsMessageAttributeWidget() {
+    return funnyCharsMessageAttributeWidget;
+  }
+
+  public ParagraphElement getFunnyCharsMessageDomAttributeParagraph() {
+    return funnyCharsMessageDomAttributeParagraph;
+  }
+
+  public ParagraphElement getFunnyCharsMessageParagraph() {
+    return funnyCharsMessageParagraph;
+  }
+
+  public ParagraphElement getFunnyCharsParagraph() {
+    return funnyCharsParagraph;
+  }
+
+  public Label getGwtFieldLabel() {
+    return gwtFieldLabel;
+  }
+
+  public ParagraphElement getMain() {
+    return main;
+  }
+
+  public MenuItem getMenuItemLegacy() {
+    return menuItemLegacy;
+  }
+
+  public MenuItem getMenuItemMop() {
+    return menuItemMop;
+  }
+
+  public SpanElement getMessageInMain() {
+    return messageInMain;
+  }
+
+  public Button getMyButton() {
+    return myButton;
+  }
+
+  public DisclosurePanel getMyDisclosurePanel() {
+    return myDisclosurePanel;
+  }
+
+  public Widget getMyDisclosurePanelItem() {
+    return myDisclosurePanelItem;
+  }
+
+  public RadioButton getMyRadioAble() {
+    return myRadioAble;
+  }
+
+  public RadioButton getMyRadioBaker() {
+    return myRadioBaker;
+  }
+
+  public StackPanel getMyStackPanel() {
+    return myStackPanel;
+  }
+
+  public Widget getMyStackPanelItem() {
+    return myStackPanelItem;
+  }
+  public Element getNonStandardElement() {
+    return nonStandardElement;
+  }
+  
+  public DockPanel getRoot() {
+    return root;
+  }
+  
+  public DivElement getSideBar() {
+    return sideBar;
+  }
+
+  public SpanElement getSpanInMsg() {
+    return spanInMsg;
+  }
+  
+  public FooLabel getTheFoo() {
+    return theFoo;
+  }
+
+  public Element getTmElement() {
+    return tmElement;
+  }
+
+  public SpanElement getTrimmedMessage() {
+    return trimmedMessage;
+  }
+  
+  public DListElement getWidgetCrazyDefinitionList() {
+    return widgetCrazyDefinitionList;
+  }
+  
+  public OListElement getWidgetCrazyOrderedList() {
+    return widgetCrazyOrderedList;
+  }
+  public TableElement getWidgetCrazyTable() {
+    return widgetCrazyTable;
+  }
+  
+  @UiFactory
+  StrictLabel createStrictLabel(String text) {
+    return new StrictLabel(text);
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
new file mode 100644
index 0000000..6fd1345
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
@@ -0,0 +1,456 @@
+<!--
+  Copyright 2008 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
+  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. License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<!DOCTYPE gwt:UiBinder 
+  SYSTEM "http://google-web-toolkit.googlecode.com/svn/resources/xhtml.ent"
+  [
+    <!ENTITY % MyEntities SYSTEM "MyEntities.ent">
+    %MyEntities;
+  ]
+>
+<!-- 
+  This scary DOCTYPE section is not required. It's here to demonstrate two 
+  things.
+  
+  First, this bit:
+  
+    SYSTEM "http://google-web-toolkit.googlecode.com/svn/trunk/resources/xhtml.ent"
+ 
+  allows you to use familiar HTML entities like %nbsp; and &bull;, 
+  which are not part of XML.
+    
+  Next, the bit in square brackets pulls in additional definitions for
+  &point-left; and &point-right; from local file MyEntities.ent.
+
+  You don't have to be so verbose to include a local file! For
+  example, you might instead grab your own copy of xhtml.ent
+  and add your custom entity definitions to it, and use this 
+  for your DOCTYPE:
+  
+  <!DOCTYPE gwt:UiBinder SYSTEM "my-xhtml.ent">
+-->
+
+<gwt:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:gwt='urn:import:com.google.gwt.user.client.ui'
+  xmlns:demo='urn:import:com.google.gwt.uibinder.sample.client'
+
+  xmlns:foo='urn:with:com.google.gwt.uibinder.sample.client.FakeBundle'
+  xmlns:res='urn:with:com.google.gwt.uibinder.sample.client.WidgetBasedUiResources'
+
+  ui:defaultLocale="en_us"
+  ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator"
+  ui:generateFormat="com.google.gwt.i18n.rebind.format.PropertiesFormat"
+  ui:generateFilename="myapp_translate_source"
+  ui:generateLocales="default"
+>
+<gwt:DockPanel ui:field="root" width="100%">
+  <gwt:Dock direction='NORTH'>
+    <gwt:HTML>
+      <div style='border: 4px solid gray; padding: 4px; margin: 4px;'>
+        <img src='http://www.google.com/images/logo_sm.gif' alt="logo" />
+        <span ui:field="trimmedMessage"><ui:msg description='"title" of the doc'
+          meaning='would not normally do a "meaning" here'
+          key='and I would never use "key" at all'>
+          <!-- A test expects this *not* to be on one line  -->
+          Title area, specified largely in HTML.
+        </ui:msg></span>
+      </div>
+    </gwt:HTML>
+  </gwt:Dock>
+  <gwt:Dock direction='WEST'>
+    <gwt:HTML>
+      <div ui:field="sideBar"
+      style='border: 4px solid gray; padding: 4px; margin: 4px;'
+      >This could<br/>
+        be a<br/>
+        side bar<br/>
+        or something<br/>
+        like that...</div>
+    </gwt:HTML>
+  </gwt:Dock>
+  <gwt:Dock direction='CENTER'>
+    <gwt:HTMLPanel>
+      <p><ui:msg>This is a demonstration and test bed of GWT's nascent UiBinder
+      package. At the moment it works mainly as described in
+      <a href="http://code.google.com/p/google-web-toolkit-incubator/wiki/DeclarativeUi"
+        ui:ph="oldBlogLink">
+      this blog post</a>, but it is en route to the use cases desribed at
+      <a href="http://go/gwt-uibinder" ui:ph="newBlogLink">http://go/gwt-uibinder</a> and
+      <a href="http://go/gwt-uibinder-i18n" ui:ph="i18nBlogLink">http://go/gwt-uibinder-i18n</a>. Early
+      adopters should recognize themselves as such, and be prepared to have the
+      api rug yanked out from under them.</ui:msg></p>
+      <p ui:field="main">
+        &point-right;&nbsp;<span ui:field="messageInMain"><ui:msg>This is the main area. It's an <b>HTMLPanel</b>, which allows you to mix 
+          &point-right; &bull; XHTML &bull; &point-left; and regular GWT widgets, more or less with 
+          impunity. Take the following button for example.</ui:msg></span>&nbsp;&point-left;
+      </p>
+      <gwt:Button ui:field='myButton'>
+        Button with <b id='shouldSayHTML'>HTML</b> contents...
+      </gwt:Button>
+      <p>
+        It's specified using the Button widget, so that it's easy to work with from
+        Java code. Its markup has a ui:field value of 'myButton', which allows you to get it using the
+        <i ui:field="nonStandardElement">getMyButton()</i> method from MyTemplate.java.  </p>
+      <p>
+        Here is a similarly clickable
+        <demo:ClickyLink text="hyperlink based on a custom widget"
+          ui:field="customLinkWidget" popupText="That tickles! "/>.
+      </p>
+      <p>
+        <ui:msg description="">
+          Of course, it could just as easily be a Tree under a MenuBar...
+        </ui:msg>
+      </p>
+      <div res:class="style.menuBar">
+        <gwt:MenuBar ui:field="topMenuBar" vertical="false">
+          <gwt:MenuItem>
+            <div id="higgledy">
+              <ui:msg description="Last 7 Day Period">
+                Higgeldy
+               </ui:msg>
+            </div>
+            <gwt:MenuBar vertical="true" res:styleName="style.menuBar">
+              <gwt:MenuItem>able</gwt:MenuItem>
+              <gwt:MenuItem>baker</gwt:MenuItem>
+              <gwt:MenuItem>charlie</gwt:MenuItem>
+            </gwt:MenuBar>
+            </gwt:MenuItem>
+          <gwt:MenuItem>
+            <div id="piggledy">
+              <ui:msg description="Last 7 Day Period">
+                Piggledy
+              </ui:msg>
+              </div>
+            <gwt:MenuBar vertical="true" res:styleName="style.menuBar">
+              <gwt:MenuItem>delta</gwt:MenuItem>
+              <gwt:MenuItem>echo</gwt:MenuItem>
+              <gwt:MenuItem>foxtrot</gwt:MenuItem>
+            </gwt:MenuBar>
+          </gwt:MenuItem>
+          <gwt:MenuItem>
+            <div id="pop-can-has-submenu">
+              <ui:msg description="Last 7 Day Period">Pop</ui:msg>
+            </div>
+            <gwt:MenuBar vertical="true" ui:field="dropdownMenuBar" 
+              res:styleName="style.menuBar">
+              <gwt:MenuItem ui:field='menuItemCustomDateRange'>
+                <ui:msg description="Custom date range">
+                  the Dog
+                </ui:msg>
+              </gwt:MenuItem>
+              <gwt:MenuItem>
+                <ui:msg>has eaten</ui:msg>
+              </gwt:MenuItem>
+              <gwt:MenuItem ui:field="menuItemMop" styleName="moppy">
+                <ui:msg>the Mop</ui:msg>
+              </gwt:MenuItem>
+              <gwt:MenuItem ui:field='menuItemLegacy'>
+                <span style="white-space:nowrap">The <gwt:MenuItemHTML>pig's in</gwt:MenuItemHTML> a hurry</span>
+              </gwt:MenuItem>
+            </gwt:MenuBar>
+          </gwt:MenuItem>
+        </gwt:MenuBar>
+        </div>
+        <gwt:Tree ui:field='myTree' width="100px" />
+
+      <p>...TextBoxes...</p>
+      <gwt:TextBox maxLength="21">21 chars only, please</gwt:TextBox>
+
+      <p>...or perhaps a handful of RadioButtons:</p>
+      <gwt:RadioButton ui:field='myRadioAble' name="radios" text="Able" checked="true">
+        <ui:attribute name="text" description="radio button name"/>
+      </gwt:RadioButton>
+      <gwt:RadioButton ui:field='myRadioBaker' name="radios" text="Baker">
+        <ui:attribute name="text" description="radio button name"/>
+      </gwt:RadioButton>
+      <demo:PointlessRadioButtonSubclass ui:field='emptyRadio' name="radios"
+        text="Charlie (this one is a subclass of RadioButton)">
+        <ui:attribute name="text" description="radio button name"/>
+      </demo:PointlessRadioButtonSubclass>
+      <p> ... a StackPanel ... </p>
+      <gwt:StackPanel stylePrimaryName="myStyle" width="280px" ui:field='myStackPanel'>
+        <gwt:Label text="Stack One Text"  gwt:StackPanel-text="Stack One"
+            ui:field='myStackPanelItem'>
+          <!-- ui:attribute name="gwt:StackPanel-text" description="Label for Stack One"/ -->
+          <ui:attribute name="text" description="Content for Stack One Text"/>
+        </gwt:Label>
+        <gwt:HTMLPanel gwt:StackPanel-text="Stack Two">
+          <!-- ui:attribute name="gwt:StackPanel-text" description="Label for Stack Two"/ -->
+          <div>
+            <ui:msg description="Describe div content">Some other content</ui:msg>
+          </div>
+        </gwt:HTMLPanel>
+        <gwt:Label text="Stack Three Text"  gwt:StackPanel-text="Stack Three" />
+      </gwt:StackPanel>
+      <p> ... a DisclosurePanel with a text header ... </p>
+      <gwt:DisclosurePanel text="I just have a text header" ui:field='myDisclosurePanel'>
+        <!--ui:attribute name="text" description="Label for Disclosure One"/ -->
+        <gwt:Label text="Disclosure Text" ui:field='myDisclosurePanelItem'>
+          <ui:attribute name="text" description="Content for Disclosure One Text"/>
+        </gwt:Label>
+      </gwt:DisclosurePanel>
+      <p> ... a DisclosurePanel with a widget header ... </p>
+      <gwt:DisclosurePanel>
+        <gwt:Label text="Disclosure Header - closed" gwt:DisclosurePanel-header="true">
+          <ui:attribute name="text" description="Content for Disclosure Two Text"/>
+        </gwt:Label>
+        <gwt:Label text="Disclosure Two - closed">
+          <ui:attribute name="text" description="Content for Disclosure Two"/>
+        </gwt:Label>
+      </gwt:DisclosurePanel>
+      <p> ... an open DisclosurePanel with a widget header ... </p>
+      <gwt:DisclosurePanel initiallyOpen="true">
+        <gwt:Label text="Disclosure Header - open" gwt:DisclosurePanel-header="true">
+          <ui:attribute name="text" description="Content for Disclosure Two Text"/>
+        </gwt:Label>
+        <gwt:Label text="Disclosure Two - open">
+          <ui:attribute name="text" description="Content for Disclosure Two"/>
+        </gwt:Label>
+      </gwt:DisclosurePanel>
+      <p> ... a DisclosurePanel with no header ... </p>
+      <gwt:DisclosurePanel initiallyOpen="true">
+        <gwt:Label text="Disclosure Three">
+          <ui:attribute name="text" description="Content for Disclosure Three"/>
+        </gwt:Label>
+      </gwt:DisclosurePanel>
+      <p> ... an open DisclosurePanel with a text label and no animation</p>
+      <gwt:DisclosurePanel text="Disclosure Three - open" initiallyOpen="true"
+          enableAnimation="false">
+        <gwt:Label text="Disclosure Three">
+          <ui:attribute name="text" description="Content for Disclosure Three"/>
+        </gwt:Label>
+      </gwt:DisclosurePanel>
+      <p> ... a DisclosurePanel with no content ... </p>
+      <gwt:DisclosurePanel text="I just have a text header" />
+ 
+      <h2>Stylish</h2>
+      <p>
+       Templates work with what is now called ImmutableResourceBundle, but
+       which you will soon come to know and love as ClientBundle. For example,
+       this label gets its text from somplace tricky and its style from a resource:</p>
+      <gwt:Label ui:field='bundledLabel' foo:text="helloText" res:styleName="style.prettyText"/>
+      <p res:class="style.prettyText" id='prettyPara'>
+        This stylish paragraph also gets its good looks from a
+        resource.
+      </p>
+      <h2>Evolving</h2>
+      <p>
+        Things change. This label uses the new ui:field attribute to declare
+        its identity. Soon everything
+        will work that way:
+      </p>
+      <gwt:Label ui:field='gwtFieldLabel'>Tommy can you hear me? Can you field me near you?</gwt:Label>
+      <h2>Localization and character escaping</h2>
+      <p ui:field="funnyCharsParagraph">Templates can be marked up for <b>localization</b>,
+      which presents alls kinds of exciting opportunities for bugs related to
+      character escaping. Consider these funny characters " &quot; ' &#39; &amp; &lt; &gt; > { },
+      and the various places they might make your life miserable, like this
+      untranslated paragraph.</p>
+      <p ui:field="funnyCharsMessageParagraph"><ui:msg>They might show up
+      in body text that has been <b>marked for translation</b>: funny characters " &quot; ' &#39; &amp; &lt; &gt; > { }</ui:msg></p>
+      <p ui:field="funnyCharsMessageDomAttributeParagraph" title="funny characters &quot; ' &#39; &amp; &lt; &gt; > { }">
+        <ui:attribute name="title"/>
+        Attributes of dom elements can be translated too, like the
+        tooltip of this paragraph, which features the challenging characters.
+      </p>
+      <p ui:field="funnyCharsDomAttributeParagraph"
+        title="funny characters &quot; ' &#39; &amp; &lt; &gt; > { }">
+        They might also appear in an unstranslated attribute of a dom element, like
+        the tooltip for this paragraph.
+      </p>
+      <p>And all the cases above apply to widgets as well:</p>
+      <gwt:Label>I am an untranslatable HasText with funny characters " &quot; ' &#39; &amp; &lt; &gt; > { }</gwt:Label>
+      <gwt:HTML>I am an untranslatable HasHTML with funny characters " &quot; ' &#39; &amp; &lt; &gt; > { }</gwt:HTML>
+      <gwt:Label><ui:msg>I am a translatable <ui:ph name="widgetType" example="myWidget">HasText</ui:ph> with funny characters " &quot; ' &#39; &amp; &lt; &gt; > { }</ui:msg></gwt:Label>
+      <gwt:HTML><ui:msg>I am a translatable <ui:ph name="widgetType" example="myWidget">HasHTML</ui:ph> with funny characters " &quot; ' &#39; &amp; &lt; &gt; > { }</ui:msg></gwt:HTML>
+      <demo:ClickyLink ui:field="funnyCharsAttributeWidget"
+        popupText="funny characters &quot; ' &#39; &amp; &lt; &gt; > { }">
+        Click to see my untranslatable text passed by attribute
+      </demo:ClickyLink>
+      <br/>
+      <demo:ClickyLink ui:field="funnyCharsMessageAttributeWidget"
+        popupText="funny characters &quot; ' &#39; &amp; &lt; &gt; > { }">
+        <ui:attribute name="popupText"/>
+        Click to see my translatable text passed by attribute
+      </demo:ClickyLink>
+
+      <h2>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
+      these with <span id="placeholdersSpan" style="font-weight:bold" res:class="style.prettyText"
+        ui:ph="boldSpan">placeholders</span><ui:ph name="tm"><sup ui:field="tmElement"
+        res:class="style.tmText">TM</sup></ui:ph>.</ui:msg></p>
+
+      <p><ui:msg>You may also need to have <span ui:field="spanInMsg"
+      style="font-weight:bold" res:class="style.prettyText">named
+      portions</span> of <span res:class="style.prettyText">translatable text</span></ui:msg></p>
+
+      <p><ui:msg>Of course you'll want to be able to do this kind of thing
+      with widgets <demo:MyDatePicker/> as well, whether they <gwt:Label>HasText<ui:ph name="tm">*TM*</ui:ph></gwt:Label>
+      or <gwt:HTML>HasHTML<ui:ph name="TM2"><sup ui:field="tmElementJr"
+        res:class="style.tmText">TM</sup></ui:ph></gwt:HTML></ui:msg></p> 
+
+      <h2>Any Widget You Like</h2>
+      <p><demo:AnnotatedStrictLabel text="This widget is not default instantiable." ui:field="strictLabel"/></p>
+      <p><demo:AnnotatedStrictLabel text="Another constructor based widget, translated this time." ui:field="translatedStrictLabel">
+        <ui:attribute name="text" description="Arbitrary text"/>
+      </demo:AnnotatedStrictLabel></p>
+      <p><demo:StrictLabel text="More of the same." ui:field="veryStrictLabel"/></p>
+      <p><demo:StrictLabel text="Likewise, again translated this time." ui:field="translatedVeryStrictLabel">
+        <ui:attribute name="text" description="Arbitrary text"/>
+      </demo:StrictLabel></p>
+      <p><demo:NeedlesslyAnnotatedLabel ui:field="needlessLabel"
+        text="Sometimes people do things they don't need to do. Don't punish them."/></p>
+      <demo:FooLabel ui:field='theFoo' foo:foo="foo"/>
+
+    <h2>How to use the debugId, addStyleNames, addDependentStyleNames attributes</h2>
+    <p>You can use the <strong>debugId</strong>, <strong>addStyleNames</strong>, and <strong>addDependentStyleNames</strong>
+       attributes on any widget that extends UIObject.
+       <ul>
+         <li><b>debugId</b> will set an <em>id</em> on the element prefixed with <em>gwt-debug</em></li>
+         <li><b>addStyleNames</b> (comma separated) will add all class names to the existing style (space separated)</li>
+         <li><b>addDependentStyleNames</b> (comma separated) will concatenate all class names to the existing style with a "-" as a separator</li>
+       </ul>
+       You can/should use FireBug or a simple view source to see the attributes that were created.
+       <br />
+    </p>
+
+    <b>Template:</b>
+    <pre style="border: 1px dashed #666; padding: 5px 0;">
+  &lt;gwt:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:gwt='urn:import:com.google.gwt.user.client.ui'&gt;
+
+    &lt;gwt:FlowPanel&gt;
+      &lt;gwt:Label ui:field="lblDebugId" debugId="joe" addStyleNames="newStyle, anotherStyle" addStyleDependentNames="dependentStyle, anotherDependentStyle" text="a label with debug id" /&gt;
+
+      &lt;!-- A button that only adds a single style name, no comma's needed --&gt;
+      &lt;gwt:Button ui:field="btnGo" debugId="myButton" addStyleNames="buttonStyle" text="a button with extra attributes" /&gt;
+    &lt;/gwt:FlowPanel>
+
+  &lt;/gwt:UiBinder&gt;</pre>
+
+    <b>HTML:</b>
+    <pre style="border: 1px dashed #666; padding: 5px 0;">
+  &lt;div id="gwt-debug-joe" class="gwt-Label newStyle anotherStyle gwt-Label-dependentStyle gwt-Label-anotherDependentStyle"&gt;A label with a debug id&lt;/div&gt;
+  &lt;button id="gwt-debug-myButton" class="gwt-Button buttonStyle" tabindex="0" type="button"&gt;Go&lt;/button&gt;</pre>
+
+    <gwt:FlowPanel>
+      <gwt:Label ui:field="lblDebugId" debugId="joe" addStyleNames="newStyle, anotherStyle" addStyleDependentNames="dependentStyle, anotherDependentStyle">
+        A label with a debug id
+      </gwt:Label>
+      <gwt:Button ui:field="btnGo" debugId="myButton" addStyleNames="buttonStyle">Go</gwt:Button>
+    </gwt:FlowPanel>
+
+    <h2>How to tie handlers automatically!!</h2>
+    <p>You can add event handlers to template widgets as long as they have
+      valid ui:field attributes. If the template is shared, each widget can
+      register listeners for the events it's interested in. In the example
+      below, we listen to MouseOver events on the status label and clicks
+      on the button.</p>
+
+    <demo:HandlerDemo />
+
+    <b>Template:</b>
+    <pre style="border: 1px dashed #666; padding: 5px 0;">
+  &lt;gwt:UiBinder
+    xmlns:ui='urn:ui:com.google.gwt.uibinder'
+    xmlns:gwt='urn:import:com.google.gwt.user.client.ui'&gt;
+
+    &lt;gwt:FlowPanel&gt;
+      &lt;gwt:Label ui:field="label" text="Looking for happiness?" /&gt;
+      &lt;gwt:Button ui:field="button" text="Click here to happiness!!" /&gt;
+      &lt;gwt:TextBox ui:field="textbox" /&gt;
+    &lt;/gwt:FlowPanel>
+
+  &lt;/gwt:UiBinder&gt;</pre>
+
+  <b>The code:</b>
+  <pre style="border: 1px dashed #666; padding: 5px 0;">
+  public class HandlerDemo extends Composite {
+
+    @UiTemplate("HandlerDemo.ui.xml")
+    interface MyUiBinder extends UiBinder&lt;FlowPanel, HandlerDemo&gt; {}
+    private static final MyUiBinder binder = GWT.create(MyUiBinder.class);
+
+    @UiField Label label;
+
+    // No need of declare button here via @UiField if it is not really used
+    // in the class. Just don't forget to "ui:field" it in the template.
+
+    private int counter;
+
+    public HandlerDemo() {
+      initWidget(binder.createAndBindUi(this));
+    }
+
+    @UiHandler({"label", "button"})
+    protected void doClick(ClickEvent event) {
+      Window.alert("Yes, now I'm happy!");
+      counter = 0;
+      label.setText("Don't mouse over, click me!!!");
+    }
+
+    @UiHandler("button")
+    protected void doMouseOver(MouseOverEvent event) {
+      ++counter;
+      label.setText(counter + ". Don't mouse over, click me!");
+    }
+
+    @UiHandler("textbox")
+    protected void doChangeValue(ValueChangeEvent&lt;String&gt; event) {
+      label.setText("Typing = " + event.getValue());
+    }
+  }</pre>
+
+
+  <h2>People write the darndest markup</h2>
+  <table border="1" bcolor="yellow" cellpadding="3" ui:field="widgetCrazyTable">
+    <demo:ExplicitElementPanel tag="thead">
+      <demo:ExplicitElementWidget tag="td">They have been known</demo:ExplicitElementWidget>
+      <demo:ExplicitElementWidget tag="td">to write widgets</demo:ExplicitElementWidget>
+    </demo:ExplicitElementPanel>
+    <demo:ExplicitElementPanel tag="tr">
+      <demo:ExplicitElementWidget tag="td">that masquerade</demo:ExplicitElementWidget>
+      <demo:ExplicitElementWidget tag="td">as table cells,</demo:ExplicitElementWidget>
+    </demo:ExplicitElementPanel>
+    <demo:ExplicitElementPanel tag="tfoot">
+      <demo:ExplicitElementWidget tag="td">just like these.</demo:ExplicitElementWidget>
+      <demo:ExplicitElementWidget tag="td">Burma Shave</demo:ExplicitElementWidget>
+    </demo:ExplicitElementPanel>
+  </table>
+  <ul>
+    <demo:ExplicitElementWidget tag="li">You</demo:ExplicitElementWidget>
+    <demo:ExplicitElementWidget tag="li">can</demo:ExplicitElementWidget>
+    <demo:ExplicitElementWidget tag="li">imagine</demo:ExplicitElementWidget>
+  </ul>
+  <ol ui:field="widgetCrazyOrderedList">
+    <demo:ExplicitElementWidget tag="li">similar</demo:ExplicitElementWidget>
+    <demo:ExplicitElementWidget tag="li">things</demo:ExplicitElementWidget>
+  </ol>
+  <dl ui:field="widgetCrazyDefinitionList">
+    <demo:ExplicitElementWidget tag="dt">Being</demo:ExplicitElementWidget>
+    <demo:ExplicitElementWidget tag="dd">done</demo:ExplicitElementWidget>
+    <demo:ExplicitElementWidget tag="dd">with</demo:ExplicitElementWidget>
+    <demo:ExplicitElementWidget tag="dd">lists</demo:ExplicitElementWidget>
+  </dl>
+  <gwt:HTMLPanel tag="table" ui:field="customTagHtmlPanel">
+    <tr><td>Even HTMLPanel gets in on the game</td></tr>
+    <tr><td>Lately, anyway.</td></tr>
+  </gwt:HTMLPanel>
+    </gwt:HTMLPanel>
+  </gwt:Dock>
+</gwt:DockPanel>
+</gwt:UiBinder>
diff --git a/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiResources.java b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiResources.java
new file mode 100644
index 0000000..4af5810
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUiResources.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 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.sample.client;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.CssResource.Strict;
+
+/**
+ * Sample resources used by {@link WidgetBasedUi}. This would more typically be
+ * nested in {@code WidgetBasedUi}, but it's top level to ensure that the ui
+ * code generator is able to deal with that kind of thing.
+ */
+public interface WidgetBasedUiResources extends ClientBundle {
+  /**
+   * Sample CssResource.
+   */
+  public interface MyCss extends CssResource {
+    String menuBar();
+
+    String prettyText();
+
+    String tmText();
+  }
+
+  @Source("WidgetBasedUi.css")
+  @Strict
+  MyCss style();
+}
diff --git a/user/src/com/google/gwt/uibinder/sample/public/UiBinderDemo.html b/user/src/com/google/gwt/uibinder/sample/public/UiBinderDemo.html
new file mode 100644
index 0000000..441677c
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/sample/public/UiBinderDemo.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<!--                                                                        -->
+<!-- Copyright 2008 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+<html>
+  <head></head>
+  <body>
+    <script language='javascript' src='com.google.gwt.uibinder.sample.UiBinderDemo.nocache.js'></script>
+  </body>
+</html>
diff --git a/user/src/com/google/gwt/uibinder/testing/BUILD b/user/src/com/google/gwt/uibinder/testing/BUILD
new file mode 100644
index 0000000..1d7d5fb
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/testing/BUILD
@@ -0,0 +1,11 @@
+# -*- mode: python; -*-
+
+# Description:
+# testing things
+
+gwt_module(
+  name = "testing",
+  srcs = glob([
+    "*.java",
+  ]),
+)
diff --git a/user/src/com/google/gwt/uibinder/testing/UiBinderTesting.java b/user/src/com/google/gwt/uibinder/testing/UiBinderTesting.java
new file mode 100644
index 0000000..dbba0f1
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/testing/UiBinderTesting.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2007 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.testing;
+
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Support methods for testing uibinder. Not intended for production use!
+ */
+public class UiBinderTesting {
+  /**
+   * Creates an XML document model with the given contents.
+   *
+   * @param string the document contents
+   */
+  public static Document documentForString(String string)
+      throws ParserConfigurationException, SAXException, IOException {
+    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+    factory.setNamespaceAware(true);
+    factory.setExpandEntityReferences(true);
+    DocumentBuilder builder = factory.newDocumentBuilder();
+    Document doc = builder.parse(new ByteArrayInputStream(string.getBytes()));
+    return doc;
+  }
+
+  /**
+   * Not instantiable.
+   */
+  private UiBinderTesting() {
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java b/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
new file mode 100644
index 0000000..8831922
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/UiBinderGwtSuite.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 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;
+
+import com.google.gwt.junit.tools.GWTTestSuite;
+import com.google.gwt.uibinder.client.UiBinderTest;
+
+import junit.framework.Test;
+
+/**
+ * Test suite for UiBinder GWTTestCases.
+ */
+public class UiBinderGwtSuite {
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite("Test suite for UiBinder GWTTestCases");
+    
+    suite.addTestSuite(UiBinderTest.class);
+    
+    return suite;
+  }
+  
+  private UiBinderGwtSuite() {
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/UiBinderTestModule.gwt.xml b/user/test/com/google/gwt/uibinder/UiBinderTestModule.gwt.xml
new file mode 100644
index 0000000..73a0112
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/UiBinderTestModule.gwt.xml
@@ -0,0 +1,18 @@
+<!--                                                                        -->
+<!-- Copyright 2007 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   -->
+<!-- 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. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<!-- UiBinder test support.                                                 -->
+<module>
+	<inherits name="com.google.gwt.uibinder.sample.UiBinderDemo" />
+</module>
diff --git a/user/test/com/google/gwt/uibinder/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/client/UiBinderTest.java
new file mode 100644
index 0000000..e014444
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/client/UiBinderTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2009 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.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.ParagraphElement;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.uibinder.sample.client.ClickyLink;
+import com.google.gwt.uibinder.sample.client.DomBasedUi;
+import com.google.gwt.uibinder.sample.client.FakeBundle;
+import com.google.gwt.uibinder.sample.client.Foo;
+import com.google.gwt.uibinder.sample.client.WidgetBasedUi;
+import com.google.gwt.uibinder.sample.client.WidgetBasedUiResources;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.DisclosurePanel;
+import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.RadioButton;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.StackPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Functional test of UiBinder.
+ */
+public class UiBinderTest extends GWTTestCase {
+  private WidgetBasedUi widgetUi;
+  private DomBasedUi domUi;
+  private DockPanel root;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.uibinder.UiBinderTestModule";
+  }
+
+  @Override
+  public void gwtSetUp() throws Exception {
+    super.gwtSetUp();
+    RootPanel.get().clear();
+    domUi = new DomBasedUi("Cherished User");
+    Document.get().getBody().appendChild(domUi.getRoot());
+
+    widgetUi = new WidgetBasedUi();
+    root = widgetUi.getRoot();
+    RootPanel.get().add(widgetUi);
+  }
+
+  @Override
+  public void gwtTearDown() throws Exception {
+    RootPanel.get().clear();
+    super.gwtTearDown();
+  }
+
+  public void testAccessToNonStandardElement() {
+    Element elm = widgetUi.getNonStandardElement();
+    assertEquals("I", elm.getTagName());
+  }
+
+  // TODO(rjrjr) The direction stuff in these tests really belongs in
+  // DockPanelParserTest
+
+  public void testAllowIdOnDomElements() {
+    Element elm = DOM.getElementById("shouldSayHTML");
+    assertEquals("HTML", elm.getInnerHTML());
+  }
+
+  public void testBundle() {
+    assertEquals(getCenter(), widgetUi.getBundledLabel().getParent());
+    assertEquals(new FakeBundle().helloText(),
+        widgetUi.getBundledLabel().getText());
+    WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+    assertEquals("bundledLabel should have styleName",
+        resources.style().prettyText(),
+        widgetUi.getBundledLabel().getStyleName());
+
+    Element pretty = DOM.getElementById("prettyPara");
+    assertEquals(resources.style().prettyText(), pretty.getClassName());
+
+    Foo f = new Foo();
+    assertTrue("Expect " + f,
+        widgetUi.getTheFoo().getText().contains(f.toString()));
+  }
+
+  public void testCenter() {
+    // TODO(rjrjr) More of a test of HTMLPanelParser
+
+    Widget center = getCenter();
+    assertEquals(DockPanel.CENTER, root.getWidgetDirection(center));
+    assertEquals(HTMLPanel.class, center.getClass());
+    String html = center.getElement().getInnerHTML();
+    assertTrue(html.contains("main area"));
+    assertTrue(html.contains("Button with"));
+    assertTrue(html.contains("Of course"));
+
+    assertEquals(center, widgetUi.getMyButton().getParent());
+  }
+
+  public void testComputedAttributeInPlaceholderedElement() {
+    WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+    assertEquals(resources.style().prettyText(),
+        widgetUi.getSpanInMsg().getClassName());
+  }
+
+  public void testComputedStyleInAPlaceholder() {
+    WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+    assertEquals(resources.style().tmText(),
+        widgetUi.getTmElement().getClassName());
+  }
+
+  public void testDomAccessAndComputedAttributeOnPlaceholderedElement() {
+    WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+    Element elem = DOM.getElementById("placeholdersSpan");
+    assertEquals("bold", elem.getStyle().getProperty("fontWeight"));
+    assertEquals(resources.style().prettyText(), elem.getClassName());
+  }
+
+  public void testDomAccessInHtml() {
+    DivElement sideBar = widgetUi.getSideBar();
+    assertTrue("sideBar should start: \"This could\"",
+        sideBar.getInnerText().startsWith("This could"));
+    assertTrue("sideBar should end: \"example:\"",
+        sideBar.getInnerText().endsWith("like that..."));
+    assertEquals("Should have no id", "", sideBar.getAttribute("id"));
+  }
+
+  public void testDomAccessInHtmlPanel() {
+    SpanElement messageInMain = widgetUi.getMessageInMain();
+    String text = messageInMain.getInnerText().trim();
+    assertTrue("sideBar should start: \"This is the main area\"",
+        text.startsWith("This is the main area"));
+    assertTrue("sideBar should end: \"example.\"", text.endsWith("example."));
+  }
+
+  public void testDomAttributeMessageWithFunnyChars() {
+    ParagraphElement p =
+      widgetUi.getFunnyCharsMessageDomAttributeParagraph();
+    String t = p.getAttribute("title");
+    assertEquals("funny characters \" ' ' & < > > { }", t);
+  }
+
+  public void testDomAttributeNoMessageWithFunnyChars() {
+    ParagraphElement p = widgetUi.getFunnyCharsDomAttributeParagraph();
+    String t = p.getAttribute("title");
+    assertEquals("funny characters \" ' ' & < > > { }", t);
+  }
+
+  public void testDomTextMessageWithFunnyChars() {
+    String t = widgetUi.getFunnyCharsMessageParagraph().getInnerText();
+    assertEquals("They might show up in body text that has been marked for "
+        + "translation: funny characters \" \" ' ' & < > > { }",
+        t);
+  }
+
+  public void testDomTextNoMessageWithFunnyChars() {
+    ParagraphElement p = widgetUi.getFunnyCharsParagraph();
+    // WebKit does \n replace thing, so let's do it everywhere
+    String t = p.getInnerHTML().replace("\n", " ").toLowerCase(); 
+    String expected = "Templates can be marked up for <b>localization</b>, which presents alls "
+        + "kinds of exciting opportunities for bugs related to character escaping. "
+        + "Consider these funny characters \" \" ' ' &amp; &lt; &gt; &gt; { }, and "
+        + "the various places they might make your life miserable, like this "
+        + "untranslated paragraph.";
+    expected = expected.toLowerCase();
+    assertEquals(expected, t);
+  }
+
+  public void testFieldAttribute() {
+    assertEquals(getCenter(), widgetUi.getGwtFieldLabel().getParent());
+  }
+
+  public void testFieldInPlaceholderedElement() {
+    assertEquals("named portions", widgetUi.getSpanInMsg().getInnerText());
+  }
+
+  public void testMenuAttributes() {
+    WidgetBasedUiResources resources = GWT.create(WidgetBasedUiResources.class);
+    assertEquals(widgetUi.getDropdownMenuBar().getStyleName(),
+        resources.style().menuBar());
+  }
+
+  public void testMenuItems() {
+    // Has a legacy MenuItemHTML in its midst
+    assertEquals("The pig's in a hurry",
+        widgetUi.getMenuItemLegacy().getElement().getInnerText());
+    assertTrue("Style should include \"moppy\"",
+        widgetUi.getMenuItemMop().getStyleName().contains("moppy"));
+  }
+
+  public void testMessageTrimming() {
+    assertEquals("Title area, specified largely in HTML.",
+        widgetUi.getTrimmedMessage().getInnerHTML());
+    assertEquals("Tommy can you hear me? Can you field me near you?",
+        widgetUi.getGwtFieldLabel().getText());
+  }
+  
+  public void testMinimalDom() {
+    assertEquals("Expect no wrapper div around root", widgetUi.getElement(),
+        root.getElement());
+  }
+
+  public void testNamedElementInAPlaceholder() {
+    assertEquals("TM", widgetUi.getTmElement().getInnerText());
+  }
+
+  public void testNestedBundle() {
+    DomBasedUi.Resources resources =
+      GWT.create(DomBasedUi.Resources.class);
+    assertEquals(resources.style().bodyColor()
+        + " " + resources.style().bodyFont() ,
+        domUi.getRoot().getClassName());
+  }
+
+  public void suppressedForIEfail_testNonXmlEntities() {
+    // This fragment includes both translated and non-translated strings
+    ParagraphElement mainParagraph = widgetUi.getMain();
+    final String innerHTML = mainParagraph.getInnerHTML().trim();
+    assertTrue(innerHTML.contains(" \u261E \u2022 XHTML \u2022 \u261C"));
+    assertTrue(innerHTML.startsWith("\u261E&nbsp;<span>"));
+    assertTrue(innerHTML.endsWith("</span>&nbsp;\u261C"));
+  }
+
+  public void testNorth() {
+    Widget north = root.getWidget(0);
+    assertEquals(DockPanel.NORTH, root.getWidgetDirection(north));
+    assertEquals(HTML.class, north.getClass());
+    assertTrue(((HTML) north).getHTML().contains("Title area"));
+  }
+
+  public void testRadioButton() {
+    RadioButton able = widgetUi.getMyRadioAble();
+    RadioButton baker = widgetUi.getMyRadioBaker();
+    assertTrue("able should be checked", able.getValue());
+    assertFalse("baker should not be checked", baker.getValue());
+    assertEquals("radios", able.getName());
+    assertEquals("radios", baker.getName());
+  }
+
+  public void testStackPanel() {
+    StackPanel p = widgetUi.getMyStackPanel();
+    assertNotNull("Panel exists", p);
+    Widget w = widgetUi.getMyStackPanelItem();
+    assertNotNull("Widget exists", w);
+    boolean containsWidget = false;
+    for (int i = 0; i < p.getWidgetCount(); i++) {
+      if (p.getWidget(i) == w) {
+        containsWidget = true;
+      }
+    }
+    assertTrue("Panel contains widget", containsWidget);
+  }
+
+  public void testDisclosurePanel() {
+    DisclosurePanel p = widgetUi.getMyDisclosurePanel();
+    assertNotNull("Panel exists", p);
+    Widget w = widgetUi.getMyDisclosurePanelItem();
+    assertNotNull("Widget exists", w);
+    assertEquals("Panel contains widget", w, p.getContent());
+  }
+
+  public void testStringAttributeIgnoresStaticSetter() {
+    // Assumes setPopupText() is overloaded such that there is a static
+    // setPopupText(Foo, String) method.
+    ClickyLink clicky = widgetUi.getCustomLinkWidget();
+    assertEquals("overloaded setter should have been called",
+                 "That tickles!", clicky.getPopupText());
+  }
+
+  public void testStringAttributeWithFormatChars() {
+    assertEquals("100%", root.getElement().getStyle().getProperty("width"));
+  }
+
+  public void testWest() {
+    Widget west = root.getWidget(1);
+    assertEquals(DockPanel.WEST, root.getWidgetDirection(west));
+    assertEquals(HTML.class, west.getClass());
+    String html = ((HTML) west).getHTML();
+    assertTrue(html.contains("side bar"));
+  }
+
+  public void testWidgetAttributeMessageWithFunnyChars() {
+    ClickyLink clicky = widgetUi.getFunnyCharsMessageAttributeWidget();
+    String t = clicky.getPopupText();
+    assertEquals("funny characters \" ' ' & < > > { }", t);
+  }
+
+  public void testWidgetAttributeNoMessageWithFunnyChars() {
+    ClickyLink clicky = widgetUi.getFunnyCharsAttributeWidget();
+    String t = clicky.getPopupText();
+    assertEquals("funny characters \" ' ' & < > > { }", t);
+  }
+  
+  public void suppressForIEfail_testBizarrelyElementedWidgets() {
+    assertInOrder(widgetUi.getWidgetCrazyTable().getInnerHTML().toLowerCase(),
+        "<td>they have been known</td>", "<td>to write widgets</td>",
+        "<td>that masquerade</td>", "<td>as table cells,</td>",
+        "<td>just like these.</td>", "<td>burma shave</td>");
+
+    assertInOrder(widgetUi.getWidgetCrazyOrderedList().getInnerHTML(),
+        "<li>similar</li>", "<li>things</li>");
+
+    assertInOrder(widgetUi.getWidgetCrazyDefinitionList().getInnerHTML(),
+        "<dt>Being</dt>", "<dd>done</dd>", "<dd>with</dd>", "<dd>lists</dd>");
+  }
+
+  public void testCustomHtmlPanelTag() {
+    assertInOrder(widgetUi.getCustomTagHtmlPanel().getElement().getInnerHTML(),
+        "<td>Even HTMLPanel gets in on the game</td>",
+        "<td>Lately, anyway.</td>");
+  }
+
+  /**
+   * Assert that the expect strings are found in body, and in the order given.
+   * WARNING: both body and expected are normalized to lower case, to get around
+   * IE's habit of returning capitalized DOM elements.
+   */
+  private void assertInOrder(String body, String... expected) {
+    body = body.toLowerCase();
+    int lastIndex = 0;
+    String lastExpected = "";
+    
+    for (String next : expected) {
+      next = next.toLowerCase();
+      int index = body.indexOf(next);
+      assertTrue(body + " should contain " + next, index > -1);
+      assertTrue("Expect " + next + " after " + lastExpected, index > lastIndex);
+      lastIndex = index;
+    }
+  }
+
+  private Widget getCenter() {
+    Widget center = root.getWidget(2);
+    return center;
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/GwtResourceEntityResolverTest.java b/user/test/com/google/gwt/uibinder/rebind/GwtResourceEntityResolverTest.java
new file mode 100644
index 0000000..da630e7
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/GwtResourceEntityResolverTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2009 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 junit.framework.TestCase;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Text of GwtResourceEntityResolver.
+ */
+public class GwtResourceEntityResolverTest extends TestCase {
+  
+  private static class MockResourceLoader implements
+      GwtResourceEntityResolver.ResourceLoader {
+    String fetched;
+    InputStream stream;
+
+    public InputStream fetch(String name) {
+      return stream;
+    }
+  }
+  
+  private GwtResourceEntityResolver resolver;
+  private MockResourceLoader loader;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    loader = new MockResourceLoader();
+    resolver = new GwtResourceEntityResolver(loader);
+  }
+
+  public void testNotOurProblem() throws SAXException, IOException {
+    assertNull(resolver.resolveEntity(null, "http://arbitrary"));
+    assertNull(resolver.resolveEntity("meaningless", "http://arbitrary"));
+    assertNull(resolver.resolveEntity(null, "arbitrary/relative"));
+  }
+
+  public void testOursGood() throws SAXException, IOException {
+    String publicId = "some old public thing";
+    String systemId = "http://google-web-toolkit.googlecode.com/svn/resources/xhtml.ent";
+    loader.stream = new InputStream() {
+      @Override
+      public int read() throws IOException {
+        throw new UnsupportedOperationException();
+      }      
+    };
+    
+    InputSource s = resolver.resolveEntity(publicId, systemId);
+    assertEquals(publicId, s.getPublicId());
+    assertEquals(systemId, s.getSystemId());
+    assertEquals(loader.stream, s.getByteStream());
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/TokenatorTest.java b/user/test/com/google/gwt/uibinder/rebind/TokenatorTest.java
new file mode 100644
index 0000000..05442b9
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/TokenatorTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2008 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 junit.framework.TestCase;
+
+/**
+ * Having a style tool that forces one to create javadoc that communicates no
+ * more information than the class name itself is the kind of foolishness that
+ * gives Java development a bad name.
+ */
+public class TokenatorTest extends TestCase {
+  private static final String FORMAT = "Token %d replaces \"%s\"";
+  private static final String BEGIN = "This is a bunch of text.";
+  private static final String EXPECTED =
+      "Token 1 replaces \"This\" is a " + "Token 2 replaces \"bunch\" of "
+          + "Token 3 replaces \"text.\"";
+
+  private int serial = 0;
+  private Tokenator tokenator;
+  private String betokened;
+
+  @Override
+  public void setUp() throws Exception {
+    tokenator = new Tokenator();
+    betokened =
+      BEGIN.replace("This", betoken("This")).replace("bunch",
+          betoken("bunch")).replace("text.", betoken("text."));
+    serial = 0;
+  };
+
+  public void testHasToken() {
+    String noTokens = BEGIN;
+    assertFalse(Tokenator.hasToken(noTokens));
+    assertTrue(Tokenator.hasToken(betokened));
+  }
+
+  public void testSimple() {
+    assertEquals(EXPECTED, tokenator.detokenate(betokened));
+  }
+
+  public void testStatic() {
+    assertEquals("0 is a 1 of 2", Tokenator.detokenate(betokened,
+        new Tokenator.Resolver() {
+          int serial = 0;
+
+          public String resolveToken(String token) {
+            return String.format("%d", serial++);
+          }
+        }));
+  }
+
+  private String betoken(String in) {
+    return tokenator.nextToken(String.format(FORMAT, ++serial, in));
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
new file mode 100644
index 0000000..7ea8294
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/rebind/XMLElementTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2008 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.uibinder.testing.UiBinderTesting;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * Tests XMLElement.
+ */
+public class XMLElementTest extends TestCase {
+  private static final String dom =
+    "<doc><elm attr1=\"attr1Value\" attr2=\"attr2Value\"/></doc>";
+  private Document doc;
+  private Element item;
+  private XMLElement elm;
+  
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    doc = UiBinderTesting.documentForString(dom);
+    item = (Element) doc.getDocumentElement().getElementsByTagName(
+        "elm").item(0);
+    elm = new XMLElement(item, null);
+  }
+
+  public void testConsumeAttribute() {
+    assertEquals("attr1Value", elm.consumeAttribute("attr1"));
+    assertEquals("", elm.consumeAttribute("attr1"));
+  }
+  
+  public void testEmptyStringOnMissingAttribute()
+      throws ParserConfigurationException, SAXException, IOException {
+    assertEquals("", elm.consumeAttribute("fnord"));
+  }
+
+  public void testIterator() throws ParserConfigurationException, SAXException,
+      IOException {
+    String[] expecteds = {"attr1", "attr2"};
+    Set<String> seen = new HashSet<String>();
+    for (int i = elm.getAttributeCount() - 1; i >= 0; i--) {
+      XMLAttribute attr = elm.getAttribute(i);
+      String expected = expecteds[i];
+      assertEquals(expected, attr.getLocalName());
+      assertFalse(attr.isConsumed());
+      assertEquals(expected + "Value", attr.consumeValue());
+      assertTrue(attr.isConsumed());
+      seen.add(expected);
+    }
+    assertEquals(2, seen.size());
+  }
+
+  public void testNoEndTags() throws Exception {
+    Document doc = UiBinderTesting.documentForString("<doc><br/></doc>");
+
+    Element item = (Element) doc.getDocumentElement().getElementsByTagName("br").item(
+        0);
+    XMLElement elm = new XMLElement(item, null);
+    assertEquals("br", item.getTagName());
+    assertEquals("", elm.getClosingTag());
+  }
+}